diff --git a/docs/【编辑器】画布Agent对话面板-2026-07-03.md b/docs/【编辑器】画布Agent对话面板-2026-07-03.md index 63749082d..2d6f642c3 100644 --- a/docs/【编辑器】画布Agent对话面板-2026-07-03.md +++ b/docs/【编辑器】画布Agent对话面板-2026-07-03.md @@ -84,7 +84,7 @@ - `GET /conversation` 会在同一个 conversation lock 内扫描 `status=not_completed` 且已有 `externalJobId` 的工具消息:只对这些消息按 job id 定向读取主任务;任务完成后复用对应工具的 `format_execute_message` 替换 system text、回填轻量图片 / 视频 / 音频引用并写为 `completed`,任务失败则回填 `error` 并写为 `failed`。任务结果读取或 completed payload 解析 / formatter 回填失败时,必须在同一次 GET 内完成首次尝试及最多 3 次重试,三次重试各间隔 100ms 并重新读取任务结果;仍失败才把该工具消息写为 `failed` 并保存最后错误。该重试不依赖前端再次刷新。排队和执行中都保持 `not_completed`,整轮扫描结果一次性写回 OSS。 - `EditorAgentToolCall.args` 保留为工具返回的原始 JSON,是确认接口重新反序列化并执行工具的唯一参数真相。图片参数继续只保存由真实 data key 计算出的 opaque SHA-256 `imageId`;不得为了前端预览把 `args` 中的图片 ID 改写成 `objectKey`、URL 或展示对象,也不得由前端重组或回传一份新的执行参数。 - `EditorAgentToolCall.displayArgs` 是必填、只读的用户确认展示投影,与 `args` 分离: - - `stringArgs` 保存提示词、比例、清晰度、模型、时长等可展示参数的稳定名称、用户可见标题和值; + - `stringArgs` 保存提示词、比例、清晰度、模型、时长等可展示参数的稳定名称、用户可见标题和值;前端渲染模型字段时复用图片编辑器公共展示名映射,`gemini-3.1-flash-image-preview` 显示为 `nanobanana2`、`audio1.0` 显示为 `Vidu`、`chirp-v5` 显示为 `Suno`,视频模型显示现有产品标签,不得改写后端参数真相; - `imageArgs` 按“目标图片 / 参考图片”等参数分组,每个 `refs` 项包含与原始参数对应的 `imageId`,以及后端从已校验会话上下文解析出的 `objectKey`、`imageSrc`、可选 `thumbnailSrc` / `label` / `width` / `height`。 - `extras.priceMudPoints` 保存创建待确认消息时按后端运行时模型定价快照计算的预计泥点消耗;前端统一展示为“预计消耗 N泥点”,不自行计算价格。 - `displayArgs` 只能由 api-server 按已注册 tool 白名单,基于已经通过 ToolArgs 校验的 `args` 和当前 OSS 会话文档中的附件 / 历史生成结果构建;不能信任 LLM 自报的展示地址、标题或素材元数据。展示投影不参与确认执行,确认接口仍只读取同一条持久化 tool call 的 `args`,避免“看到的素材”和“实际执行的素材”分叉。 diff --git a/src/components/image-editor/EditorAgentConversation/PendingToolCall.tsx b/src/components/image-editor/EditorAgentConversation/PendingToolCall.tsx index cc53e1341..25881d1ec 100644 --- a/src/components/image-editor/EditorAgentConversation/PendingToolCall.tsx +++ b/src/components/image-editor/EditorAgentConversation/PendingToolCall.tsx @@ -2,6 +2,7 @@ import { Check, Coins, Loader2, Pencil, X } from 'lucide-react'; import type { EditorAgentToolCall } from '@/packages/shared/src/contracts'; import { editorAgentToolLabel } from '@/src/components/image-editor/EditorAgentConversation/toolCallPresentation.ts'; +import { getEditorGenerationModelDisplayName } from '@/src/components/image-editor/ImageCanvasGenerationModel.ts'; import { ResolvedAssetImage } from '@/src/components/ResolvedAssetImage.tsx'; type PendingToolCallAction = 'confirm' | 'cancel' | null; @@ -18,6 +19,10 @@ function readString(value: unknown) { return typeof value === 'string' && value.trim() ? value.trim() : null; } +function displayStringArgument(name: string, value: string) { + return name === 'model' ? getEditorGenerationModelDisplayName(value) : value; +} + export function PendingToolCall({ messageId, toolCall, @@ -64,7 +69,7 @@ export function PendingToolCall({ {argument.label}
- {argument.value} + {displayStringArgument(argument.name, argument.value)}
))} diff --git a/src/components/image-editor/ImageCanvasGenerationModel.ts b/src/components/image-editor/ImageCanvasGenerationModel.ts index e04b43e91..96e7ec07a 100644 --- a/src/components/image-editor/ImageCanvasGenerationModel.ts +++ b/src/components/image-editor/ImageCanvasGenerationModel.ts @@ -582,6 +582,28 @@ export function getEditorImageModelDisplayName( ); } +export function getEditorGenerationModelDisplayName( + model: string | null | undefined, +) { + const normalizedModel = model?.trim(); + if (normalizedModel === BACKGROUND_MUSIC_MODEL_SUNO) { + return 'Suno'; + } + const soundEffectLabel = EDITOR_SOUND_EFFECT_MODEL_OPTIONS.find( + (option) => option.value === normalizedModel, + )?.label; + if (soundEffectLabel) { + return soundEffectLabel; + } + const videoLabel = EDITOR_VIDEO_MODEL_OPTIONS.find( + (option) => option.value === normalizedModel, + )?.label; + if (videoLabel) { + return videoLabel; + } + return getEditorImageModelDisplayName(normalizedModel); +} + export function buildQuickEditModelOptions(currentModel: string) { void currentModel; const options = [...QUICK_EDIT_MODEL_OPTIONS]; @@ -1292,17 +1314,17 @@ export function isCanvasGenerationDialog( ): dialog is CanvasGenerationDialogState { return Boolean( dialog?.id && - (dialog.mode === 'generate' || - dialog.mode === 'spec' || - dialog.mode === 'character' || - dialog.mode === 'icon' || - dialog.mode === 'publication' || - dialog.mode === 'ui-design' || - dialog.mode === 'quick-edit' || - dialog.mode === 'character-animation' || - dialog.mode === 'video' || - dialog.mode === 'audio-sound-effect' || - dialog.mode === 'audio-background-music'), + (dialog.mode === 'generate' || + dialog.mode === 'spec' || + dialog.mode === 'character' || + dialog.mode === 'icon' || + dialog.mode === 'publication' || + dialog.mode === 'ui-design' || + dialog.mode === 'quick-edit' || + dialog.mode === 'character-animation' || + dialog.mode === 'video' || + dialog.mode === 'audio-sound-effect' || + dialog.mode === 'audio-background-music'), ); }