import { useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { Plus, Search, Upload, BookOpen, BarChart3, Zap, FileText, Download, RefreshCw } from 'lucide-react' import { toast, errorMessage } from '@/app/store/toast' import { confirmDialog } from '@/app/store/confirm' import { PageLoading, ErrorState, EmptyState } from '@/components/ui/feedback' import { PageHeader } from '@/components/common/PageHeader' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Card, CardHeader } from '@/components/ui/card' import { Switch } from '@/components/ui/switch' import { Input, Textarea, Field } from '@/components/ui/input' import { Tabs } from '@/components/ui/tabs' import { Dialog } from '@/components/ui/dialog' import { getKnowledge, createKnowledge, updateKnowledge, deleteKnowledge, uploadKnowledgeFiles, knowledgeSearchTest, getKnowledgeStats, } from '@/services/api/knowledge' import { KNOWLEDGE_CATEGORIES } from '@/config/constants' import { formatRelative } from '@/utils/format' import { cn } from '@/utils/cn' import type { KnowledgeItem } from '@/types' export function KnowledgePage() { const queryClient = useQueryClient() const [tab, setTab] = useState('list') const [category, setCategory] = useState('') const [keyword, setKeyword] = useState('') const [createOpen, setCreateOpen] = useState(false) const [editItem, setEditItem] = useState(null) const [searchQ, setSearchQ] = useState('') const [createDraft, setCreateDraft] = useState({ title: '', content: '' }) const { data, isLoading, isError, refetch } = useQuery({ queryKey: ['knowledge', category, keyword], queryFn: () => getKnowledge({ category, keyword }), }) const { data: stats } = useQuery({ queryKey: ['knowledge-stats'], queryFn: getKnowledgeStats }) const [searchResult, setSearchResult] = useState(null) const [searchLoading, setSearchLoading] = useState(false) const [uploadFiles, setUploadFiles] = useState([]) const [uploadDialog, setUploadDialog] = useState(false) const [uploadLoading, setUploadLoading] = useState(false) const createMut = useMutation({ mutationFn: (p: any) => createKnowledge(p), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['knowledge'] }) queryClient.invalidateQueries({ queryKey: ['knowledge-stats'] }) toast.success('知识已创建并完成索引') setCreateDraft({ title: '', content: '' }) setCreateOpen(false) }, onError: (e) => toast.error(errorMessage(e)), }) const toggleMut = useMutation({ mutationFn: ({ id, enabled }: any) => updateKnowledge(id, { enabled }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['knowledge'] }) }, }) const deleteMut = useMutation({ mutationFn: (id: string) => deleteKnowledge(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['knowledge'] }); toast.success('已删除') }, }) const list = data?.list ?? [] if (isLoading) return if (isError) return refetch()} /> return (
} />
{[ ['当前条目', data?.total || 0], ['已分段索引', list.reduce((sum, item) => sum + Number(item.chunkCount || 0), 0)], ['真实引用', stats?.totalCitations || 0], ['待优化内容', (stats?.lowQuality?.length || 0) + (stats?.outdated?.length || 0)], ].map(([label, value]) => (

{label}

{value}

))}
{tab === 'list' && (
{KNOWLEDGE_CATEGORIES.map((c) => ( ))}
setKeyword(e.target.value)} className="h-9 w-full rounded-md border border-input bg-card pl-8 pr-3 text-sm focus:border-primary focus:outline-none" />
{list.map((item) => (
{item.title} {KNOWLEDGE_CATEGORIES.find(c => c.value === item.category)?.label} v{item.version}

{item.content}

引用 {item.citedCount} 次 · {item.chunkCount || 0} 个检索片段 · {Number(item.characterCount || item.content?.length || 0).toLocaleString('zh-CN')} 字符 · {formatRelative(item.updatedAt)} {item.status !== 'ready' && <>·{item.status === 'parsing' ? '解析中…' : '解析失败'}}
{item.parseMessage &&

{item.parseMessage}

}
toggleMut.mutate({ id: item.id, enabled: v })} />
))}
)} {tab === 'search' && (
setSearchQ(e.target.value)} className="flex-1" />
{searchResult && (

参考答案

{searchResult.answer}

命中知识 ({searchResult.hits.length})

{searchResult.hits.map((h: any) => (
{h.item.title}相关度 {(h.score * 100).toFixed(0)}%

{h.snippet}

命中片段 #{Number(h.chunkIndex || 0) + 1}

))}
)}
)} {tab === 'stats' && stats && (

知识引用概况

{stats.totalCitations} 次总引用

AI回复引用率:{(stats.aiReplyCitationRate * 100).toFixed(1)}%

最常引用

{stats.topUsed.map((k) => (
{k.title}{k.citedCount}次
))}
)} { if (!createDraft.title.trim()) { toast.warning('请输入标题'); return } if (createDraft.content.trim().length < 20) { toast.warning('知识内容至少需要 20 个字符'); return } createMut.mutate({ ...createDraft, category: category || 'faq' }) }} loading={createMut.isPending}>创建并索引 }> setCreateDraft((value) => ({ ...value, title: event.target.value }))} placeholder="例如:欧洲地区退换货政策" />