# Agent 组件开发问答(Q&A) > 目录范围:`src/components/agent` > > 涵盖文件: > - `AgentExpertCenterPage.tsx` > - `AgentExpertEditPage.tsx` > - `AgentChatPage.tsx` > - `SkillCreatorPage.tsx` > - `SkillsWorkshop.tsx` > - `SkillCreatorThreadSidebar.tsx` > - `AgentConversationList.tsx` > - `SkillCreatorConversationItem.tsx` > - `ChatInputComposer.tsx` > - `AgentChatPage.css` --- ## Q1:这个目录整体是干什么的? **A:这是“专家 Agent + 技能工坊 + 技能创建对话”的前端 UI 组件集合。** - `AgentExpertCenterPage`:专家列表入口(我的专家/发布专家)。 - `AgentExpertEditPage`:专家配置编辑页(左侧对话预览 + 右侧配置)。 - `AgentChatPage`:某个专家的真实聊天页(含线程、文件上传、流式回答)。 - `SkillsWorkshop`:技能资产中心(搜索/筛选/接入/发布/删除/编辑)。 - `SkillCreatorPage`:技能创建/改写专用对话页(支持文件树预览和保存弹窗)。 - `SkillCreatorThreadSidebar`:复用的会话侧边栏(获取线程、新建、删除、切换)。 - `AgentConversationList` + `SkillCreatorConversationItem`:统一消息列表与消息卡片渲染。 - `ChatInputComposer`:统一输入框(发送/停止/上传)。 - `AgentChatPage.css`:`AgentChatPage` 专用动画和滚动条样式。 --- ## Q2:核心共享数据模型有哪些? **A:对话层主要围绕 `ConversationMessage`。** - 定义位置:`AgentConversationList.tsx` - 字段: - `id`: 消息唯一标识。 - `role`: `'user' | 'assistant'` - `content`: 文本内容(markdown 渲染)。 - `steps`: 思考步骤列表(数据来自 `data-thinking-step`)。 - `thinkings`: 推理内容列表(来自 `reasoning-*` 事件)。 - `toolCalls`: 工具调用(输入输出)列表。 **A:文件树使用 `FileNode`。** - 在 `AgentChatPage`、`SkillCreatorPage` 内部定义并使用。 - 字段:`name/path/type/size/extension/children`。 **A:线程侧边栏使用 `SkillCreatorThread`。** - 定义位置:`SkillCreatorThreadSidebar.tsx` - 字段:`id/title/archived/expert_agent_id/created_at/updated_at`。 --- ## Q3:整个目录的“数据流主干”怎么走? **A:可以看成 3 条主链路。** ### 1) 专家链路(Center -> Edit -> Chat) 1. `AgentExpertCenterPage` 拉专家列表(我的/发布)。 2. 点击“编辑”跳 `AgentExpertEditPage`,加载专家详情 + 工具 + 技能绑定状态。 3. 点击“进行对话”跳 `AgentChatPage`,加载该专家详情和对应会话消息。 ### 2) 技能链路(Workshop -> SkillCreator) 1. `SkillsWorkshop` 拉技能列表(official/mine),本地映射成 UI `Skill`。 2. 新建技能:创建会话后跳 `SkillCreatorPage?id=threadId`。 3. AI 辅助编辑已有技能:先创建线程,再 copy skill 到线程,再跳转 `SkillCreatorPage`。 ### 3) 对话链路(输入 -> SSE -> UI -> 持久化) 1. `ChatInputComposer` 触发 `onSend`。 2. 页面组件(`AgentChatPage` 或 `SkillCreatorPage`)执行 `handleSendMessage`。 3. 先写入用户消息 + assistant 占位消息。 4. 调流式接口读取 SSE: - `data-thinking-step` -> 更新 `steps` - `reasoning-start/delta/end` -> 更新 `thinkings` - `tool-input-available/tool-output-available` -> 更新 `toolCalls` - `text-delta` -> 追加 `content` 5. 完成后将 assistant 结果再次 POST 到消息接口持久化。 --- ## Q4:`AgentExpertCenterPage` 组件是干什么的? **A:专家资产“总览入口”,负责展示列表和基础操作入口。** ### 组件职责 - 展示“我的专家”和“发布专家”两种视图。 - 打开专家详情弹窗(支持发布状态切换)。 - 提供“编辑”和“进行对话”跳转入口。 ### 数据来源与接口 - 列表查询: - `POST /api/v1/expert-agents/list`(按 `is_draft`) - 我的专家场景还会额外 `GET /api/v1/expert-agents` 做合并去重。 - 状态切换: - `PATCH /api/v1/expert-agents/{id}`(更新 `is_draft`) ### 数据流 - `activeTab` 改变 -> `fetchExpertList` 触发 -> 更新 `agentList`。 - `agentList` -> `cards(useMemo)` -> 渲染卡片。 - 卡片按钮: - 编辑 -> `navigate('/qa-center/agent-expert-edit?id=...')` - 对话 -> `navigate('/qa-center/agent-chat?id=...')` ### 维护注意 - “创建专家”按钮目前是 UI 壳子,未接创建接口。 - “删除”按钮当前仅 `console.log`,未接真实删除逻辑。 --- ## Q5:`AgentExpertEditPage` 组件是干什么的? **A:专家配置编辑工作台,同时内置一个“测试对话区”。** ### 组件职责 - 左侧:和该专家进行流式对话(非真实聊天线程,使用 no_real 线程)。 - 右侧:编辑专家基础配置(名称、模型、描述、系统提示词)。 - 管理工具绑定、技能绑定(弹窗操作)。 ### 关键状态 - 对话相关:`messages/inputValue/threadId/isStreaming` - 配置相关:`formValues` - 工具相关:`toolOptions/selectedTools/pendingTools/boundToolIdMap` - 技能相关:`skillOptions/selectedSkills/boundSkillMap/skillKeyword` ### 主要接口 - 专家详情:`GET /api/v1/expert-agents/{agentId}` - 保存专家:`PUT /api/v1/expert-agents/{agentId}` - 绑定工具: - 查询:`GET /api/v1/expert-agents/{agentId}/tools` - 添加:`POST /api/v1/expert-agents/{agentId}/tools` - 删除:`DELETE /api/v1/expert-agents/{agentId}/tools/{toolId}` - 绑定技能: - 查询:`GET /api/v1/expert-agents/{agentId}/skills` - 添加:`POST /api/v1/expert-agents/{agentId}/skills` - 删除:`DELETE /api/v1/expert-agents/{agentId}/skills/{skillId}` - 测试对话: - 线程:`GET /api/v1/expert-agent-chat/threads/no_real/{agentId}` - 用户消息:`POST /api/v1/expert-agent-chat/messages` - SSE:`POST /api/v1/expert-agent-chat/run/stream/help_expert` ### 数据流(对话) - 用户发送 -> append user msg -> 保证 threadId -> 保存用户消息 -> append assistant 占位 -> SSE 增量更新。 ### 维护注意 - 右侧 `preview` 区底部 `ChatInputComposer` 是只读展示,不参与真实请求。 - 工具与技能弹窗是“前端状态 + 后端同步”双层状态,调试时优先检查 refresh 查询是否成功。 --- ## Q6:`AgentChatPage` 组件是干什么的? **A:专家“真实聊天页”,并带线程管理、文件上传和文件预览。** ### 组件职责 - 根据 URL `id` 读取专家详情。 - 管理当前线程(localStorage 持久化)。 - 获取历史消息并转换为统一消息结构。 - 流式发送消息并渲染思考过程。 - 管理会话文件(上传、树形结构、预览、侧栏缩放)。 ### 关键状态 - 会话:`currentThreadId/messages/inputValue/isStreaming/isLoading` - UI:`isThreadSidebarOpen/autoScrollEnabled/showScrollButton` - 文件:`fileList/fileListThreadId/isFileSidebarOpen/expandedPaths/selectedFile/filePreview*` ### 主要接口 - 专家详情:`GET /api/v1/expert-agents/{agentId}` - 会话消息: - 历史:`GET /api/v1/expert-agent-chat/threads/{id}/messages` - 保存用户:`POST /api/v1/expert-agent-chat/messages` - SSE:`POST /api/v1/expert-agent-chat/run/stream` - 保存助手:`POST /api/v1/expert-agent-chat/messages` - 文件: - 列表:`GET /api/v1/expert-agent-chat/threads/{id}/files/all` - 上传:`POST /api/v1/expert-agent-chat/threads/{id}/files/upload` - 预览:`GET /api/v1/expert-agent-chat/threads/{id}/files/preview?path=...` ### 关键行为细节 - **发送即出现 assistant 占位**:点击发送后立即插入 assistant 空消息,保证用户立刻看到“连接中”态。 - **连接中显示规则**:由 `AgentConversationList` 判断“最后一条 assistant 且还没有任何生成内容”才显示连接状态。 - **失败回滚**:发送或流式失败时移除该 assistant 占位,避免残留空卡片。 - **停止流式**:`AbortController` 中断请求,并清理 loading 态字段。 ### 维护注意 - `threadStorageKey` 与 `agentId` 强绑定,切换专家会切换独立 thread 记忆。 - `normalizeHistoryContent` 是历史消息兼容核心,后端 content 结构变更时先改这里。 --- ## Q7:`SkillCreatorPage` 组件是干什么的? **A:技能创建/重构专用对话页,结构上是 `AgentChatPage` 的“技能场景定制版”。** ### 组件职责 - 从 query/localStorage 同步当前线程 ID。 - 加载技能会话历史与会话文件。 - 走技能专属流式接口输出思考步骤/推理/tool call/文本。 - 提供“保存技能”弹窗(当前主要是 UI + 本地状态)。 ### 与 `AgentChatPage` 的主要差异 - 线程 ID 同步到 URL(`useSearchParams`)。 - 有独立 `isConnecting` 状态,用于连接中阶段展示。 - 流式接口是:`POST /api/v1/expert-agent-chat/skills-agent` - 页面内注入了 dot/shimmer 动画样式(供消息连接中态使用)。 ### 主要接口 - 会话消息:`GET /threads/{id}/messages` - 流式:`POST /skills-agent` - 保存 assistant:`POST /messages` - 文件:`GET all` / `POST upload` / `GET preview` ### 数据流 - 发消息 -> append 用户 + assistant 占位 -> `isConnecting=true` -> 收到首个流块后 `isConnecting=false` -> 增量刷新 assistant -> 持久化。 ### 维护注意 - 保存技能按钮目前仅 `console.log` 输出技能名和描述,未接正式后端保存接口。 --- ## Q8:`SkillsWorkshop` 组件是干什么的? **A:技能资产管理页(发现、筛选、接入、发布、删除、进入编辑)。** ### 组件职责 - 拉取 `official` 与 `mine` 技能,映射成统一 UI 结构。 - 支持搜索、类型筛选、分类筛选。 - 支持官方技能接入到“我的技能”。 - 支持发布、删除、编辑(编辑弹窗支持 AI 编辑和上传覆盖)。 - 支持“新建技能体”:Agent 对话创建 / 上传文件创建。 ### 主要接口 - 列表:`GET /api/v1/skills?type=official|mine` - 发布:`POST /api/v1/expert-agents/skills/publish` - 删除我的技能:`DELETE /api/v1/skills/mine/{skillId}` - 上传覆盖:`PUT /api/v1/skills/{skillId}/upload` - 接入官方技能:`POST /api/v1/skills/mine/add` - 上传创建:`POST /api/v1/skills/upload` - Agent 创建线程:`POST /api/v1/expert-agent-chat/threads` - AI 编辑复制技能:`POST /threads/{threadId}/copy-skill/{skillId}` ### 数据流 - filter/search 改变 -> `useEffect` 重新拉取技能 -> 本地去重并缓存到 `skills`。 - 卡片编辑 -> `editingSkill` 打开弹窗 -> 选择 AI 编辑时创建线程后跳 `SkillCreatorPage`。 ### 维护注意 - `inferCategory` 是关键词规则映射,新增技能类型时可优先扩展该规则。 - “进入”按钮目前使用 `skill.id` 跳 `skill-creator`,若后端语义改成 threadId 需调整。 --- ## Q9:`SkillCreatorThreadSidebar` 组件是干什么的? **A:会话线程管理的复用侧边栏。** ### 组件职责 - 打开时加载会话列表。 - 新建会话并自动选中。 - 删除会话并在当前会话被删时自动回退到其它会话。 - 支持线程过滤(如仅展示当前专家线程)。 ### 输入输出(Props 视角) - 输入:`authedFetch/currentThreadId/isOpen/isBusy/threadFilter/createThreadPayload...` - 输出: - `onThreadSelect(threadId)` - `onClose()` ### 数据流 - `isOpen` 从 false -> true:触发 `refreshThreads`。 - 新建成功:更新本地列表并触发 `onThreadSelect`。 - 删除成功:本地移除;若删的是当前线程,自动选择 fallback。 ### 维护注意 - `createThreadPayload` 是高复用扩展点,场景页可注入 `expert_agent_id/title` 等字段。 --- ## Q10:`AgentConversationList` 组件是干什么的? **A:统一遍历消息并决定“连接中状态”是否显示。** ### 核心逻辑 - 逐条渲染 `SkillCreatorConversationItem`。 - 只在以下条件满足时给最后一条 assistant 传 `isConnecting=true`: - 页面处于连接/流式阶段; - 当前消息是最后一条 assistant; - 且还没有任何实际生成内容(`content/steps/thinkings/toolCalls` 都为空)。 ### 价值 - 将“连接中占位显示时机”从页面组件抽离,统一控制,减少重复判断代码。 --- ## Q11:`SkillCreatorConversationItem` 组件是干什么的? **A:单条消息卡片渲染器(用户消息 + 助手多面板消息)。** ### 渲染结构 - 用户消息:右侧气泡 + user avatar。 - 助手消息:左侧 avatar + 多段内容: - `StepsSummary` - `ThinkingPanel[]` - `ToolCallPanel[]` - 连接中动画文案(`正在连接技能服务...`) - Markdown 正文 ### 数据来源 - 完全由父级传入 `message` 和 `isConnecting` 控制,不直接请求后端。 ### 维护注意 - 此组件是所有对话页的统一呈现层;若改动 message schema,要同步更新这里的各面板。 --- ## Q12:`ChatInputComposer` 组件是干什么的? **A:通用输入组件,封装输入、发送/停止、上传文件。** ### 输入输出(Props) - 输入:`value/disabled/readOnly/isSending/isDarkMode/showUpload/placeholder...` - 回调: - `onChange(value)` - `onSend()` - `onStop()` - `onUploadFile(file)` ### 关键交互 - Enter(非 Shift)触发发送(前提:`submitEnabled` 且非 `isSending`)。 - 发送按钮在发送中切换为停止按钮(`onClick={isSending ? onStop : onSend}`)。 - 文件上传通过隐藏 `input[type=file]` + 按钮触发。 ### 维护注意 - 当前实现里文本框只受 `disabled` 控制,不再因为 `isSending` 禁止输入。 - 上传按钮仍受 `disabled || !onUploadFile` 控制。 --- ## Q13:`AgentChatPage.css` 主要做了什么? **A:提供 `AgentChatPage` 的辅助样式。** - `.agent-resizing`:拖拽文件侧栏时全局 cursor/user-select 控制。 - `.agent-file-tree-scroll` / `.agent-file-preview-scroll`:滚动条样式统一。 - `.agent-file-markdown-preview`:markdown 首尾边距和图片样式收敛。 - `.animate-breathing` 等动画类:预留呼吸动效。 --- ## Q14:典型业务流程:发送一条消息时,数据如何传输? **A:以 `AgentChatPage` 为例。** 1. 用户在 `ChatInputComposer` 输入后点击发送。 2. `handleSendMessage` 校验 `text/threadId/isStreaming`。 3. 立即写入: - user message - assistant 占位 message(空内容) 4. 调 `POST /messages` 落用户消息。 5. 调 `POST /run/stream` 开始 SSE。 6. 每个 SSE event 增量更新 assistant message: - step -> `steps` - reasoning -> `thinkings` - tool -> `toolCalls` - text -> `content` 7. 流结束后将聚合结果组装成 `persistedContent`,再 `POST /messages` 落库。 8. 刷新文件列表(对可能新增输出文件做同步)。 --- ## Q15:典型业务流程:从技能工坊进入技能创建页,数据如何传输? **A:两种入口。** ### 入口 A:新建技能(Agent 构建) 1. `SkillsWorkshop.handleCreateSkillByAgent` 创建线程。 2. 将 `threadId` 写入 localStorage。 3. 跳转 `navigate('/qa-center/skill-creator?id=threadId')`。 4. `SkillCreatorPage` 用 query/localStorage 同步 `currentThreadId`,然后拉消息和文件。 ### 入口 B:AI 辅助编辑已有技能 1. 创建线程。 2. `POST /threads/{threadId}/copy-skill/{skillId}` 把技能内容复制到线程工作区。 3. 跳转 SkillCreator 页面,后续在线程中对话编辑。 --- ## Q16:如果要新增一个“对话页”,建议复用哪些组件? **A:建议最小复用组合如下。** - 消息列表:`AgentConversationList` - 单消息卡片:`SkillCreatorConversationItem`(自动包含 steps/thinking/tool 等面板) - 输入框:`ChatInputComposer` - 线程侧边栏:`SkillCreatorThreadSidebar` **页面层只负责:** - 状态管理(`messages/input/isStreaming/currentThreadId`) - 接口请求(历史 + SSE + 持久化) - 事件映射(把后端事件转为 `ConversationMessage` 结构) --- ## Q17:维护时最容易踩坑的点有哪些? **A:建议优先关注这 8 个点。** 1. **SSE 事件兼容性**:后端 event type 或字段名变化会直接导致 UI 不更新。 2. **历史内容兼容**:`normalizeHistoryContent` 没跟上 content schema 会出现历史消息空白。 3. **线程切换中断**:切换线程时要记得 abort 流并清状态,避免串流到新线程。 4. **占位消息回收**:发送失败/流失败需移除 assistant 占位,避免残留空卡片。 5. **threadId 语义一致性**:某些页面参数传 skillId/agentId,需避免误当 threadId 使用。 6. **工具/技能绑定状态双源**:本地状态和后端状态不同步时,优先以 refresh 查询结果为准。 7. **上传入口 accept 限制**:上传文件类型修改时要同步后端校验和 UI 提示。 8. **深色主题样式**:该目录深色/浅色分支很多,改样式需双主题回归。 --- ## Q18:后续文档扩展建议 **A:当前文档适合开发入门,下一步可补充“接口契约版”。** - 补完整 SSE event JSON 示例(每种事件一例)。 - 增加错误码到 UI 提示映射表。 - 补一份“页面状态机图”(Idle / Connecting / Streaming / Stopped / Error)。 - 给每个页面补“可测点清单”(单测/联调/回归)。 --- ## Q19:能否给一份可联调的“接口契约附录”? **A:可以,以下按模块整理。** ### 1) 专家模块(Expert) - 查询专家列表 - `POST /api/v1/expert-agents/list` - 请求体: - `is_draft: boolean` - 响应(数组项): - `id/name/avatar_url/description/model_name/is_draft/user_id/created_at` - 查询专家详情 - `GET /api/v1/expert-agents/{agentId}` - 响应关键字段: - `id/name/description/system_prompt/model_name` - 更新专家配置 - `PUT /api/v1/expert-agents/{agentId}` - 请求体关键字段: - `name` - `description` - `system_prompt` - `model_name` - `avatar_url`(当前传 `null`) - 切换草稿/发布状态 - `PATCH /api/v1/expert-agents/{agentId}` - 请求体: - `is_draft: boolean` ### 2) 线程与消息模块(Chat Thread + Message) - 查询线程列表 - `GET /api/v1/expert-agent-chat/threads` - 响应: - `threads: SkillCreatorThread[]` - `archived_threads: SkillCreatorThread[]` - 新建线程 - `POST /api/v1/expert-agent-chat/threads` - 请求体常见字段: - `title` - `archived` - `is_real_chat` - `expert_agent_id`(专家聊天场景) - 删除线程 - `DELETE /api/v1/expert-agent-chat/threads/{threadId}` - 查询线程消息历史 - `GET /api/v1/expert-agent-chat/threads/{threadId}/messages` - 响应消息项关键字段: - `id/thread_id/role/content/created_at` - 保存消息(用户或助手) - `POST /api/v1/expert-agent-chat/messages` - 请求体(用户消息): - `thread_id` - `role: "user"` - `content: { type: "text", text: string }` - 请求体(助手消息): - `thread_id` - `role: "assistant"` - `content: persistedContent[]` ### 3) 流式回复模块(SSE) - 专家聊天流式 - `POST /api/v1/expert-agent-chat/run/stream` - 请求体关键字段: - `chat_id` - `expert_agent_id` - `user_query` - `messages` - 专家编辑页测试流式 - `POST /api/v1/expert-agent-chat/run/stream/help_expert` - 技能创建流式 - `POST /api/v1/expert-agent-chat/skills-agent` - 请求体关键字段: - `chat_id` - `user_query` ### 4) 文件模块(线程文件) - 查询线程文件树 - `GET /api/v1/expert-agent-chat/threads/{threadId}/files/all` - 上传文件 - `POST /api/v1/expert-agent-chat/threads/{threadId}/files/upload` - form-data: - `file` - 文件预览 - `GET /api/v1/expert-agent-chat/threads/{threadId}/files/preview?path=...` - 兼容响应: - 文本 - JSON 字符串 - JSON 对象中的 `content` ### 5) 技能模块(Skills) - 查询技能列表 - `GET /api/v1/skills?type=official|mine|all&keyword=&page=1&page_size=100` - 发布技能 - `POST /api/v1/expert-agents/skills/publish` - 请求体: - `skill_id` - 添加官方技能到我的技能 - `POST /api/v1/skills/mine/add` - 请求体: - `official_skill_id` - 删除我的技能 - `DELETE /api/v1/skills/mine/{skillId}` - 上传技能文件创建 - `POST /api/v1/skills/upload` - 上传覆盖已有技能 - `PUT /api/v1/skills/{skillId}/upload` ### 6) 专家绑定工具/技能 - 工具 - 查询:`GET /api/v1/expert-agents/{agentId}/tools` - 绑定:`POST /api/v1/expert-agents/{agentId}/tools`(`tool_name`) - 解绑:`DELETE /api/v1/expert-agents/{agentId}/tools/{toolId}` - 技能 - 查询:`GET /api/v1/expert-agents/{agentId}/skills` - 绑定:`POST /api/v1/expert-agents/{agentId}/skills`(`skill_id`) - 解绑:`DELETE /api/v1/expert-agents/{agentId}/skills/{skillId}` --- ## Q20:SSE 事件规范可以补全吗? **A:可以,以下是前端当前实现依赖的事件集合。** ### 1) 事件封包格式 - 每行以 `data: ` 开头 - 正文为 JSON 字符串 - 结束标记:`data: [DONE]` ### 2) 前端识别的事件类型 - `data-thinking-step` - 用途:更新执行步骤面板 - 关键字段: - `data.id` - `data.title` - `data.status`(`completed` 或其它) - `data.items[]` - `reasoning-start` - 用途:开始一段思考 - 行为:创建新的 thinking 段,记录开始时间 - `reasoning-delta` - 用途:思考内容增量 - 关键字段: - `delta` - `reasoning-end` - 用途:结束当前思考段 - 行为:计算该段耗时并写入 `duration` - `tool-input-available` - 用途:记录工具调用入参 - 关键字段: - `toolCallId` - `toolName` - `input` - `tool-output-available` - 用途:记录工具调用输出 - 关键字段: - `toolCallId` - `output` - `text-delta` - 用途:助手正文 token 增量 - 关键字段: - `delta` ### 3) 推荐后端保证 - 同一次回复里的 `toolCallId` 全局唯一。 - `reasoning-start` 与 `reasoning-end` 成对(允许无 `delta`)。 - `data-thinking-step` 同一个 `id` 可重复更新状态。 - `text-delta` 在结束前允许为空片段,但尽量减少空片段。 ### 4) 前端容错策略(已实现) - JSON parse 失败:跳过该行,不中断整流。 - 未识别 event.type:忽略。 - `output` 非字符串:尝试 `result` 字段,否则 `JSON.stringify`。 - `AbortError`:按“用户主动停止”处理,不弹致命错误。 --- ## Q21:可以给一份页面级测试清单吗? **A:可以,以下清单按页面拆分,适合联调和回归。** ### A. `AgentChatPage` 测试清单 - 打开页面后,能加载专家信息(名称、描述)。 - 未选线程时,空态正确显示;选中线程后历史消息正确渲染。 - 点击发送后立即出现 assistant 占位卡片,显示连接中状态。 - 首个有效流式事件到达后,连接中占位消失,转为正常内容增量渲染。 - 发送失败/流失败时,assistant 占位会被回收,不留空白卡片。 - 点击停止后,流式中断,按钮状态恢复,后续不再追加内容。 - 上传文件后文件树刷新;文件预览支持 text/json 两类返回。 - 切换线程时会中断当前流,且不会把旧线程流内容写到新线程。 ### B. `SkillCreatorPage` 测试清单 - URL `id` 与 localStorage 线程 ID 同步正确。 - 新线程空态和快捷提示按钮显示正常。 - 发送后 `isConnecting=true`,收到首包后 `isConnecting=false`。 - steps/thinking/tool/text 四类流式内容都能正确出现。 - 线程切换、停止流、文件上传/预览路径行为与 `AgentChatPage` 一致。 - 保存技能弹窗开关正常(当前为 UI 行为)。 ### C. `AgentExpertEditPage` 测试清单 - 进入页面后基础信息(name/model/description/system_prompt)能回填。 - 保存专家配置后接口成功,按钮 loading 状态恢复。 - 工具弹窗:勾选新增 -> 确认添加 -> 列表刷新一致。 - 技能弹窗:绑定/解绑/一键绑定后状态与后端一致。 - 左侧测试对话能正常流式返回,滚动与发送状态正确。 ### D. `SkillsWorkshop` 测试清单 - `all`/`mine` 切换结果正确,搜索词可生效。 - 分类筛选(writing/analysis/decision/validation/workflow)正确过滤。 - 官方技能“+”接入后卡片状态转“已接入”。 - 我的技能菜单中的发布/删除操作正确执行。 - 编辑弹窗中 AI 编辑路径可成功创建线程并跳转 SkillCreator。 - 上传创建、上传覆盖两条路径成功后状态正确恢复(含 input 清空)。 ### E. `AgentExpertCenterPage` 测试清单 - mine/published 标签切换后列表正确变化。 - mine 模式会按当前用户过滤,且与全量接口合并去重后正确展示。 - 详情弹窗中的发布开关可切换并刷新列表。 - 编辑、对话跳转路径参数正确。 ### F. 主题与可用性回归 - 深色/浅色主题下对话气泡、按钮、边框、hover 都可读。 - 长会话场景滚动性能可接受,自动滚动按钮行为符合预期。 - 键盘发送(Enter)与换行(Shift+Enter)行为正确。