import type { CanvasViewOperationPlan } from "@/types/canvas/agent-protocol"; export type CanvasMediaStreamPayload = { messageId?: string; imageUrls?: string[]; videoUrls?: string[]; prompt?: string; sourceImageUrls?: string[]; agentPresetId?: string; agentPresetName?: string; agentCategoryId?: string; agentCategoryName?: string; agentExecutionSnapshotId?: string; mediaItemIds?: string[]; handledCanvasMediaItemIds?: string[]; }; export type CanvasOperationStreamPayload = { projectId?: string; operationId?: string; operation?: string; status?: "applied" | "failed" | "shadowed"; createdNodeIds?: string[]; createdNodeIdsByMediaItemId?: Record; updatedNodeIds?: string[]; createdEdgeIds?: string[]; canvasRevision?: string; anchorNodeId?: string; handledCanvasMediaItemIds?: string[]; mediaItemIds?: string[]; errorCode?: string; errorMessage?: string; fallbackToCanvasMedia?: boolean; message?: string; }; export type CanvasViewOperationStreamPayload = CanvasViewOperationPlan; export function normalizeCanvasStreamIdList(value: unknown) { return Array.isArray(value) ? value.map((id) => String(id || "").trim()).filter(Boolean) : []; } export function normalizeCanvasAgentClientBoolean( value: unknown, fallback: boolean ) { if (typeof value !== "string") return fallback; const normalized = value.trim().toLowerCase(); if (!normalized) return fallback; if (["1", "true", "yes", "on", "enabled"].includes(normalized)) return true; if (["0", "false", "no", "off", "disabled"].includes(normalized)) return false; return fallback; } export function isCanvasAgentViewOperationClientEnabled(value: unknown) { return normalizeCanvasAgentClientBoolean(value, true); } export function getCanvasOperationHandledMediaItemIds( payload: CanvasOperationStreamPayload ) { if (payload.status === "failed" || payload.status === "shadowed") return []; return normalizeCanvasStreamIdList(payload.handledCanvasMediaItemIds); } export function getCanvasMediaPayloadHandledItemIds(payload: CanvasMediaStreamPayload) { return normalizeCanvasStreamIdList(payload.handledCanvasMediaItemIds); } export function shouldSkipCanvasMediaPayloadForHandledItems(params: { payload: CanvasMediaStreamPayload; handledCanvasMediaItemIds: ReadonlySet; }) { const mediaItemIds = normalizeCanvasStreamIdList(params.payload.mediaItemIds); return ( mediaItemIds.length > 0 && mediaItemIds.every((id) => params.handledCanvasMediaItemIds.has(id)) ); } function normalizeCanvasStreamMediaUrls(value: unknown) { return Array.isArray(value) ? value .map((url) => String(url || "").trim()) .filter((url) => /^https?:\/\//i.test(url)) : []; } export function filterCanvasMediaPayloadForUnhandledItems(params: { payload: CanvasMediaStreamPayload; handledCanvasMediaItemIds: ReadonlySet; }): CanvasMediaStreamPayload { const mediaItemIds = normalizeCanvasStreamIdList(params.payload.mediaItemIds); if (mediaItemIds.length === 0 || params.handledCanvasMediaItemIds.size === 0) { return params.payload; } const imageUrls = normalizeCanvasStreamMediaUrls(params.payload.imageUrls); const videoUrls = normalizeCanvasStreamMediaUrls(params.payload.videoUrls); const mediaItems = [ ...imageUrls.map((url) => ({ type: "image" as const, url })), ...videoUrls.map((url) => ({ type: "video" as const, url })), ]; if (mediaItems.length === 0 || mediaItems.length !== mediaItemIds.length) { return params.payload; } const unhandledItems = mediaItems .map((item, index) => ({ ...item, mediaItemId: mediaItemIds[index]! })) .filter((item) => !params.handledCanvasMediaItemIds.has(item.mediaItemId)); return { ...params.payload, imageUrls: unhandledItems .filter((item) => item.type === "image") .map((item) => item.url), videoUrls: unhandledItems .filter((item) => item.type === "video") .map((item) => item.url), mediaItemIds: unhandledItems.map((item) => item.mediaItemId), }; } export function shouldRetryCanvasViewOperationAfterRefresh( payload: CanvasViewOperationStreamPayload, currentProjectId?: string ) { const operation = String(payload?.operation || "").trim(); if (!operation) return false; const targetProjectId = String(payload?.projectId || "").trim(); const normalizedCurrentProjectId = String(currentProjectId || "").trim(); if ( targetProjectId && normalizedCurrentProjectId && targetProjectId !== normalizedCurrentProjectId ) { return false; } if (normalizeCanvasStreamIdList(payload.nodeIds).length > 0) return true; if (String(payload.anchorNodeId || "").trim()) return true; return ( operation === "focus_node" || operation === "focus_nodes" || operation === "focus_selection" || operation === "focus_latest_result" || operation === "fit_all" ); } export type ImageGenerationStatusStreamPayload = { phase?: "queued" | "processing" | "finalizing" | "failed" | "completed"; providerType?: string; modelName?: string; imageCount?: number; queuePosition?: number; sameModelRunningCount?: number; concurrencyLimit?: number; estimatedWaitMs?: number; }; export type AgentTargetLockRefPreview = { url: string; title?: string; nodeId?: string; assetId?: string; source: | "current_message" | "canvas_selection" | "canvas_context" | "history_user_upload" | "history_assistant_result" | "working_memory"; role: "upload" | "selected" | "reference" | "history_result" | "working_memory"; isSelected?: boolean; positionLabel?: string; }; export type AgentTargetLockStreamPayload = { stage?: "locked" | "clarify" | "unresolved"; resolutionMode?: | "explicit_selection" | "current_upload" | "text_reference" | "working_memory" | "latest_result" | "clarification" | "none"; confidence?: number; reason?: string; needsClarification?: boolean; clarificationMessage?: string; targetCount?: number; candidateCount?: number; targets?: AgentTargetLockRefPreview[]; candidates?: AgentTargetLockRefPreview[]; }; export type AgentProgressStreamPayload = { stage?: "planning" | "clarify" | "executing" | "completed" | "failed"; currentStep?: "lock_target" | "analyze" | "select_tool" | "execute" | "finalize"; title?: string; message?: string; toolName?: string; capability?: string; }; export const AGENT_PROGRESS_STEPS: Array<{ key: NonNullable; label: string; }> = [ { key: "lock_target", label: "整理素材" }, { key: "analyze", label: "理解需求" }, { key: "select_tool", label: "优化提示" }, { key: "execute", label: "提交生成" }, { key: "finalize", label: "整理结果" }, ]; export function getAgentTargetLockTitle(payload?: AgentTargetLockStreamPayload | null) { if (payload?.stage === "clarify") return "需要确认目标"; if (payload?.stage === "locked") return "当前锁定目标"; return "待锁定目标"; } export function getAgentTargetResolutionLabel( mode?: AgentTargetLockStreamPayload["resolutionMode"] ) { switch (mode) { case "explicit_selection": return "画布选中"; case "current_upload": return "当前上传"; case "text_reference": return "文本定位"; case "working_memory": return "工作记忆"; case "latest_result": return "最新结果"; case "clarification": return "待确认"; case "none": return "未解析"; default: return "自动解析"; } } export function getAgentProgressFallbackTitle(payload?: AgentProgressStreamPayload | null) { switch (payload?.stage) { case "clarify": return "需要补充信息"; case "executing": return "正在生成结果"; case "completed": return "创作完成"; case "failed": return "创作失败"; default: return "正在理解需求"; } }