From 5fb7ec03d1bf6f05c05b498cf37e42839bfdd79d Mon Sep 17 00:00:00 2001 From: kvtodev Date: Wed, 8 Jul 2026 10:30:28 +0800 Subject: [PATCH] Refactor: split EditorAgent conversation components --- .../EditorAgentConversationPanelView.test.tsx | 11 +- .../EditorAgentConversationPanelView.tsx | 37 +-- .../EditorAgentConversation/MessageBubble.tsx | 247 ++++++++++++++++++ .../EditorAgentConversation/common.ts | 5 + .../image-editor/ImageCanvasStageView.tsx | 2 +- 5 files changed, 279 insertions(+), 23 deletions(-) rename src/components/image-editor/{ => EditorAgentConversation}/EditorAgentConversationPanelView.test.tsx (97%) rename src/components/image-editor/{ => EditorAgentConversation}/EditorAgentConversationPanelView.tsx (96%) create mode 100644 src/components/image-editor/EditorAgentConversation/MessageBubble.tsx create mode 100644 src/components/image-editor/EditorAgentConversation/common.ts diff --git a/src/components/image-editor/EditorAgentConversationPanelView.test.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx similarity index 97% rename from src/components/image-editor/EditorAgentConversationPanelView.test.tsx rename to src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx index 30bf11244..b8a5d0033 100644 --- a/src/components/image-editor/EditorAgentConversationPanelView.test.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx @@ -10,9 +10,10 @@ import { } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { EditorAgentConversationPanelView } from './EditorAgentConversationPanelView'; -import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; -import type { EditorAgentConversationClient } from './useEditorAgentConversation'; +import type { EditorAgentConversationClient } from '@/src/components/image-editor/useEditorAgentConversation.ts'; +import { useImageCanvasContextStore } from '@/src/components/image-editor/useImageCanvasContextStore.ts'; + +import { EditorAgentConversationPanelView } from './EditorAgentConversationPanelView.tsx'; const createEditorProjectResourceMock = vi.hoisted(() => vi.fn()); const uploadEditorMediaAssetFileMock = vi.hoisted(() => vi.fn()); @@ -20,7 +21,7 @@ const probeImageFileDimensionsMock = vi.hoisted(() => vi.fn()); vi.mock('../../services/image-editor/editorProjectClient', async () => { const actual = await vi.importActual< - typeof import('../../services/image-editor/editorProjectClient') + typeof import('../../../services/image-editor/editorProjectClient.ts') >('../../services/image-editor/editorProjectClient'); return { ...actual, @@ -33,7 +34,7 @@ vi.mock('../../services/image-editor/editorMediaAssetUploadClient', () => ({ })); vi.mock('./ImageCanvasFileModel', async () => { - const actual = await vi.importActual( + const actual = await vi.importActual( './ImageCanvasFileModel', ); return { diff --git a/src/components/image-editor/EditorAgentConversationPanelView.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx similarity index 96% rename from src/components/image-editor/EditorAgentConversationPanelView.tsx rename to src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx index 665162fc8..fa6e68bfc 100644 --- a/src/components/image-editor/EditorAgentConversationPanelView.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx @@ -13,33 +13,40 @@ import { import { type ClipboardEvent as ReactClipboardEvent, type FormEvent, - type WheelEvent as ReactWheelEvent, useEffect, useMemo, useState, + type WheelEvent as ReactWheelEvent, } from 'react'; import { EDITOR_AGENT_MAX_ATTACHMENTS, type EditorAgentAttachmentRef, type EditorAgentGenerationResultEvent, - type EditorAgentGenerationRecord, - type EditorAgentMessage, type EditorAgentStage, -} from '../../../packages/shared/src/contracts/editorAgent'; -import { PlatformActionButton } from '../common/PlatformActionButton'; -import { PlatformDangerConfirmDialog } from '../common/PlatformDangerConfirmDialog'; -import { UnifiedModal } from '../common/UnifiedModal'; -import { ResolvedAssetImage } from '../ResolvedAssetImage'; -import { uploadEditorMediaAssetFile } from '../../services/image-editor/editorMediaAssetUploadClient'; -import { createEditorProjectResource } from '../../services/image-editor/editorProjectClient'; -import type { CanvasLayer, EditorAsset } from './ImageCanvasEditorTypes'; -import { probeImageFileDimensions } from './ImageCanvasFileModel'; -import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; +} from '@/packages/shared/src/contracts'; +import { PlatformActionButton } from '@/src/components/common/PlatformActionButton.tsx'; +import { PlatformDangerConfirmDialog } from '@/src/components/common/PlatformDangerConfirmDialog.tsx'; +import { UnifiedModal } from '@/src/components/common/UnifiedModal.tsx'; +import { attachmentKey } from '@/src/components/image-editor/EditorAgentConversation/common.ts'; +import { + AttachmentChip, + MessageBubble, +} from '@/src/components/image-editor/EditorAgentConversation/MessageBubble.tsx'; +import type { + CanvasLayer, + EditorAsset, +} from '@/src/components/image-editor/ImageCanvasEditorTypes.ts'; +import { probeImageFileDimensions } from '@/src/components/image-editor/ImageCanvasFileModel.ts'; +import { ResolvedAssetImage } from '@/src/components/ResolvedAssetImage.tsx'; +import { uploadEditorMediaAssetFile } from '@/src/services/image-editor/editorMediaAssetUploadClient.ts'; +import { createEditorProjectResource } from '@/src/services/image-editor/editorProjectClient.ts'; + import { type EditorAgentConversationClient, useEditorAgentConversation, } from './useEditorAgentConversation'; +import { useImageCanvasContextStore } from '../useImageCanvasContextStore.ts'; type AttachmentPickerTab = 'canvas' | 'library'; @@ -62,10 +69,6 @@ function stopAgentPanelWheel(event: ReactWheelEvent) { event.stopPropagation(); } -function attachmentKey(attachment: EditorAgentAttachmentRef) { - return `${attachment.source}:${attachment.referenceId}`; -} - function isImageLayer(layer: CanvasLayer) { return ( (layer.mediaType ?? 'image') === 'image' && diff --git a/src/components/image-editor/EditorAgentConversation/MessageBubble.tsx b/src/components/image-editor/EditorAgentConversation/MessageBubble.tsx new file mode 100644 index 000000000..ff005bae8 --- /dev/null +++ b/src/components/image-editor/EditorAgentConversation/MessageBubble.tsx @@ -0,0 +1,247 @@ +import { FileAudio, Image as ImageIcon, Loader2, X } from 'lucide-react'; + +import type { + EditorAgentAttachmentRef, + EditorAgentGenerationRecord, + EditorAgentMessage, +} from '@/packages/shared/src/contracts'; +import { attachmentKey } from '@/src/components/image-editor/EditorAgentConversation/common.ts'; +import { ResolvedAssetImage } from '@/src/components/ResolvedAssetImage.tsx'; +import { useResolvedAssetReadUrl } from '@/src/hooks/useResolvedAssetReadUrl.ts'; + +function toolLabel(toolName: EditorAgentGenerationRecord['toolName']) { + if (toolName === 'edit_image') { + return '修改图片'; + } + if (toolName === 'generate_character') { + return '生成角色'; + } + if (toolName === 'generate_icon_spritesheet') { + return '生成图标'; + } + if (toolName === 'generate_ui_design') { + return '生成 UI'; + } + if (toolName === 'generate_video') { + return '生成视频'; + } + if (toolName === 'generate_sound_effect') { + return '生成音效'; + } + if (toolName === 'generate_background_music') { + return '生成背景音乐'; + } + return '生成图片'; +} + +function messageRoleLabel(role: EditorAgentMessage['role']) { + return role === 'user' ? '你' : 'Agent'; +} + +export function AttachmentChip({ + attachment, + onRemove, +}: { + attachment: EditorAgentAttachmentRef; + onRemove?: () => void; +}) { + const label = attachment.label?.trim() || attachment.referenceId; + return ( + + + ); +} + +function GenerationRecordsView({ + generations, +}: { + generations: EditorAgentGenerationRecord[]; +}) { + if (!generations.length) { + return null; + } + return ( +
+ {generations.map((generation) => ( +
+ {generation.status === 'generating' || generation.error ? ( +
+ {generation.status === 'generating' ? ( +
+ ) : null} + {generation.error ? ( +
{generation.error}
+ ) : null} + {generation.images.length ? ( +
+ {generation.images.map((image, index) => ( +
+ +
+ ))} +
+ ) : null} + {generation.videos.length ? ( +
+ {generation.videos.map((video, index) => ( + + ))} +
+ ) : null} + {generation.audios.length ? ( +
+ {generation.audios.map((audio, index) => ( + + ))} +
+ ) : null} +
+ ))} +
+ ); +} + +function GenerationVideoPreview({ + video, +}: { + video: EditorAgentGenerationRecord['videos'][number]; +}) { + const { resolvedUrl } = useResolvedAssetReadUrl(video.videoSrc, { + objectKey: video.objectKey, + }); + const posterSrc = video.thumbnailSrc ?? undefined; + return ( +
+
+ ); +} + +function GenerationAudioPreview({ + audio, +}: { + audio: EditorAgentGenerationRecord['audios'][number]; +}) { + const { resolvedUrl } = useResolvedAssetReadUrl(audio.audioSrc, { + objectKey: audio.objectKey, + }); + return ( +
+
+
+
+ ); +} + +export function MessageBubble({ message }: { message: EditorAgentMessage }) { + const isUser = message.role === 'user'; + return ( +
+
+
+ {message.text || (message.status === 'streaming' ? '...' : '')} +
+ {message.attachments.length ? ( +
+ {message.attachments.map((attachment) => ( + + ))} +
+ ) : null} + +
+
+ ); +} diff --git a/src/components/image-editor/EditorAgentConversation/common.ts b/src/components/image-editor/EditorAgentConversation/common.ts new file mode 100644 index 000000000..05f675a8d --- /dev/null +++ b/src/components/image-editor/EditorAgentConversation/common.ts @@ -0,0 +1,5 @@ +import type { EditorAgentAttachmentRef } from '@/packages/shared/src/contracts'; + +export function attachmentKey(attachment: EditorAgentAttachmentRef) { + return `${attachment.source}:${attachment.referenceId}`; +} diff --git a/src/components/image-editor/ImageCanvasStageView.tsx b/src/components/image-editor/ImageCanvasStageView.tsx index fb9a8630a..a77b556b3 100644 --- a/src/components/image-editor/ImageCanvasStageView.tsx +++ b/src/components/image-editor/ImageCanvasStageView.tsx @@ -9,7 +9,7 @@ import type { import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration'; import type { EditorAgentGenerationResultEvent } from '../../../packages/shared/src/contracts/editorAgent'; -import { EditorAgentConversationPanelView } from './EditorAgentConversationPanelView'; +import { EditorAgentConversationPanelView } from '@/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx'; import { ImageCanvasBottomToolbarView } from './ImageCanvasBottomToolbarView'; import { ImageCanvasContextMenusView } from './ImageCanvasContextMenusView'; import type {