From c634425cc669f1da66e725649a386bf09661a70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 13 Oct 2026 11:34:53 +0800 Subject: [PATCH] Improve knowledge workspace and chat file import --- .../20261013-knowledge-workspace-5ac9e2d.md | 74 ++++ README.md | 2 +- src/pages/CloudAgents/CloudKnowledge.tsx | 26 +- .../CloudKnowledgeAttachmentPicker.tsx | 280 ++++++++++++++ .../CloudAgents/CloudKnowledgeDocuments.tsx | 62 +--- .../CloudAgents/CloudKnowledgeSelector.tsx | 18 +- src/pages/CloudAgents/agent-config.css | 69 +++- tests/e2e/cloud-agent-knowledge.spec.ts | 284 ++++++++++++-- tests/unit/cloud-knowledge-import.test.tsx | 345 ++++++++++++++++++ tests/unit/cloud-knowledge-picker.test.tsx | 18 +- tests/unit/cloud-knowledge-status.test.tsx | 18 +- 11 files changed, 1078 insertions(+), 118 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20261013-knowledge-workspace-5ac9e2d.md create mode 100644 src/pages/CloudAgents/CloudKnowledgeAttachmentPicker.tsx create mode 100644 tests/unit/cloud-knowledge-import.test.tsx diff --git a/.project-docs/30-worklog/tasks/20261013-knowledge-workspace-5ac9e2d.md b/.project-docs/30-worklog/tasks/20261013-knowledge-workspace-5ac9e2d.md new file mode 100644 index 00000000..e598e29b --- /dev/null +++ b/.project-docs/30-worklog/tasks/20261013-knowledge-workspace-5ac9e2d.md @@ -0,0 +1,74 @@ +# Task: Implement split knowledge workspace and multi-file chat import then update trial + +## Identity + +- Task ID: 20261013-knowledge-workspace-5ac9e2d +- Mode: Feature +- Branch: codex/20261013-knowledge-workspace-5ac9e2d-knowledge-workspace +- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20261013-knowledge-workspace-5ac9e2d +- Base commit: 23ddce11da5c4c7aa31af81213a5aafd474cdad0 +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Implement the user-approved knowledge workspace: own-file management on the left, multi-select MakeLore/my knowledge on the right, and a clearer multi-file conversation attachment picker. Verify source changes only; the latest user correction explicitly postpones any trial update. + +## Intent And Constraints + +- Current user authorization covers implementation; the latest correction explicitly says not to update the temporary application after changes. Keep current Main/login/profile, prior accepted Agent/chat UI, resource ownership, operation recovery, saved/apply semantics, primary dirty teacher record and remote main. No desktop focus changes, no backend schema/protocol change or generated-file import. Do not prepare or activate a new trial payload, refresh/restart the application, or change its pointer. Existing Main/process/payloads must remain intact. + +## Outcome + +- Implemented stable left own-file management and right grouped multi-select knowledge panels. Small windows stack in the same order; existing draft/save/apply, ownership and selected-library semantics are preserved. +- Replaced the two import dropdowns with a dialog: searchable dated chats with agent names, searchable file metadata, cross-chat selection, explicit target, per-file results and original-operation retry. Confirmed receipts are not replayed; actual ownership loss preserves pending intent while hiding the portal. +- Added read-only catalog refresh and explicit two-step local retry abandonment, retaining server files; in-flight requests cannot be abandoned. Unsupported legacy formats and oversized attachments cannot be selected. +- README reflects current branch behavior. Product source, tests and task record are isolated on this managed feature branch. No Main/shared/backend/API contract changes. +- The latest user correction overrides the initial trial-update request: no temporary application refresh, restart, new deployment payload or pointer change was performed. Retain this checkout for later review/integration/update instructions. + +## Verification + +- Pinned corepack pnpm 10.33.4; focused existing knowledge/catalog/configuration tests: 52/52 passed. New import and adapted status tests: 27/27 passed. No real cloud mutation, native picker or model request was performed. +- Final Renderer typecheck, scoped ESLint, production build:vite and git diff --check passed. Required project docs and own-task drift checks passed. +- Final headless UI fixture: 9/9 passed in 26.6s with exit 0 using the repository-pinned Playwright 1.59 Chromium 1217 Headless Shell; no system browser channel. Covered multi-select/save/reopen, shared read-only access, own uploads, cross-chat selection, original-ID failure retry, reachable footer and explicit abandonment without deletion/replay at 1440px and 375px. The static Vite fixture, HTTP server, temporary directory, esbuild service and test browser all shut down normally; no owned test processes or temporary fixture directories remain. System Chrome had previously passed all assertions but hung on Browser.close because its stdout/stderr pipes stayed open after exit; the pinned test browser completed the same suite normally. +- Inspected generated 1440px and 375px screenshots. Desktop dialog width and layered panels, small-window stacking, scrollable content and reachable footer verified. Preview evidence is under /Users/chillishark/Makelore 麦洛/.codex-evidence/knowledge-workspace-20261013-5ac9e2d. +- Independent read-only review passed for pending intent, authorized in-flight completion, stale scope isolation, confirmed import receipts and two-step local abandonment. +- Final read-only runtime baseline checks passed: PID 7459, Main/Renderer bytes, resources, dependencies, bootstrap, rollback copies, source trees and current renderer be53e44dcf444c9b62c3107b04f079e8ffeac450 are unchanged; runtime pointer SHA256 stays 0a01f8af424b0547c00219564d3f0bb56d0f31eaee40453c52c46ab1601bbacf. Primary stays 10da39341ead9e3db17ea4395d66181f6fe4433d; teacher record SHA256 remains 734f4de9ee0c6308b10ffa614c7678a2814f81d3b1e6d46135f495dbadeac9d3. Remote main advanced externally to bf39f4071fa957704521542e1909d53b0452c5a2: the prior full release verifier now rejects its old remote-equality condition after all runtime checks pass. No remote or primary code was changed by this task; later integration must reconcile the new remote head. + +## Follow-ups + +- The user explicitly deferred temporary application update. Await a later instruction before integration/deployment; retain the feature checkout and preview evidence. +- Test fixtures establish UI behavior; this task does not include a live cloud import/indexing smoke test. + +## Promotion Candidates + +- None recorded. + +## Project Context Loaded + +Task context: +- Identity above matches successful bundled start, feature ownership and task reservation. +- 26 other owners: peer Scope/Intent/Promotion records reread read-only. Teacher consensus occupies primary main; preserve it. Older child-language task has undefined scope but no dependency here. No concrete semantic conflict; current human instruction authorizes this personal knowledge interaction change. + +Read: +- AGENTS.md, new own record, read-before-planning/planning-gate and same-base context retained from the immediately preceding knowledge import review. +- Unchanged positioning, integrated state, decisions, personal cloud Agents ADR, system/module/data flow, domain/success criteria, evidence/reflection/commitment context, README and review findings at 23ddce11. Canonical memory remains read-only. +- Design skill and design-system/React guidance; use existing Makelore cream/blue/lavender and typography rather than generic search palette recommendations. +- Latest runtime durable verifier and pointer: Main7459/Host62864, committed renderer be53e44, full prior rollback/snapshot preserved. + +Relevant understanding: +- Goal: own documents can be indexed and explicitly selected alongside readable shared knowledge for personal Agents. +- Scope: left upload/manage, right grouped multi-select, source/file/target-aware picker, batch independent file operation IDs and error recovery. +- Decisions: selected knowledge alters draft only until explicit save/apply; upload/import stores documents immediately and actual indexing/chunks determine readiness; no payer-derived access. +- Modules: CloudKnowledgeSelector, CloudKnowledge, CloudKnowledgeDocuments/new import component, scoped Agent CSS and focused unit/headless UI tests. Main services/DTO remain unchanged. +- Unknowns: current runtime pointer/hash/process will be checked unchanged at completion; no real cloud write/model smoke is required for this UI update. Backend generated delivery import remains outside this task. + +Gate result: +- Passed before implementation plan. + +## Plan + +1. Split knowledge into stable left upload/manage and right selection panels; keep loading/permission/pending safeguards and responsive stack. +2. Replace chat import dropdowns with searchable chat/file picker, supported-document selection, independent per-file results and same-operation retry. +3. Run focused behavior tests, Renderer type checks, scoped lint, production build and headless size/interaction verification. +4. Commit documented result locally; verify current runtime pointer/files/process unchanged. Trial update is deferred pending a later user instruction. diff --git a/README.md b/README.md index 9447b4ef..2c3b8814 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;「去作品广场逛逛」入口紧贴输入框上方,两者一起位于底部。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏保留「作品广场」入口,只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。 - `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回;智能体模块的侧栏只通过按钮点击或键盘激活展开,鼠标经过左侧、收纳按钮或顶栏不会展开,切换模块不沿用旧悬停预览;其他模块仍保留原预览方式。“我的智能体”使用浅紫对话机器人图标,“渠道”使用浅蓝连接节点图标。macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片恢复原有尺寸与留白,显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、工具箱(Tool)、做事手册(Skill)、MCP 服务、子智能体和知识库纵向呈现,不再设内部分类切换;不展示 Tool/Skill 解释、资源开放规则提示、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:顶部展示返回、只读头像和名称;有附件时在同一行右侧显示回形针“附件”入口和文件数量,不展示调整、保存/应用状态、客户端/微信来源、费用/版本说明或历史/新对话图标;配置由列表卡片的“调整”进入。空对话展示当前智能体头像与名称,示例只填入草稿;用户消息使用浅紫圆角气泡,助手 Markdown、表格和代码在消息区内滚动。文件或图片从输入框内添加,上传成功后以待发送卡片呈现,可移除本次携带;移除不会删除云端文件,添加不会自动发送。确认失败沿用同一文件和对话重试,发送前需完成或取消添加;支持仅发送附件,使用明确的附件提问。输入框的“+”添加文件或图片;右上角“附件”打开右侧面板,按“你上传的”和“助手生成的”分组,支持再次选用已上传附件及保存明确交付的生成文件。生成文件仅取当前对话已保存助手回复中的本地文件链接(含图片与引用式链接),核对实际文件后展示和计数;只读取链接所在的文件夹,不遍历全部工作文件。未交付的脚本、缓存和工具记录不展示、不计数,明确交付的代码照常展示;仅写了路径的旧成果可能暂时隐藏,后续接入 Yuxi 的正式交付记录。没有文件时隐藏入口;打开、关闭面板保留聊天与输入,Escape 关闭并返回入口焦点;最近对话自动恢复、审批、停止和未发送内容的离开保护保留。模型返回的思考独立于正文展示,运行时可展开、手动折叠,完成后折叠;无思考内容时不显示。重连事件去重并隔离当前运行与主对话,终态历史未到达时保留已收到的片段并有限补读;服务端返回的最终回答思考可在历史中回看,不进入后续提问。历史回看需要 Yuxi API 思考投影补丁上线,当前未部署;实时思考沿用既有事件流。头像上传、Main-owned Host API、访问隔离、费用与不可变版本规则保持;介绍留空需要配套 Yuxi DraftCreate/DraftSave 的空字符串契约。 - 高级配置的知识库支持下拉多选,分为「MakeLore知识」和「我的知识库」,已选项可单独移除。MakeLore知识来自当前账号有权读取的 Yuxi 共享目录,只显示名称与简介;移除仅取消当前智能体选用,不删除原库。我的知识库以服务端本人管理目录为准,提供「上传自己的文件」入口,可新建库或追加到已有个人库,上传后自动解析、建立索引并持续显示真实处理状态;失败可重试且保留已有可用内容。暂不可选用的个人库仍可管理文件,已有绑定保留并提示不可用;未确认请求在当前库保留同一操作身份供重试。知识库选择只改草稿,显式保存/应用后生效;个人云端执行已自动接入所选知识库的检索,无需另外勾选 knowledge-base Skill。此交互复用现有接口,无需新增后端补丁。 + 高级配置的知识库支持下拉多选,分为「MakeLore知识」和「我的知识库」,已选项可单独移除。MakeLore知识来自当前账号有权读取的 Yuxi 共享目录,只显示名称与简介;移除仅取消当前智能体选用,不删除原库。知识库区域左右分区:左侧「上传自己的文件」管理本人知识库,右侧选择「MakeLore知识」和「我的知识库」,小窗口上下排列。左侧可新建库或追加资料,提供「从电脑上传」和「从聊天选择文件」;聊天文件选择面板显示目标知识库、可搜索的来源聊天和文件名称/类型/大小,支持多选,逐文件导入并保留独立操作身份,部分失败只重试失败项;也可明确确认放弃本页重试,已保存文件仍保留。仅选本人已上传的聊天附件,聊天正文和助手生成的文件不直接入库。上传和导入后自动解析、建立索引并持续显示真实处理状态;失败可重试且保留已有可用内容。使用说明与费用以折叠说明呈现。暂不可选用的个人库仍可管理文件,已有绑定保留并提示不可用;未确认请求在当前库保留同一操作身份供重试。知识库选择只改草稿,显式保存/应用后生效;个人云端执行已自动接入所选知识库的检索,无需另外勾选 knowledge-base Skill。此交互复用现有接口,无需新增后端补丁。 云知识库的 embedding 模型只由平台管理员在 Yuxi 后台维护:在「智能体管理 → 模型供应商」启用供应商,按供应商要求填写有效的服务地址和 API Key,添加并启用 embedding 模型并填写实际向量维度;embedding 向量化费用由平台承担,不计入用户用量、词元点数或智能体费用上限;one-api 不参与 embedding 配置,Works Square 只负责其他应计费模型调用的创建者词元点数账本。客户端区分目录加载中、加载失败与无可用模型,空状态提供管理员配置说明;「刷新知识库与模型」保留未提交名称,未确认的创建请求沿用原操作身份。 diff --git a/src/pages/CloudAgents/CloudKnowledge.tsx b/src/pages/CloudAgents/CloudKnowledge.tsx index 2e95e870..a54ee14b 100644 --- a/src/pages/CloudAgents/CloudKnowledge.tsx +++ b/src/pages/CloudAgents/CloudKnowledge.tsx @@ -1,4 +1,5 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useId, useRef, useState } from 'react'; +import { Upload } from 'lucide-react'; import { cloudAgentsApi } from '@/lib/cloud-agents-api'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -27,6 +28,7 @@ export function CloudKnowledgePanel({ slug, onCreated, onDeleted, onSelected, on const [refresh, setRefresh] = useState(0); const [creating, setCreating] = useState(false); const [documentPending, setDocumentPending] = useState(false); + const targetId = useId(); const createIntent = useRef<{ operation_id: string; name: string; embedding_model: string } | null>(null); usePendingCloudInput(busy || documentPending || Boolean(createIntent.current) || (creating && Boolean(name.trim()))); const operationInFlight = useRef(false); @@ -62,15 +64,15 @@ export function CloudKnowledgePanel({ slug, onCreated, onDeleted, onSelected, on const selectedKnowledge = manageable.find(kb => kb.kb_id === kbId); const managementDisabled = disabled || busy || catalogLoading || Boolean(catalogError); const bindable = (id: string) => selectableIds === undefined || Boolean(selectableIds?.includes(id)); - return
- 上传自己的文件 -
-

