f5a1ae7961
Resolve the most recent canvas-node reference from the prompt's reference images (by node id or matching asset URL) and append it as a hidden "reference anchor node:" settings directive on agent submissions, so the server can place generated images beside the source. Keep the persisted user-visible copy unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
3.2 KiB
TypeScript
78 lines
3.2 KiB
TypeScript
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<Pick<UploadedReferenceImage, "id" | "publicUrl">>, nodes: Array<Pick<CanvasNode, "id" | "content">>) {
|
|
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<Pick<UploadedReferenceImage, "id" | "publicUrl">>, nodes: Array<Pick<CanvasNode, "id" | "content">>): 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<string>();
|
|
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;
|
|
}
|