- {!logs.length ?
网站 Agent 的排查日志会显示在这里
: null}
- {logs.map((log) => (
-
-
- {log.title}
- {log.time}
-
- {log.data !== undefined ? {stringifyLog(log.data)} : null}
-
- ))}
+
+
+
setMode(value as "text" | "json")} options={[{ label: "排查日志", value: "text" }, { label: "原始 JSON", value: "json" }]} />
+
+ {logs.length} 条
+ } disabled={!logs.length} onClick={() => void copy()}>复制
+
+ } disabled={!logs.length} onClick={onClear}>清空
+
+
+
);
}
@@ -707,8 +721,23 @@ function stringifyLog(value: unknown) {
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
}
-function formatOnlineLogs(logs: OnlineAgentLog[]) {
- return logs.map((log) => [`[${log.time}] ${log.title}`, log.data === undefined ? "" : stringifyLog(log.data)].filter(Boolean).join("\n")).join("\n\n");
+function formatOnlineLogText(logs: OnlineAgentLog[], context: OnlineAgentLogContext) {
+ const head = [
+ "Infinite Canvas 网站 Agent 诊断日志",
+ `model: ${context.model || "none"}`,
+ `running: ${context.running}`,
+ `confirmTools: ${context.confirmTools}`,
+ `messages: ${context.messages}`,
+ `nodes: ${context.nodes}`,
+ `connections: ${context.connections}`,
+ `logs: ${logs.length}`,
+ ].join("\n");
+ const body = logs.map((log, index) => [`#${index + 1} ${log.time} ${log.title}`, log.data === undefined ? "" : stringifyLog(log.data)].filter(Boolean).join("\n")).join("\n\n---\n\n");
+ return [head, body || "暂无事件日志"].join("\n\n");
+}
+
+function formatOnlineLogJson(logs: OnlineAgentLog[], context: OnlineAgentLogContext) {
+ return JSON.stringify({ context, logs: logs.map(({ time, title, data }) => ({ time, title, data })) }, null, 2);
}
function pendingReply(text: string) {
@@ -724,6 +753,23 @@ function pendingReply(text: string) {
.replace(/已(?:经)?将/g, "准备将");
}
+function isReadOnlyCanvasQuestion(text: string) {
+ return /(?:有什么|有哪些|看一下|查看|说一下|总结|概括|描述|介绍|分析|当前|现在).*(?:画布|内容|节点|连线)|(?:画布|内容|节点|连线).*(?:有什么|有哪些|是什么|多少|情况|状态)/.test(text);
+}
+
+function cleanReadOnlyReply(reply: string, snapshot: CanvasAgentSnapshot) {
+ const text = reply.replace(/^等待确认[::]\s*/, "").trim();
+ return text || describeCanvasSnapshot(snapshot);
+}
+
+function describeCanvasSnapshot(snapshot: CanvasAgentSnapshot) {
+ const counts = snapshot.nodes.reduce
>((acc, node) => {
+ acc[node.type] = (acc[node.type] || 0) + 1;
+ return acc;
+ }, {});
+ return `当前画布有 ${snapshot.nodes.length} 个节点、${snapshot.connections.length} 条连线。文本 ${counts[CanvasNodeType.Text] || 0} 个,图片 ${counts[CanvasNodeType.Image] || 0} 个,生成配置 ${counts[CanvasNodeType.Config] || 0} 个,视频 ${counts[CanvasNodeType.Video] || 0} 个,音频 ${counts[CanvasNodeType.Audio] || 0} 个。`;
+}
+
function normalizeOnlineOps(ops: CanvasAgentOp[], intent: string, snapshot: CanvasAgentSnapshot) {
if (/删|删除|移除|清空/.test(intent) && /连线|连接线|线条|边/.test(intent)) return snapshot.connections.length ? [...ops.filter((op) => op.type !== "connect_nodes"), { type: "delete_connections", all: true }] : ops;
if (/删|删除|移除/.test(intent) && /生成配置|配置节点|config/i.test(intent)) {
@@ -731,13 +777,39 @@ function normalizeOnlineOps(ops: CanvasAgentOp[], intent: string, snapshot: Canv
return ids.length ? (ops.some((op) => op.type === "delete_node") ? ops.map((op) => (op.type === "delete_node" && !op.id && !op.ids?.length ? { ...op, ids } : op)) : [...ops, { type: "delete_node", ids }]) : ops;
}
if (/连线|连接|串联/.test(intent)) {
- const nodes = snapshot.nodes.filter((node) => node.type !== CanvasNodeType.Config).sort((a, b) => a.position.x - b.position.x || a.position.y - b.position.y);
- const links = nodes.slice(1).map((node, index) => ({ type: "connect_nodes" as const, fromNodeId: nodes[index].id, toNodeId: node.id }));
- return links.length && !ops.some((op) => op.type === "connect_nodes" && op.fromNodeId && op.toNodeId) ? [...ops, ...links] : ops;
+ const connectOps = ops.filter((op): op is Extract => op.type === "connect_nodes");
+ const resolved = connectOps.map((op) => ({ ...op, fromNodeId: resolveCanvasNodeId(op.fromNodeId, snapshot, intent), toNodeId: resolveCanvasNodeId(op.toNodeId, snapshot, intent) })).filter((op) => op.fromNodeId && op.toNodeId && op.fromNodeId !== op.toNodeId);
+ if (resolved.length) return [...ops.filter((op) => op.type !== "connect_nodes"), ...resolved];
+ const fallback = fallbackConnectOps(snapshot);
+ return fallback.length ? [...ops.filter((op) => op.type !== "connect_nodes"), ...fallback] : ops;
}
return ops;
}
+function fallbackConnectOps(snapshot: CanvasAgentSnapshot) {
+ const textNode = snapshot.nodes.find((node) => node.type === CanvasNodeType.Text);
+ const configNode = snapshot.nodes.find((node) => node.type === CanvasNodeType.Config);
+ if (textNode && configNode) return [{ type: "connect_nodes" as const, fromNodeId: textNode.id, toNodeId: configNode.id }];
+ const nodes = snapshot.nodes.filter((node) => node.type !== CanvasNodeType.Config).sort((a, b) => a.position.x - b.position.x || a.position.y - b.position.y);
+ return nodes.slice(1).map((node, index) => ({ type: "connect_nodes" as const, fromNodeId: nodes[index].id, toNodeId: node.id }));
+}
+
+function resolveCanvasNodeId(value: unknown, snapshot: CanvasAgentSnapshot, intent: string) {
+ const raw = typeof value === "string" ? value : "";
+ if (!raw) return "";
+ if (snapshot.nodes.some((node) => node.id === raw)) return raw;
+ const text = normalizeNodeToken(raw);
+ const titleMatch = snapshot.nodes.find((node) => normalizeNodeToken(node.title) === text);
+ if (titleMatch) return titleMatch.id;
+ if (/note|文本|文字|提示词|prompt/i.test(raw) || /文本节点|note/i.test(intent)) return snapshot.nodes.find((node) => node.type === CanvasNodeType.Text)?.id || raw;
+ if (/config|生成配置|配置节点|配置/i.test(raw) || /生成配置|配置节点|config/i.test(intent)) return snapshot.nodes.find((node) => node.type === CanvasNodeType.Config)?.id || raw;
+ return raw;
+}
+
+function normalizeNodeToken(value: unknown) {
+ return (typeof value === "string" ? value : "").trim().toLowerCase().replace(/\s+/g, "");
+}
+
function snapshotSignature(snapshot: CanvasAgentSnapshot) {
return JSON.stringify({ nodes: snapshot.nodes, connections: snapshot.connections, selectedNodeIds: snapshot.selectedNodeIds, viewport: snapshot.viewport });
}