feat(agent): implement local image handling and enhance generated image integration in canvas

This commit is contained in:
HouYunFei
2026-07-29 17:19:20 +08:00
parent 7a48f441cf
commit 2afe2f3898
6 changed files with 71 additions and 5 deletions
+3
View File
@@ -7,4 +7,7 @@
- 修改当前画布时根据任务使用已配置的 infinite-canvas MCP 工具;复杂批量改动使用 `canvas_apply_ops`
- 用户要求把上传附件放入画布或作为生成参考图时,必须先用 `canvas_create_attachment_nodes` 创建真实图片节点,再把节点 ID 传给生成流程,不要创建空图片占位节点。
- 生图与视频工作台分别使用 `workbench_image_*``workbench_video_*` 工具;提示词和素材分别使用 `prompts_search``assets_*` 工具。
- 用户要求生成图片、视频、音频或文本时,默认调用对应的 `canvas_generate_image``canvas_generate_video``canvas_generate_audio``canvas_generate_text`,通过当前画布的生成节点完成任务。
- 只有用户明确要求使用“Codex 内置生图”“ImageGen 技能”或意思明确相同的能力时,才使用 Codex 自带的 `imagegen`;不要因为用户只说“生成图片”就自行改用内置生图。内置生图完成后,其结果会由 Canvas Agent 自动展示到对话并插入当前画布,无需再创建空节点或重复生成。
- 只有用户明确说要在生图/视频工作台生成时,才使用 `workbench_image_*``workbench_video_*`。生成任务提交后应说明已经在画布或工作台开始生成,不要在实际没有结果时声称“已生成”。
- 需要生成内容时直接调用对应生成工具,不要绑定特定业务场景,不要模拟鼠标点击,不要要求用户手动复制 JSON。
+2 -1
View File
@@ -61,7 +61,8 @@ function initializeWorkspace(workspacePath: string) {
if (initializedWorkspaces.has(workspacePath)) return;
fs.mkdirSync(workspacePath, { recursive: true });
const instructionsFile = path.join(workspacePath, "AGENTS.md");
if (!fs.existsSync(instructionsFile)) fs.writeFileSync(instructionsFile, AGENT_PROMPT);
const current = fs.existsSync(instructionsFile) ? fs.readFileSync(instructionsFile, "utf8") : "";
if (!current || current.startsWith("# Infinite Canvas Agent")) fs.writeFileSync(instructionsFile, AGENT_PROMPT);
initializedWorkspaces.add(workspacePath);
}
+9 -1
View File
@@ -1,5 +1,5 @@
import { spawn } from "node:child_process";
import { stat } from "node:fs/promises";
import { readFile, stat } from "node:fs/promises";
import path from "node:path";
import express, { type NextFunction, type Request, type Response } from "express";
@@ -82,6 +82,14 @@ export function startHttpServer() {
await revealLocalFile(filePath, file.isDirectory());
res.json({ ok: true });
}));
app.post("/agent/local-image", route(async (req, res) => {
const filePath = String(req.body?.path || "");
if (!path.isAbsolute(filePath) || !/\.(?:avif|gif|jpe?g|png|webp)$/i.test(filePath)) return res.status(400).json({ ok: false, error: "图片路径无效" });
const file = await stat(filePath);
if (!file.isFile()) return res.status(400).json({ ok: false, error: "图片文件无效" });
res.setHeader("Cache-Control", "no-store");
res.type(path.extname(filePath)).send(await readFile(filePath));
}));
app.post("/api/tools", route(async (req, res) => res.json({ ok: true, result: await session.callTool(req.body?.name, req.body?.input || {}) })));
app.get("/agent/codex/workspace", (_req, res) => {
const workspace = ensureSiteWorkspace(config);