From 04e6d0aa000cb2353e6125cee630d105ee051648 Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Thu, 25 Jun 2026 01:20:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(ai=E5=86=99=E4=BD=9C):=20=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E6=8E=A5=E6=94=B6=E6=8A=80=E8=83=BD=E6=AD=A5?= =?UTF-8?q?=E9=AA=A4(=E6=AD=A4=E5=89=8D=E8=A2=AB=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E4=B8=A2=E5=BC=83=EF=BC=8C=E5=8F=AA=E5=9C=A8=E5=BF=AB?= =?UTF-8?q?=E7=85=A7=E9=87=8C=E4=B8=80=E6=AC=A1=E6=80=A7=E6=98=BE=E7=A4=BA?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因在前端:后端 _emit_skill_step 以 writer({"type":"skill_search_step", **step}) 发出,**step 里的 type(invoke_skill/list_skills/skill_result/select_skills/ match_skills/skill_error/fallback/stop_early)会覆盖外层 "skill_search_step", 所以 custom 事件实际 type 是这些内层值。customToProgressEvent 的白名单没有它们, 直接 return null 丢弃——后端逐条实时发(SSE id 时间戳已证实分散在 0~76s),前端却 全扔了,技能步骤只能等节点结束的素材包快照一次性出现(前端一直 loading)。 修复:customToProgressEvent 按内层 step 类型识别,实时归一成 skill_search_step 事件(与快照里的 step 同格式),让技能调用过程一步步实时进时间线。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../open-canvas/hooks/useAIWritingStream.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/frontend-web/src/open-canvas/hooks/useAIWritingStream.ts b/frontend-web/src/open-canvas/hooks/useAIWritingStream.ts index ee956ad2..9c682bb3 100644 --- a/frontend-web/src/open-canvas/hooks/useAIWritingStream.ts +++ b/frontend-web/src/open-canvas/hooks/useAIWritingStream.ts @@ -568,6 +568,27 @@ export function customToProgressEvent(raw: unknown, sessionId: string): Progress const type = String(o.type ?? ''); // 后端 _save_draft_to_repo 等 helper 不走 custom 通道,type 必为节点 emit 的 chunk 类型 if (!type) return null; + + // 技能检索步骤:后端 _emit_skill_step 以 `writer({"type": "skill_search_step", **step})` + // 发出,但 **step 里也有 type(invoke_skill / list_skills / skill_result / select_skills / + // match_skills / skill_error / fallback / stop_early),会**覆盖**掉外层的 "skill_search_step"。 + // 所以 custom 事件实际 type 是这些内层值。这里按内层 step 类型识别,**实时**归一成 + // skill_search_step 事件(与素材包快照里的 step 同格式),让技能调用过程一步步进时间线, + // 而不是被这里的白名单丢弃、只在节点结束的快照里一次性出现。 + const SKILL_STEP_TYPES = [ + 'list_skills', 'match_skills', 'select_skills', 'invoke_skill', + 'skill_result', 'skill_error', 'fallback', 'stop_early', + ]; + if (SKILL_STEP_TYPES.includes(type)) { + return { + type: 'skill_search_step', + sessionId, + message: String(o.message ?? ''), + agentName: (o.agent_name ?? o.agentName) as string | undefined, + skillStep: normalizeSkillStep(o), + } as unknown as ProgressEvent; + } + const allowed: ProgressEventType[] = [ 'intent_chunk', 'intent_ready', 'research_progress', 'outline_chunk', 'draft_chunk',