import type { AgentContent, CanvasNode } from "@/domain/design"; import type { UploadedReferenceImage } from "@/ui/components/PromptComposer"; const canvasNodeReferencePrefix = "canvas-node:"; export function canvasNodeToReferenceImage(node: CanvasNode): UploadedReferenceImage | null { const content = node.content.trim(); if (node.type !== "image" || node.layerRole === "pen-stroke" || node.layerRole === "image-generator" || node.status === "generating" || node.status === "error" || !isImageReferenceUrl(content)) { return null; } return { id: `${canvasNodeReferencePrefix}${node.id}`, name: node.title.trim() || "Image", publicUrl: content, previewUrl: node.renderContent?.trim() || content, semanticContext: node.semanticContext }; } export function isCanvasNodeReferenceImage(reference: UploadedReferenceImage) { return reference.id.startsWith(canvasNodeReferencePrefix); } export function lastCanvasNodeIdForReferences(references: Array>, nodes: Array>) { for (let index = references.length - 1; index >= 0; index -= 1) { const reference = references[index]; const directNodeId = reference.id.startsWith(canvasNodeReferencePrefix) ? reference.id.slice(canvasNodeReferencePrefix.length) : ""; if (directNodeId && nodes.some((node) => node.id === directNodeId)) return directNodeId; const referenceKeys = assetReferenceKeys(reference.publicUrl); const matchedNode = [...nodes].reverse().find((node) => Array.from(assetReferenceKeys(node.content)).some((key) => referenceKeys.has(key))); if (matchedNode) return matchedNode.id; } return ""; } export function withLastCanvasReferenceAnchor(contents: AgentContent[], references: Array>, nodes: Array>): AgentContent[] { const anchorNodeId = lastCanvasNodeIdForReferences(references, nodes); if (!anchorNodeId) return contents; return [ ...contents, { type: "text", text: `Reference anchor node: ${anchorNodeId}`, textSource: "settings" } ]; } export function isAssetReferencedByCanvas(publicUrl: string, nodes: CanvasNode[]) { const targetKeys = assetReferenceKeys(publicUrl); if (targetKeys.size === 0) return false; return nodes.some((node) => { const contentKeys = assetReferenceKeys(node.content); return Array.from(contentKeys).some((key) => targetKeys.has(key)); }); } function isImageReferenceUrl(value: string) { return /^(https?:\/\/|data:image\/|blob:|\/)/.test(value); } function assetReferenceKeys(value: string) { const trimmed = value.trim(); const keys = new Set(); if (!trimmed || trimmed.startsWith("data:image/") || trimmed.startsWith("blob:")) return keys; keys.add(trimmed); const queryIndex = trimmed.indexOf("?"); if (queryIndex > 0) keys.add(trimmed.slice(0, queryIndex)); try { const url = new URL(trimmed, typeof window === "undefined" ? "http://localhost" : window.location.origin); keys.add(url.pathname); keys.add(`${url.pathname}${url.search}`); } catch { // Keep exact/path keys above for non-URL storage identifiers. } return keys; }