25 KiB
Agent 组件开发问答(Q&A)
目录范围:
src/components/agent涵盖文件:
AgentExpertCenterPage.tsxAgentExpertEditPage.tsxAgentChatPage.tsxSkillCreatorPage.tsxSkillsWorkshop.tsxSkillCreatorThreadSidebar.tsxAgentConversationList.tsxSkillCreatorConversationItem.tsxChatInputComposer.tsxAgentChatPage.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)
AgentExpertCenterPage拉专家列表(我的/发布)。- 点击“编辑”跳
AgentExpertEditPage,加载专家详情 + 工具 + 技能绑定状态。 - 点击“进行对话”跳
AgentChatPage,加载该专家详情和对应会话消息。
2) 技能链路(Workshop -> SkillCreator)
SkillsWorkshop拉技能列表(official/mine),本地映射成 UISkill。- 新建技能:创建会话后跳
SkillCreatorPage?id=threadId。 - AI 辅助编辑已有技能:先创建线程,再 copy skill 到线程,再跳转
SkillCreatorPage。
3) 对话链路(输入 -> SSE -> UI -> 持久化)
ChatInputComposer触发onSend。- 页面组件(
AgentChatPage或SkillCreatorPage)执行handleSendMessage。 - 先写入用户消息 + assistant 占位消息。
- 调流式接口读取 SSE:
data-thinking-step-> 更新stepsreasoning-start/delta/end-> 更新thinkingstool-input-available/tool-output-available-> 更新toolCallstext-delta-> 追加content
- 完成后将 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 + 多段内容:
StepsSummaryThinkingPanel[]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 为例。
- 用户在
ChatInputComposer输入后点击发送。 handleSendMessage校验text/threadId/isStreaming。- 立即写入:
- user message
- assistant 占位 message(空内容)
- 调
POST /messages落用户消息。 - 调
POST /run/stream开始 SSE。 - 每个 SSE event 增量更新 assistant message:
- step ->
steps - reasoning ->
thinkings - tool ->
toolCalls - text ->
content
- step ->
- 流结束后将聚合结果组装成
persistedContent,再POST /messages落库。 - 刷新文件列表(对可能新增输出文件做同步)。
Q15:典型业务流程:从技能工坊进入技能创建页,数据如何传输?
A:两种入口。
入口 A:新建技能(Agent 构建)
SkillsWorkshop.handleCreateSkillByAgent创建线程。- 将
threadId写入 localStorage。 - 跳转
navigate('/qa-center/skill-creator?id=threadId')。 SkillCreatorPage用 query/localStorage 同步currentThreadId,然后拉消息和文件。
入口 B:AI 辅助编辑已有技能
- 创建线程。
POST /threads/{threadId}/copy-skill/{skillId}把技能内容复制到线程工作区。- 跳转 SkillCreator 页面,后续在线程中对话编辑。
Q16:如果要新增一个“对话页”,建议复用哪些组件?
A:建议最小复用组合如下。
- 消息列表:
AgentConversationList - 单消息卡片:
SkillCreatorConversationItem(自动包含 steps/thinking/tool 等面板) - 输入框:
ChatInputComposer - 线程侧边栏:
SkillCreatorThreadSidebar
页面层只负责:
- 状态管理(
messages/input/isStreaming/currentThreadId) - 接口请求(历史 + SSE + 持久化)
- 事件映射(把后端事件转为
ConversationMessage结构)
Q17:维护时最容易踩坑的点有哪些?
A:建议优先关注这 8 个点。
- SSE 事件兼容性:后端 event type 或字段名变化会直接导致 UI 不更新。
- 历史内容兼容:
normalizeHistoryContent没跟上 content schema 会出现历史消息空白。 - 线程切换中断:切换线程时要记得 abort 流并清状态,避免串流到新线程。
- 占位消息回收:发送失败/流失败需移除 assistant 占位,避免残留空卡片。
- threadId 语义一致性:某些页面参数传 skillId/agentId,需避免误当 threadId 使用。
- 工具/技能绑定状态双源:本地状态和后端状态不同步时,优先以 refresh 查询结果为准。
- 上传入口 accept 限制:上传文件类型修改时要同步后端校验和 UI 提示。
- 深色主题样式:该目录深色/浅色分支很多,改样式需双主题回归。
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}- 请求体关键字段:
namedescriptionsystem_promptmodel_nameavatar_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- 请求体常见字段:
titlearchivedis_real_chatexpert_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_idrole: "user"content: { type: "text", text: string }
- 请求体(助手消息):
thread_idrole: "assistant"content: persistedContent[]
3) 流式回复模块(SSE)
-
专家聊天流式
POST /api/v1/expert-agent-chat/run/stream- 请求体关键字段:
chat_idexpert_agent_iduser_querymessages
-
专家编辑页测试流式
POST /api/v1/expert-agent-chat/run/stream/help_expert
-
技能创建流式
POST /api/v1/expert-agent-chat/skills-agent- 请求体关键字段:
chat_iduser_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.iddata.titledata.status(completed或其它)data.items[]
-
reasoning-start- 用途:开始一段思考
- 行为:创建新的 thinking 段,记录开始时间
-
reasoning-delta- 用途:思考内容增量
- 关键字段:
delta
-
reasoning-end- 用途:结束当前思考段
- 行为:计算该段耗时并写入
duration
-
tool-input-available- 用途:记录工具调用入参
- 关键字段:
toolCallIdtoolNameinput
-
tool-output-available- 用途:记录工具调用输出
- 关键字段:
toolCallIdoutput
-
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)行为正确。