From 33f25f4b21e0e44c4b61609f145dd2dde6abef5e Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 18 May 2026 17:50:33 +0800 Subject: [PATCH] fix: stabilize agent project startup --- app/api/messages/route.ts | 34 ++++- .../[id]/CanvasEditorLegacyPageClient.tsx | 139 +++++++----------- lib/agents/workflow-ui-config.ts | 14 ++ services/agent-projects/index.ts | 111 ++++++++++---- 4 files changed, 187 insertions(+), 111 deletions(-) diff --git a/app/api/messages/route.ts b/app/api/messages/route.ts index d7108c6fd..bfef25f75 100644 --- a/app/api/messages/route.ts +++ b/app/api/messages/route.ts @@ -25,6 +25,30 @@ export const revalidate = 0; const FAL_IMAGE_TASK_POLL_INTERVAL_MS = 2_000; const falTaskLastPolledAt = new Map(); const falTaskSyncInFlight = new Set(); +const AGENT_MODE_SCOPED_CHAT_SUFFIX = "a93e17a60001"; + +function isAgentModeScopedChatId(chatId: string) { + const normalized = String(chatId || "") + .trim() + .toLowerCase() + .replace(/-/g, ""); + + return ( + /^[0-9a-f]{32}$/.test(normalized) && + normalized.endsWith(AGENT_MODE_SCOPED_CHAT_SUFFIX) + ); +} + +function createEmptyMessagesResponse() { + return NextResponse.json( + { messages: [] }, + { + headers: { + "Cache-Control": "no-store, max-age=0", + }, + } + ); +} async function markFalTaskFailed(params: { message: DBMessage; @@ -212,7 +236,15 @@ export async function GET(request: NextRequest) { } const selectedChat = await getChatById({ id: chatId }); - if (!selectedChat || selectedChat.userId !== session.user.id) { + if (!selectedChat) { + if (isAgentModeScopedChatId(chatId)) { + return createEmptyMessagesResponse(); + } + + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + + if (selectedChat.userId !== session.user.id) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } diff --git a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx index 01d5e736c..b26146327 100644 --- a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx +++ b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx @@ -967,38 +967,6 @@ function normalizeImportedCanvasState(payload: unknown): { }; } -function hasMeaningfulWorkflowState( - workflowState: AgentWorkflowPersistedState | null | undefined -) { - if (!workflowState) return false; - - const inputs = workflowState.inputs; - const hasInputs = Boolean( - String(inputs?.primarySku || "").trim() || - String(inputs?.secondarySku || "").trim() || - String(inputs?.brand || "").trim() || - String(inputs?.store || "").trim() || - String(inputs?.officialSecondCategory || "").trim() || - String(inputs?.officialThirdCategory || "").trim() || - String(inputs?.thirdCategory || "").trim() || - String(inputs?.fourthCategory || "").trim() || - String(inputs?.fifthCategory || "").trim() || - String(inputs?.priceBand || "").trim() - ); - - const hasSelections = Object.values(workflowState.selections || {}).some( - (value) => Boolean(String(value || "").trim()) - ); - const hasBrandModel = Boolean( - String(workflowState.selectedBrandModel?.imageUrl || "").trim() - ); - const hasPreset = Boolean( - String(workflowState.selectedAgentPresetId || "").trim() - ); - - return hasInputs || hasSelections || hasBrandModel || hasPreset; -} - const FLOATING_PANEL_WIDTH = 460; const FLOATING_PANEL_INSET = 16; const DOCKED_PANEL_INSET = 0; @@ -1712,12 +1680,14 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { const hasClientPoint = typeof anchorPoint?.clientX === "number" && typeof anchorPoint?.clientY === "number"; + const clientX = hasClientPoint ? anchorPoint.clientX : null; + const clientY = hasClientPoint ? anchorPoint.clientY : null; anchorX = hasClientPoint - ? Math.min(Math.max(anchorPoint.clientX - rect.left, 0), rect.width) + ? Math.min(Math.max((clientX ?? 0) - rect.left, 0), rect.width) : rect.width / 2; anchorY = hasClientPoint - ? Math.min(Math.max(anchorPoint.clientY - rect.top, 0), rect.height) + ? Math.min(Math.max((clientY ?? 0) - rect.top, 0), rect.height) : rect.height / 2; } @@ -2895,14 +2865,6 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { workflowMaterials, sanitizedWorkflowSelections, ]); - const currentWorkflowPersistedStateRef = useRef( - buildCurrentWorkflowPersistedState() - ); - - useEffect(() => { - currentWorkflowPersistedStateRef.current = - buildCurrentWorkflowPersistedState(); - }, [buildCurrentWorkflowPersistedState]); useEffect(() => { void checkCanvasAuthSession(); @@ -2936,15 +2898,11 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { console.log("[Canvas] Loading project:", id); const loadProject = async () => { - const preservedWorkflowState = currentWorkflowPersistedStateRef.current; - const shouldPreserveCurrentWorkflowState = - requestedAgentMode && - lastLoadedCanvasIdRef.current === id && - hasMeaningfulWorkflowState(preservedWorkflowState); - setIsLoading(true); - setWorkflowHydrationSource("pending"); - if (!shouldPreserveCurrentWorkflowState) { + if (!requestedAgentMode) { + setWorkflowHydrationSource("pending"); + } + if (!requestedAgentMode) { setWorkflowInputs({ primarySku: "", secondarySku: "", @@ -2962,13 +2920,15 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { setSelectedBrandModel(null); setSelectedAgentPresetId(null); } - setAgentModeState(createEmptyAgentModeWorkspaceState()); - setAgentWorkspaceState(createEmptyAgentGenerationProjectWorkspaceState()); - setAgentProjectTitle("未命名生图项目"); - setAgentProjectThumbnail(null); - setAgentProjectResultCollections({}); - setAgentProjectDeletedResultCollections({}); - setLoadedAgentProjectId(null); + if (!requestedAgentMode) { + setAgentModeState(createEmptyAgentModeWorkspaceState()); + setAgentWorkspaceState(createEmptyAgentGenerationProjectWorkspaceState()); + setAgentProjectTitle("未命名生图项目"); + setAgentProjectThumbnail(null); + setAgentProjectResultCollections({}); + setAgentProjectDeletedResultCollections({}); + setLoadedAgentProjectId(null); + } // 🆕 先清空当前画布状态,确保切换时不会显示旧数据 useCanvasStore.getState().clearCanvas(); @@ -3077,8 +3037,6 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { normalizeAgentModeWorkspaceState(persistedWorkflow.agentModeState) ); setWorkflowHydrationSource("project"); - } else if (requestedAgentMode && shouldPreserveCurrentWorkflowState) { - setWorkflowHydrationSource("project"); } else { setWorkflowHydrationSource("empty"); } @@ -3995,32 +3953,42 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { }, [agentProjectId, agentProjectTitle, handleRenameAgentProject]); const handleCreateAgentProject = useCallback(async () => { - if (agentProjectId) { - await persistAgentProject("switch"); - } - - const result = await agentProjectService.createProject({ - title: buildDefaultAgentProjectTitle(), - projectState: createEmptyAgentGenerationProjectPersistedState(), - }); - if (!result.success || !result.data) { - if (isUnauthorizedApiError(result.error)) { - setIsAuthenticated(false); - openLoginDialog( - result.error || "当前登录状态已失效,请重新登录后继续操作。" - ); - } - toast.error(result.error || "创建生图项目失败"); + if (isAgentProjectSaving) { return; } - toast.success("已新建空白项目"); - updateCanvasSearchParams({ - mode: "agent", - agentProjectId: result.data.id, - }); + setIsAgentProjectSaving(true); + try { + if (agentProjectId) { + await persistAgentProject("switch"); + } + + const result = await agentProjectService.createProject({ + title: buildDefaultAgentProjectTitle(), + projectState: createEmptyAgentGenerationProjectPersistedState(), + }); + if (!result.success || !result.data) { + if (isUnauthorizedApiError(result.error)) { + setIsAuthenticated(false); + openLoginDialog( + result.error || "当前登录状态已失效,请重新登录后继续操作。" + ); + } + toast.error(result.error || "创建生图项目失败"); + return; + } + + toast.success("已新建空白项目"); + updateCanvasSearchParams({ + mode: "agent", + agentProjectId: result.data.id, + }); + } finally { + setIsAgentProjectSaving(false); + } }, [ agentProjectId, + isAgentProjectSaving, openLoginDialog, persistAgentProject, updateCanvasSearchParams, @@ -9034,14 +9002,21 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {