fix agent project state sync and workflow prompt freshness
This commit is contained in:
parent
785177bed4
commit
2e87ab91d3
@ -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 (
|
||||
<AgentGenerationWorkspaceDarkV2
|
||||
chatId={agentModeChatId}
|
||||
projectId={id}
|
||||
projectId={agentProjectId || id}
|
||||
selectedAgentPresetId={isAgentWorkspaceEnabled ? selectedAgentPresetId : null}
|
||||
selectedBrandModel={selectedBrandModel}
|
||||
workflowContext={workflowContext}
|
||||
|
||||
@ -894,6 +894,11 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
const lastSyncedDetailModelSlotKeyRef = useRef<string | null>(null);
|
||||
const detailGenerationCancelRef = useRef(false);
|
||||
const workspaceStateSignatureRef = useRef<string>("");
|
||||
const generationRequestSnapshotRef = useRef<{
|
||||
hiddenPrompt: string | null;
|
||||
}>({
|
||||
hiddenPrompt: null,
|
||||
});
|
||||
const [localAgentModeState, setLocalAgentModeState] = useState<AgentModeWorkspaceState>(
|
||||
() => 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 : "发送生成请求失败");
|
||||
}
|
||||
|
||||
@ -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<string>("");
|
||||
const [activeEmbeddedOptionDepth, setActiveEmbeddedOptionDepth] = useState(1);
|
||||
const [pendingAutoAdvanceStepKey, setPendingAutoAdvanceStepKey] = useState<string | null>(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}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"truncate text-[13px] font-semibold transition-colors",
|
||||
active
|
||||
? "text-white"
|
||||
<span
|
||||
className={cn(
|
||||
"truncate text-[13px] font-semibold transition-colors",
|
||||
active
|
||||
? "text-white"
|
||||
: completed
|
||||
? "text-white/82"
|
||||
: "text-white/52 group-hover:text-white/76"
|
||||
@ -729,6 +754,11 @@ export function AgentWorkflowPanelDarkV2({
|
||||
>
|
||||
{step.title}
|
||||
</span>
|
||||
{active ? (
|
||||
<span className="rounded-full border border-emerald-300/30 bg-emerald-400/10 px-2 py-0.5 text-[10px] font-medium text-emerald-200">
|
||||
当前步骤
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
@ -773,6 +803,44 @@ export function AgentWorkflowPanelDarkV2({
|
||||
|
||||
{activeEmbeddedOptionLevel ? (
|
||||
<div className="space-y-4">
|
||||
{autoAdvanceNotice ? (
|
||||
<motion.div
|
||||
key={`${autoAdvanceNotice.completedStepTitle}-${autoAdvanceNotice.nextStepTitle}`}
|
||||
initial={{ opacity: 0, y: -10, scale: 0.98 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="rounded-2xl border border-emerald-300/16 bg-[linear-gradient(135deg,rgba(16,185,129,0.12),rgba(6,10,12,0.86))] px-4 py-3 shadow-[0_14px_40px_rgba(0,0,0,0.28)]"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm font-semibold text-white">
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-emerald-300 text-black">
|
||||
<Check className="h-4 w-4" />
|
||||
</span>
|
||||
<span>已完成「{autoAdvanceNotice.completedStepTitle}」</span>
|
||||
<ArrowRight className="h-4 w-4 text-emerald-200/80" />
|
||||
<span className="text-emerald-200">已自动切换到「{autoAdvanceNotice.nextStepTitle}」</span>
|
||||
</div>
|
||||
<div className="mt-1 pl-9 text-xs leading-5 text-emerald-50/72">
|
||||
刚刚完成的内容:{autoAdvanceNotice.completedSummary}
|
||||
</div>
|
||||
</motion.div>
|
||||
) : null}
|
||||
|
||||
<div className="rounded-2xl border border-emerald-300/12 bg-emerald-400/[0.05] px-4 py-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="rounded-full border border-emerald-300/24 bg-emerald-400/10 px-2.5 py-1 text-[11px] font-medium text-emerald-200">
|
||||
当前正在选择
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-white">
|
||||
{activeEmbeddedStepEntry.step.title}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs leading-5 text-emerald-50/68">
|
||||
{activeEmbeddedOptionLevel.depth === 1
|
||||
? "当前区域展示的是这个步骤的主卡片。选中后系统会继续切到该步骤的下一级,或自动进入下一个步骤。"
|
||||
: `当前区域展示的是「${activeEmbeddedStepEntry.step.title}」的第 ${activeEmbeddedOptionLevel.depth} 级子卡片,不是上一个已完成步骤的卡片。`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-full border border-white/12 px-2.5 py-1 text-[11px] font-medium text-white/45">
|
||||
{activeEmbeddedOptionLevel.depth === 1
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user