From 9b42c64300b77a5a472ee821b97d08194865d7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 17 Jul 2026 16:04:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=81=9C=E6=AD=A2=EF=BC=88?= =?UTF-8?q?=E6=80=9D=E8=80=83=EF=BC=89=20=E8=A7=A3=E9=87=8A=EF=BC=9A=20?= =?UTF-8?q?=E5=81=9C=E6=AD=A2=E6=98=AF=E5=8E=9F=E6=9D=A5sse=E7=95=99?= =?UTF-8?q?=E4=B8=8B=E6=9D=A5=E7=9A=84=EF=BC=8C=20=E7=8E=B0=E5=9C=A8?= =?UTF-8?q?=E6=80=9D=E8=80=83=E6=97=B6=E9=97=B4=E7=9B=B8=E5=AF=B9=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E5=90=84=E7=A7=8D=E7=BE=8E=E6=9C=AF=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E7=9F=AD=EF=BC=8C=20=E4=B8=94=E7=BE=8E?= =?UTF-8?q?=E6=9C=AF=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8=E4=B8=8D=E9=98=BB?= =?UTF-8?q?=E5=A1=9E=E5=AF=B9=E8=AF=9D=EF=BC=8C=E8=B0=83=E7=94=A8=E5=89=8D?= =?UTF-8?q?=E5=B7=B2=E6=9C=89=E7=A1=AE=E8=AE=A4=E5=8F=96=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared-memory/decision-log.md | 8 ++++ .../【编辑器】画布Agent对话面板-2026-07-03.md | 4 +- .../EditorAgentConversationPanelView.test.tsx | 48 +++++++++++++++++++ .../EditorAgentConversationPanelView.tsx | 21 ++------ .../useEditorAgentConversation.test.tsx | 32 ++++++++----- .../useEditorAgentConversation.ts | 30 +----------- 6 files changed, 84 insertions(+), 59 deletions(-) diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 0ad2d0498..7d346c938 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -15,6 +15,14 @@ ``` --- +## 2026-07-17 画布 Agent 普通消息不提供客户端停止 + +- 背景:普通消息进入 LLM 前,后端已经把用户消息写入 OSS;前端中断 fetch 只能停止本地等待,不能保证后端停止规划,且会保留无法与后端消息对齐的 optimistic message。 +- 决策:移除画布 Agent 普通消息的“停止”按钮和 `stopCurrentTurn`,发送期间保持按钮禁用并等待后端响应。待确认工具调用的“取消”仍保留,不受本决策影响。 +- 影响范围:画布 Agent 对话 hook、发送区交互、前端测试和专题文档。 +- 验证方式:运行画布 Agent hook / 面板定向测试、`npm run typecheck`、`npm run check:encoding` 和 `git diff --check`。 +- 关联文档:`docs/【编辑器】画布Agent对话面板-2026-07-03.md`。 + ## 2026-07-10 画布 Agent 工具确认分离执行参数与展示投影 - 背景:画布 Agent 已在实际生成前进入 `pending_confirmation`,但 `EditorAgentToolCall.args` 只保存工具私有 JSON,其中图片参数是保护真实 data key 的 SHA-256 opaque ID。前端直接解析 raw args 只能显示内部哈希或图片数量,无法向用户准确展示即将使用的目标图、参考图和完整参数;若直接把图片 URL 或对象塞回 raw args,又会破坏确认执行反序列化和 LLM 不可见真实 data key 的安全边界。 diff --git a/docs/【编辑器】画布Agent对话面板-2026-07-03.md b/docs/【编辑器】画布Agent对话面板-2026-07-03.md index a1bfa260c..bbb9f3286 100644 --- a/docs/【编辑器】画布Agent对话面板-2026-07-03.md +++ b/docs/【编辑器】画布Agent对话面板-2026-07-03.md @@ -113,7 +113,7 @@ 4. 消息内生成结果缩略图(纯预览,不显示名称,不点击聚焦图层); 5. 生成中的进行中动画; 6. 错误气泡(失败/余额不足,带原因); -7. 客户端可取消等待中的普通消息请求;已经确认入队的生成任务不追回,仍按任务状态写回画板。 +7. 普通消息请求等待期间禁用发送按钮,不提供客户端停止操作;前端持续等待后端响应,避免后端已持久化消息但前端中断请求后产生会话状态错位。 不做(明确排除,防止后人补齐): @@ -153,6 +153,6 @@ ## 第一阶段验收补充 - 打开画布 Agent 后任务侧栏应关闭,再次打开任务侧栏时 Agent 面板应关闭;素材 / 图层面板与 Agent 可同时展开,互不改写开关状态。 -- 发送消息时先本地追加用户消息,再应用 JSON 响应中的 `deltaMessages`;客户端取消等待不追回已经确认入队的生成工具调用。 +- 发送消息时先本地追加用户消息,再应用 JSON 响应中的 `deltaMessages`;请求等待期间发送按钮保持禁用,前端不主动中断当前回合。 - Agent 消息内生成结果缩略图只用于预览,不显示名称,也不点击跳转图层;轮询到任务终态并完成会话懒回填后统一刷新工程快照和素材库。 - 对话内容可被用户选中复制;用户从输入框或对话内容点击回画布图层 / 生成器时,焦点应回到画布对象,Backspace / Delete 等画布快捷键继续生效。 diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx index 7168dd0e4..737068d1e 100644 --- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx @@ -306,6 +306,54 @@ describe('EditorAgentConversationPanelView', () => { ); }); + it('disables sending while a message request is pending without showing stop', async () => { + const client = createClient(); + let resolveSend!: (response: EditorAgentMessageResponse) => void; + vi.mocked(client.sendMessage).mockImplementation( + () => + new Promise((resolve) => { + resolveSend = resolve; + }), + ); + + render( + , + ); + + await waitFor(() => { + expect(screen.getByText('已经看到画布内容')).toBeTruthy(); + }); + + fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { + target: { value: '继续规划' }, + }); + fireEvent.click(screen.getByRole('button', { name: '发送' })); + + await waitFor(() => { + expect( + screen.getByRole('button', { name: '发送' }).hasAttribute('disabled'), + ).toBe(true); + }); + expect(screen.queryByRole('button', { name: '停止' })).toBeNull(); + + await act(async () => { + resolveSend({ + conversation: { + conversationId: 'conversation-1', + projectId: 'project-1', + title: '角色参考', + updatedAt: '2026-07-03T00:00:20.000Z', + }, + deltaMessages: [], + errorMessage: null, + }); + }); + }); + it('uploads pasted images as canvas attachments before sending', async () => { const client = createClient(); diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx index ca176a1df..5c92259a0 100644 --- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx @@ -5,7 +5,6 @@ import { Paperclip, Plus, Send, - Square, Trash2, X, } from 'lucide-react'; @@ -266,7 +265,6 @@ export function EditorAgentConversationPanelView({ selectConversation, refreshActiveConversation, sendMessage, - stopCurrentTurn, confirmToolCall, cancelToolCall, deleteActiveConversation, @@ -344,7 +342,6 @@ export function EditorAgentConversationPanelView({ const submitMessage = (event: FormEvent) => { event.preventDefault(); if (isWaiting) { - stopCurrentTurn(); return; } if (isPastingAttachment) { @@ -671,22 +668,14 @@ export function EditorAgentConversationPanelView({ type="submit" className="inline-flex h-10 min-w-16 shrink-0 items-center justify-center gap-1.5 rounded-full bg-slate-900 px-3 text-sm font-semibold text-white disabled:opacity-45" disabled={ + isWaiting || isToolCallActionPending || - (!isWaiting && - ((!draftText.trim() && !attachments.length) || !hasProject)) + (!draftText.trim() && !attachments.length) || + !hasProject } > - {isWaiting ? ( - <> -