feat(canvas): Agent 审批 - 节点直接生成时自动关闭同节点的待审批任务

用户在画布节点上直接提交生成后,同画布 Agent 对该节点等待中的 generate_media 审批关闭为 superseded_by_node,Agent 带着「用户已手动提交」的结果继续,不重复提交、不扣费。审批卡片识别目标节点正在生成时禁用「同意执行」并提示原因。
This commit is contained in:
ddcat666
2026-09-30 03:40:14 +08:00
parent 8586565272
commit 354d86a70b
9 changed files with 411 additions and 10 deletions

View File

@@ -1,4 +1,6 @@
import type { AgentMediaSettings } from "@/services/api/agent";
import { isCanvasNodeGenerating } from "@/lib/canvas/canvas-node-task-state";
import type { CanvasNodeData } from "@/types/canvas";
import { logicalModelIDForConfig, modelOptionName, resolveModelChannel, selectableModelsByCapability, type AiConfig } from "@/stores/use-config-store";
export type AgentImageApproval = AgentMediaSettings & { prompt: string; referenceNodeIds: string[] };
@@ -43,3 +45,30 @@ export function agentApprovalModelSelection(config: AiConfig, model: string): Pi
if (channel.scope !== "system") throw new Error("Agent 生成仅支持平台模型");
return { channelId: channel.id, channelModelKey: modelOptionName(model) };
}
/** 后端在用户直接从节点提交生成时写入的审批决定(与 Go 侧 cloudAgentApprovalSuperseded 一致)。 */
export const AGENT_APPROVAL_SUPERSEDED_BY_NODE = "superseded_by_node";
/** 待审批 generate_media 的目标节点;其他工具返回空串。 */
export function agentMediaApprovalTargetNodeId(detail: Record<string, unknown>): string {
const call = detail.call as { function?: { name?: string; arguments?: unknown } } | undefined;
if ((call?.function?.name || detail.toolName) !== "generate_media") return "";
const raw = call?.function?.arguments ?? detail.arguments;
try {
const args = typeof raw === "string" ? JSON.parse(raw) : raw;
return args && typeof args.nodeId === "string" ? args.nodeId.trim() : "";
} catch {
return "";
}
}
/**
* 审批的目标节点已经在画布上生成中(用户直接点了节点的生成)。
* 后端会随后推送 superseded 决定;这里只负责在事件到达前挡住重复点击“同意执行”。
*/
export function agentApprovalTargetGenerating(detail: Record<string, unknown>, nodes: readonly CanvasNodeData[] | undefined, runningNodeId?: string | null): CanvasNodeData | undefined {
const nodeId = agentMediaApprovalTargetNodeId(detail);
if (!nodeId || !nodes) return undefined;
const target = nodes.find((node) => node.id === nodeId);
return target && isCanvasNodeGenerating(target, runningNodeId) ? target : undefined;
}