选择或新建自己的知识库,再上传文件。选用只会加入当前配置,保存并应用后生效。

-

上传到知识库的文档可供获准使用此智能体的人检索。解析并建立索引后才会生效;embedding 向量化由平台承担,不计入用户用量、词元点数或智能体费用上限。知识处理如触发对话模型调用,仍按原模型实际用量计费。

+ return
+
+
+

选择或新建自己的知识库,再从电脑或聊天中添加资料。选用会加入右侧配置,保存并应用后生效。

+ {error &&

{error}

} {catalogError &&

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

}
- setThreadQuery(event.target.value)} />
+
+ {threadsLoading ?

正在读取对话…

: threadError ?

对话读取失败:{threadError}

+ : !threads.length ?

还没有个人智能体对话。

: !visibleThreads.length ?

没有匹配的对话。

: visibleThreads.map(thread => )} +
+
+
+

{currentThread?.title || '选择文件'}

+
+
+ {!threadId ?

先选择一段对话,再挑选其中的文件。

: filesLoading ?

正在读取文件…

+ : fileError ?

文件读取失败:{fileError}

+ : !files.items.length ?

这段对话还没有上传文件。

: !visibleFiles.length ?

没有匹配的文件。

: visibleFiles.map(file => { + const details = fileDetails(file), key = JSON.stringify([threadId, file.file_id]); + return ; + })} +
+
+
} + {selected.length > 0 &&

已选 {selected.length} 个文件{locked ? '。' : ',可继续切换对话选择。'}

+
    {selected.map(item =>
  • {item.file.file_name}{item.threadTitle} +
  • )}
+
} + {jobs.length > 0 && } + {abandoning &&
+

这只会结束当前页面的重试。已保存的文件会保留,可刷新知识库确认。

+
+
} + +
+

