feat(agent-ui): add inline skill and canvas references

This commit is contained in:
yu
2026-08-06 19:11:40 +08:00
parent 10dc65d2d6
commit 89a4e192e5
15 changed files with 758 additions and 232 deletions
@@ -2,6 +2,8 @@ import { imageReferenceLabel } from "@/lib/image-reference-prompt";
import i18n from "@/i18n";
import { seedanceReferenceLabel } from "@/lib/seedance-video";
import { getNodeDefinition } from "@/lib/canvas/node-registry";
import { getDataUrlByteSize, readImageMeta } from "@/lib/image-utils";
import { imageToDataUrl } from "@/services/image-storage";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "@/types/canvas";
export type CanvasResourceKind = "image" | "video" | "audio" | "text";
@@ -21,6 +23,34 @@ export function buildNodeMentionReferences(node: CanvasNodeData, nodes: CanvasNo
return labelResourceNodes(getMentionResourceNodes(node.id, nodes, connections), true);
}
export function buildCanvasResourceReferences(nodes: CanvasNodeData[]) {
return labelResourceNodes(nodes, true);
}
export async function resolveCanvasReferenceImages(references: CanvasResourceReference[], nodes: CanvasNodeData[]) {
const nodesById = new Map(nodes.map((node) => [node.id, node]));
return Promise.all(references.filter((reference) => reference.kind === "image").map(async (reference) => {
const node = nodesById.get(reference.nodeId);
if (!node) throw new Error(i18n.t("agent.composer.mentions.resourceMissing", { title: reference.title }));
const metadata = node.metadata;
const dataUrl = await imageToDataUrl({ storageKey: metadata?.storageKey, url: reference.previewUrl });
if (!dataUrl.startsWith("data:image/")) throw new Error(i18n.t("agent.composer.mentions.imageReadFailed", { title: reference.title }));
const meta = metadata?.naturalWidth && metadata.naturalHeight
? { width: metadata.naturalWidth, height: metadata.naturalHeight, mimeType: metadata.mimeType || dataUrl.match(/^data:([^;]+)/)?.[1] || "image/png" }
: await readImageMeta(dataUrl);
return {
id: `canvas:${node.id}`,
name: reference.title,
type: metadata?.mimeType || meta.mimeType,
size: metadata?.bytes || getDataUrlByteSize(dataUrl),
width: meta.width,
height: meta.height,
url: reference.previewUrl || dataUrl,
dataUrl,
};
}));
}
export function getMentionResourceNodes(nodeId: string, nodes: CanvasNodeData[], connections: CanvasConnection[]) {
const configInputs = getConnectedConfigResourceNodes(nodeId, nodes, connections);
if (configInputs.length) return configInputs;
@@ -56,6 +86,7 @@ function labelResourceNodes(nodes: CanvasNodeData[], active: boolean) {
return nodes.flatMap((node): CanvasResourceReference[] => {
const kind = resourceKind(node);
if (!kind) return [];
const resource = getNodeDefinition(node.type)?.resource?.(node);
const index = counts[kind]++;
const label = labelForKind(kind, index);
return [
@@ -65,7 +96,7 @@ function labelResourceNodes(nodes: CanvasNodeData[], active: boolean) {
kind,
label,
title: node.title || label,
previewUrl: node.metadata?.content,
previewUrl: node.metadata?.content || resource?.url,
text: resourceText(node),
active,
},