import type { AgentCallRecord, AgentRunSnapshot } from "./protocol"; // A spontaneous download command is not an unfinished image edit. Keep genuine // download requests and all editor panels waiting for their actual interaction. export function isAncillaryDownload(snapshot: AgentRunSnapshot, call: AgentCallRecord) { const intent = [snapshot.objective, ...snapshot.inputs.map(input => input.text)].join("\n") .replace(/(?:不要|无需|不需要|不用|不必|禁止|别)(?:再|主动|自动|继续)?\s*(?:下载|导出|保存.{0,8}(?:本地|电脑))|(?:do\s+not|don't|never)\s+(?:download|export)/gi, ""); return call.name === "image_download" && call.status === "succeeded" && call.output?.mode === "interactive" && finalResultNodeIds(snapshot).length > 0 && !/下载|导出|保存.*(?:本地|电脑)|download|export/i.test(intent); } /** This command merely adds an existing image to the composer. It does not * open an editor or require the user to supply files/confirm a media operation. */ export function isCompletedComposerReference(call: AgentCallRecord) { return call.name === "image_add_to_chat" && call.status === "succeeded" && call.output?.status === "succeeded" && call.output?.mode === "interactive"; } export function requiresCanvasInteraction(snapshot: AgentRunSnapshot, call: AgentCallRecord) { return call.output?.mode === "interactive" && !isAncillaryDownload(snapshot, call) && !isCompletedComposerReference(call); } export function finalImageResultNodeIds( snapshot: AgentRunSnapshot, nodes: Array<{ id: string; type: string }>, ) { const images = new Set(nodes.filter((node) => node.type === "image").map((node) => node.id)); return finalResultNodeIds(snapshot).filter((id) => images.has(id)); } export function finalResultNodeIds(snapshot: AgentRunSnapshot) { const consumed = new Set( snapshot.calls .filter( (call) => Array.isArray(call.output?.createdNodeIds) && call.output.createdNodeIds.length, ) .flatMap((call) => sourceIds(call.input, call.output)), ); return snapshot.resultNodeIds.filter((id) => !consumed.has(id)); } function sourceIds(input: Record, output?: Record) { return [ ...(Array.isArray(input.referenceNodeIds) ? input.referenceNodeIds : []), ...(Array.isArray(input.nodeIds) ? input.nodeIds : []), ...(Array.isArray(output?.outputs) ? output.outputs.map((item) => item?.sourceNodeId) : []), ].filter((id): id is string => typeof id === "string"); } export function originalSourceNodeIds(snapshot: AgentRunSnapshot, nodeIds: string[]) { const visited = new Set(); const originals = new Set(); const visit = (id: string) => { if (visited.has(id)) return; visited.add(id); const producer = snapshot.calls.find( (call) => Array.isArray(call.output?.createdNodeIds) && call.output.createdNodeIds.includes(id), ); const sources = producer ? sourceIds(producer.input, producer.output) : []; if (sources.length) sources.forEach(visit); else if (!snapshot.resultNodeIds.includes(id)) originals.add(id); }; nodeIds.forEach(visit); return [...originals]; }