最大 5 MB;支持文本、Word、Excel、文本 PDF。导入后自动处理,可用于回答后才能检索。

+ {onRefreshKnowledge && } + {unresolved && !running && !abandoning && } + +
+ {completed > 0 &&

已导入 {completed} 个文件,处理状态见知识库。

} + ; +} diff --git a/src/pages/CloudAgents/CloudKnowledgeDocuments.tsx b/src/pages/CloudAgents/CloudKnowledgeDocuments.tsx index 14e0ac64..1a778f84 100644 --- a/src/pages/CloudAgents/CloudKnowledgeDocuments.tsx +++ b/src/pages/CloudAgents/CloudKnowledgeDocuments.tsx @@ -3,7 +3,8 @@ import { CheckCircle2, CircleAlert, FileText, Loader2 } from 'lucide-react'; import { cloudAgentsApi } from '@/lib/cloud-agents-api'; import { Button } from '@/components/ui/button'; import { usePendingCloudInput } from './CloudPending'; -import type { CloudKnowledgeFile, CloudThread, CloudAttachment } from '../../../shared/cloud-agents'; +import { CloudKnowledgeAttachmentPicker } from './CloudKnowledgeAttachmentPicker'; +import type { CloudKnowledgeFile } from '../../../shared/cloud-agents'; function documentState(file: CloudKnowledgeFile) { const task = file.processing_task; @@ -47,11 +48,11 @@ function documentState(file: CloudKnowledgeFile) { return { label: '状态待确认', detail: '请刷新状态后再操作。', stage: 0 }; } -export function CloudKnowledgeDocuments({ slug, kbId, busy, act, onDeleteKnowledge, canRead = true, onPendingChange }: { - slug: string; kbId: string; busy: boolean; +export function CloudKnowledgeDocuments({ slug, kbId, kbName = '当前知识库', busy, act, onDeleteKnowledge, canRead = true, onPendingChange, onRefreshKnowledge }: { + slug: string; kbId: string; kbName?: string; busy: boolean; act: (operation: () => Promise) => Promise; onDeleteKnowledge: () => void; - canRead?: boolean; onPendingChange?: (pending: boolean) => void; + canRead?: boolean; onPendingChange?: (pending: boolean) => void; onRefreshKnowledge?: () => void; }) { const [files, setFiles] = useState([]); const [pageCount, setPageCount] = useState(1); @@ -154,14 +155,14 @@ export function CloudKnowledgeDocuments({ slug, kbId, busy, act, onDeleteKnowled const active = states.some(state => state.active) || submitting !== null; return
- - + +

上传后自动解析并建立索引。最大 5 MB;支持 TXT、Markdown、CSV、JSON、Word、Excel、文本 PDF。

{uploading &&

{uploading}

} - {importing && { setImporting(false); await acceptFile(file); }} />} + {importing && setImporting(false)} onRefreshKnowledge={onRefreshKnowledge} />}
{files.length ? '已显示 ' + files.length + ' 份文档 · ' + states.filter(state => state.ready).length + ' 份可用' : '知识文档'} {active ? '处理中 · 状态自动更新' : '状态自动更新'} @@ -215,52 +216,11 @@ export function CloudKnowledgeDocuments({ slug, kbId, busy, act, onDeleteKnowled {deleting &&

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

}
; } - -function AttachmentImport({ slug, kbId, busy, act, onImported, onPendingChange }: { - slug: string; kbId: string; busy: boolean; act: (operation: () => Promise) => Promise; - onImported: (file: CloudKnowledgeFile) => Promise; - onPendingChange: (pending: boolean) => void; -}) { - const [threads, setThreads] = useState([]); - const [thread, setThread] = useState(''); - const [files, setFiles] = useState([]); - const [file, setFile] = useState(''); - const [error, setError] = useState(''); - const operation = useRef(null); - const unresolved = Boolean(operation.current); - useEffect(() => { onPendingChange(unresolved); return () => onPendingChange(false); }, [onPendingChange, unresolved]); - 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(() => { - let live = true; setFiles([]); setFile(''); - if (thread) 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}

} -
; -} diff --git a/src/pages/CloudAgents/CloudKnowledgeSelector.tsx b/src/pages/CloudAgents/CloudKnowledgeSelector.tsx index 65ce7dd9..9888fb2e 100644 --- a/src/pages/CloudAgents/CloudKnowledgeSelector.tsx +++ b/src/pages/CloudAgents/CloudKnowledgeSelector.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { X } from 'lucide-react'; +import { Library, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { CloudKnowledgePanel, type CloudOwnedKnowledgeCatalog } from './CloudKnowledge'; import type { CloudResource } from '../../../shared/cloud-agents'; @@ -16,7 +16,15 @@ export function CloudKnowledgeSelector({ slug, options, selected, disabled, load const unknown = ownedIds === null ? available : []; const selectionFull = selected.length >= 100; const add = (id: string) => { if (!selectionFull && !selected.includes(id)) onChange([...selected, id]); }; - return
+ return
+ option.key)} + onSelected={kb => { if (!loading && !error && options.some(option => option.key === kb.kb_id)) { add(kb.kb_id); onRefresh(); } }} + onDeleted={id => { onChange(selected.filter(value => value !== id)); onRefresh(); }} + onCreated={kb => { add(kb.kb_id); onRefresh(); }} /> +
+
+

从 MakeLore知识和我的知识库中选择,可同时使用多个。