import type { CanvasViewOperationPlan } from "@/types/canvas/agent-protocol"; import type { CanvasCapabilityClientCommand } from "@/lib/canvas/capabilities/client-command"; export type CanvasCapabilityCommandStreamPayload = CanvasCapabilityClientCommand; 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 type AgentProgressStepState = | "upcoming" | "current" | "completed" | "failed"; export type AgentProgressNodeStep = { key: NonNullable; label: string; state: AgentProgressStepState; }; 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 "正在理解需求"; } } export function getAgentProgressActionLabel( payload?: AgentProgressStreamPayload | null ) { switch (payload?.capability) { case "analyze": return "图片分析"; case "generic_edit": return "修图"; case "remove_background": return "抠图"; case "remove_watermark": return "去水印"; case "enhance": return "图像增强"; case "resize": return "尺寸调整"; case "extend": return "扩图"; case "angle_variant": return "多视角"; case "local_edit": return "局部编辑"; case "box_cutout": return "框选抠图"; case "generate_image": return "生图"; case "generate_video": return "视频生成"; default: return "工具"; } } export function getAgentProgressStepLabel( stepKey: NonNullable, payload?: AgentProgressStreamPayload | null ) { const actionLabel = getAgentProgressActionLabel(payload); switch (stepKey) { case "lock_target": return "理解用户需求"; case "analyze": return payload?.capability === "analyze" ? "分析图片" : "分析使用工具"; case "select_tool": return actionLabel === "工具" ? "确认执行工具" : `确认${actionLabel}工具`; case "execute": return actionLabel === "工具" ? "执行中" : `${actionLabel}中`; case "finalize": return "整理结果"; default: return "处理中"; } } export function buildAgentProgressNodeSteps( payload?: AgentProgressStreamPayload | null ): AgentProgressNodeStep[] { const currentStep = payload?.currentStep || "lock_target"; const currentStepIndex = Math.max( 0, AGENT_PROGRESS_STEPS.findIndex((step) => step.key === currentStep) ); return AGENT_PROGRESS_STEPS.map((step, index) => { let state: AgentProgressStepState = "upcoming"; if (payload?.stage === "failed") { state = index < currentStepIndex ? "completed" : index === currentStepIndex ? "failed" : "upcoming"; } else if (payload?.stage === "completed") { state = "completed"; } else { state = index < currentStepIndex ? "completed" : index === currentStepIndex ? "current" : "upcoming"; } return { key: step.key, label: getAgentProgressStepLabel(step.key, payload), state, }; }); }