fix(agent): restore detail replacement result pane
This commit is contained in:
parent
87bbcf0ccd
commit
c988f2dee0
@ -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<HTMLDivElement | null>(null);
|
||||
const resultsSectionRef = useRef<HTMLElement | null>(null);
|
||||
const replacementResultsContentRef = useRef<HTMLDivElement | null>(null);
|
||||
const lastAutoRevealedResultRequestRef = useRef<string | null>(null);
|
||||
const fileInputRefs = useRef<Array<HTMLInputElement | null>>([]);
|
||||
const detailSlotInputRefs = useRef<Array<HTMLInputElement | null>>([]);
|
||||
const hiddenPromptRef = useRef<string | null>(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}
|
||||
|
||||
<motion.section
|
||||
ref={resultsSectionRef}
|
||||
id="agent-anchor-results"
|
||||
className={cn(
|
||||
"relative flex min-h-[620px] flex-col rounded-[28px] border border-white/12 bg-white/[0.04] shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]",
|
||||
"relative flex min-h-[620px] scroll-mt-24 flex-col rounded-[28px] border border-white/12 bg-white/[0.04] shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]",
|
||||
isDetailReplacementPreset ? "overflow-visible" : "overflow-hidden"
|
||||
)}
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
@ -9860,84 +9886,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
{isDetailReplacementPreset &&
|
||||
!isDetailAiGenerationMethod &&
|
||||
activeResultModeType === currentResultModeType ? (
|
||||
<div className="grid flex-1 min-h-0 grid-cols-1 gap-0 xl:grid-cols-[320px_repeat(2,minmax(0,1fr))]">
|
||||
<div className="border-b border-white/10 p-4 xl:border-b-0 xl:border-r">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-white">输入图</div>
|
||||
<div className="text-xs text-white/40">
|
||||
选中的白底图会作为参考图替换的输入图组
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-white/55">
|
||||
{selectedProductAttachments.length} 张
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{selectedProductAttachments.length > 0 ? (
|
||||
selectedProductAttachments.map((attachment, index) => (
|
||||
<div
|
||||
key={`${attachment?.url || "input"}-${index}`}
|
||||
style={AGENT_DEFERRED_LIST_CARD_STYLE}
|
||||
className="overflow-hidden rounded-[22px] border border-white/10 bg-black/20"
|
||||
>
|
||||
<div className="aspect-square overflow-hidden bg-black/30">
|
||||
<AgentPreviewImage
|
||||
src={attachment!.url}
|
||||
alt={attachment!.name || `输入图 ${index + 1}`}
|
||||
className="h-full w-full object-cover"
|
||||
previewWidth={400}
|
||||
previewQuality={74}
|
||||
onError={(event) =>
|
||||
handlePreviewImageLoadError(event, attachment!.url)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t border-white/10 px-3 py-2">
|
||||
<div className="text-xs font-medium text-white/80">
|
||||
输入图 {index + 1}
|
||||
</div>
|
||||
<div className="truncate text-[11px] text-white/40">
|
||||
{attachment?.name || "已选择白底图"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="flex min-h-[160px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-4 text-center text-sm text-white/42">
|
||||
请先在当前“选图与生成”页的输入图槽位中确认用于参考图替换的白底输入图。
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedModelAttachment ? (
|
||||
<div className="overflow-hidden rounded-[22px] border border-emerald-400/20 bg-emerald-500/[0.06]">
|
||||
<div className="relative aspect-[5/4] overflow-hidden bg-[radial-gradient(circle_at_top,rgba(16,185,129,0.12),transparent_58%),linear-gradient(180deg,#08110d_0%,#101916_100%)]">
|
||||
<AgentPreviewImage
|
||||
src={selectedModelAttachment.url}
|
||||
alt={selectedModelAttachment.name || "模特图"}
|
||||
className="h-full w-full object-cover"
|
||||
previewWidth={720}
|
||||
previewQuality={76}
|
||||
onError={(event) =>
|
||||
handlePreviewImageLoadError(event, selectedModelAttachment.url)
|
||||
}
|
||||
/>
|
||||
<div className="absolute left-3 top-3 rounded-full border border-emerald-300/25 bg-black/45 px-2.5 py-1 text-[11px] text-emerald-100">
|
||||
模特图
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-emerald-400/15 px-3 py-2">
|
||||
<div className="truncate text-xs font-medium text-emerald-100">
|
||||
{selectedModelAttachment.name || "已选择模特图"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 border-b border-white/10 p-4 xl:border-b-0 xl:border-r">
|
||||
<div className="grid min-h-0 flex-1 grid-cols-1 gap-0 lg:grid-cols-2">
|
||||
<div className="min-h-0 border-b border-white/10 p-4 lg:border-b-0 lg:border-r">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-white">{detailReferenceSourceLabel}参考图</div>
|
||||
@ -10116,7 +10066,10 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 p-4">
|
||||
<div
|
||||
ref={replacementResultsContentRef}
|
||||
className="min-h-0 scroll-mt-24 p-4"
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-white">替换结果</div>
|
||||
@ -10777,7 +10730,10 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 p-4">
|
||||
<div
|
||||
ref={replacementResultsContentRef}
|
||||
className="min-h-0 scroll-mt-24 p-4"
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-white">替换结果</div>
|
||||
|
||||
36
lib/agents/generation-result-visibility.ts
Normal file
36
lib/agents/generation-result-visibility.ts
Normal file
@ -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
|
||||
);
|
||||
}
|
||||
67
scripts/test-agent-generation-result-visibility.ts
Normal file
67
scripts/test-agent-generation-result-visibility.ts
Normal file
@ -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");
|
||||
Loading…
x
Reference in New Issue
Block a user