268 lines
9.2 KiB
TypeScript
268 lines
9.2 KiB
TypeScript
import { isZcodeWorkspaceAuthorized, VIDEO_ONLY_AGENT_TOOLBAR_KEYS } from "./authorization";
|
||
import { CANVAS_CAPABILITY_REGISTRY } from "@/lib/canvas/capabilities/registry";
|
||
import { DEFAULT_CANVAS_TOOLBAR_ITEMS } from "@/lib/canvas-toolbar-config";
|
||
import {
|
||
defaultVideoCanvasFunctionConfig,
|
||
type VideoCanvasFunctionConfig,
|
||
} from "@/lib/video-canvas/config";
|
||
|
||
export type AgentCapabilityAccess = {
|
||
id: string;
|
||
workspace: "canvas" | "video";
|
||
title: string;
|
||
authorized: boolean;
|
||
previouslyExposed: boolean;
|
||
execution: "server" | "interactive" | "backend_required";
|
||
entry: string;
|
||
note?: string;
|
||
};
|
||
|
||
// The full audit manifest is for the report/API. Repeating audit metadata and UI
|
||
// descriptions in model history can exhaust context before the first edit.
|
||
export function summarizeAgentCapabilities(items: AgentCapabilityAccess[]) {
|
||
return items.map(({ id, title, execution, entry, note, authorized }) => ({
|
||
id,
|
||
title,
|
||
authorized,
|
||
execution,
|
||
...(authorized && execution === "server" ? { entry } : {}),
|
||
...(!authorized ? { note: "暂未授权" } : execution === "backend_required" ? { note } : {}),
|
||
}));
|
||
}
|
||
export const VIDEO_CANVAS_EXTRA_ACTIONS = [
|
||
["rename", "节点重命名"],
|
||
["move", "节点移动"],
|
||
["duplicate", "复制节点"],
|
||
["delete", "删除节点"],
|
||
["connect", "工作流连线"],
|
||
["disconnect", "移除连线"],
|
||
["note", "节点批注"],
|
||
["text", "编辑文本"],
|
||
["create-text", "添加文本节点"],
|
||
["arrange", "排列节点"],
|
||
["viewport", "画布缩放与定位"],
|
||
] as const;
|
||
export const VIDEO_CANVAS_INTERACTIVE_ACTIONS = [
|
||
["upload-image", "上传图片"],
|
||
["upload-video", "上传视频"],
|
||
["upload-audio", "上传音频"],
|
||
["download", "下载素材"],
|
||
["preview", "预览素材"],
|
||
["local-edit", "局部编辑"],
|
||
["annotate", "标注"],
|
||
["video-hd", "视频高清"],
|
||
["video-clip", "选择视频剪辑范围"],
|
||
["video-crop", "视频裁剪面板"],
|
||
["subtitle-erase", "去字幕面板"],
|
||
["audio-split", "音视频分离面板"],
|
||
["voice-split", "人声分离面板"],
|
||
["background-split", "背景音分离面板"],
|
||
["director", "导演台"],
|
||
["storyboard", "分镜工作台"],
|
||
["batch-storyboard", "批量生成分镜"],
|
||
["batch-video", "批量生成视频"],
|
||
["assets", "素材库"],
|
||
["roles", "角色库"],
|
||
["styles", "风格库"],
|
||
["effects", "特效库"],
|
||
["history", "生成历史"],
|
||
["workflows", "工作流模板"],
|
||
["save", "保存画布"],
|
||
["export", "导出画布"],
|
||
["import", "导入画布"],
|
||
["share", "分享设置"],
|
||
["publish", "发布设置"],
|
||
["skills", "视频技能"],
|
||
["help", "帮助与快捷键"],
|
||
["frame-first", "提取首帧"],
|
||
["frame-last", "提取尾帧"],
|
||
["frame-current", "提取指定时间帧"],
|
||
] as const;
|
||
export const IMAGE_CANVAS_OPERATIONS = [
|
||
"create_agent_slot",
|
||
"create_text_node",
|
||
"fill_slot",
|
||
"insert_beside",
|
||
"append_revision",
|
||
"replace_node_media",
|
||
"add_annotation",
|
||
"link_nodes",
|
||
"move_nodes",
|
||
"delete_nodes",
|
||
"rename_node",
|
||
"update_note",
|
||
"update_text",
|
||
] as const;
|
||
export function isExplicitDestructiveIntent(text: string) {
|
||
return (
|
||
/删除|移除|清空|覆盖|替换原图/.test(text) &&
|
||
!/(?:不要|不许|禁止|别|不需要).{0,12}(?:删除|移除|清空|覆盖)/.test(text)
|
||
);
|
||
}
|
||
const imageOperationTitles: Record<string, string> = {
|
||
create_agent_slot: "创建输出槽位",
|
||
create_text_node: "创建文本节点",
|
||
fill_slot: "填充输出槽位",
|
||
insert_beside: "在素材旁添加结果",
|
||
append_revision: "追加素材版本",
|
||
replace_node_media: "替换节点媒体",
|
||
add_annotation: "添加标注",
|
||
link_nodes: "节点连线",
|
||
move_nodes: "移动节点",
|
||
delete_nodes: "删除节点",
|
||
rename_node: "重命名节点",
|
||
update_note: "更新批注",
|
||
update_text: "更新文本",
|
||
};
|
||
export function getCanvasAgentAccessManifest(
|
||
videoConfig: VideoCanvasFunctionConfig = defaultVideoCanvasFunctionConfig,
|
||
): AgentCapabilityAccess[] {
|
||
return [
|
||
...CANVAS_CAPABILITY_REGISTRY.map(
|
||
(capability): AgentCapabilityAccess => ({
|
||
id: capability.id,
|
||
workspace: "canvas",
|
||
title: capability.title,
|
||
authorized: true,
|
||
previouslyExposed:
|
||
!["image.add_to_chat", "image.layout_replace", "image.download"].includes(
|
||
capability.id,
|
||
) &&
|
||
capability.agent.exposed &&
|
||
capability.executionMode !== "local" &&
|
||
!capability.id.includes("3d") &&
|
||
capability.risk !== "destructive",
|
||
execution:
|
||
capability.executionMode === "interactive" ||
|
||
capability.executionMode === "local" ||
|
||
[
|
||
"image.text_edit",
|
||
"image.local_edit",
|
||
"image.scale_adjust",
|
||
"image.crop",
|
||
"image.flip_rotate",
|
||
].includes(capability.id)
|
||
? "interactive"
|
||
: "server",
|
||
entry: capability.agent.toolName || capability.id.replaceAll(".", "_"),
|
||
note:
|
||
capability.risk === "destructive"
|
||
? "需要用户明确要求及明确目标;模型不能自行确认"
|
||
: capability.executionMode === "hybrid"
|
||
? "参数齐全时执行,否则打开真实交互面板"
|
||
: undefined,
|
||
}),
|
||
),
|
||
...DEFAULT_CANVAS_TOOLBAR_ITEMS.map(
|
||
(item): AgentCapabilityAccess => ({
|
||
id: `canvas.ui.${item.key}`,
|
||
workspace: VIDEO_ONLY_AGENT_TOOLBAR_KEYS.has(item.key) ? "video" : "canvas",
|
||
title: `${item.group} · ${item.label}`,
|
||
authorized: true,
|
||
previouslyExposed: false,
|
||
execution: "interactive",
|
||
entry: "openCanvasFeature",
|
||
note: "操作当前可见的真实入口;隐藏入口保持隐藏,文件选择、支付、登录、发布和发送消息由用户完成",
|
||
}),
|
||
),
|
||
...IMAGE_CANVAS_OPERATIONS.map(
|
||
(operation): AgentCapabilityAccess => ({
|
||
id: `canvas.operation.${operation}`,
|
||
workspace: "canvas",
|
||
title: `画布操作 · ${imageOperationTitles[operation] || operation}`,
|
||
authorized: true,
|
||
previouslyExposed: false,
|
||
execution: "server",
|
||
entry: "editCanvasNodes",
|
||
}),
|
||
),
|
||
...Object.values(videoConfig.functions).map(
|
||
(definition): AgentCapabilityAccess => ({
|
||
id: `video.function.${definition.key}`,
|
||
workspace: "video",
|
||
title: definition.label,
|
||
authorized: true,
|
||
previouslyExposed: [
|
||
"story-script",
|
||
"video-analysis",
|
||
"image-to-prompt",
|
||
"text-to-video",
|
||
"first-frame-video",
|
||
"first-last-frame-video",
|
||
].includes(definition.key),
|
||
execution:
|
||
definition.key === "audio-to-video"
|
||
? "backend_required"
|
||
: ["local", "pick-object"].includes(definition.route)
|
||
? "interactive"
|
||
: "server",
|
||
entry: "runVideoCanvasFunction",
|
||
note:
|
||
definition.key === "audio-to-video"
|
||
? "当前页面未接真实音频驱动视频后端,已授权入口但不能伪称完成音频驱动"
|
||
: !definition.enabled
|
||
? "已授权;管理员关闭了底层功能,需启用对应服务后才能执行"
|
||
: undefined,
|
||
}),
|
||
),
|
||
...VIDEO_CANVAS_EXTRA_ACTIONS.map(
|
||
([id, title]): AgentCapabilityAccess => ({
|
||
id: `video.node.${id}`,
|
||
workspace: "video",
|
||
title,
|
||
authorized: true,
|
||
previouslyExposed: false,
|
||
execution: "server",
|
||
entry: "editVideoCanvas",
|
||
}),
|
||
),
|
||
...(
|
||
[
|
||
["extract-frame", "真实视频截帧"],
|
||
["trim", "真实视频剪辑导出"],
|
||
["crop", "真实视频裁剪"],
|
||
["extract-audio", "提取完整音轨"],
|
||
["mute", "生成无声视频"],
|
||
] as const
|
||
).map(
|
||
([id, title]): AgentCapabilityAccess => ({
|
||
id: `video.media.${id}`,
|
||
workspace: "video",
|
||
title,
|
||
authorized: true,
|
||
previouslyExposed: false,
|
||
execution: "server",
|
||
entry: "processVideoMedia",
|
||
note: "使用真实媒体处理并上传现有存储,不复用页面的模拟产物;单次视频不超过120秒",
|
||
}),
|
||
),
|
||
...VIDEO_CANVAS_INTERACTIVE_ACTIONS.map(
|
||
([id, title]): AgentCapabilityAccess => ({
|
||
id: `video.ui.${id}`,
|
||
workspace: "video",
|
||
title,
|
||
authorized: true,
|
||
previouslyExposed: false,
|
||
execution: ["video-hd", "subtitle-erase", "voice-split", "background-split"].includes(id)
|
||
? "backend_required"
|
||
: "interactive",
|
||
entry: "openVideoCanvasFeature",
|
||
note: [
|
||
"video-crop",
|
||
"video-hd",
|
||
"subtitle-erase",
|
||
"audio-split",
|
||
"voice-split",
|
||
"background-split",
|
||
"frame-first",
|
||
"frame-last",
|
||
"frame-current",
|
||
].includes(id)
|
||
? "页面当前为占位/模拟流程;只授权设置入口,不执行模拟产物或声称媒体已处理"
|
||
: undefined,
|
||
}),
|
||
),
|
||
].map(item => ({ ...item, authorized: isZcodeWorkspaceAuthorized(item.workspace),
|
||
...(!isZcodeWorkspaceAuthorized(item.workspace) ? { note: ["用户暂未授权视频画布能力", item.note].filter(Boolean).join(";") } : {}) }));
|
||
}
|