import { useEffect, useRef, useState } from 'react'; import { cloudAgentsApi } from '@/lib/cloud-agents-api'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { usePendingCloudInput } from './CloudPending'; import type { CloudKnowledge, CloudKnowledgeFile, CloudThread, CloudAttachment } from '../../../shared/cloud-agents'; const statusName: Record = { uploaded: '待解析', parsing: '解析中', parsed: '待索引', indexing: '索引中', indexed: '可检索', error_parsing: '解析失败', error_indexing: '索引失败', }; export function CloudKnowledgePanel({ slug, onCreated, onDeleted }: { slug: string; onCreated: (kb: CloudKnowledge) => void; onDeleted?: (id: string) => void }) { const [catalog, setCatalog] = useState<{ databases: CloudKnowledge[]; models: { id: string; name: string }[] } | null>(null); const [kbId, setKbId] = useState(''); const [files, setFiles] = useState([]); const [next, setNext] = useState(null); const [name, setName] = useState(''); const [model, setModel] = useState(''); const [error, setError] = useState(''); const [catalogError, setCatalogError] = useState(''); const [catalogLoading, setCatalogLoading] = useState(true); const [busy, setBusy] = useState(false); const [refresh, setRefresh] = useState(0); const [creating, setCreating] = useState(false); const [deleting, setDeleting] = useState(null); const [importing, setImporting] = useState(false); const replacements = useRef>({}); usePendingCloudInput(busy || (creating && Boolean(name.trim()))); const createIntent = useRef<{ operation_id: string; name: string; embedding_model: string } | null>(null); const uploadIntent = useRef(null); const processIntents = useRef>({}); const alive = useRef(true); const paged = useRef(false); useEffect(() => { alive.current = true; return () => { alive.current = false; }; }, []); useEffect(() => { let current = true; setCatalogLoading(true); setCatalogError(''); cloudAgentsApi.call('knowledge', { slug }).then(v => { if (!current) return; setCatalog(v); if (!createIntent.current) setModel(previous => v.models.some(item => item.id === previous) ? previous : ''); }) .catch(e => { if (current) setCatalogError(String(e.message || e)); }) .finally(() => { if (current) setCatalogLoading(false); }); return () => { current = false; }; }, [slug, refresh]); useEffect(() => { if (!kbId) { setFiles([]); return; } paged.current = false; let current = true; const load = () => cloudAgentsApi.call('knowledgeFiles', { slug, kb_id: kbId }) .then(v => { if (current) { setFiles(v.files); setNext(v.next_offset); } }) .catch(e => { if (current) setError(String(e.message || e)); }); void load(); const timer = setInterval(() => { if (!paged.current) void load(); }, 5000); return () => { current = false; clearInterval(timer); }; }, [slug, kbId, refresh]); // A refreshed first page replaces pagination only after an explicit operation. const act = async (operation: () => Promise) => { setBusy(true); setError(''); try { await operation(); } catch(e) { if (alive.current) setError(e instanceof Error ? e.message : '知识库操作失败'); } finally { if (alive.current) setBusy(false); } }; return
管理我的知识文档

上传到知识库的文档可供获准使用此智能体的人检索。解析后建立索引才会生效,向量调用扣除你的词元点数。

{error &&

{error}

} {catalogError &&

知识库与模型目录加载失败:{catalogError}。请刷新重试。

}
{creating &&
setName(e.target.value)} /> {!catalogLoading && !catalogError && catalog && !catalog.models.length &&

暂时没有可用的向量模型。请联系平台管理员完成云端配置,然后刷新。

管理员配置说明
  1. Yuxi 后台「智能体管理 → 模型供应商」:启用供应商,添加并启用 embedding 模型,填写模型实际向量维度。
  2. Works Square 运营后台「模型管理」:从 one-api 刷新,激活同一模型并保存。
  3. 两端模型 ID 必须完全一致,one-api 需支持该模型的向量接口。配置完成后点击「刷新知识库与模型」。
}
} {kbId && <>

最大 5 MB;支持 TXT、Markdown、CSV、JSON、Word、Excel、文本 PDF。

{importing && { setImporting(false); setRefresh(v => v + 1); }} />} {deleting &&

{deleting === 'database' ? '删除整个知识库及其文档、索引?所有使用此库的智能体将无法继续检索这些内容。' : `删除文档“${deleting.name}”及其索引?`}

}
{files.map(file =>

{file.name}

{statusName[file.status] || file.status} · {file.chunk_count || 0} 个分块

{file.error &&

{file.error}

}
{!['parsing', 'indexing', 'indexed'].includes(file.status) && }
)} {!files.length &&

尚未上传文档。

}

替换文档上传后,需对新文档点击“解析并索引”。新索引成功后才移除旧文档;失败时旧文档仍可检索。

{next !== null && } }
; } function AttachmentImport({ slug, kbId, onImported }: { slug: string; kbId: string; onImported: () => void }) { const [threads, setThreads] = useState([]); const [thread, setThread] = useState(''); const [files, setFiles] = useState([]); const [file, setFile] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const operation = useRef(null); usePendingCloudInput(busy || Boolean(operation.current)); useEffect(() => { let live = true; const load = async () => { let page = await cloudAgentsApi.call('threads', {}); while (page.next_offset !== null) { const next = await cloudAgentsApi.call('threads', { offset: page.next_offset }); page = { ...next, threads: [...page.threads, ...next.threads] }; } if (live) setThreads(page.threads); }; void load().catch(e => { if (live) setError(e instanceof Error ? e.message : '对话读取失败'); }); return () => { live = false; }; }, []); useEffect(() => { if (!thread) return; let live = true; setFiles([]); setFile(''); cloudAgentsApi.call('attachments', { thread_id: thread }).then(page => { if (live) setFiles(page.attachments); }).catch(e => { if (live) setError(e instanceof Error ? e.message : '附件读取失败'); }); return () => { live = false; }; }, [thread]); return

只显示你自己的对话附件。导入后,该文档可以被使用此知识库的智能体检索。

{error &&

{error}

}
; }