diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 09963665e..fef9f9697 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -5948,7 +5948,6 @@ export async function POST(request: Request) { (isDetailReplacementPreset || isSkuReplacementPreset) ) { const replacementSlot = orderedSlots[0]; - const replacementAssistantMessageId = generateUUID(); const replacementTemplateReferenceImageUrls = Array.from( new Set( (replacementSlot?.referenceAssets || []) @@ -6000,12 +5999,6 @@ export async function POST(request: Request) { typeof mergedImageParams.imageSize === "string" ? mergedImageParams.imageSize : effectiveImageParams?.imageSize || defaultImageQualityParams.imageSize; - const replacementSections: string[] = []; - const replacementResultImages: Array<{ - originalUrl: string; - thumbnailUrl?: string | null; - }> = []; - let generatedReplacementCount = 0; let resolvedModelDisplayName = selectedAgentPreset.defaultModel?.displayName || selectedAgentPreset.name; const replacementVisionModels = await getEnabledAiModelsWithProvider().catch( @@ -6022,6 +6015,103 @@ export async function POST(request: Request) { "agent-preset:replacement", 12_000, ); + const emitReplacementResultMessage = async (params: { + reference: (typeof replacementReferences)[number]; + bodyText?: string | null; + image: { + originalUrl: string; + thumbnailUrl?: string | null; + }; + modelDisplayName?: string | null; + sourceImageUrls: string[]; + sourceType: "detail_replacement" | "sku_replacement"; + streamLabel: string; + }) => { + const assistantText = prependImageModelBadge( + [ + `### ${params.reference.label}`, + String(params.bodyText || "").trim(), + buildAgentToolImagesMarkdown([ + { + originalUrl: params.image.originalUrl, + thumbnailUrl: params.image.thumbnailUrl, + imageSize: resolvedImageSize, + }, + ]), + ] + .filter(Boolean) + .join("\n\n"), + params.modelDisplayName || null, + ); + const assistantMessageId = generateUUID(); + + await saveMessages({ + messages: [ + { + id: assistantMessageId, + chatId: id, + role: "assistant", + parts: [{ type: "text", text: assistantText }], + attachments: [], + createdAt: new Date(), + videoStorageStatus: null, + temporaryVideoUrl: null, + permanentVideoUrl: null, + archivedAt: null, + videoMetadata: null, + }, + ], + }); + + await saveGeneratedImages( + assistantText, + session.user.id, + id, + assistantMessageId, + baseReplacementPrompt || selectedAgentPreset.name, + resolvedModelRef, + { + sourceAssetIds, + sourceImageUrls: params.sourceImageUrls, + relationType: "edited_from", + sourceType: params.sourceType, + projectId: contextPayload?.projectId || null, + userPrompt: requestTextContent, + optimizedPrompt: baseReplacementPrompt || undefined, + requestedImageCount: 1, + aspectRatio: resolvedAspectRatio, + imageSize: resolvedImageSize, + }, + ); + + writeAssistantTextChunk( + writer, + assistantText, + params.streamLabel, + assistantMessageId, + ); + + if (contextPayload?.source === "canvas") { + writeCanvasMediaChunk( + writer, + { + messageId: assistantMessageId, + imageUrls: [params.image.originalUrl], + prompt: baseReplacementPrompt || selectedAgentPreset.name, + sourceImageUrls: params.sourceImageUrls, + agentPresetId: selectedAgentPreset.id, + agentPresetName: selectedAgentPreset.name, + agentCategoryId: + selectedAgentPreset.category?.id || + selectedAgentPreset.categoryId || + null, + agentCategoryName: selectedAgentPreset.category?.name || null, + agentExecutionSnapshotId: presetSnapshot.id, + }, + `${params.streamLabel}:canvas-media`, + ); + } + }; try { for (const [referenceIndex, reference] of replacementReferences.entries()) { @@ -6042,23 +6132,32 @@ export async function POST(request: Request) { imageUrl: reference.imageUrl, models: replacementVisionModels, }); + const replacementSourceType = isDetailReplacementPreset + ? "detail_replacement" + : "sku_replacement"; + const referenceSourceImageUrls = Array.from( + new Set([ + ...sharedUploadedImageUrls, + ...replacementTemplateReferenceImageUrls, + reference.imageUrl, + ]), + ); if (!referenceDecision.shouldReplace) { - replacementResultImages.push({ - originalUrl: reference.imageUrl, - thumbnailUrl: reference.imageUrl, + await emitReplacementResultMessage({ + reference, + bodyText: referenceDecision.reason, + image: { + originalUrl: reference.imageUrl, + thumbnailUrl: reference.imageUrl, + }, + modelDisplayName: null, + sourceImageUrls: referenceSourceImageUrls, + sourceType: replacementSourceType, + streamLabel: isDetailReplacementPreset + ? "agent-preset:image:detail-replacement:item" + : "agent-preset:image:sku-replacement:item", }); - replacementSections.push( - `### ${reference.label}\n\n${referenceDecision.reason}\n\n${buildAgentToolImagesMarkdown( - [ - { - originalUrl: reference.imageUrl, - thumbnailUrl: reference.imageUrl, - imageSize: resolvedImageSize, - }, - ], - )}`, - ); successCount += 1; continue; } @@ -6105,132 +6204,53 @@ export async function POST(request: Request) { if (!firstImage) { throw new Error("未返回结果图片"); } - replacementResultImages.push(firstImage); - replacementSections.push( - `### ${reference.label}\n\n${buildAgentToolImagesMarkdown([ - { - originalUrl: firstImage.originalUrl, - thumbnailUrl: firstImage.thumbnailUrl, - imageSize: resolvedImageSize, - }, - ])}`, - ); + await emitReplacementResultMessage({ + reference, + image: firstImage, + modelDisplayName: resolvedModelDisplayName, + sourceImageUrls: Array.from( + new Set([ + ...sharedUploadedImageUrls, + ...replacementTemplateReferenceImageUrls, + ...replacementReferences.map((item) => item.imageUrl), + ]), + ), + sourceType: replacementSourceType, + streamLabel: isDetailReplacementPreset + ? "agent-preset:image:detail-replacement:item" + : "agent-preset:image:sku-replacement:item", + }); successCount += 1; - generatedReplacementCount += 1; } catch (replacementItemError) { console.error("[Chat API] Agent replacement item execution failed:", { referenceIndex, referenceLabel: reference.label, error: replacementItemError, }); - replacementSections.push( - `### ${reference.label}\n\n执行失败:${ + await emitReplacementResultMessage({ + reference, + bodyText: `生成失败,当前先沿用参考图:${ replacementItemError instanceof Error ? replacementItemError.message : "请稍后重试" }`, - ); + image: { + originalUrl: reference.imageUrl, + thumbnailUrl: reference.imageUrl, + }, + modelDisplayName: null, + sourceImageUrls: referenceSourceImageUrls, + sourceType: replacementSourceType, + streamLabel: isDetailReplacementPreset + ? "agent-preset:image:detail-replacement:item-fallback" + : "agent-preset:image:sku-replacement:item-fallback", + }); + successCount += 1; } } } finally { stopKeepalive(); } - - const assistantText = prependImageModelBadge( - [ - `### ${selectedAgentPreset.name}`, - ...replacementSections, - ] - .filter(Boolean) - .join("\n\n"), - generatedReplacementCount > 0 ? resolvedModelDisplayName : null, - ); - - await saveMessages({ - messages: [ - { - id: replacementAssistantMessageId, - chatId: id, - role: "assistant", - parts: [{ type: "text", text: assistantText }], - attachments: [], - createdAt: new Date(), - videoStorageStatus: null, - temporaryVideoUrl: null, - permanentVideoUrl: null, - archivedAt: null, - videoMetadata: null, - }, - ], - }); - - if (replacementResultImages.length > 0) { - await saveGeneratedImages( - assistantText, - session.user.id, - id, - replacementAssistantMessageId, - baseReplacementPrompt || selectedAgentPreset.name, - resolvedModelRef, - { - sourceAssetIds, - sourceImageUrls: Array.from( - new Set([ - ...sharedUploadedImageUrls, - ...replacementTemplateReferenceImageUrls, - ...replacementReferences.map((item) => item.imageUrl), - ]), - ), - relationType: "edited_from", - sourceType: isDetailReplacementPreset - ? "detail_replacement" - : "sku_replacement", - projectId: contextPayload?.projectId || null, - userPrompt: requestTextContent, - optimizedPrompt: baseReplacementPrompt || undefined, - requestedImageCount: replacementResultImages.length, - aspectRatio: resolvedAspectRatio, - imageSize: resolvedImageSize, - }, - ); - } - - writeAssistantTextChunk( - writer, - assistantText, - isDetailReplacementPreset - ? "agent-preset:image:detail-replacement" - : "agent-preset:image:sku-replacement", - replacementAssistantMessageId, - ); - if (contextPayload?.source === "canvas" && replacementResultImages.length > 0) { - writeCanvasMediaChunk( - writer, - { - messageId: replacementAssistantMessageId, - imageUrls: replacementResultImages.map((image) => image.originalUrl), - prompt: baseReplacementPrompt || selectedAgentPreset.name, - sourceImageUrls: Array.from( - new Set([ - ...sharedUploadedImageUrls, - ...replacementTemplateReferenceImageUrls, - ...replacementReferences.map((item) => item.imageUrl), - ]), - ), - agentPresetId: selectedAgentPreset.id, - agentPresetName: selectedAgentPreset.name, - agentCategoryId: - selectedAgentPreset.category?.id || - selectedAgentPreset.categoryId || - null, - agentCategoryName: selectedAgentPreset.category?.name || null, - agentExecutionSnapshotId: presetSnapshot.id, - }, - isDetailReplacementPreset - ? "agent-preset:image:detail-replacement:canvas-media" - : "agent-preset:image:sku-replacement:canvas-media", - ); - } } else if (selectedAgentPreset.type === "image" && isSceneGenerationPreset) { const sceneSlot = orderedSlots[0]; const sceneAssistantMessageId = generateUUID(); diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index a3f448eed..ad57e5137 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -834,6 +834,20 @@ export function AgentGenerationWorkspaceDarkV2({ () => Array.from(new Set(generatedGroups.flatMap((group) => group.imageUrls))), [generatedGroups] ); + const replacementGeneratedGroupsAfterLastUser = useMemo(() => { + const lastUserIndex = [...messages] + .map((message) => message.role) + .lastIndexOf("user"); + if (lastUserIndex < 0) { + return [] as GeneratedImageGroup[]; + } + + return messages + .slice(lastUserIndex + 1) + .filter((message) => message.role === "assistant") + .map((message) => generatedGroups.find((group) => group.messageId === message.id) || null) + .filter((group): group is GeneratedImageGroup => Boolean(group)); + }, [generatedGroups, messages]); const latestGeneratedGroup = useMemo(() => { const lastUserIndex = [...messages] .map((message) => message.role) @@ -857,7 +871,11 @@ export function AgentGenerationWorkspaceDarkV2({ } return generatedGroups[generatedGroups.length - 1] || null; }, [generatedGroups, messages]); - const latestReplacementResultUrls = latestGeneratedGroup?.imageUrls || []; + const latestReplacementResultUrls = useMemo( + () => + replacementGeneratedGroupsAfterLastUser.flatMap((group) => group.imageUrls), + [replacementGeneratedGroupsAfterLastUser] + ); const displayedResultUrl = selectedResultUrl || latestGeneratedGroup?.imageUrls[0] || null; const isGenerating = status === "streaming" || status === "submitted"; const detailResultCards = useMemo( @@ -2240,10 +2258,21 @@ export function AgentGenerationWorkspaceDarkV2({ )} {selectedModelAttachment ? ( -
-
模特图
-
- {selectedModelAttachment.name || "已选择模特图"} +
+
+ {selectedModelAttachment.name +
+ 模特图 +
+
+
+
+ {selectedModelAttachment.name || "已选择模特图"} +
) : null} @@ -2402,7 +2431,7 @@ export function AgentGenerationWorkspaceDarkV2({
-
模特图
-
- {selectedModelAttachment.name || "已选择模特图"} +
+
+ {selectedModelAttachment.name +
+ 模特图 +
+
+
+
+ {selectedModelAttachment.name || "已选择模特图"} +
) : null} @@ -2799,7 +2839,7 @@ export function AgentGenerationWorkspaceDarkV2({
-
-
+
+
{previewUrl ? (
-
+
{preset.name}