diff --git a/src/components/NoteList.tsx b/src/components/NoteList.tsx
index 0180b6a0..d35552b0 100644
--- a/src/components/NoteList.tsx
+++ b/src/components/NoteList.tsx
@@ -15,7 +15,7 @@ import {
loadSortPreferences, saveSortPreferences,
} from '../utils/noteListHelpers'
-// Re-export for consumers
+// eslint-disable-next-line react-refresh/only-export-components -- re-exports for consumers
export { sortByModified, filterEntries, buildRelationshipGroups, getSortComparator }
export type { SortOption }
@@ -38,9 +38,10 @@ function PinnedCard({ entry, typeEntryMap, onSelectNote, showDate }: {
const te = typeEntryMap[entry.isA ?? '']
const color = getTypeColor(entry.isA ?? '', te?.color)
const bgColor = getTypeLightColor(entry.isA ?? '', te?.color)
- const Icon = getTypeIcon(entry.isA, te?.icon)
+ const Icon = useMemo(() => getTypeIcon(entry.isA, te?.icon), [entry.isA, te?.icon])
return (
onSelectNote(entry)}>
+ {/* eslint-disable-next-line react-hooks/static-components -- icon lookup from static map, no internal state */}
{entry.title}
{entry.snippet}
@@ -174,10 +175,10 @@ function countExpiredTrash(entries: VaultEntry[]): number {
interface NoteListDataParams {
entries: VaultEntry[]; selection: SidebarSelection; allContent: Record
- query: string; listSort: SortOption; modifiedFiles?: ModifiedFile[]
+ query: string; listSort: SortOption
}
-function useNoteListData({ entries, selection, allContent, query, listSort, modifiedFiles }: NoteListDataParams) {
+function useNoteListData({ entries, selection, allContent, query, listSort }: NoteListDataParams) {
const isEntityView = selection.kind === 'entity'
const isTrashView = selection.kind === 'filter' && selection.filter === 'trash'
@@ -188,9 +189,9 @@ function useNoteListData({ entries, selection, allContent, query, listSort, modi
const searched = useMemo(() => {
if (isEntityView) return []
- const sorted = [...filterEntries(entries, selection, modifiedFiles)].sort(getSortComparator(listSort))
+ const sorted = [...filterEntries(entries, selection)].sort(getSortComparator(listSort))
return filterByQuery(sorted, query)
- }, [entries, selection, modifiedFiles, isEntityView, listSort, query])
+ }, [entries, selection, isEntityView, listSort, query])
const searchedGroups = useMemo(() => {
if (!isEntityView) return []
@@ -208,7 +209,7 @@ function useNoteListData({ entries, selection, allContent, query, listSort, modi
// --- Main component ---
-function NoteListInner({ entries, selection, selectedNote, allContent, modifiedFiles, onSelectNote, onCreateNote }: NoteListProps) {
+function NoteListInner({ entries, selection, selectedNote, allContent, onSelectNote, onCreateNote }: NoteListProps) {
const [search, setSearch] = useState('')
const [searchVisible, setSearchVisible] = useState(false)
const [collapsedGroups, setCollapsedGroups] = useState>(new Set())
@@ -219,13 +220,13 @@ function NoteListInner({ entries, selection, selectedNote, allContent, modifiedF
}, [])
const toggleGroup = useCallback((label: string) => {
- setCollapsedGroups((prev) => { const next = new Set(prev); next.has(label) ? next.delete(label) : next.add(label); return next })
+ setCollapsedGroups((prev) => { const next = new Set(prev); if (next.has(label)) next.delete(label); else next.add(label); return next })
}, [])
const typeEntryMap = useTypeEntryMap(entries)
const query = search.trim().toLowerCase()
const listSort = sortPrefs['__list__'] ?? 'modified'
- const { isEntityView, isTrashView, typeDocument, searched, searchedGroups, expiredTrashCount } = useNoteListData({ entries, selection, allContent, query, listSort, modifiedFiles })
+ const { isEntityView, isTrashView, typeDocument, searched, searchedGroups, expiredTrashCount } = useNoteListData({ entries, selection, allContent, query, listSort })
const renderItem = useCallback((entry: VaultEntry) => (
diff --git a/src/components/QuickOpenPalette.tsx b/src/components/QuickOpenPalette.tsx
index 0d50f84e..4f6aae0b 100644
--- a/src/components/QuickOpenPalette.tsx
+++ b/src/components/QuickOpenPalette.tsx
@@ -39,8 +39,8 @@ export function QuickOpenPalette({ open, entries, onSelect, onClose }: QuickOpen
useEffect(() => {
if (open) {
- setQuery('')
- setSelectedIndex(0)
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- reset on dialog open
+ setQuery(''); setSelectedIndex(0)
setTimeout(() => inputRef.current?.focus(), 50)
}
}, [open])
@@ -58,7 +58,7 @@ export function QuickOpenPalette({ open, entries, onSelect, onClose }: QuickOpen
}, [entries, query])
useEffect(() => {
- setSelectedIndex(0)
+ setSelectedIndex(0) // eslint-disable-line react-hooks/set-state-in-effect -- reset selection on query change
}, [query])
useEffect(() => {
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 0a79ddad..645f0df1 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -56,7 +56,7 @@ function useOutsideClick(ref: React.RefObject, isOpen: boole
}
document.addEventListener('mousedown', handler)
return () => document.removeEventListener('mousedown', handler)
- }, [isOpen, onClose])
+ }, [ref, isOpen, onClose])
}
function buildTypeEntryMap(entries: VaultEntry[]): Record {
diff --git a/src/components/SidebarParts.tsx b/src/components/SidebarParts.tsx
index e6b10b75..84dc5ead 100644
--- a/src/components/SidebarParts.tsx
+++ b/src/components/SidebarParts.tsx
@@ -12,6 +12,7 @@ export interface SectionGroup {
customColor?: string | null
}
+// eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component
export function isSelectionActive(current: SidebarSelection, check: SidebarSelection): boolean {
if (current.kind !== check.kind) return false
switch (check.kind) {
diff --git a/src/components/TypeCustomizePopover.tsx b/src/components/TypeCustomizePopover.tsx
index 83bef593..222a456d 100644
--- a/src/components/TypeCustomizePopover.tsx
+++ b/src/components/TypeCustomizePopover.tsx
@@ -11,6 +11,7 @@ import { ACCENT_COLORS } from '../utils/typeColors'
import { cn } from '@/lib/utils'
/** Curated Phosphor icons (normal weight) for type customization */
+// eslint-disable-next-line react-refresh/only-export-components -- constant co-located with component
export const ICON_OPTIONS: { name: string; Icon: ComponentType }[] = [
{ name: 'file-text', Icon: FileText },
{ name: 'wrench', Icon: Wrench },
@@ -54,6 +55,7 @@ const ICON_MAP: Record> = Object.fromEntries(
)
/** Resolves a Phosphor icon name to its component, with fallback to FileText */
+// eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component
export function resolveIcon(name: string | null): ComponentType {
return (name && ICON_MAP[name]) || FileText
}
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
index beb56ed1..db73602b 100644
--- a/src/components/ui/badge.tsx
+++ b/src/components/ui/badge.tsx
@@ -45,4 +45,5 @@ function Badge({
)
}
+// eslint-disable-next-line react-refresh/only-export-components -- shadcn/ui pattern
export { Badge, badgeVariants }
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index b5ea4abd..5fef1d15 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -61,4 +61,5 @@ function Button({
)
}
+// eslint-disable-next-line react-refresh/only-export-components -- shadcn/ui pattern
export { Button, buttonVariants }
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
index 7bf18aa7..b8b95294 100644
--- a/src/components/ui/tabs.tsx
+++ b/src/components/ui/tabs.tsx
@@ -86,4 +86,5 @@ function TabsContent({
)
}
+// eslint-disable-next-line react-refresh/only-export-components -- shadcn/ui pattern
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
diff --git a/src/hooks/useAIChat.ts b/src/hooks/useAIChat.ts
index 52ded113..9a817369 100644
--- a/src/hooks/useAIChat.ts
+++ b/src/hooks/useAIChat.ts
@@ -61,7 +61,7 @@ export function useAIChat(
return
}
- const { prompt: systemPrompt } = buildSystemPrompt(contextNotes, allContent, model)
+ const { prompt: systemPrompt } = buildSystemPrompt(contextNotes, allContent)
let accumulated = ''
const onChunk = (chunk: string) => {
diff --git a/src/hooks/useAppKeyboard.ts b/src/hooks/useAppKeyboard.ts
index 4ada4e22..a6044972 100644
--- a/src/hooks/useAppKeyboard.ts
+++ b/src/hooks/useAppKeyboard.ts
@@ -1,4 +1,4 @@
-import { useEffect, useMemo } from 'react'
+import { useEffect } from 'react'
interface KeyboardActions {
onQuickOpen: () => void
@@ -16,22 +16,22 @@ export function useAppKeyboard({
onQuickOpen, onCreateNote, onSave, onTrashNote, onArchiveNote,
activeTabPathRef, handleCloseTabRef,
}: KeyboardActions) {
- const withActiveTab = (fn: (path: string) => void): ShortcutHandler => () => {
- const path = activeTabPathRef.current
- if (path) fn(path)
- }
-
- const keyMap = useMemo((): Record => ({
- p: onQuickOpen,
- n: onCreateNote,
- s: onSave,
- e: withActiveTab(onArchiveNote),
- w: withActiveTab((path) => handleCloseTabRef.current(path)),
- Backspace: withActiveTab(onTrashNote),
- Delete: withActiveTab(onTrashNote),
- }), [onQuickOpen, onCreateNote, onSave, onTrashNote, onArchiveNote, activeTabPathRef, handleCloseTabRef])
-
useEffect(() => {
+ const withActiveTab = (fn: (path: string) => void): ShortcutHandler => () => {
+ const path = activeTabPathRef.current
+ if (path) fn(path)
+ }
+
+ const keyMap: Record = {
+ p: onQuickOpen,
+ n: onCreateNote,
+ s: onSave,
+ e: withActiveTab(onArchiveNote),
+ w: withActiveTab((path) => handleCloseTabRef.current(path)),
+ Backspace: withActiveTab(onTrashNote),
+ Delete: withActiveTab(onTrashNote),
+ }
+
const handleKeyDown = (e: KeyboardEvent) => {
const mod = e.metaKey || e.ctrlKey
if (!mod) return
@@ -43,5 +43,5 @@ export function useAppKeyboard({
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
- }, [keyMap])
+ }, [onQuickOpen, onCreateNote, onSave, onTrashNote, onArchiveNote, activeTabPathRef, handleCloseTabRef])
}
diff --git a/src/hooks/useKeyboardNavigation.ts b/src/hooks/useKeyboardNavigation.ts
index ed3fa566..8e93baf2 100644
--- a/src/hooks/useKeyboardNavigation.ts
+++ b/src/hooks/useKeyboardNavigation.ts
@@ -88,7 +88,7 @@ function arrowDirection(key: string): 1 | -1 {
function useLatestRef(value: T): React.RefObject {
const ref = useRef(value)
- ref.current = value
+ useEffect(() => { ref.current = value })
return ref
}
@@ -119,5 +119,5 @@ export function useKeyboardNavigation({
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
- }, [])
+ }, [tabsRef, activeTabPathRef, visibleNotesRef, onSwitchTabRef, onReplaceRef, onSelectNoteRef])
}
diff --git a/src/hooks/useMcpBridge.ts b/src/hooks/useMcpBridge.ts
index 2279b014..e82890e2 100644
--- a/src/hooks/useMcpBridge.ts
+++ b/src/hooks/useMcpBridge.ts
@@ -11,8 +11,8 @@ import { useCallback, useRef, useState } from 'react'
const DEFAULT_WS_URL = 'ws://localhost:9710'
interface PendingRequest {
- resolve: (value: any) => void
- reject: (reason: any) => void
+ resolve: (value: unknown) => void
+ reject: (reason: unknown) => void
}
interface SearchResult {
@@ -68,12 +68,12 @@ export function useMcpBridge(wsUrl = DEFAULT_WS_URL) {
})
}, [wsUrl])
- const callTool = useCallback(async (tool: string, args: Record): Promise => {
+ const callTool = useCallback(async (tool: string, args: Record): Promise => {
const ws = await ensureConnection()
const id = `mcp-${++idCounterRef.current}`
return new Promise((resolve, reject) => {
- pendingRef.current.set(id, { resolve, reject })
+ pendingRef.current.set(id, { resolve: resolve as (value: unknown) => void, reject })
ws.send(JSON.stringify({ id, tool, args }))
// Timeout after 30 seconds
diff --git a/src/hooks/useTabManagement.ts b/src/hooks/useTabManagement.ts
index c62fe05f..f204ed74 100644
--- a/src/hooks/useTabManagement.ts
+++ b/src/hooks/useTabManagement.ts
@@ -82,9 +82,9 @@ export function useTabManagement() {
const [tabs, setTabs] = useState([])
const [activeTabPath, setActiveTabPath] = useState(null)
const activeTabPathRef = useRef(activeTabPath)
- activeTabPathRef.current = activeTabPath
+ useEffect(() => { activeTabPathRef.current = activeTabPath })
const tabsRef = useRef(tabs)
- tabsRef.current = tabs
+ useEffect(() => { tabsRef.current = tabs })
const handleCloseTabRef = useRef<(path: string) => void>(() => {})
const handleSelectNote = useCallback(async (entry: VaultEntry) => {
@@ -111,7 +111,7 @@ export function useTabManagement() {
return next
})
}, [])
- handleCloseTabRef.current = handleCloseTab
+ useEffect(() => { handleCloseTabRef.current = handleCloseTab })
const handleSwitchTab = useCallback((path: string) => {
setActiveTabPath(path)
@@ -152,9 +152,8 @@ export function useTabManagement() {
useEffect(() => {
const savedOrder = loadTabOrder()
if (savedOrder.length > 0) {
- setTabs((prev) => restoreOrder(prev, savedOrder))
+ setTabs((prev) => restoreOrder(prev, savedOrder)) // eslint-disable-line react-hooks/set-state-in-effect -- restore tab order on mount
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return {
diff --git a/src/hooks/useVaultLoader.ts b/src/hooks/useVaultLoader.ts
index 762b19a9..38467908 100644
--- a/src/hooks/useVaultLoader.ts
+++ b/src/hooks/useVaultLoader.ts
@@ -36,6 +36,7 @@ export function useVaultLoader(vaultPath: string) {
const [modifiedFiles, setModifiedFiles] = useState([])
useEffect(() => {
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- clear stale data then load new vault
setEntries([]); setAllContent({}); setModifiedFiles([])
loadVaultData(vaultPath)
.then(({ entries: e, allContent: c }) => { setEntries(e); setAllContent(c) })
@@ -51,7 +52,7 @@ export function useVaultLoader(vaultPath: string) {
}
}, [vaultPath])
- useEffect(() => { loadModifiedFiles() }, [loadModifiedFiles])
+ useEffect(() => { loadModifiedFiles() }, [loadModifiedFiles]) // eslint-disable-line react-hooks/set-state-in-effect -- trigger initial load
const addEntry = useCallback((entry: VaultEntry, content: string) => {
setEntries((prev) => [entry, ...prev])
diff --git a/src/mock-tauri.ts b/src/mock-tauri.ts
index af51761e..12996a31 100644
--- a/src/mock-tauri.ts
+++ b/src/mock-tauri.ts
@@ -1641,6 +1641,7 @@ index abc1234..${shortHash} 100644
let mockHasChanges = true
+// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock handler map accepts heterogeneous arg types
const mockHandlers: Record any> = {
list_vault: () => MOCK_ENTRIES,
get_note_content: (args: { path: string }) => MOCK_CONTENT[args.path] ?? '',
@@ -1656,7 +1657,7 @@ const mockHandlers: Record any> = {
git_push: () => {
return 'Everything up-to-date'
},
- ai_chat: (args: { request: { messages: any[]; model?: string; system?: string } }) => {
+ ai_chat: (args: { request: { messages: { role: string; content: string }[]; model?: string; system?: string } }) => {
const lastMsg = args.request.messages[args.request.messages.length - 1]?.content ?? ''
const lower = lastMsg.toLowerCase()
let content = `I can help you with that. Could you provide more details about what you'd like to know?`
@@ -1742,24 +1743,24 @@ export function updateMockContent(path: string, content: string) {
}
}
-export async function mockInvoke(cmd: string, args?: any): Promise {
+export async function mockInvoke(cmd: string, args?: Record): Promise {
// Try the vault API first for commands that read vault data
const apiAvailable = await checkVaultApi()
if (apiAvailable) {
try {
if (cmd === 'list_vault' && args?.path) {
- const res = await fetch(`/api/vault/list?path=${encodeURIComponent(args.path)}`)
+ const res = await fetch(`/api/vault/list?path=${encodeURIComponent(args.path as string)}`)
if (res.ok) return (await res.json()) as T
}
if (cmd === 'get_note_content' && args?.path) {
- const res = await fetch(`/api/vault/content?path=${encodeURIComponent(args.path)}`)
+ const res = await fetch(`/api/vault/content?path=${encodeURIComponent(args.path as string)}`)
if (res.ok) {
const { content } = await res.json()
return content as T
}
}
if (cmd === 'get_all_content' && args?.path) {
- const res = await fetch(`/api/vault/all-content?path=${encodeURIComponent(args.path)}`)
+ const res = await fetch(`/api/vault/all-content?path=${encodeURIComponent(args.path as string)}`)
if (res.ok) return (await res.json()) as T
}
} catch (err) {
diff --git a/src/utils/ai-chat.ts b/src/utils/ai-chat.ts
index df789e76..2c0535f3 100644
--- a/src/utils/ai-chat.ts
+++ b/src/utils/ai-chat.ts
@@ -25,7 +25,7 @@ export function estimateTokens(text: string | number): number {
const DEFAULT_CONTEXT_LIMIT = 180_000
-export function getContextLimit(_model: string): number {
+export function getContextLimit(): number {
return DEFAULT_CONTEXT_LIMIT
}
@@ -35,13 +35,12 @@ export function getContextLimit(_model: string): number {
export function buildSystemPrompt(
notes: VaultEntry[],
allContent: Record,
- model: string,
): { prompt: string; totalTokens: number; truncated: boolean } {
if (notes.length === 0) {
return { prompt: '', totalTokens: 0, truncated: false }
}
- const contextBudget = Math.floor(getContextLimit(model) * 0.6)
+ const contextBudget = Math.floor(getContextLimit() * 0.6)
const preamble = [
'You are a helpful AI assistant integrated into Laputa, a personal knowledge management app.',
'The user has selected the following notes as context. Use them to answer questions accurately.',
@@ -177,8 +176,8 @@ export async function streamChat(
await readSseStream(reader, onChunk)
onDone()
- } catch (err: any) {
- onError(err.message || 'Network error')
+ } catch (err: unknown) {
+ onError(err instanceof Error ? err.message : 'Network error')
}
}
diff --git a/src/utils/frontmatter.ts b/src/utils/frontmatter.ts
index 6d0f77b4..bea126c7 100644
--- a/src/utils/frontmatter.ts
+++ b/src/utils/frontmatter.ts
@@ -40,7 +40,7 @@ export function parseFrontmatter(content: string | null): ParsedFrontmatter {
let inList = false
for (const line of match[1].split('\n')) {
- const listMatch = line.match(/^ - (.*)$/)
+ const listMatch = line.match(/^ {2}- (.*)$/)
if (listMatch && currentKey) {
inList = true
currentList.push(unquote(listMatch[1]))
diff --git a/src/utils/noteListHelpers.ts b/src/utils/noteListHelpers.ts
index 8c2271ba..2d9edbb6 100644
--- a/src/utils/noteListHelpers.ts
+++ b/src/utils/noteListHelpers.ts
@@ -1,4 +1,4 @@
-import type { VaultEntry, SidebarSelection, ModifiedFile } from '../types'
+import type { VaultEntry, SidebarSelection } from '../types'
export interface RelationshipGroup {
label: string
@@ -196,6 +196,6 @@ function filterByFilterType(entries: VaultEntry[], filter: string): VaultEntry[]
return []
}
-export function filterEntries(entries: VaultEntry[], selection: SidebarSelection, _modifiedFiles?: ModifiedFile[]): VaultEntry[] {
+export function filterEntries(entries: VaultEntry[], selection: SidebarSelection): VaultEntry[] {
return filterByKind(entries, selection)
}
diff --git a/src/utils/wikilinks.ts b/src/utils/wikilinks.ts
index 2fafa685..3d656159 100644
--- a/src/utils/wikilinks.ts
+++ b/src/utils/wikilinks.ts
@@ -8,21 +8,36 @@ export function preProcessWikilinks(md: string): string {
return md.replace(/\[\[([^\]]+)\]\]/g, (_m, target) => `${WL_START}${target}${WL_END}`)
}
+// Minimal shape of a BlockNote block for wikilink processing
+interface BlockLike {
+ content?: InlineItem[]
+ children?: BlockLike[]
+ [key: string]: unknown
+}
+
+interface InlineItem {
+ type: string
+ text?: string
+ props?: Record
+ content?: unknown
+ [key: string]: unknown
+}
+
/** Walk blocks and replace placeholder text with wikilink inline content */
-export function injectWikilinks(blocks: any[]): any[] {
- return blocks.map(block => {
+export function injectWikilinks(blocks: unknown[]): unknown[] {
+ return (blocks as BlockLike[]).map(block => {
if (block.content && Array.isArray(block.content)) {
block.content = expandWikilinksInContent(block.content)
}
if (block.children && Array.isArray(block.children)) {
- block.children = injectWikilinks(block.children)
+ block.children = injectWikilinks(block.children) as BlockLike[]
}
return block
})
}
-function expandWikilinksInContent(content: any[]): any[] {
- const result: any[] = []
+function expandWikilinksInContent(content: InlineItem[]): InlineItem[] {
+ const result: InlineItem[] = []
for (const item of content) {
if (item.type !== 'text' || typeof item.text !== 'string' || !item.text.includes(WL_START)) {
result.push(item)
@@ -62,7 +77,7 @@ export function splitFrontmatter(content: string): [string, string] {
export function countWords(content: string): number {
const [, body] = splitFrontmatter(content)
- const text = body.replace(/[#*_\[\]`>~\-|]/g, '').trim()
+ const text = body.replace(/[#*_[\]`>~\-|]/g, '').trim()
if (!text) return 0
return text.split(/\s+/).filter(Boolean).length
}
diff --git a/vite.config.ts b/vite.config.ts
index 28b70e1b..2fdcd67e 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -226,7 +226,7 @@ function vaultApiPlugin(): Plugin {
// --- AI Chat proxy helpers ---
-function readRequestBody(req: any): Promise {
+function readRequestBody(req: import('http').IncomingMessage): Promise {
return new Promise((resolve) => {
let body = ''
req.on('data', (chunk: Buffer) => { body += chunk.toString() })
@@ -235,7 +235,7 @@ function readRequestBody(req: any): Promise {
}
async function forwardToAnthropic(params: {
- apiKey: string; model?: string; messages: any[]; system?: string; maxTokens?: number
+ apiKey: string; model?: string; messages: { role: string; content: string }[]; system?: string; maxTokens?: number
}): Promise {
return fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
@@ -254,7 +254,7 @@ async function forwardToAnthropic(params: {
})
}
-async function streamResponseBody(source: ReadableStream, res: any): Promise {
+async function streamResponseBody(source: ReadableStream, res: import('http').ServerResponse): Promise {
const reader = source.getReader()
const decoder = new TextDecoder()
let done = false
@@ -300,10 +300,10 @@ function aiChatProxyPlugin(): Plugin {
} else {
res.end()
}
- } catch (err: any) {
+ } catch (err: unknown) {
res.statusCode = 500
res.setHeader('Content-Type', 'application/json')
- res.end(JSON.stringify({ error: err.message || 'Internal server error' }))
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : 'Internal server error' }))
}
})
},