From 74361af6bd0f3d7204758f2a33a20e52198aa7e7 Mon Sep 17 00:00:00 2001 From: kvtodev Date: Fri, 10 Jul 2026 11:25:14 +0800 Subject: [PATCH] replace `onEditorAgentGenerationResult` with `onEditorAgentCanvasRefreshRequested` and update related logic --- .../ImageCanvasEditorView.test.tsx | 179 ++++++++++-------- .../image-editor/ImageCanvasEditorView.tsx | 32 ++-- .../image-editor/ImageCanvasStageView.tsx | 12 +- 3 files changed, 117 insertions(+), 106 deletions(-) diff --git a/src/components/image-editor/ImageCanvasEditorView.test.tsx b/src/components/image-editor/ImageCanvasEditorView.test.tsx index de156fb2b..a5237411b 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.test.tsx @@ -34,7 +34,7 @@ type EditorAgentGetConversation = EditorAgentConversationClient['getConversation']; type EditorAgentDeleteConversation = EditorAgentConversationClient['deleteConversation']; -type EditorAgentStreamMessage = EditorAgentConversationClient['streamMessage']; +type EditorAgentStreamMessage = EditorAgentConversationClient['sendMessage']; type EditorAgentConversationSummary = Awaited< ReturnType >[number]; @@ -68,8 +68,6 @@ const getEditorAgentConversationMock = vi.hoisted(() => >(async () => ({ conversationId: 'editor-agent-conv-test', projectId: 'editor-project-default', - title: '画布 Agent', - createdAt: '2026-07-03T00:00:00.000Z', updatedAt: '2026-07-03T00:00:00.000Z', messages: [], })), @@ -86,12 +84,17 @@ const deleteEditorAgentConversationMock = vi.hoisted(() => updatedAt: '2026-07-03T00:00:00.000Z', })), ); -const streamEditorAgentMessageMock = vi.hoisted(() => +const sendEditorAgentMessageMock = vi.hoisted(() => vi.fn< Parameters, ReturnType - >(async () => undefined), + >(async () => ({ + deltaMessages: [], + errorMessage: null, + })), ); +const confirmEditorAgentToolCallMock = vi.hoisted(() => vi.fn()); +const cancelEditorAgentToolCallMock = vi.hoisted(() => vi.fn()); const generateEditorImageMock = vi.hoisted(() => vi.fn()); const generateEditorIconSpritesheetMock = vi.hoisted(() => vi.fn()); const generateEditorCharacterAnimationMock = vi.hoisted(() => vi.fn()); @@ -145,11 +148,13 @@ vi.mock('../../services/frontendRuntimeConfigService', () => ({ })); vi.mock('../../services/image-editor/editorAgentClient', () => ({ + cancelEditorAgentToolCall: cancelEditorAgentToolCallMock, + confirmEditorAgentToolCall: confirmEditorAgentToolCallMock, createEditorAgentConversation: createEditorAgentConversationMock, deleteEditorAgentConversation: deleteEditorAgentConversationMock, getEditorAgentConversation: getEditorAgentConversationMock, listEditorAgentConversations: listEditorAgentConversationsMock, - streamEditorAgentMessage: streamEditorAgentMessageMock, + sendEditorAgentMessage: sendEditorAgentMessageMock, })); function createEditorAgentConversationSummary( @@ -158,8 +163,6 @@ function createEditorAgentConversationSummary( return { conversationId: 'editor-agent-conv-test', projectId: 'editor-project-default', - title: '画布 Agent', - createdAt: '2026-07-03T00:00:00.000Z', updatedAt: '2026-07-03T00:00:00.000Z', ...overrides, }; @@ -171,33 +174,30 @@ function createEditorAgentDetailWithGeneration( const summary = createEditorAgentConversationSummary(); return { ...summary, + title: '画布 Agent', + createdAt: '2026-07-03T00:00:00.000Z', messages: [ { - id: 'agent-message-result', - role: 'assistant', - kind: 'chat', - text: '生成完成', + id: 1, + role: 'system', + text: 'internal completed tool output', attachments: [], - generations: [ - { - toolCallId: 'agent-tool-call-1', - toolName: 'generate_image', - taskId: 'task-agent-1', - status: 'completed', - model: 'gpt-image-2', - images: [ - { - resourceId, - imageSrc: '/agent-result.png', - thumbnailSrc: '/agent-result-thumb.png', - width: 512, - height: 512, - }, - ], - error: null, - }, - ], - status: 'completed', + toolCall: { + toolName: 'generate_image', + summary: '', + status: 'completed', + args: {}, + images: [ + { + resourceId, + imageSrc: '/agent-result.png', + thumbnailSrc: '/agent-result-thumb.png', + width: 512, + height: 512, + }, + ], + error: null, + }, createdAt: '2026-07-03T00:00:01.000Z', }, ], @@ -260,16 +260,25 @@ describe('ImageCanvasEditorView', () => { ...createEditorAgentConversationSummary({ conversationId: 'editor-agent-conv-created', }), + title: '画布 Agent', + createdAt: '2026-07-03T00:00:00.000Z', messages: [], }); getEditorAgentConversationMock.mockResolvedValue({ ...createEditorAgentConversationSummary(), + title: '画布 Agent', + createdAt: '2026-07-03T00:00:00.000Z', messages: [], }); deleteEditorAgentConversationMock.mockResolvedValue( createEditorAgentConversationSummary(), ); - streamEditorAgentMessageMock.mockResolvedValue(undefined); + sendEditorAgentMessageMock.mockResolvedValue({ + deltaMessages: [], + errorMessage: null, + }); + confirmEditorAgentToolCallMock.mockReset(); + cancelEditorAgentToolCallMock.mockReset(); getPlatformProfileDashboardMock.mockResolvedValue({ walletBalance: 1234, totalPlayTimeMs: 0, @@ -283,7 +292,9 @@ describe('ImageCanvasEditorView', () => { createEditorAgentConversationMock.mockReset(); getEditorAgentConversationMock.mockReset(); deleteEditorAgentConversationMock.mockReset(); - streamEditorAgentMessageMock.mockReset(); + sendEditorAgentMessageMock.mockReset(); + confirmEditorAgentToolCallMock.mockReset(); + cancelEditorAgentToolCallMock.mockReset(); getPlatformProfileDashboardMock.mockReset(); loadFrontendRuntimeConfigMock.mockReset(); }); @@ -1436,9 +1447,7 @@ describe('ImageCanvasEditorView', () => { render(); expect(screen.queryByRole('button', { name: '画布 Agent' })).toBeNull(); - expect( - screen.queryByRole('button', { name: '打开画布 Agent' }), - ).toBeNull(); + expect(screen.queryByRole('button', { name: '打开画布 Agent' })).toBeNull(); expect(screen.queryByLabelText('发送给画布 Agent')).toBeNull(); expect(listEditorAgentConversationsMock).not.toHaveBeenCalled(); }); @@ -1827,46 +1836,55 @@ describe('ImageCanvasEditorView', () => { ]); getEditorAgentConversationMock.mockResolvedValueOnce({ ...createEditorAgentConversationSummary(), - messages: [], + title: '画布 Agent', + createdAt: '2026-07-03T00:00:00.000Z', + messages: [ + { + id: 0, + role: 'system', + text: 'internal pending tool prompt', + attachments: [], + toolCall: { + toolName: 'edit-image', + summary: '', + status: 'pending_confirmation', + args: { + object_image_id: 'source-image', + prompt: '把图片换成像素风', + }, + images: [], + error: null, + }, + createdAt: '2026-07-03T00:00:00.000Z', + }, + ], + }); + confirmEditorAgentToolCallMock.mockImplementation(async () => { + showGeneratedAsset = true; + return { + id: 0, + role: 'system', + text: 'internal completed tool output', + attachments: [], + toolCall: { + toolName: 'edit-image', + summary: '', + status: 'completed', + args: {}, + images: [ + { + resourceId: null, + imageSrc: '/agent-stream.png', + thumbnailSrc: null, + width: 512, + height: 512, + }, + ], + error: null, + }, + createdAt: '2026-07-03T00:00:00.000Z', + }; }); - streamEditorAgentMessageMock.mockImplementation( - async (conversationId, _payload, options) => { - options.onEvent?.({ - event: 'message_delta', - data: { - conversationId, - messageId: 'assistant-agent-stream', - role: 'assistant', - kind: 'chat', - textDelta: '我来生成图片。', - }, - }); - showGeneratedAsset = true; - options.onEvent?.({ - event: 'generation_result', - data: { - conversationId, - messageId: 'assistant-agent-stream', - toolCallId: 'tool-call-agent-stream', - toolName: 'generate_image', - model: 'gpt-image-2', - images: [ - { - resourceId: 'resource-agent-stream', - imageSrc: '/agent-stream.png', - thumbnailSrc: null, - width: 512, - height: 512, - }, - ], - }, - }); - options.onEvent?.({ - event: 'done', - data: { conversationId, title: null }, - }); - }, - ); loadEditorProjectMock.mockResolvedValueOnce({ projectId: 'editor-project-default', title: '默认项目', @@ -1915,12 +1933,13 @@ describe('ImageCanvasEditorView', () => { 'editor-agent-conv-test', ); }); - fireEvent.change(await screen.findByLabelText('发送给画布 Agent'), { - target: { value: '生成一张图' }, - }); - fireEvent.click(screen.getByRole('button', { name: '发送' })); + fireEvent.click(await screen.findByRole('button', { name: '确认' })); await waitFor(() => { + expect(confirmEditorAgentToolCallMock).toHaveBeenCalledWith( + 'editor-agent-conv-test', + 0, + ); expect(loadEditorProjectMock).toHaveBeenCalledWith( 'editor-project-default', ); diff --git a/src/components/image-editor/ImageCanvasEditorView.tsx b/src/components/image-editor/ImageCanvasEditorView.tsx index 2177a9f13..00708bf93 100644 --- a/src/components/image-editor/ImageCanvasEditorView.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.tsx @@ -10,16 +10,15 @@ import { } from 'react'; import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration'; -import type { EditorAgentGenerationResultEvent } from '../../../packages/shared/src/contracts/editorAgent'; +import { loadFrontendRuntimeConfig } from '../../services/frontendRuntimeConfigService'; import { createEditorAsset, createEditorProjectResource, type EditorAssetSnapshot, type EditorProjectSnapshot, - loadEditorProject, loadEditorGenerationPricing, + loadEditorProject, } from '../../services/image-editor/editorProjectClient'; -import { loadFrontendRuntimeConfig } from '../../services/frontendRuntimeConfigService'; import { shouldShowRechargeEntry } from '../../services/payment/paymentPlatform'; import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient'; import { useAuthUi } from '../auth/AuthUiContext'; @@ -78,11 +77,11 @@ import { } from './useImageCanvasAssetCanvasBridge'; import { useImageCanvasAssetExportWorkflow } from './useImageCanvasAssetExportWorkflow'; import { useImageCanvasAssetLibrary } from './useImageCanvasAssetLibrary'; +import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; import { useImageCanvasEditorChrome } from './useImageCanvasEditorChrome'; import { useImageCanvasGenerationSurface } from './useImageCanvasGenerationSurface'; import { useImageCanvasKeyboardShortcuts } from './useImageCanvasKeyboardShortcuts'; import { useImageCanvasLayerCommands } from './useImageCanvasLayerCommands'; -import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; import { useImageCanvasProjectPersistence } from './useImageCanvasProjectPersistence'; import { useImageCanvasStageController } from './useImageCanvasStageController'; import { useImageCanvasStageInteractions } from './useImageCanvasStageInteractions'; @@ -1096,20 +1095,14 @@ export function ImageCanvasEditorView({ }, [applyGeneratedProjectSnapshot, projectId, refreshEditorWalletBalance], ); - const handleEditorAgentGenerationResult = useCallback( - (event: EditorAgentGenerationResultEvent) => { - const hasGeneratedResource = event.images.some((image) => - image.resourceId?.trim(), - ); - if (!projectId || !hasGeneratedResource) { - return; - } - void loadEditorProject(projectId) - .then(applyGeneratedProjectSnapshot) - .catch(() => undefined); - }, - [applyGeneratedProjectSnapshot, projectId], - ); + const handleEditorAgentCanvasRefreshRequested = useCallback(() => { + if (!projectId) { + return; + } + void loadEditorProject(projectId) + .then(applyGeneratedProjectSnapshot) + .catch(() => undefined); + }, [applyGeneratedProjectSnapshot, projectId]); const persistUpdatedLayerResource = useCallback( (layer: CanvasLayer) => { if (!projectId) { @@ -2078,7 +2071,8 @@ export function ImageCanvasEditorView({ onActivateGenerationDialog: activateCanvasGenerationDialog, onFocusExternalTask: focusExternalGenerationTask, onExternalTasksCompleted: handleExternalGenerationTasksCompleted, - onEditorAgentGenerationResult: handleEditorAgentGenerationResult, + onEditorAgentCanvasRefreshRequested: + handleEditorAgentCanvasRefreshRequested, onToggleTaskSidebar: toggleTaskSidebar, onToggleAgentConversation: toggleAgentConversation, onCropExpandHandlePointerDown: generationSurface.startCropExpandFrameResize, diff --git a/src/components/image-editor/ImageCanvasStageView.tsx b/src/components/image-editor/ImageCanvasStageView.tsx index a77b556b3..2eae2a029 100644 --- a/src/components/image-editor/ImageCanvasStageView.tsx +++ b/src/components/image-editor/ImageCanvasStageView.tsx @@ -7,9 +7,9 @@ import type { RefObject, } from 'react'; -import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration'; -import type { EditorAgentGenerationResultEvent } from '../../../packages/shared/src/contracts/editorAgent'; import { EditorAgentConversationPanelView } from '@/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx'; + +import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration'; import { ImageCanvasBottomToolbarView } from './ImageCanvasBottomToolbarView'; import { ImageCanvasContextMenusView } from './ImageCanvasContextMenusView'; import type { @@ -126,9 +126,7 @@ export type ImageCanvasStageViewProps = { onActivateGenerationDialog: (dialog: CanvasGenerationDialogState) => void; onFocusExternalTask: (task: ExternalGenerationTaskRecord) => void; onExternalTasksCompleted?: (tasks: ExternalGenerationTaskRecord[]) => void; - onEditorAgentGenerationResult?: ( - event: EditorAgentGenerationResultEvent, - ) => void; + onEditorAgentCanvasRefreshRequested?: () => void; onToggleTaskSidebar: () => void; onToggleAgentConversation: () => void; onCropExpandHandlePointerDown: ( @@ -259,7 +257,7 @@ export function ImageCanvasStageView({ onActivateGenerationDialog, onFocusExternalTask, onExternalTasksCompleted, - onEditorAgentGenerationResult, + onEditorAgentCanvasRefreshRequested, onToggleTaskSidebar, onToggleAgentConversation, onCropExpandHandlePointerDown, @@ -476,7 +474,7 @@ export function ImageCanvasStageView({ onToggleOpen={onToggleAgentConversation} layers={layers} assets={editorAgentAssets} - onGenerationResult={onEditorAgentGenerationResult} + onCanvasRefreshRequested={onEditorAgentCanvasRefreshRequested} /> ) : null}