diff --git a/app/canvas/[id]/page.tsx b/app/canvas/[id]/page.tsx index c0495e780..130a4ab28 100644 --- a/app/canvas/[id]/page.tsx +++ b/app/canvas/[id]/page.tsx @@ -103,6 +103,7 @@ import { } from "@/lib/agents/mode-workspace-state"; import { createEmptyAgentGenerationProjectPersistedState, + createEmptyAgentWorkflowPersistedState, createEmptyAgentGenerationProjectWorkspaceState, normalizeAgentGenerationProjectPersistedState, } from "@/lib/agents/generation-project-state"; @@ -840,7 +841,7 @@ export default function CanvasEditorPage({ params }: PageProps) { try { const result = await agentProjectService.createProject({ title: buildDefaultAgentProjectTitle(), - projectState: persistedAgentProjectStateRef.current, + projectState: createEmptyAgentGenerationProjectPersistedState(), }); if (!result.success || !result.data) { throw new Error(result.error || "创建生图项目失败"); @@ -889,12 +890,8 @@ export default function CanvasEditorPage({ params }: PageProps) { const persistedState = normalizeAgentGenerationProjectPersistedState( result.data.projectState ); - const currentWorkflowState = currentWorkflowPersistedStateRef.current; - const persistedWorkflow = hasMeaningfulWorkflowState(persistedState.workflowState) - ? persistedState.workflowState - : hasMeaningfulWorkflowState(currentWorkflowState) - ? currentWorkflowState - : persistedState.workflowState; + const persistedWorkflow = + persistedState.workflowState || createEmptyAgentWorkflowPersistedState(); setWorkflowInputs({ primarySku: String(persistedWorkflow.inputs?.primarySku || "").trim(), @@ -1883,7 +1880,7 @@ export default function CanvasEditorPage({ params }: PageProps) { return ( (null); const detailGenerationCancelRef = useRef(false); const workspaceStateSignatureRef = useRef(""); + const generationRequestSnapshotRef = useRef<{ + hiddenPrompt: string | null; + }>({ + hiddenPrompt: null, + }); const [localAgentModeState, setLocalAgentModeState] = useState( () => createEmptyAgentModeWorkspaceState() ); @@ -1379,7 +1384,10 @@ export function AgentGenerationWorkspaceDarkV2({ body: { id: rawChatId, message: request.messages.at(-1), - hiddenPrompt: hiddenPromptRef.current || undefined, + hiddenPrompt: + generationRequestSnapshotRef.current.hiddenPrompt || + hiddenPromptRef.current || + undefined, agentPresetId: transportState.selectedAgentPresetId || selectedAgentPresetId || undefined, selectedChatModel: @@ -1439,6 +1447,25 @@ export function AgentGenerationWorkspaceDarkV2({ ]); // 初始化附件 + useEffect(() => { + workspaceStateSignatureRef.current = ""; + lastSyncedProductSlotKeysRef.current = []; + lastSyncedBrandModelUrlRef.current = null; + lastSyncedDetailProductSlotKeysRef.current = []; + lastSyncedDetailBundleSlotKeysRef.current = []; + lastSyncedDetailModelSlotKeyRef.current = null; + setInput(""); + setAttachments([]); + setSelectedResultUrl(null); + setFullscreenPreview(null); + setPendingGeneration(null); + setImageParams({ + aspectRatio: "auto", + imageSize: "2K", + imageCount: 1, + }); + }, [projectId]); + useEffect(() => { if (!hasAgentUploadSlots) { setAttachments((current) => (current.length === 0 ? current : [])); @@ -1471,20 +1498,12 @@ export function AgentGenerationWorkspaceDarkV2({ (_, index) => current[index] || null ); for (let index = 0; index < productSlotCount; index += 1) { - const currentSlot = result[index]; const nextAttachment = nextSlots[index]; - const currentSlotKey = getAttachmentIdentityKey(currentSlot); const previousSyncedKey = lastSyncedProductSlotKeysRef.current[index] || null; - const currentIsPreviousAutoSelection = - Boolean(previousSyncedKey) && currentSlotKey === previousSyncedKey; - const currentIsBrandModel = - currentSlotKey ? brandModelAttachmentKeys.has(currentSlotKey) : false; if (nextAttachment) { - if (!currentSlot || currentIsPreviousAutoSelection || currentIsBrandModel) { - result[index] = nextAttachment as PickedAttachment; - } - } else if (currentIsPreviousAutoSelection || currentIsBrandModel) { + result[index] = nextAttachment as PickedAttachment; + } else if (previousSyncedKey) { result[index] = null; } } @@ -1528,11 +1547,7 @@ export function AgentGenerationWorkspaceDarkV2({ const currentSlot = modelSlotIndex >= 0 ? slots[modelSlotIndex] ?? null : null; const previousSyncedUrl = lastSyncedBrandModelUrlRef.current; - if ( - modelSlotIndex >= 0 && - nextAttachment && - (!currentSlot || currentSlot.url === previousSyncedUrl) - ) { + if (modelSlotIndex >= 0 && nextAttachment) { slots[modelSlotIndex] = nextAttachment; } else if (modelSlotIndex >= 0 && previousSyncedUrl && currentSlot?.url === previousSyncedUrl) { slots[modelSlotIndex] = null; @@ -2457,9 +2472,13 @@ export function AgentGenerationWorkspaceDarkV2({ toast.error("请至少补充要求或上传图片"); return; } - hiddenPromptRef.current = workflowContext?.promptText + const nextHiddenPrompt = workflowContext?.promptText ? `【工作流设定】\n${workflowContext.promptText}\n\n请严格遵循以上工作流设定输出内容,并保持整体结果一致。` : null; + hiddenPromptRef.current = nextHiddenPrompt; + generationRequestSnapshotRef.current = { + hiddenPrompt: nextHiddenPrompt, + }; setActiveResultModeType(currentResultModeType); setSelectedResultUrl(null); setPendingGeneration({ @@ -2478,6 +2497,9 @@ export function AgentGenerationWorkspaceDarkV2({ sendMessage({ role: "user", parts }); setInput(""); } catch (error) { + generationRequestSnapshotRef.current = { + hiddenPrompt: null, + }; setPendingGeneration(null); toast.error(error instanceof Error ? error.message : "发送生成请求失败"); } diff --git a/components/agents/AgentWorkflowPanelDarkV2.tsx b/components/agents/AgentWorkflowPanelDarkV2.tsx index 336b62184..d12ab6721 100644 --- a/components/agents/AgentWorkflowPanelDarkV2.tsx +++ b/components/agents/AgentWorkflowPanelDarkV2.tsx @@ -417,6 +417,11 @@ export function AgentWorkflowPanelDarkV2({ ] as const); return new Map(entries); }, [resolvedConfig.steps, selections]); + const [autoAdvanceNotice, setAutoAdvanceNotice] = useState<{ + completedStepTitle: string; + completedSummary: string; + nextStepTitle: string; + } | null>(null); const [activeEmbeddedStepKey, setActiveEmbeddedStepKey] = useState(""); const [activeEmbeddedOptionDepth, setActiveEmbeddedOptionDepth] = useState(1); const [pendingAutoAdvanceStepKey, setPendingAutoAdvanceStepKey] = useState(null); @@ -519,6 +524,12 @@ export function AgentWorkflowPanelDarkV2({ ); if (nextStep && nextStep.step.key !== activeEmbeddedStepKey) { + setAutoAdvanceNotice({ + completedStepTitle: activeEmbeddedStepEntry.step.title, + completedSummary: + stepSelectionSummaryMap[activeEmbeddedStepEntry.step.key] || "已完成当前步骤选择", + nextStepTitle: nextStep.step.title, + }); setActiveEmbeddedStepKey(nextStep.step.key); setActiveEmbeddedOptionDepth(1); } @@ -532,8 +543,21 @@ export function AgentWorkflowPanelDarkV2({ flattenedResolvedSteps, pendingAutoAdvanceStepKey, selections, + stepSelectionSummaryMap, ]); + useEffect(() => { + if (!autoAdvanceNotice) { + return; + } + + const timer = window.setTimeout(() => { + setAutoAdvanceNotice(null); + }, 3200); + + return () => window.clearTimeout(timer); + }, [autoAdvanceNotice]); + const renderOptionLevels = ( step: AgentWorkflowUiConfig["steps"][number], options: AgentWorkflowUiConfig["steps"][number]["options"], @@ -700,6 +724,7 @@ export function AgentWorkflowPanelDarkV2({ type="button" onClick={() => { setPendingAutoAdvanceStepKey(null); + setAutoAdvanceNotice(null); setActiveEmbeddedStepKey(step.key); }} className="group relative flex min-w-0 flex-col items-start pr-2 text-left transition-transform duration-200 hover:-translate-y-[1px]" @@ -717,11 +742,11 @@ export function AgentWorkflowPanelDarkV2({ > {stepIndex + 1} - {step.title} + {active ? ( + + 当前步骤 + + ) : null} + {autoAdvanceNotice ? ( + +
+ + + + 已完成「{autoAdvanceNotice.completedStepTitle}」 + + 已自动切换到「{autoAdvanceNotice.nextStepTitle}」 +
+
+ 刚刚完成的内容:{autoAdvanceNotice.completedSummary} +
+
+ ) : null} + +
+
+ + 当前正在选择 + + + {activeEmbeddedStepEntry.step.title} + +
+
+ {activeEmbeddedOptionLevel.depth === 1 + ? "当前区域展示的是这个步骤的主卡片。选中后系统会继续切到该步骤的下一级,或自动进入下一个步骤。" + : `当前区域展示的是「${activeEmbeddedStepEntry.step.title}」的第 ${activeEmbeddedOptionLevel.depth} 级子卡片,不是上一个已完成步骤的卡片。`} +
+
+
{activeEmbeddedOptionLevel.depth === 1