501 lines
17 KiB
TypeScript
501 lines
17 KiB
TypeScript
import { z } from "zod";
|
|
import type {
|
|
CanvasCapabilityClientAction,
|
|
CanvasCapabilityDefinition,
|
|
CanvasCapabilityId,
|
|
CanvasCapabilityManifestEntry,
|
|
SelectedImageToolbarActionKey,
|
|
} from "./types";
|
|
|
|
const emptyArgsSchema = z.object({}).passthrough();
|
|
const promptArgsSchema = z
|
|
.object({
|
|
prompt: z.string().trim().min(1).max(8_000).optional(),
|
|
})
|
|
.passthrough();
|
|
const resizeArgsSchema = z
|
|
.object({
|
|
width: z.number().int().positive().max(16_384).optional(),
|
|
height: z.number().int().positive().max(16_384).optional(),
|
|
aspectRatio: z.string().trim().max(32).optional(),
|
|
})
|
|
.passthrough();
|
|
const extendArgsSchema = z
|
|
.object({
|
|
prompt: z.string().trim().min(1).max(8_000).optional(),
|
|
xScale: z.number().min(1.1).max(4).optional(),
|
|
yScale: z.number().min(1.1).max(4).optional(),
|
|
})
|
|
.passthrough();
|
|
const angleArgsSchema = z
|
|
.object({
|
|
horizontalAngle: z.number().min(0).max(360).optional(),
|
|
verticalAngle: z.number().min(-30).max(90).optional(),
|
|
zoom: z.number().min(0).max(10).optional(),
|
|
})
|
|
.passthrough();
|
|
const cropArgsSchema = z
|
|
.object({
|
|
x: z.number().min(0).optional(),
|
|
y: z.number().min(0).optional(),
|
|
width: z.number().positive().optional(),
|
|
height: z.number().positive().optional(),
|
|
})
|
|
.passthrough();
|
|
const transformArgsSchema = z
|
|
.object({
|
|
rotate: z.number().min(-360).max(360).optional(),
|
|
flipHorizontal: z.boolean().optional(),
|
|
flipVertical: z.boolean().optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
type DefinitionInput = Omit<
|
|
CanvasCapabilityDefinition,
|
|
"selection" | "requiresRemoteImage" | "confirmation" | "agent" | "mcp"
|
|
> & {
|
|
selection?: Partial<CanvasCapabilityDefinition["selection"]>;
|
|
requiresRemoteImage?: boolean;
|
|
confirmation?: CanvasCapabilityDefinition["confirmation"];
|
|
agent?: Partial<CanvasCapabilityDefinition["agent"]>;
|
|
mcp?: Partial<CanvasCapabilityDefinition["mcp"]>;
|
|
};
|
|
|
|
function defineCapability(input: DefinitionInput): CanvasCapabilityDefinition {
|
|
const mcpToolName = input.id.replaceAll(".", "_");
|
|
return {
|
|
...input,
|
|
requiresRemoteImage: input.requiresRemoteImage ?? true,
|
|
selection: {
|
|
nodeTypes: ["image"],
|
|
min: input.selection?.min ?? 1,
|
|
...(input.selection?.max ? { max: input.selection.max } : {}),
|
|
},
|
|
confirmation: input.confirmation ?? "never",
|
|
agent: {
|
|
exposed: input.agent?.exposed ?? true,
|
|
aliases: input.agent?.aliases ?? [],
|
|
toolName: input.agent?.toolName ?? mcpToolName,
|
|
},
|
|
mcp: {
|
|
exposed: input.mcp?.exposed ?? true,
|
|
toolName: input.mcp?.toolName ?? mcpToolName,
|
|
},
|
|
};
|
|
}
|
|
|
|
export const CANVAS_CAPABILITY_REGISTRY = [
|
|
defineCapability({
|
|
id: "image.add_to_chat",
|
|
title: "添加到对话",
|
|
description: "将选中图片加入当前 Agent 对话上下文。",
|
|
clientAction: "add-to-chat",
|
|
toolbarSettingKey: "imageAddToChat",
|
|
executionMode: "local",
|
|
risk: "read",
|
|
modelPolicy: "none",
|
|
requiresRemoteImage: false,
|
|
inputSchema: emptyArgsSchema,
|
|
agent: { exposed: false },
|
|
mcp: { exposed: false },
|
|
}),
|
|
defineCapability({
|
|
id: "image.pick_object",
|
|
title: "识物",
|
|
description: "进入图片点选识物模式,并把识别结果加入对话上下文。",
|
|
clientAction: "pick-object",
|
|
toolbarActionKey: "pickObject",
|
|
toolbarSettingKey: "imagePickObject",
|
|
executionMode: "interactive",
|
|
risk: "read",
|
|
modelPolicy: "feature-setting",
|
|
confirmation: "interactive",
|
|
inputSchema: z
|
|
.object({ x: z.number().min(0).max(1).optional(), y: z.number().min(0).max(1).optional() })
|
|
.passthrough(),
|
|
agent: { aliases: ["识物", "识别物品", "选择物体"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.enhance",
|
|
title: "高清增强",
|
|
description: "使用系统配置的增强服务提升图片清晰度。",
|
|
clientAction: "enhance",
|
|
toolbarActionKey: "enhance",
|
|
toolbarSettingKey: "imageEnhance",
|
|
executionMode: "headless",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.imageEnhance",
|
|
inputSchema: z.object({ scaleFactor: z.number().min(1).max(8).optional() }).passthrough(),
|
|
agent: { toolName: "enhanceImage", aliases: ["高清", "增强", "变清晰"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.remove_background",
|
|
title: "抠图去背",
|
|
description: "调用系统抠图能力移除图片背景。",
|
|
clientAction: "rmbg",
|
|
toolbarActionKey: "removeBackground",
|
|
toolbarSettingKey: "imageRemoveBackground",
|
|
executionMode: "headless",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.removeBackground",
|
|
inputSchema: promptArgsSchema,
|
|
agent: { toolName: "removeBackground", aliases: ["抠图", "去背景", "透明背景"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.ai_wash",
|
|
title: "AI洗图",
|
|
description: "按管理员配置的预设、档位和模型执行 AI 洗图。",
|
|
clientAction: "ai-wash",
|
|
toolbarActionKey: "aiWashQuick",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.aiWash",
|
|
confirmation: "interactive",
|
|
inputSchema: z
|
|
.object({
|
|
prompt: z.string().trim().min(1).max(8_000).optional(),
|
|
presetId: z.string().trim().min(1).optional(),
|
|
modelId: z.string().trim().min(1).optional(),
|
|
levels: z.record(z.number().int().nonnegative()).optional(),
|
|
})
|
|
.passthrough(),
|
|
agent: { aliases: ["洗图", "AI洗图", "重新洗图"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.text_edit",
|
|
title: "图片改字",
|
|
description: "识别图片文字并打开可编辑文字面板。",
|
|
clientAction: "text-edit",
|
|
toolbarActionKey: "textEdit",
|
|
toolbarSettingKey: "imageTextEdit",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.textEdit",
|
|
confirmation: "interactive",
|
|
inputSchema: promptArgsSchema,
|
|
agent: { aliases: ["改字", "修改图片文字", "替换文案"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.box_cutout",
|
|
title: "框选抠图",
|
|
description: "框选图片区域后提取指定主体。",
|
|
clientAction: "box-cutout",
|
|
toolbarActionKey: "boxCutout",
|
|
toolbarSettingKey: "imageBoxCutout",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
confirmation: "interactive",
|
|
inputSchema: cropArgsSchema,
|
|
agent: { toolName: "boxCutout", aliases: ["框选抠图", "抠出这部分"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.local_edit",
|
|
title: "局部编辑",
|
|
description: "按框选区域与区域提示词执行局部重绘。",
|
|
clientAction: "local-edit",
|
|
toolbarActionKey: "localEdit",
|
|
toolbarSettingKey: "imageLocalEdit",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "agent-selected",
|
|
confirmation: "interactive",
|
|
inputSchema: promptArgsSchema,
|
|
agent: { toolName: "localEditImage", aliases: ["局部编辑", "局部重绘"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.psd_template_replace",
|
|
title: "模版替换",
|
|
description: "把选中图片带入已配置的 PSD 模版工作台进行素材替换。",
|
|
clientAction: "psd-template",
|
|
toolbarActionKey: "psdTemplate",
|
|
toolbarSettingKey: "imagePsdTemplateReplace",
|
|
executionMode: "interactive",
|
|
risk: "write",
|
|
modelPolicy: "none",
|
|
confirmation: "interactive",
|
|
inputSchema: emptyArgsSchema,
|
|
agent: { exposed: false },
|
|
mcp: { exposed: false },
|
|
}),
|
|
defineCapability({
|
|
id: "image.scale_adjust",
|
|
title: "比例调整",
|
|
description: "按目标框等比缩放和移动选中主体。",
|
|
clientAction: "scale-adjust",
|
|
toolbarSettingKey: "imageLocalEdit",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
confirmation: "interactive",
|
|
inputSchema: cropArgsSchema,
|
|
agent: { aliases: ["比例调整", "缩放主体", "调整主体位置"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.remove_watermark",
|
|
title: "去水印",
|
|
description: "使用系统配置的服务移除水印和画面瑕疵。",
|
|
clientAction: "watermark",
|
|
toolbarActionKey: "removeWatermark",
|
|
toolbarSettingKey: "imageRemoveWatermark",
|
|
executionMode: "headless",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.removeWatermark",
|
|
inputSchema: promptArgsSchema,
|
|
agent: { toolName: "removeWatermark", aliases: ["去水印", "去印", "清理水印"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.resize",
|
|
title: "尺寸调整",
|
|
description: "调整图片尺寸、比例和主体位置。",
|
|
clientAction: "resize",
|
|
toolbarActionKey: "resize",
|
|
toolbarSettingKey: "imageResize",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
confirmation: "interactive",
|
|
inputSchema: resizeArgsSchema,
|
|
agent: { toolName: "resizeImage", aliases: ["调整尺寸", "改比例", "改分辨率"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.multi_angle",
|
|
title: "多视角",
|
|
description: "按水平角、俯仰角和景别生成主体新视角。",
|
|
clientAction: "angle",
|
|
toolbarActionKey: "multiAngle",
|
|
toolbarSettingKey: "imageMultiAngle",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
confirmation: "interactive",
|
|
inputSchema: angleArgsSchema,
|
|
agent: { toolName: "angleEditImage", aliases: ["多视角", "换角度", "旋转视角"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.generate_3d_model",
|
|
title: "生成3D模型",
|
|
description: "使用系统配置的 Tripo 模型从图片生成 3D 模型。",
|
|
clientAction: "generate-3d-model",
|
|
toolbarActionKey: "generate3dModel",
|
|
toolbarSettingKey: "imageGenerate3dModel",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.tripo3d",
|
|
confirmation: "interactive",
|
|
inputSchema: z
|
|
.object({
|
|
modelId: z.string().trim().min(1).optional(),
|
|
prompt: z.string().trim().min(1).max(8_000).optional(),
|
|
})
|
|
.passthrough(),
|
|
agent: { aliases: ["生成3D模型", "转成3D模型", "Tripo"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.generate_3d_world",
|
|
title: "生成3D世界",
|
|
description: "使用系统配置的 World Labs 模型生成可漫游 3D 世界。",
|
|
clientAction: "generate-3d-world",
|
|
toolbarActionKey: "generate3dWorld",
|
|
toolbarSettingKey: "imageGenerate3dWorld",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.worldLabs3d",
|
|
confirmation: "interactive",
|
|
inputSchema: z
|
|
.object({
|
|
modelId: z.string().trim().min(1).optional(),
|
|
prompt: z.string().trim().min(1).max(8_000).optional(),
|
|
})
|
|
.passthrough(),
|
|
agent: { aliases: ["生成3D世界", "生成场景", "World Labs"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.layer_split",
|
|
title: "图层拆分",
|
|
description: "将图片拆分为可独立编辑的图层节点。",
|
|
clientAction: "layer-split",
|
|
toolbarActionKey: "layerSplit",
|
|
toolbarSettingKey: "imageLayerSplit",
|
|
executionMode: "headless",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.layerSplit",
|
|
inputSchema: emptyArgsSchema,
|
|
agent: { aliases: ["图层拆分", "拆图层", "分层"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.extend",
|
|
title: "扩图",
|
|
description: "向指定方向扩展画面并补全内容。",
|
|
clientAction: "extend",
|
|
toolbarActionKey: "extend",
|
|
toolbarSettingKey: "imageExtend",
|
|
executionMode: "headless",
|
|
risk: "external",
|
|
modelPolicy: "feature-setting",
|
|
modelSettingKey: "canvas.extend",
|
|
inputSchema: extendArgsSchema,
|
|
agent: { toolName: "extendImage", aliases: ["扩图", "补边", "扩展画面"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.crop",
|
|
title: "裁剪",
|
|
description: "打开裁剪面板,或按完整坐标直接裁剪。",
|
|
clientAction: "crop",
|
|
toolbarActionKey: "crop",
|
|
toolbarSettingKey: "imageCrop",
|
|
executionMode: "hybrid",
|
|
risk: "write",
|
|
modelPolicy: "none",
|
|
requiresRemoteImage: false,
|
|
confirmation: "interactive",
|
|
inputSchema: cropArgsSchema,
|
|
agent: { aliases: ["裁剪", "剪裁", "截取画面"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.adjust",
|
|
title: "画面调整",
|
|
description: "打开亮度、对比度、饱和度等图片调整面板。",
|
|
clientAction: "adjust",
|
|
toolbarActionKey: "adjust",
|
|
toolbarSettingKey: "imageAdjust",
|
|
executionMode: "interactive",
|
|
risk: "write",
|
|
modelPolicy: "none",
|
|
requiresRemoteImage: false,
|
|
confirmation: "interactive",
|
|
inputSchema: z.record(z.number()).optional().default({}),
|
|
agent: { aliases: ["调色", "调整亮度", "调整对比度"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.flip_rotate",
|
|
title: "翻转与旋转",
|
|
description: "翻转或旋转选中图片。",
|
|
clientAction: "flip-rotate",
|
|
toolbarActionKey: "flipRotate",
|
|
toolbarSettingKey: "imageFlipRotate",
|
|
executionMode: "hybrid",
|
|
risk: "write",
|
|
modelPolicy: "none",
|
|
requiresRemoteImage: false,
|
|
confirmation: "interactive",
|
|
inputSchema: transformArgsSchema,
|
|
agent: { aliases: ["翻转", "旋转图片", "镜像"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.prompt_regenerate",
|
|
title: "提示词重绘",
|
|
description: "按新的提示词和系统生图模型重绘选中图片。",
|
|
clientAction: "prompt-regenerate",
|
|
toolbarActionKey: "promptRegenerate",
|
|
toolbarSettingKey: "imagePromptRegenerate",
|
|
executionMode: "hybrid",
|
|
risk: "external",
|
|
modelPolicy: "agent-selected",
|
|
confirmation: "interactive",
|
|
inputSchema: promptArgsSchema,
|
|
agent: { toolName: "editTargetImage", aliases: ["重绘", "按提示词重做"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.note",
|
|
title: "添加批注",
|
|
description: "给图片添加可作为 Agent 上下文的批注。",
|
|
clientAction: "note",
|
|
toolbarActionKey: "note",
|
|
toolbarSettingKey: "imageNote",
|
|
executionMode: "hybrid",
|
|
risk: "write",
|
|
modelPolicy: "none",
|
|
requiresRemoteImage: false,
|
|
confirmation: "interactive",
|
|
inputSchema: z.object({ text: z.string().trim().max(1_000).optional() }).passthrough(),
|
|
agent: { aliases: ["添加批注", "备注", "标注"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.rename",
|
|
title: "重命名",
|
|
description: "修改选中图片节点标题。",
|
|
clientAction: "rename",
|
|
toolbarSettingKey: "imageRename",
|
|
executionMode: "hybrid",
|
|
risk: "write",
|
|
modelPolicy: "none",
|
|
requiresRemoteImage: false,
|
|
inputSchema: z.object({ title: z.string().trim().min(1).max(120).optional() }).passthrough(),
|
|
agent: { aliases: ["重命名", "改名", "修改标题"] },
|
|
}),
|
|
defineCapability({
|
|
id: "image.download",
|
|
title: "下载图片",
|
|
description: "下载选中图片资源。",
|
|
clientAction: "download",
|
|
toolbarSettingKey: "imageDownload",
|
|
executionMode: "local",
|
|
risk: "read",
|
|
modelPolicy: "none",
|
|
requiresRemoteImage: false,
|
|
inputSchema: emptyArgsSchema,
|
|
agent: { exposed: false },
|
|
mcp: { exposed: false },
|
|
}),
|
|
defineCapability({
|
|
id: "image.delete",
|
|
title: "删除图片",
|
|
description: "从当前画布删除选中图片节点。",
|
|
clientAction: "delete",
|
|
toolbarSettingKey: "imageDelete",
|
|
executionMode: "local",
|
|
risk: "destructive",
|
|
modelPolicy: "none",
|
|
requiresRemoteImage: false,
|
|
confirmation: "always",
|
|
inputSchema: z.object({ confirmed: z.literal(true).optional() }).passthrough(),
|
|
agent: { aliases: ["删除图片", "移除图片"] },
|
|
}),
|
|
] as const satisfies readonly CanvasCapabilityDefinition[];
|
|
|
|
const capabilityById = new Map<CanvasCapabilityId, CanvasCapabilityDefinition>(
|
|
CANVAS_CAPABILITY_REGISTRY.map((capability) => [capability.id, capability]),
|
|
);
|
|
const capabilityByClientAction = new Map<CanvasCapabilityClientAction, CanvasCapabilityDefinition>(
|
|
CANVAS_CAPABILITY_REGISTRY.map((capability) => [capability.clientAction, capability]),
|
|
);
|
|
const capabilityByToolbarAction = new Map<
|
|
SelectedImageToolbarActionKey,
|
|
CanvasCapabilityDefinition
|
|
>(
|
|
CANVAS_CAPABILITY_REGISTRY.flatMap((capability) =>
|
|
capability.toolbarActionKey ? [[capability.toolbarActionKey, capability]] : [],
|
|
),
|
|
);
|
|
|
|
export function getCanvasCapability(id: CanvasCapabilityId) {
|
|
return capabilityById.get(id) ?? null;
|
|
}
|
|
|
|
export function getCanvasCapabilityByClientAction(action: CanvasCapabilityClientAction) {
|
|
return capabilityByClientAction.get(action) ?? null;
|
|
}
|
|
|
|
export function getCanvasCapabilityByToolbarAction(action: SelectedImageToolbarActionKey) {
|
|
return capabilityByToolbarAction.get(action) ?? null;
|
|
}
|
|
|
|
export function parseCanvasCapabilityArgs(id: CanvasCapabilityId, value: unknown) {
|
|
const capability = getCanvasCapability(id);
|
|
if (!capability) throw new Error(`Unknown canvas capability: ${id}`);
|
|
return capability.inputSchema.parse(value ?? {});
|
|
}
|
|
|
|
export function getCanvasCapabilityManifest(): CanvasCapabilityManifestEntry[] {
|
|
return CANVAS_CAPABILITY_REGISTRY.map(({ inputSchema: _inputSchema, ...capability }) => capability);
|
|
}
|