import { BookOpen, Database, FileText, Globe2, Loader2, Plus, RefreshCw, Search, Settings2, Trash2, } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; import { getStoredAuth } from "@/core/auth"; import { type LlmWikiKnowledgeBase, type LlmWikiScope, searchLlmWiki, useLlmWikiActions, useLlmWikiKnowledgeBases, useLlmWikiSettings, useLlmWikiSettingsActions, } from "@/strategy-components/api/llmwiki"; type WorkspaceTab = "personal" | "public" | "settings"; const LIST_PATH = "/page/workspace/knowledge"; const publicationLabels: Record = { private: "仅自己", pending: "仅自己", published: "已公开", rejected: "仅自己", }; function messageOf(error: unknown): string { return error instanceof Error ? error.message : "操作失败,请稍后重试"; } export function WeKnoraKnowledgeWorkspace() { const navigate = useNavigate(); const isAdmin = getStoredAuth()?.system_role === "admin"; const [tab, setTab] = useState("personal"); const scope: LlmWikiScope = tab === "public" ? "public" : "personal"; const knowledge = useLlmWikiKnowledgeBases(scope, tab !== "settings"); const actions = useLlmWikiActions(); const [createOpen, setCreateOpen] = useState(false); const [searchText, setSearchText] = useState(""); const [searching, setSearching] = useState(false); const [searchResults, setSearchResults] = useState>([]); useEffect(() => setSearchResults([]), [tab]); const busy = actions.create.isPending || actions.remove.isPending || actions.publication.isPending; const openDetails = (item: LlmWikiKnowledgeBase) => { if (item.remote_status === "missing") { toast.error("该知识库已从知识服务删除,只能清理当前记录"); return; } navigate(`${LIST_PATH}/${encodeURIComponent(item.id)}`); }; const runSearch = async () => { const query = searchText.trim(); if (!query) return; setSearching(true); try { setSearchResults(await searchLlmWiki({ query, scope })); } catch (error) { toast.error(messageOf(error)); } finally { setSearching(false); } }; const removeKnowledgeBase = async (item: LlmWikiKnowledgeBase) => { const warning = item.remote_status === "missing" ? `知识库“${item.name}”已不在知识服务中。确定清理 cmzs 中的这条失效记录吗?` : `确定删除知识库“${item.name}”吗?知识服务中的文档和索引也会删除。`; if (!window.confirm(warning)) return; try { await actions.remove.mutateAsync(item.id); toast.success("知识库已删除"); } catch (error) { toast.error(messageOf(error)); } }; const togglePublication = async (item: LlmWikiKnowledgeBase) => { const publish = item.publication_status !== "published"; try { await actions.publication.mutateAsync({ id: item.id, publish }); toast.success(publish ? "知识库已发布到公共 LLMWiki" : "知识库已取消公开"); } catch (error) { toast.error(messageOf(error)); } }; return (

知识库管理

知识服务

由知识服务负责解析与检索,用户、权限和发布范围由 cmzs 管理。

{tab !== "settings" && ( )} {tab === "personal" && }
setTab(value as WorkspaceTab)}>
我的知识库 公共 LLMWiki {isAdmin && 连接配置} {tab !== "settings" && (
setSearchText(event.target.value)} onKeyDown={(event) => event.key === "Enter" && void runSearch()} placeholder={tab === "public" ? "检索公共知识" : "检索我的知识"} className="border-0 bg-transparent pl-9 shadow-none focus-visible:ring-0" />
)}
{isAdmin && }
{searchResults.length > 0 && tab !== "settings" && ( 检索结果结果由知识服务检索返回,问答中使用同一授权链路。 {searchResults.map((result) =>
{result.title || "未命名来源"}{result.knowledge_base_name}

{result.content}

)}
)}
{ try { const created = await actions.create.mutateAsync(input); setCreateOpen(false); navigate(`${LIST_PATH}/${encodeURIComponent(created.id)}`); toast.success("知识库已创建"); } catch (error) { toast.error(messageOf(error)); } }} />
); } function KnowledgeGrid({ rows, loading, error, emptyText, busy, onOpen, onPublish, onDelete, }: { rows: LlmWikiKnowledgeBase[]; loading: boolean; error: unknown; emptyText: string; busy: boolean; onOpen: (item: LlmWikiKnowledgeBase) => void; onPublish: (item: LlmWikiKnowledgeBase) => void; onDelete: (item: LlmWikiKnowledgeBase) => void; }) { if (loading) return
正在读取知识库
; if (error) return
{messageOf(error)}
; if (!rows.length) return
{emptyText}
; return (
{rows.map((item) => (
{item.remote_status === "missing" ? 知识服务资源不存在,可清理本地记录 : } {item.remote_status !== "missing" && item.is_owner && !item.is_conversation_deposit && }
{item.can_write && !item.is_conversation_deposit && }
))}
); } function CreateKnowledgeDialog({ open, pending, onOpenChange, onCreate, }: { open: boolean; pending: boolean; onOpenChange: (value: boolean) => void; onCreate: (input: { name: string; description: string; type: "document" | "faq"; wiki_enabled: boolean }) => Promise; }) { const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [wikiEnabled, setWikiEnabled] = useState(true); useEffect(() => { if (!open) { setName(""); setDescription(""); setWikiEnabled(true); } }, [open]); return ( 新建个人知识库选择知识库模式;模型、切块和存储策略仍由知识服务管理。
setName(event.target.value)} maxLength={255} />