import { useState } from 'react' import { useTranslation } from 'react-i18next' import type { NamingConflict } from '@shared/types' import { useBookStore } from '@renderer/stores/useBookStore' import { useSettingsStore } from '@renderer/stores/useSettingsStore' import { useAppStore } from '@renderer/stores/useAppStore' import { useEditStore } from '@renderer/stores/useEditStore' import { ipcCall } from '@renderer/lib/ipc-client' interface NamingCheckModalProps { open: boolean onClose: () => void } export function NamingCheckModal({ open, onClose }: NamingCheckModalProps): React.JSX.Element | null { const { t } = useTranslation() const bookId = useBookStore((s) => s.currentBookId) const openBook = useBookStore((s) => s.openBook) const namingIgnoreWords = useSettingsStore((s) => s.namingIgnoreWords) const updateSettings = useSettingsStore((s) => s.update) const showToast = useAppStore((s) => s.showToast) const reloadTarget = useEditStore((s) => s.reloadTarget) const [loading, setLoading] = useState(false) const [conflicts, setConflicts] = useState([]) if (!open) return null const runCheck = async (): Promise => { const activeBookId = useBookStore.getState().currentBookId if (!activeBookId) return setLoading(true) try { const rows = await ipcCall(() => window.electronAPI.ai.namingCheck(activeBookId)) const ignore = new Set(namingIgnoreWords) setConflicts( rows.filter((row) => !ignore.has(row.termA) && !ignore.has(row.termB)) ) if (rows.length === 0) { showToast(t('naming.noConflicts')) } } catch (err) { showToast(err instanceof Error ? err.message : String(err)) setConflicts([]) } finally { setLoading(false) } } const handleIgnore = (row: NamingConflict): void => { const next = Array.from(new Set([...namingIgnoreWords, row.termA, row.termB])) void updateSettings({ namingIgnoreWords: next }) setConflicts((list) => list.filter((item) => item !== row)) showToast(t('naming.ignored')) } const handleReplace = async (row: NamingConflict): Promise => { if (!bookId) return const canonical = row.suggestedCanonical || row.termA const from = row.termB if (!from || from === canonical) return try { const result = await ipcCall(() => window.electronAPI.search.replace(bookId, from, canonical, false, { caseSensitive: true }) ) await openBook(bookId) reloadTarget() setConflicts((list) => list.filter((item) => item !== row)) showToast(t('naming.replaced', { count: result.count })) } catch (err) { showToast(err instanceof Error ? err.message : String(err)) } } return (

{t('naming.title')}

{conflicts.length === 0 && !loading && (

{t('naming.emptyHint')}

)} {conflicts.map((row, index) => (
{row.termA}{row.termB}
{t('naming.confidence', { value: Math.round(row.confidence * 100) })} {row.reason ? ` · ${row.reason}` : ''}
))}
) }