// KnowledgePanel:知识卡片独立大面板(用户拍板 2026-09-10:形态 A 独立 // overlay 双栏;数据 = 全局单库 wiki,工作区只是卡片上的元数据标签)。 // // 两种建卡路径: // 1. 从工作区生成(主路径,用户指定):输入现有工作区路径 → wiki.generate // → 宿主跑 wiki agent 阅读工作区文档与代码后产卡(进度经 // wiki.generateStatus 出站信号投影到 store.wikiGen)。 // 2. 手动新建:表单直填 type/title/summary/content。 // // 数据面全走 gate 命令(前后端只有信号关系): // - 列表 → wiki.listCards(wiki server slim 卡片:id/title/type/summary) // - 预览 → wiki.renderCard → 宿主 render-md 渲染 HTML → iframe srcdoc // (渲染基座唯一渲染管线,与 ArtifactTab 同款沙箱形态) // - 创建 → wiki.addCard(id 由宿主生成) // wiki.changed 广播(生成任务完成 / 手动创建 / 其他客户端变更)→ 重拉列表。 import { useCallback, useEffect, useState } from 'react' import * as Dialog from '@radix-ui/react-dialog' import { command } from '../../api/token' import { useSessionStore } from '../../state/session-store' import { recentWorkDirs } from '../overlays/NewSessionDialog' import type { WikiCardSummary } from '../../api/contract' const TYPE_LABELS: Record = { module: '模块', interface: '接口', function: '函数', class: '类', pattern: '模式', concept: '概念', } const EMPTY_CARDS: WikiCardSummary[] = [] type Props = { open: boolean onOpenChange: (open: boolean) => void } export default function KnowledgePanel({ open, onOpenChange }: Props) { const wikiChangedAt = useSessionStore((s) => s.wikiChangedAt) const wikiGen = useSessionStore((s) => s.wikiGen) const [cards, setCards] = useState(EMPTY_CARDS) const [error, setError] = useState(null) const [selectedId, setSelectedId] = useState(null) const [preview, setPreview] = useState<{ title: string; html: string } | null>(null) const [previewLoading, setPreviewLoading] = useState(false) const [creating, setCreating] = useState(false) // 生成区(从工作区生成) const [genPath, setGenPath] = useState('') // 表单字段(创建模式) const [title, setTitle] = useState('') const [type, setType] = useState('concept') const [summary, setSummary] = useState('') const [content, setContent] = useState('') const generating = wikiGen?.status === 'started' const recent = recentWorkDirs() const refresh = useCallback(async () => { try { const res = await command({ kind: 'wiki.listCards' }) setError(null) setCards( res.ok === true && res.result !== null && typeof res.result === 'object' && 'cards' in res.result ? (res.result as { cards: WikiCardSummary[] }).cards : EMPTY_CARDS, ) } catch (e) { setError(e instanceof Error ? e.message : String(e)) } }, []) useEffect(() => { if (open) void refresh() }, [open, refresh]) // wiki.changed(本面板创建成功或其他客户端写入)→ 重拉列表。 useEffect(() => { if (open && wikiChangedAt > 0) void refresh() }, [open, wikiChangedAt, refresh]) const openCard = async (card: WikiCardSummary) => { setSelectedId(card.id) setCreating(false) setPreviewLoading(true) try { const res = await command({ kind: 'wiki.renderCard', cardId: card.id }) if (res.ok === true && res.result !== null && typeof res.result === 'object' && 'html' in res.result) { const r = res.result as { title: string; html: string } setPreview({ title: r.title, html: r.html }) } } catch (e) { setError(e instanceof Error ? e.message : String(e)) } finally { setPreviewLoading(false) } } const submit = async () => { if (title.trim().length === 0 || summary.trim().length === 0) return try { const res = await command({ kind: 'wiki.addCard', card: { type, title: title.trim(), summary: summary.trim(), content, }, }) const id = res.ok === true && res.result !== null && typeof res.result === 'object' && 'id' in res.result ? (res.result as { id: string }).id : null setTitle('') setSummary('') setContent('') setCreating(false) // 列表刷新由 wiki.changed 广播驱动(宿主 emit);这里直接定位新卡预览。 if (id !== null) { setSelectedId(id) setPreviewLoading(true) try { const r2 = await command({ kind: 'wiki.renderCard', cardId: id }) if (r2.ok === true && r2.result !== null && typeof r2.result === 'object' && 'html' in r2.result) { const r = r2.result as { title: string; html: string } setPreview({ title: r.title, html: r.html }) } } finally { setPreviewLoading(false) } } } catch (e) { setError(e instanceof Error ? e.message : String(e)) } } const startGenerate = async () => { const dir = genPath.trim() if (dir.length === 0 || generating) return try { await command({ kind: 'wiki.generate', workDir: dir }) } catch (e) { setError(e instanceof Error ? e.message : String(e)) } } const inputStyle = { borderColor: 'var(--border)', background: 'var(--bg)' } return (
知识卡片

全局单库 · wiki —— 选择一个工作区,wiki agent 会阅读其文档与代码生成知识库

{/* 从工作区生成(主路径,用户指定):路径 + 生成按钮 + 进度状态。 */}
setGenPath(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && void startGenerate()} /> {recent.map((d) => ( {wikiGen !== undefined && ( {wikiGen.status === 'started' && `🔄 正在生成:${wikiGen.workDir}`} {wikiGen.status === 'completed' && `✓ 生成完成:新增 ${wikiGen.cardsCreated} 张卡片(${wikiGen.workDir})`} {wikiGen.status === 'failed' && `✗ 生成失败:${wikiGen.error}`} )}
{/* 左栏:新建入口 + 卡片列表 */}
{cards.length === 0 && !creating && (

{error ?? '知识库是空的——新建一张卡片开始。'}

)} {cards.map((c) => { const selected = selectedId === c.id return ( ) })}
{/* 右栏:创建表单 或 渲染预览 */}
{creating ? (
setTitle(e.target.value)} />
setSummary(e.target.value)} />