diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 9e54def76..4cce42441 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -114,6 +114,10 @@ import { getAgentGenerationAcceptanceRemainingMs, isAgentGenerationRequestAccepted, } from "@/lib/agents/pending-generation-guard"; +import { + isReplacementResultMode, + shouldShowAgentGridResultSection, +} from "@/lib/agents/generation-result-visibility"; import { defaultAgentWorkflowBusinessModeSettings, normalizeAgentWorkflowBusinessModeSettings, @@ -3118,6 +3122,9 @@ export function AgentGenerationWorkspaceDarkV2({ const selectNodes = useCanvasStore((state) => state.selectNodes); const requestImageNodeAction = useCanvasStore((state) => state.requestImageNodeAction); const workspaceRootRef = useRef(null); + const resultsSectionRef = useRef(null); + const replacementResultsContentRef = useRef(null); + const lastAutoRevealedResultRequestRef = useRef(null); const fileInputRefs = useRef>([]); const detailSlotInputRefs = useRef>([]); const hiddenPromptRef = useRef(null); @@ -4804,17 +4811,9 @@ export function AgentGenerationWorkspaceDarkV2({ userMessageCount, ]); const isAnyGenerationRunning = transportIsGenerating || Boolean(pendingGeneration); - const isSceneResultModeGenerating = - (activeResultModeType === "scene_generation" && - pendingGeneration?.modeType === "scene_generation") || - (activeResultModeType === currentResultModeType && - currentResultModeType === "scene_generation" && - transportIsGenerating); const isDetailResultModeGenerating = pendingGeneration?.modeType === "detail_replacement" || (currentResultModeType === "detail_replacement" && transportIsGenerating); - const isDetailGenerationResultModeGenerating = - pendingGeneration?.modeType === "detail_generation"; const isSkuResultModeGenerating = pendingGeneration?.modeType === "sku_replacement" || (currentResultModeType === "sku_replacement" && transportIsGenerating); @@ -5210,11 +5209,14 @@ export function AgentGenerationWorkspaceDarkV2({ resultCompareSourceByUrl, ] ); - const shouldShowGridResultSection = - activeResultModeUrls.length > 0 || - ((activeResultModeType === "scene_generation" && isSceneResultModeGenerating) || - (activeResultModeType === "detail_generation" && - (isDetailGenerationResultModeGenerating || hasDetailGenerationPlaceholderState))); + const shouldShowGridResultSection = shouldShowAgentGridResultSection({ + activeModeType: activeResultModeType, + activeResultCount: activeResultModeUrls.length, + currentModeType: currentResultModeType, + pendingModeType: pendingGeneration?.modeType, + transportIsGenerating, + hasDetailGenerationPlaceholderState, + }); const hasBrandModelSelection = Boolean( selectedBrandModel?.imageUrl || referenceImages.some((image) => image.source === "brandModel") ); @@ -5388,6 +5390,29 @@ export function AgentGenerationWorkspaceDarkV2({ } }, [pendingGeneration, pendingGenerationProgressCount]); + useEffect(() => { + if (!pendingGeneration || !isReplacementResultMode(pendingGeneration.modeType)) { + return; + } + + const requestKey = + pendingGeneration.requestMessageId || + `${pendingGeneration.chatId}:${pendingGeneration.timestamp}`; + if (lastAutoRevealedResultRequestRef.current === requestKey) { + return; + } + lastAutoRevealedResultRequestRef.current = requestKey; + + const frameId = window.requestAnimationFrame(() => { + const target = replacementResultsContentRef.current || resultsSectionRef.current; + target?.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + }); + return () => window.cancelAnimationFrame(frameId); + }, [pendingGeneration]); + useEffect(() => { if (!pendingGeneration || !chatError) { return; @@ -9685,9 +9710,10 @@ export function AgentGenerationWorkspaceDarkV2({ ) : null} -
-
-
-
输入图
-
- 选中的白底图会作为参考图替换的输入图组 -
-
-
- {selectedProductAttachments.length} 张 -
-
- -
- {selectedProductAttachments.length > 0 ? ( - selectedProductAttachments.map((attachment, index) => ( -
-
- - handlePreviewImageLoadError(event, attachment!.url) - } - /> -
-
-
- 输入图 {index + 1} -
-
- {attachment?.name || "已选择白底图"} -
-
-
- )) - ) : ( -
- 请先在当前“选图与生成”页的输入图槽位中确认用于参考图替换的白底输入图。 -
- )} - - {selectedModelAttachment ? ( -
-
- - handlePreviewImageLoadError(event, selectedModelAttachment.url) - } - /> -
- 模特图 -
-
-
-
- {selectedModelAttachment.name || "已选择模特图"} -
-
-
- ) : null} -
-
- -
+
+
{detailReferenceSourceLabel}参考图
@@ -10116,7 +10066,10 @@ export function AgentGenerationWorkspaceDarkV2({
-
+
替换结果
@@ -10777,7 +10730,10 @@ export function AgentGenerationWorkspaceDarkV2({
-
+
替换结果
diff --git a/lib/agents/generation-result-visibility.ts b/lib/agents/generation-result-visibility.ts new file mode 100644 index 000000000..923c0f3dd --- /dev/null +++ b/lib/agents/generation-result-visibility.ts @@ -0,0 +1,36 @@ +import type { AgentGenerationResultModeType } from "@/types/agent"; + +export function isReplacementResultMode( + modeType: AgentGenerationResultModeType | null | undefined +) { + return modeType === "detail_replacement" || modeType === "sku_replacement"; +} + +export function shouldShowAgentGridResultSection(params: { + activeModeType: AgentGenerationResultModeType; + activeResultCount: number; + currentModeType: AgentGenerationResultModeType; + pendingModeType?: AgentGenerationResultModeType | null; + transportIsGenerating: boolean; + hasDetailGenerationPlaceholderState: boolean; +}) { + if (params.activeResultCount > 0) { + return true; + } + + if (params.pendingModeType === params.activeModeType) { + return true; + } + + if ( + params.transportIsGenerating && + params.activeModeType === params.currentModeType + ) { + return true; + } + + return ( + params.activeModeType === "detail_generation" && + params.hasDetailGenerationPlaceholderState + ); +} diff --git a/scripts/test-agent-generation-result-visibility.ts b/scripts/test-agent-generation-result-visibility.ts new file mode 100644 index 000000000..febf24309 --- /dev/null +++ b/scripts/test-agent-generation-result-visibility.ts @@ -0,0 +1,67 @@ +import { + isReplacementResultMode, + shouldShowAgentGridResultSection, +} from "@/lib/agents/generation-result-visibility"; + +function expect(condition: boolean, message: string) { + if (!condition) { + throw new Error(message); + } +} + +expect( + shouldShowAgentGridResultSection({ + activeModeType: "detail_replacement", + activeResultCount: 0, + currentModeType: "detail_replacement", + pendingModeType: "detail_replacement", + transportIsGenerating: false, + hasDetailGenerationPlaceholderState: false, + }), + "detail replacement must show its result area before the first image returns" +); + +expect( + shouldShowAgentGridResultSection({ + activeModeType: "sku_replacement", + activeResultCount: 0, + currentModeType: "sku_replacement", + pendingModeType: "sku_replacement", + transportIsGenerating: false, + hasDetailGenerationPlaceholderState: false, + }), + "SKU replacement must show its result area before the first image returns" +); + +expect( + shouldShowAgentGridResultSection({ + activeModeType: "scene_generation", + activeResultCount: 0, + currentModeType: "scene_generation", + pendingModeType: null, + transportIsGenerating: true, + hasDetailGenerationPlaceholderState: false, + }), + "the active transport must reveal the current result area" +); + +expect( + !shouldShowAgentGridResultSection({ + activeModeType: "detail_replacement", + activeResultCount: 0, + currentModeType: "scene_generation", + pendingModeType: null, + transportIsGenerating: false, + hasDetailGenerationPlaceholderState: false, + }), + "an inactive empty result category must remain empty" +); + +expect( + isReplacementResultMode("detail_replacement") && + isReplacementResultMode("sku_replacement") && + !isReplacementResultMode("detail_generation"), + "only replacement batches should trigger automatic result reveal" +); + +console.log("agent generation result visibility tests passed");