import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useBookStore } from '@renderer/stores/useBookStore' import { useKnowledgeStore } from '@renderer/stores/useKnowledgeStore' import { useSettingsStore } from '@renderer/stores/useSettingsStore' import { ipcCall } from '@renderer/lib/ipc-client' import { NamingCheckModal } from '@renderer/components/ai/NamingCheckModal' import { KnowledgeEditorDialog, loadKnowledgeEntries } from '@renderer/components/knowledge/KnowledgeEditorDialog' export function KnowledgePanel(): React.JSX.Element { const { t } = useTranslation() const bookId = useBookStore((s) => s.currentBookId) const chapters = useBookStore((s) => s.chapters) const selectedChapterId = useBookStore((s) => s.selectedChapterId) const extractThreshold = useSettingsStore((s) => s.knowledgeExtractConfidenceThreshold) const { entries, stats, tab, forgottenOnly, editorOpen, editingId, setTab, openEditor, closeEditor, approve, reject, deleteEntry, batchApprove, extractChapter, batchApproveHighConfidence } = useKnowledgeStore() const injectionPreviews = useKnowledgeStore((s) => s.injectionPreviews) const loadInjectionPreviews = useKnowledgeStore((s) => s.loadInjectionPreviews) const [extracting, setExtracting] = useState(false) const [namingOpen, setNamingOpen] = useState(false) const [selected, setSelected] = useState>(new Set()) const [pendingCount, setPendingCount] = useState(0) useEffect(() => { if (!bookId) return void loadKnowledgeEntries(bookId, tab, forgottenOnly).then(({ entries, stats }) => { useKnowledgeStore.setState({ entries, stats }) }) }, [bookId, tab, forgottenOnly]) useEffect(() => { if (!bookId) return void ipcCall(() => window.electronAPI.knowledge.list(bookId, { status: 'pending' })).then( (list) => setPendingCount(list.length) ) }, [bookId, entries]) useEffect(() => { if (!bookId || entries.length === 0) return void loadInjectionPreviews( bookId, entries.map((e) => e.id) ) }, [bookId, entries, loadInjectionPreviews]) const handleBatchApprove = async (): Promise => { if (!bookId || selected.size === 0) return await batchApprove(bookId, [...selected]) setSelected(new Set()) } return ( <>
{tab === 'pending' && bookId && ( )} {bookId && selectedChapterId && ( )}
{tab === 'foreshadow' && (
{t('knowledge.statsSummary', { buried: stats.buried, resolved: stats.resolved, forgotten: stats.forgotten })}
)} {tab === 'pending' && selected.size > 0 && ( )}
{entries.map((entry) => (
{tab === 'pending' && ( { const next = new Set(selected) if (e.target.checked) next.add(entry.id) else next.delete(entry.id) setSelected(next) }} /> )}
{entry.title} {entry.extractionConfidence != null && ( {Math.round(entry.extractionConfidence * 100)}% )}
{t(`knowledge.type.${entry.type}`)} ยท {t(`knowledge.status.${entry.status}`)}
{entry.mergeTargetId && (
{t('knowledge.mergeSuggest', { title: entries.find((e) => e.id === entry.mergeTargetId)?.title ?? entry.mergeTargetId })}
)} {entry.content &&
{entry.content}
} {(injectionPreviews[entry.id]?.length ?? 0) > 0 && (
{t('knowledge.injectionPreview')}
{injectionPreviews[entry.id]!.map((log) => (
{t('knowledge.injectionEntry', { date: new Date(log.injectedAt).toLocaleDateString(), mode: t(`knowledge.injectionMode.${log.flowMode}`), chapter: chapters.find((c) => c.id === log.chapterId)?.title ?? t('knowledge.noChapter') })}
))}
)}
{entry.status === 'pending' && bookId && ( <> )} {bookId && ( )}
))} {entries.length === 0 && (
{t('knowledge.empty')}
)}
setNamingOpen(false)} /> ) }