ai-chatbot/lib/ai/canvas-agent-runtime/project-operation-catalog.ts

110 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** Fixed business operations, never an arbitrary URL or administrator API proxy. */
export type ProjectAgentOperation = {
id: string; title: string; group: string; path: string; method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
hint: string; readOnly: boolean; result: "json"; authorized: true;
};
export const PROJECT_AGENT_OPERATIONS: ProjectAgentOperation[] = [];
function add(group: string, id: string, title: string, path: string, methods: string, hint: string) {
for (const method of methods.split(",") as ProjectAgentOperation["method"][]) PROJECT_AGENT_OPERATIONS.push({
id: `${id}.${method.toLowerCase()}`, title, group, path, method, hint, readOnly: method === "GET", result: "json", authorized: true,
});
}
add("技能", "skills", "可访问技能列表", "/api/agents", "GET,POST", "query: categoryId、search(可选)。执行技能前用 loadProjectSkill 读取真实提示词和参数,再由当前原生核心编排已有工具。");
add("技能", "skill-categories", "技能分类", "/api/agent-categories", "GET", "无需参数");
add("素材", "materials", "商品 SKU/SPU 素材检索", "/api/agent-workflow/material-search", "GET", "query: keyword必填,source=primary/secondary、queryType=sku/spu、requiredCount可选");
add("素材", "references", "参考图检索", "/api/agent-workflow/reference-search", "GET", "query: keyword、source、queryType;以接口返回的实际素材为准");
add("素材", "appearance", "素材外观识别", "/api/agent-workflow/material-appearance", "POST", "body: imageUrls等图片参数,接口验证服务配置与格式");
add("素材", "third-party-materials", "第三方图片素材检索", "/api/third-party-image-materials/search", "GET", "query: keyword、page、pageSize;沿用站点数据权限");
add("素材", "gallery", "图库素材", "/api/gallery", "GET", "query: page、pageSize、category、keyword可选");
add("素材", "featured", "精选素材", "/api/featured", "GET", "query: category、page可选");
add("素材", "ranking", "素材排行榜", "/api/featured/ranking", "GET", "query: type、limit可选");
add("PSD", "psd-templates", "PSD 模板列表", "/api/psd-templates", "GET", "query: category、keyword可选");
add("PSD", "psd-template", "PSD 模板图层信息", "/api/psd-templates/[id]", "GET", "pathParams: id=模板编号");
add("PSD", "psd-render", "PSD 真正渲染与任务状态", "/api/psd-templates/render", "GET,POST", "POST body: templateId必填,imageAssignments/textAssignments/hiddenLayerIds/layerOrderIds/imagePlacements等;GET query: taskId。pending/running只代表已受理,必须轮询到succeeded且有result.url才可交付。");
add("详情与设计", "detail-plan", "详情页与 UI 设计规划", "/api/canvas/detail-plan", "POST", "body: prompt、图片和设计上下文;遵循接口校验");
add("详情与设计", "detail-review", "详情结果验收", "/api/canvas/detail-review", "POST", "body: prompt、imageUrls等真实设计图信息");
add("详情与设计", "detail-subject", "详情主体分析", "/api/canvas/detail-subject-analyze", "POST", "body: 图片与产品信息");
add("详情与设计", "ui-review", "UI 页面验收", "/api/canvas/ui-screen/review", "POST", "body: 真实页面设计文档与审核要求");
add("详情与设计", "text-copy", "图片文案建议", "/api/canvas/text-copy-suggestions", "POST", "body: imageUrl、prompt等文案上下文");
add("详情与设计", "detail-stitch", "详情图片拼接", "/api/agent-workflow/detail-stitch", "POST", "body: images或imageUrls等拼接参数");
add("详情与设计", "detail-normalize", "详情图片归一化", "/api/agent-workflow/detail-image-normalize", "POST", "body: 图片链接与尺寸参数");
add("详情与设计", "style-models", "风格化可用模型", "/api/style-pro/model-options", "GET", "无需参数,不返回密钥");
add("详情与设计", "style-generate", "风格化 PRO 生成", "/api/style-pro/generate", "POST", "body: 模型、提示词与参考图,遵守用户指定模型;不改站点配置");
add("详情与设计", "wash-models", "AI 图像处理可用模型", "/api/ai-wash/model-options", "GET", "无需参数");
add("详情与设计", "wash-generate", "AI 图像处理", "/api/ai-wash/generate", "POST", "body: 用户图像与参数;结果未确认不能重提收费任务");
add("商品策划", "planning-image", "商品图片分析", "/api/product-planning/analyze-image", "POST", "body: imageUrl与产品上下文;底层功能开关仍生效");
add("商品策划", "planning-sku", "商品 SKU 分析", "/api/product-planning/analyze-sku", "POST", "body: sku与用户商品信息");
add("商品策划", "planning-analysis", "评价与问大家分析", "/api/product-planning/analyze", "POST", "body: kind=review/qa,rows为真实评价或问答数组");
add("商品策划", "planning-keywords", "关键词洞察", "/api/product-planning/keyword-insight", "GET", "query: keyword等检索参数");
for (const platform of ["taobao", "jd"]) add("商品策划", `planning-${platform}-item`, `${platform} 商品详情`, `/api/product-planning/${platform}/item`, "GET", "query: itemId等商品编号");
add("商品策划", "planning-reviews", "淘宝真实评价", "/api/product-planning/taobao/reviews", "GET", "query: itemId、page等");
add("商品策划", "planning-sales", "淘宝月销量", "/api/product-planning/taobao/month-sale", "GET", "query: itemId");
add("商品策划", "planning-qa", "淘宝问大家", "/api/product-planning/taobao/qa", "POST", "body: itemId等商品编号");
add("商品策划", "planning-market-items", "平台商品检索", "/api/product-planning/marketplace/items", "POST", "body: platform、keyword等检索参数");
add("商品策划", "planning-templates", "策划模板列表与保存", "/api/product-planning/templates", "GET,POST", "POST body: title与策划内容;只保存当前用户自己的模板");
add("商品策划", "planning-template", "策划模板读取与删除", "/api/product-planning/templates/[id]", "GET,DELETE", "pathParams: id;删除需要用户明确指定此编号");
add("流程白板", "flow-files", "白板文件列表与创建", "/api/flow-board/files", "GET,POST", "POST body: title、snapshot;snapshot必须为真实白板快照");
add("流程白板", "flow-file", "白板文件读取、修改、删除", "/api/flow-board/files/[id]", "GET,PUT,DELETE", "pathParams: id;PUT body: title、snapshot;先读取原文件,保留无关节点和工作表;删除需明确指定编号");
add("流程白板", "flow-draft", "白板草稿读取与保存", "/api/flow-board/draft", "GET,PUT", "PUT body: title、snapshot;只操作当前用户草稿");
add("分镜任务", "video-capabilities", "视频模型能力与参数", "/api/video-model-capabilities", "GET", "query: modelId等;沿用用户模型");
add("分镜任务", "video-tasks", "用户视频任务列表", "/api/video-tasks", "GET", "query: page、limit等;仅当前用户任务");
add("分镜任务", "storyboard-task", "分镜任务与阶段推进", "/api/video/storyboard/tasks/[taskId]", "GET", "pathParams: taskId;读取实际状态后再按接口动作推进,不重启已有收费任务");
add("分镜任务", "storyboard-confirm", "确认用户分镜参数", "/api/video/storyboard/tasks/[taskId]/confirm", "POST", "pathParams: taskId;body: 用户实际确认的镜头与参数,不虚构用户确认");
add("分镜任务", "storyboard-cancel", "停止指定分镜任务", "/api/video/storyboard/tasks/[taskId]/cancel", "POST", "pathParams: taskId;需用户明确要求停止此编号");
add("分镜任务", "storyboard-shot", "分镜镜头修订", "/api/video/storyboard/tasks/[taskId]/shots/[shotIndex]", "PATCH", "pathParams: taskId、shotIndex;body: 用户要求的镜头修改");
add("分镜任务", "storyboard-hd", "指定镜头高清处理", "/api/video/storyboard/tasks/[taskId]/shots/[shotIndex]/regenerate-hd", "POST", "pathParams: taskId、shotIndex;会产生费用,只有用户要求高清才执行");
add("项目", "agent-projects", "Agent 创作项目列表与创建", "/api/agent-projects", "GET,POST", "POST body: title等项目数据;不写入其他用户项目");
add("项目", "agent-project", "Agent 创作项目读写", "/api/agent-projects/[id]", "GET,PUT,DELETE", "pathParams: id;修改先读当前内容,删除需用户明确指定编号");
add("项目", "canvas-list", "用户画布列表与创建", "/api/canvas", "GET,POST", "POST body: title、workspaceType=canvas/video等");
add("项目", "canvas-read", "读取用户画布", "/api/canvas/[id]", "GET", "pathParams: id;只读取当前用户有权限的画布,节点写入使用专用画布工具");
add("项目", "detail-archives", "画布详情归档", "/api/canvas/[id]/detail-archives", "GET,POST,PATCH,DELETE", "pathParams: id=当前画布编号;body/query按详情归档接口,删除需明确授权");
add("项目", "model-assets", "3D 资产归档", "/api/canvas/[id]/model3d-assets", "GET,POST,PATCH,DELETE", "pathParams: id=当前画布编号;只处理拥有权限的资产");
add("3D", "3d-task", "3D 生成任务状态", "/api/canvas/3d/tasks/[taskId]", "GET", "pathParams: taskId;生成用已有3D专用能力,不重复提交");
add("3D", "world-task", "场景生成任务状态", "/api/canvas/world/tasks/[taskId]", "GET", "pathParams: taskId;生成用已有场景专用能力");
add("智能编辑", "smart-edit", "智能图层编辑能力", "/api/canvas/smart-edit/capabilities", "GET", "无需参数;仅当该部署版本已有此功能时可用");
add("文档", "documents", "读取与保存用户文档", "/api/document", "GET,POST", "query: id=文档UUID;POST body: title、kind=text/code/sheet/image、content。更新前先读取原文档,不操作其他用户文档。");
add("图像处理", "enhance-pro", "专业高清处理任务与查询", "/api/enhance/pro", "GET,POST", "POST body按接口实际模型与尺寸要求,GET query: taskId。仅任务成功后才能交付,不能重复付费提交。");
add("技能", "skill-item", "自有技能读取、编辑、删除", "/api/agents/[id]", "GET,PUT,DELETE", "pathParams: id;修改仅限本人技能,不能借管理员身份修改别人的公共技能。发布需用户明确要求。");
add("技能", "skill-fork", "复制可访问的技能", "/api/agents/[id]/fork", "POST", "pathParams: id;复制到当前用户名下,不修改原技能");
add("技能", "skill-tags", "快捷技能标签与个人顺序", "/api/canvas-skill-tags", "GET,PATCH", "PATCH body: tagIds=用户希望的标签编号顺序,仅调整个人顺序;不修改全站标签配置");
add("项目", "canvas-metadata", "修改当前图片画布名称与描述", "/api/canvas/[id]", "PUT", "pathParams: id=当前画布编号;body仅允许title、description、thumbnail,不允许覆盖canvasData或修改工作区类型");
add("项目", "canvas-title-suggestion", "建议画布标题(不保存)", "/api/canvas/auto-title", "POST", "body: prompt等上下文。此工具仅建议标题;需要改名时再调用canvas-metadata.put实际保存。");
add("详情与设计", "prompt-tools", "提示词润色、翻译与分析", "/api/canvas/prompt-tools", "POST", "body: text或prompt及action等;使用实际接口契约,不执行视频任务");
add("详情与设计", "layout-batches", "图片模板替换批次列表与创建", "/api/canvas/layout-replace/batches", "GET,POST", "query/body: 当前projectId、真实模板与商品图;异步状态必须查询,不重复创建收费批次");
add("详情与设计", "layout-batch", "图片模板替换批次状态与修改", "/api/canvas/layout-replace/batches/[batchId]", "GET,PATCH", "pathParams: batchId;只处理当前用户批次,先读取状态后修改");
add("详情与设计", "agent-detail-batches", "详情替换工作流批次", "/api/agents/detail-replacement/batches", "GET,POST", "query/body按真实详情替换批次接口契约;仅当前用户创作项目,异步任务必须查询实际结果");
add("项目", "execution-snapshot", "本人创作执行快照", "/api/agent-execution-snapshots/[id]", "GET", "pathParams: id;只读取当前用户有权限的执行记录");
add("项目", "image-quota", "当前用户图片额度", "/api/user/image-quota", "GET", "无需参数,不读取其他用户账户");
add("项目", "image-stats", "当前用户生图统计", "/api/user/image-stats", "GET", "无需参数");
add("帮助", "tutorials", "使用教程", "/api/tutorial-videos", "GET", "只读取教程,不新增或修改全站教程");
add("帮助", "navigation", "业务导航与帮助入口", "/api/nav-links", "GET", "只读取导航,不修改全站配置");
export function projectOperationAuthorized(operation: ProjectAgentOperation) {
return operation.group !== "分镜任务";
}
export function projectOperationUrl(operation: ProjectAgentOperation, pathParams: Record<string, string>, query: Record<string, unknown>) {
const path = operation.path.replace(/\[([^\]]+)\]/g, (_all, key: string) => {
const value = pathParams[key];
if (!value || !/^[a-zA-Z0-9_-]{1,180}$/.test(value)) throw new Error(`缺少或无效的路径参数:${key}`);
return encodeURIComponent(value);
});
const values = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
if (!/^[\w.-]{1,80}$/.test(key) || !["string", "number", "boolean"].includes(typeof value)) throw new Error("查询参数格式无效");
values.set(key, String(value));
}
return path + (values.size ? `?${values}` : "");
}
export function sanitizeProjectToolOutput(value: unknown): unknown {
if (Array.isArray(value)) return value.map(sanitizeProjectToolOutput);
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value)
.filter(([key]) => !/^(?:api[-_]?key|access[-_]?key|secret(?:[-_]?key)?|token|password(?:hash)?|ak|sk|accessKeyId|secretAccessKey|clientSecret|accessToken|refreshToken|authorization|cookie|headers|credentials|providerConfig)$/i.test(key))
.map(([key, entry]) => [key, sanitizeProjectToolOutput(entry)]));
return value;
}
export function validateCanvasMetadataInput(body: Record<string, unknown>) {
if (!Object.keys(body).length || Object.keys(body).some(key=>!["title","description","thumbnail"].includes(key))) throw new Error("仅允许修改画布名称、描述和封面");
if (body.title !== undefined && (typeof body.title !== "string" || !body.title.trim() || body.title.length>256)) throw new Error("画布名称必须为1到256字的文本");
if (body.description !== undefined && body.description !== null && (typeof body.description !== "string" || body.description.length>4096)) throw new Error("画布描述格式无效");
if (body.thumbnail !== undefined && body.thumbnail !== null && (typeof body.thumbnail !== "string" || body.thumbnail.length>4000 || (body.thumbnail && !/^https?:\/\//.test(body.thumbnail)))) throw new Error("封面必须为有效图片链接");
}