mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 08:54:23 +08:00
feat(agent): add site tools support for canvas project listing and media generation
This commit is contained in:
@@ -7,6 +7,18 @@ import type { CanvasNode, CanvasNodeType, CanvasSnapshot } from "./types.js";
|
||||
|
||||
type PendingRequest = { resolve: (value: unknown) => void; reject: (error: Error) => void };
|
||||
|
||||
const SITE_TOOLS = new Set<ToolName>([
|
||||
"site_navigate",
|
||||
"canvas_list_projects",
|
||||
"workbench_image_get_config",
|
||||
"workbench_image_generate",
|
||||
"workbench_video_get_config",
|
||||
"workbench_video_generate",
|
||||
"prompts_search",
|
||||
"assets_list",
|
||||
"assets_add",
|
||||
]);
|
||||
|
||||
export class CanvasSession {
|
||||
private clients = new Map<string, ServerResponse>();
|
||||
private pending = new Map<string, PendingRequest>();
|
||||
@@ -49,7 +61,7 @@ export class CanvasSession {
|
||||
if (!isToolName(name)) throw new Error(`未知工具:${String(name)}`);
|
||||
let tool: ToolName = name;
|
||||
let input = parseToolInput(tool, rawInput) as Record<string, unknown>;
|
||||
if (tool === "site_navigate") {
|
||||
if (SITE_TOOLS.has(tool)) {
|
||||
if (!this.clients.size) throw new Error("当前没有已连接网页");
|
||||
return await this.requestCanvasTool(tool, input);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export const DEFAULT_PORT = 17371;
|
||||
export const CONFIG_DIR = path.join(os.homedir(), ".infinite-canvas");
|
||||
export const CONFIG_FILE = path.join(CONFIG_DIR, "canvas-agent.json");
|
||||
export const VERSION = readPackageVersion();
|
||||
export const AGENT_PROMPT = "你正在帮助用户操作 Infinite Canvas 网站。切换网站页面用 site_navigate,可跳 / (首页)、/canvas (我的画布)、/canvas/:id (指定画布)、/image、/video、/prompts、/assets、/config。需要改动画布时优先使用已配置的 infinite-canvas MCP 工具:先 canvas_get_state 读取当前画布,再根据任务使用 canvas_create_text_node、canvas_generate_text、canvas_generate_image、canvas_generate_video、canvas_generate_audio、canvas_create_generation_flow、canvas_create_config_node、canvas_run_generation、canvas_update_node、canvas_connect_nodes 等通用工具;复杂批量改动再用 canvas_apply_ops,删除连线可用 delete_connections。若当前不在画布页,画布工具会报错,需先用 site_navigate 打开画布。需要生成内容时直接调用对应生成工具,不要绑定特定业务场景。不要模拟鼠标点击,不要要求用户手动复制 JSON。";
|
||||
export const AGENT_PROMPT = "你正在帮助用户操作 Infinite Canvas 网站。切换网站页面用 site_navigate,可跳 / (首页)、/canvas (我的画布)、/canvas/:id (指定画布)、/image、/video、/prompts、/assets、/config。需要改动画布时优先使用已配置的 infinite-canvas MCP 工具:先 canvas_get_state 读取当前画布,再根据任务使用 canvas_create_text_node、canvas_generate_text、canvas_generate_image、canvas_generate_video、canvas_generate_audio、canvas_create_generation_flow、canvas_create_config_node、canvas_run_generation、canvas_update_node、canvas_connect_nodes 等通用工具;复杂批量改动再用 canvas_apply_ops,删除连线可用 delete_connections。若当前不在画布页,画布工具会报错,需先用 site_navigate 打开画布。想了解或打开用户已有画布,用 canvas_list_projects 获取画布清单和 id,再用 site_navigate 跳 /canvas/:id 打开。生图工作台可用 workbench_image_get_config 看可选项、workbench_image_generate 填提示词并生成;视频创作台对应 workbench_video_get_config 与 workbench_video_generate;用 prompts_search 分页搜索提示词库;用 assets_list 查看「我的素材」、assets_add 新增文本或图片素材。需要生成内容时直接调用对应生成工具,不要绑定特定业务场景。不要模拟鼠标点击,不要要求用户手动复制 JSON。";
|
||||
|
||||
export type SiteWorkspaceConfig = { workspacePath: string; activeThreadId?: string; pinnedThreadIds?: string[] };
|
||||
export type CanvasAgentConfig = { url: string; token: string; origins?: string[]; workspace?: SiteWorkspaceConfig };
|
||||
|
||||
@@ -8,6 +8,7 @@ const generationModeSchema = z.enum(["text", "image", "video", "audio"]);
|
||||
|
||||
export const toolNames = [
|
||||
"site_navigate",
|
||||
"canvas_list_projects",
|
||||
"canvas_get_state",
|
||||
"canvas_get_selection",
|
||||
"canvas_export_snapshot",
|
||||
@@ -31,6 +32,13 @@ export const toolNames = [
|
||||
"canvas_select_nodes",
|
||||
"canvas_set_viewport",
|
||||
"canvas_run_generation",
|
||||
"workbench_image_get_config",
|
||||
"workbench_image_generate",
|
||||
"workbench_video_get_config",
|
||||
"workbench_video_generate",
|
||||
"prompts_search",
|
||||
"assets_list",
|
||||
"assets_add",
|
||||
] as const;
|
||||
export type ToolName = (typeof toolNames)[number];
|
||||
|
||||
@@ -79,6 +87,7 @@ const generationFlowSchema = z.object({
|
||||
|
||||
export const toolInputSchemas = {
|
||||
site_navigate: z.object({ path: z.string() }),
|
||||
canvas_list_projects: z.object({ keyword: z.string().optional(), page: z.number().optional(), pageSize: z.number().optional() }),
|
||||
canvas_get_state: z.object({}).passthrough(),
|
||||
canvas_get_selection: z.object({}).passthrough(),
|
||||
canvas_export_snapshot: z.object({}).passthrough(),
|
||||
@@ -102,10 +111,18 @@ export const toolInputSchemas = {
|
||||
canvas_select_nodes: z.object({ ids: z.array(z.string()) }),
|
||||
canvas_set_viewport: z.object({ viewport: viewportSchema }),
|
||||
canvas_run_generation: z.object({ nodeId: z.string(), mode: generationModeSchema.optional(), prompt: z.string().optional() }),
|
||||
workbench_image_get_config: z.object({}).passthrough(),
|
||||
workbench_image_generate: z.object({ prompt: z.string(), model: z.string().optional(), quality: z.string().optional(), size: z.string().optional(), count: z.number().optional(), run: z.boolean().optional() }),
|
||||
workbench_video_get_config: z.object({}).passthrough(),
|
||||
workbench_video_generate: z.object({ prompt: z.string(), model: z.string().optional(), size: z.string().optional(), seconds: z.string().optional(), resolution: z.string().optional(), generateAudio: z.boolean().optional(), watermark: z.boolean().optional(), run: z.boolean().optional() }),
|
||||
prompts_search: z.object({ keyword: z.string().optional(), category: z.string().optional(), tags: z.array(z.string()).optional(), page: z.number().optional(), pageSize: z.number().optional() }),
|
||||
assets_list: z.object({ kind: z.enum(["all", "text", "image", "video"]).optional(), keyword: z.string().optional(), page: z.number().optional(), pageSize: z.number().optional() }),
|
||||
assets_add: z.object({ kind: z.enum(["text", "image"]), title: z.string(), content: z.string().optional(), imageUrl: z.string().optional(), tags: z.array(z.string()).optional(), source: z.string().optional(), note: z.string().optional() }),
|
||||
} satisfies Record<ToolName, z.AnyZodObject>;
|
||||
|
||||
export const toolDescriptions: Record<ToolName, string> = {
|
||||
site_navigate: "跳转网站页面。path 可为 / (首页)、/canvas (我的画布)、/canvas/:id (指定画布)、/image (生图工作台)、/video (视频创作台)、/prompts (提示词库)、/assets (我的素材)、/config (配置)。操作画布前若不在画布页,先用本工具打开画布。",
|
||||
canvas_list_projects: "列出用户全部画布(仅标题、创建/更新时间、节点数、连线数,不含完整数据),支持 keyword 搜索和 page/pageSize 分页。返回的 id 可配合 site_navigate 跳转到 /canvas/:id 打开对应画布。",
|
||||
canvas_get_state: "读取当前网页画布的节点、连线、选区和视口。",
|
||||
canvas_get_selection: "读取当前网页画布选中的节点。",
|
||||
canvas_export_snapshot: "导出当前画布快照,用于理解布局。",
|
||||
@@ -129,4 +146,11 @@ export const toolDescriptions: Record<ToolName, string> = {
|
||||
canvas_select_nodes: "设置当前选中节点。",
|
||||
canvas_set_viewport: "调整画布视口。",
|
||||
canvas_run_generation: "触发指定节点生成,通常用于配置节点或文本/图片/视频/音频节点。",
|
||||
workbench_image_get_config: "读取生图工作台的当前参数和可选项(可用模型、质量、尺寸/宽高比、张数范围),在调用 workbench_image_generate 前先了解可选值。",
|
||||
workbench_image_generate: "在生图工作台填入提示词并按需设置 model、quality、size(如 1:1 或 1024x1024)、count,run 默认 true 会自动点击生成按钮。会自动跳转到生图工作台。生成为异步过程,工具返回代表已提交,结果请在工作台查看。",
|
||||
workbench_video_get_config: "读取视频创作台的当前参数和可选项(可用模型、尺寸/比例、时长、清晰度/分辨率、是否生成声音与水印)。",
|
||||
workbench_video_generate: "在视频创作台填入提示词并按需设置 model、size、seconds、resolution、generateAudio、watermark,run 默认 true 会自动点击生成按钮。会自动跳转到视频创作台。生成为异步过程,工具返回代表已提交。",
|
||||
prompts_search: "搜索提示词库(第三方提示词合集),支持 keyword、category、tags 过滤和 page/pageSize 分页,返回标题、提示词、分类、标签、封面等。",
|
||||
assets_list: "列出用户「我的素材」,支持 kind(text/image/video)过滤、keyword 搜索和 page/pageSize 分页。为控制体积不返回图片/视频原始 data,仅返回封面与元信息。",
|
||||
assets_add: "向「我的素材」新增素材。kind=text 时用 content 传文本内容;kind=image 时用 imageUrl 传图片地址或 dataURL。可附带 title、tags、source、note。",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user