Merge branch 'develop' of https://github.com/HBAI-Ltd/Toonflow-app into develop

This commit is contained in:
zhishi 2026-04-28 14:17:09 +08:00
commit 8197cfcdec
18 changed files with 1050 additions and 1405 deletions

View File

@ -183,15 +183,14 @@ jobs:
| 操作系统 | 架构 | 文件 | 说明 | | 操作系统 | 架构 | 文件 | 说明 |
|---------|------|------|------| |---------|------|------|------|
| 🪟 Windows | 通用 | `ToonFlow-*-win-setup.exe` | **推荐**,适用于大多数 Windows 电脑 | | 🪟 Windows | x64 | `ToonFlow-*-win-x64-setup.exe` | **推荐**,适用于大多数 Windows 电脑 |
| 🪟 Windows | x64 | `ToonFlow-*-win-x64-setup.exe` | 适用于 64 位 Windows 电脑 |
| 🪟 Windows | ARM64 | `ToonFlow-*-win-arm64-setup.exe` | 适用于 ARM 架构 Windows 设备 | | 🪟 Windows | ARM64 | `ToonFlow-*-win-arm64-setup.exe` | 适用于 ARM 架构 Windows 设备 |
| 🍎 macOS | Apple Silicon | `ToonFlow-*-mac-arm64.dmg` | 适用于 M1/M2/M3/M4 芯片的 Mac | | 🍎 macOS | Apple Silicon | `ToonFlow-*-mac-arm64.dmg` | 适用于 M1/M2/M3/M4 芯片的 Mac |
| 🍎 macOS | Intel | `ToonFlow-*-mac-x64.dmg` | 适用于 Intel 芯片的 Mac | | 🍎 macOS | Intel | `ToonFlow-*-mac-x64.dmg` | 适用于 Intel 芯片的 Mac |
| 🐧 Linux | x86_64 | `ToonFlow-*-linux-x86_64.AppImage` | 适用于大多数 Linux 发行版 | | 🐧 Linux | x64 | `ToonFlow-*-linux-x86_64.AppImage` | 适用于大多数 Linux 发行版 |
| 🐧 Linux | ARM64 | `ToonFlow-*-linux-arm64.AppImage` | 适用于 ARM 架构的 Linux 设备 | | 🐧 Linux | ARM64 | `ToonFlow-*-linux-arm64.AppImage` | 适用于 ARM 架构的 Linux 设备 |
> 💡 **不确定选哪个?** Windows 用户通常选 **win-setup.exe**Mac 用户查看「关于本机」M 系列芯片选 **arm64.dmg**Intel 选 **x64.dmg**。 > 💡 **不确定选哪个?** Windows 用户通常选 **win-x64-setup.exe**Mac 用户查看「关于本机」M 系列芯片选 **arm64.dmg**Intel 选 **x64.dmg**。
## 🚀 安装说明 ## 🚀 安装说明

View File

@ -465,7 +465,7 @@ const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<str
const isStartEndRequired = currentMode.includes("startEndRequired"); const isStartEndRequired = currentMode.includes("startEndRequired");
const isEndFrameOptional = currentMode.includes("endFrameOptional"); const isEndFrameOptional = currentMode.includes("endFrameOptional");
const isStartFrameOptional = currentMode.includes("startFrameOptional"); const isStartFrameOptional = currentMode.includes("startFrameOptional");
const hasMultiRef = currentMode.some((m) => Array.isArray(m)); const hasMultiRef = Array.isArray(currentMode) && currentMode.some((m) => Array.isArray(m));
// 提取不同类型的引用 // 提取不同类型的引用
const imageRefs = (config.referenceList || []).filter((r) => r.type === "image"); const imageRefs = (config.referenceList || []).filter((r) => r.type === "image");
@ -635,4 +635,4 @@ exports.videoRequest = videoRequest;
exports.ttsRequest = ttsRequest; exports.ttsRequest = ttsRequest;
// 这行代码用于确保当前文件被识别为模块,避免全局变量冲突 // 这行代码用于确保当前文件被识别为模块,避免全局变量冲突
export {}; export {};

427
data/vendor/yunwu.ts vendored
View File

@ -1,427 +0,0 @@
// ==================== 类型定义 ====================
// 文本模型
interface TextModel {
name: string;
modelName: string;
type: "text";
think: boolean;
}
// 图像模型
interface ImageModel {
name: string;
modelName: string;
type: "image";
mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string;
}
// 视频模型
interface VideoModel {
name: string;
modelName: string;
type: "video";
mode: (
| "singleImage"
| "startEndRequired"
| "endFrameOptional"
| "startFrameOptional"
| "text"
| ("videoReference" | "imageReference" | "audioReference" | "textReference")[]
)[];
associationSkills?: string;
audio: "optional" | false | true;
durationResolutionMap: { duration: number[]; resolution: string[] }[];
}
interface TTSModel {
name: string;
modelName: string;
type: "tts";
voices: { title: string; voice: string }[];
}
interface VendorConfig {
id: string;
author: string;
description?: string;
name: string;
icon?: string;
inputs: { key: string; label: string; type: "text" | "password" | "url"; required: boolean; placeholder?: string }[];
inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel)[];
}
// ==================== 全局工具函数声明 ====================
declare const zipImage: (completeBase64: string, size: number) => Promise<string>;
declare const zipImageResolution: (completeBase64: string, width: number, height: number) => Promise<string>;
declare const mergeImages: (completeBase64: string[], maxSize?: string) => Promise<string>;
declare const urlToBase64: (url: string) => Promise<string>;
declare const pollTask: (
fn: () => Promise<{ completed: boolean; data?: string; error?: string }>,
interval?: number,
timeout?: number,
) => Promise<{ completed: boolean; data?: string; error?: string }>;
declare const axios: any;
declare const createOpenAI: any;
declare const createDeepSeek: any;
declare const createZhipu: any;
declare const createQwen: any;
declare const createAnthropic: any;
declare const createOpenAICompatible: any;
declare const createXai: any;
declare const createMinimax: any;
declare const createGoogleGenerativeAI: any;
declare const logger: (logstring: string) => void;
declare const jsonwebtoken: any;
// ==================== 供应商数据 ====================
const vendor: VendorConfig = {
id: "yunwu",
author: "Toonflow",
description: "OpenAI标准格式接口您可以修改请求地址并手动添加缺失的模型。",
name: "云雾中转",
icon: "",
inputs: [
{ key: "apiKey", label: "API密钥", type: "password", required: true },
{ key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "以v1结束示例https://yunwu.ai/v1" },
],
inputValues: {
apiKey: "",
baseUrl: "https://yunwu.ai/v1",
},
models: [
{
name: "Doubao-Seedream-5.0-lite",
type: "image",
modelName: "doubao-seedream-5-0-260128",
mode: ["text", "singleImage", "multiReference"],
},
{
name: "Gemini-3-Pro-Image-Preview",
type: "image",
modelName: "gemini-3.1-flash-image-preview",
mode: ["text", "singleImage", "multiReference"],
associationSkills: "高质量图像生成,支持文本生成图像、图像编辑",
},
{
name: "Claude-sonnet-4.6",
type: "text",
modelName: "claude-sonnet-4-6",
think: false,
},
{
name: "Claude-haiku-4.5-20251001",
type: "text",
modelName: "claude-haiku-4-5-20251001",
think: false,
},
{
name: "Grok-Video-3",
type: "video",
modelName: "grok-video-3",
mode: ["text", "singleImage"],
audio: false,
durationResolutionMap: [
{ duration: [6, 10], resolution: ["720P", "1080P"] }
],
associationSkills: "文本生成视频,支持图片垫图"
}
],
};
exports.vendor = vendor;
// ==================== 适配器函数 ====================
// 文本请求函数
const textRequest = (textModel: TextModel) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
if (!vendor.inputValues.baseUrl) throw new Error("缺少请求地址(baseUrl)");
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
const baseURL = vendor.inputValues.baseUrl;
return createOpenAI({
baseURL: baseURL,
apiKey: apiKey,
}).chat(textModel.modelName);
};
exports.textRequest = textRequest;
// 图片请求函数(修正版:使用 /v1/chat/completions 兼容接口)
interface ImageConfig {
prompt: string;
imageBase64: string[];
size: "1K" | "2K" | "4K";
aspectRatio: `${number}:${number}`;
}
const imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {
const { apiKey, baseUrl } = vendor.inputValues;
if (!apiKey) throw new Error("缺少API Key");
if (!baseUrl) throw new Error("缺少请求地址(baseUrl)");
const cleanApiKey = apiKey.replace(/^Bearer\s+/i, "");
const baseURL = baseUrl.replace(/\/$/, "");
const endpoint = baseURL + "/chat/completions";
// 构建用户消息内容(支持多图垫图)
const content: any[] = [
{
type: "text",
text: imageConfig.prompt,
},
];
// 添加参考图片(垫图)
if (imageConfig.imageBase64 && imageConfig.imageBase64.length > 0) {
for (const imgBase64 of imageConfig.imageBase64) {
let dataUrl = imgBase64;
if (!imgBase64.startsWith("data:image")) {
dataUrl = `data:image/png;base64,${imgBase64}`;
}
content.push({
type: "image_url",
image_url: { url: dataUrl },
});
}
}
// 注意:云雾中转站可能支持通过额外参数传递图像尺寸/比例,
// 若不确定,可将 size 和 aspectRatio 拼接到 prompt 中(推荐)。
// 这里采用追加提示词的方式,确保模型理解期望的分辨率和比例。
let finalPrompt = imageConfig.prompt;
const sizeMap: Record<string, string> = { "1K": "1024x1024", "2K": "2048x2048", "4K": "4096x4096" };
const resolution = sizeMap[imageConfig.size] || "1024x1024";
finalPrompt += `\n请生成一张比例为 ${imageConfig.aspectRatio}、分辨率不低于 ${resolution} 的图片。`;
content[0].text = finalPrompt;
const requestBody = {
model: imageModel.modelName,
messages: [
{
role: "user",
content: content,
},
],
max_tokens: 4096, // 防止输出截断
response_format: { type: "json_object" }, // 部分中转站需要 JSON 输出
};
logger(`[图像生成] 请求URL: ${endpoint}`);
logger(`[图像生成] 模型: ${imageModel.modelName}`);
logger(`[图像生成] 参考图片数量: ${imageConfig.imageBase64?.length || 0}`);
try {
const response = await axios.post(endpoint, requestBody, {
headers: {
"Authorization": `Bearer ${cleanApiKey}`,
"Content-Type": "application/json",
},
timeout: 120000,
});
if (response.status !== 200) {
throw new Error(`HTTP ${response.status}: ${JSON.stringify(response.data)}`);
}
const assistantMessage = response.data?.choices?.[0]?.message?.content;
if (!assistantMessage) {
throw new Error("响应中没有 assistant 消息内容");
}
// 提取图像数据(支持直接返回 base64 data URL 或普通 URL
let imageBase64: string | null = null;
// 情况1消息内容本身就是 data:image 开头
if (assistantMessage.startsWith("data:image")) {
imageBase64 = assistantMessage;
}
// 情况2包含 Markdown 图片链接 ![alt](url)
else {
const markdownMatch = assistantMessage.match(/!\[.*?\]\((.*?)\)/);
if (markdownMatch && markdownMatch[1]) {
const url = markdownMatch[1];
if (url.startsWith("data:image")) {
imageBase64 = url;
} else {
imageBase64 = await urlToBase64(url);
}
}
// 情况3直接是纯文本 URL
else if (assistantMessage.match(/^https?:\/\/[^\s]+\.(png|jpg|jpeg|gif|webp)/i)) {
imageBase64 = await urlToBase64(assistantMessage);
}
}
if (!imageBase64) {
// 最后尝试:也许整个 content 就是 base64 字符串(无前缀)
if (/^[A-Za-z0-9+/=]+$/.test(assistantMessage) && assistantMessage.length > 100) {
imageBase64 = `data:image/png;base64,${assistantMessage}`;
} else {
throw new Error(`无法从响应中提取图像数据: ${assistantMessage.substring(0, 200)}`);
}
}
logger(`[图像生成] 成功,图片大小: ${(imageBase64.length / 1024).toFixed(2)} KB`);
return imageBase64;
} catch (error: any) {
logger(`[图像生成] 失败: ${error.message}`);
if (error.response) {
logger(`[图像生成] API 错误详情: ${JSON.stringify(error.response.data)}`);
throw new Error(`图像生成失败: ${error.response.data?.error?.message || error.message}`);
}
throw error;
}
};
exports.imageRequest = imageRequest;
// 视频请求函数(保持原有实现,若云雾中转站有专用视频接口可按需修改)
interface VideoConfig {
duration: number;
resolution: string;
aspectRatio: "16:9" | "9:16";
prompt: string;
imageBase64?: string[];
audio?: boolean;
mode:
| "singleImage"
| "multiImage"
| "gridImage"
| "startEndRequired"
| "endFrameOptional"
| "startFrameOptional"
| "text"
| ("video" | "image" | "audio" | "text")[];
}
const videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {
const { apiKey, baseUrl: rawBaseUrl } = vendor.inputValues;
if (!apiKey) throw new Error("缺少API Key");
const baseUrl = rawBaseUrl?.trim();
if (!baseUrl) throw new Error("缺少请求地址(baseUrl)");
const createEndpoint = baseUrl.replace(/\/$/, "") + "/video/create";
const queryEndpoint = baseUrl.replace(/\/$/, "") + "/video/query";
let images: string[] | undefined;
if (videoConfig.imageBase64 && videoConfig.imageBase64.length > 0) {
logger(`[视频生成] 原始图片数组: ${JSON.stringify(videoConfig.imageBase64)}`);
images = videoConfig.imageBase64
.filter(img => img && typeof img === 'string' && img.length > 0)
.map(img => {
if (img.startsWith("data:image")) return img;
return `data:image/png;base64,${img}`;
});
if (images.length === 0) {
logger(`[视频生成] 警告: 所有图片都无效,将忽略图片参数`);
images = undefined;
} else {
logger(`[视频生成] 有效图片数量: ${images.length}`);
}
}
let aspectRatioParam: string;
switch (videoConfig.aspectRatio) {
case "16:9": aspectRatioParam = "3:2"; break;
case "9:16": aspectRatioParam = "2:3"; break;
default: aspectRatioParam = "1:1";
}
let sizeParam: string = "720P";
if (videoConfig.resolution && videoConfig.resolution.includes("1080")) sizeParam = "1080P";
const createBody: any = {
model: videoModel.modelName,
prompt: videoConfig.prompt,
aspect_ratio: aspectRatioParam,
size: sizeParam,
};
if (images && images.length > 0) createBody.images = images;
try {
logger(`[视频生成] 创建请求体: ${JSON.stringify({ ...createBody, images: images ? `${images.length}张图片` : undefined })}`);
const createResp = await axios.post(createEndpoint, createBody, {
headers: {
"Authorization": `Bearer ${apiKey.replace(/^Bearer\s+/i, "")}`,
"Content-Type": "application/json",
},
timeout: 30000,
});
if (createResp.status !== 200 || !createResp.data?.id) {
throw new Error(`创建任务失败: ${JSON.stringify(createResp.data)}`);
}
const taskId = createResp.data.id;
logger(`[视频生成] 任务已创建ID: ${taskId}`);
const pollResult = await pollTask(
async () => {
try {
const queryResp = await axios.get(queryEndpoint, {
params: { id: taskId },
headers: {
"Authorization": `Bearer ${apiKey.replace(/^Bearer\s+/i, "")}`,
"Content-Type": "application/json",
},
timeout: 15000,
});
if (queryResp.status !== 200) {
return { completed: false, error: `查询失败: HTTP ${queryResp.status}` };
}
const data = queryResp.data;
const status = data.status;
logger(`[视频生成] 任务状态: ${status}`);
if (status === "succeeded" || status === "completed" || status === "success") {
if (data.video_url) {
return { completed: true, data: data.video_url };
} else {
return { completed: false, error: "任务成功但未返回视频URL" };
}
} else if (status === "failed" || status === "error") {
return { completed: false, error: `视频生成失败: ${data.error || "未知错误"}` };
} else {
return { completed: false };
}
} catch (err: any) {
logger(`[视频生成] 轮询出错: ${err.message}`);
return { completed: false, error: err.message };
}
},
3000,
300000
);
if (!pollResult.completed) {
throw new Error(pollResult.error || "视频生成超时或失败");
}
const videoUrl = pollResult.data;
logger(`[视频生成] 成功视频URL: ${videoUrl}`);
return videoUrl;
} catch (error: any) {
logger(`[视频生成] 失败: ${error.message}`);
if (error.response) {
logger(`[视频生成] API 错误详情: ${JSON.stringify(error.response.data)}`);
throw new Error(`视频生成失败: ${error.response.data?.error?.message || error.message}`);
}
throw error;
}
};
exports.videoRequest = videoRequest;
// TTS 请求函数(占位)
interface TTSConfig {
text: string;
voice: string;
speechRate: number;
pitchRate: number;
volume: number;
}
const ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {
return null;
};
exports.ttsRequest = ttsRequest;

View File

@ -1 +1 @@
1.1.5 1.1.6

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "toonflow", "name": "toonflow",
"version": "1.1.5", "version": "1.1.6",
"description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。", "description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。",
"author": "HBAI-Ltd <ltlctools@outlook.com>", "author": "HBAI-Ltd <ltlctools@outlook.com>",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -1,6 +1,6 @@
import { Socket } from "socket.io"; import { Socket } from "socket.io";
import { tool } from "ai";
import { z } from "zod"; import { z } from "zod";
import { tool, jsonSchema } from "ai";
import u from "@/utils"; import u from "@/utils";
import Memory from "@/utils/agent/memory"; import Memory from "@/utils/agent/memory";
import { createSkillTools, parseFrontmatter, scanSkills, useSkill } from "@/utils/agent/skillsTools"; import { createSkillTools, parseFrontmatter, scanSkills, useSkill } from "@/utils/agent/skillsTools";
@ -138,9 +138,11 @@ async function createSubAgent(parentCtx: AgentContext) {
return fullResponse; return fullResponse;
} }
const promptInput = z.object({ const promptInput = z
prompt: z.string().describe("交给子Agent的任务简约描述100字以内"), .object({
}); prompt: z.string().describe("交给子Agent的任务简约描述100字以内"),
})
.toJSONSchema();
const projectInfo = await u.db("o_project").where("id", resTool.data.projectId).first(); const projectInfo = await u.db("o_project").where("id", resTool.data.projectId).first();
if (!projectInfo) throw new Error(`项目不存在ID: ${resTool.data.projectId}`); if (!projectInfo) throw new Error(`项目不存在ID: ${resTool.data.projectId}`);
@ -195,7 +197,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//衍生资产分析与信息写入 //衍生资产分析与信息写入
const run_sub_agent_derive_assets = tool({ const run_sub_agent_derive_assets = tool({
description: "运行执行subAgent来完成衍生资产分析与信息写入相关任务", description: "运行执行subAgent来完成衍生资产分析与信息写入相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_derive_assets.md"); const skill = path.join(u.getPath("skills"), "production_execution_derive_assets.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -217,7 +219,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//衍生资产图片生成 //衍生资产图片生成
const run_sub_agent_generate_assets = tool({ const run_sub_agent_generate_assets = tool({
description: "运行执行subAgent来完成衍生资产图片生成相关任务", description: "运行执行subAgent来完成衍生资产图片生成相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_generate_assets.md"); const skill = path.join(u.getPath("skills"), "production_execution_generate_assets.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -239,7 +241,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//拍摄计划 //拍摄计划
const run_sub_agent_director_plan = tool({ const run_sub_agent_director_plan = tool({
description: "运行执行subAgent来完成导演规划相关任务", description: "运行执行subAgent来完成导演规划相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_director_plan.md"); const skill = path.join(u.getPath("skills"), "production_execution_director_plan.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -264,7 +266,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//分镜图生成 //分镜图生成
const run_sub_agent_storyboard_gen = tool({ const run_sub_agent_storyboard_gen = tool({
description: "运行执行subAgent来完成分镜图生成相关任务", description: "运行执行subAgent来完成分镜图生成相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_gen.md"); const skill = path.join(u.getPath("skills"), "production_execution_storyboard_gen.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -298,7 +300,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//分镜面板写入 //分镜面板写入
const run_sub_agent_storyboard_panel = tool({ const run_sub_agent_storyboard_panel = tool({
description: "运行执行subAgent来完成分镜面板写入相关任务", description: "运行执行subAgent来完成分镜面板写入相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_panel.md"); const skill = path.join(u.getPath("skills"), "production_execution_storyboard_panel.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -324,7 +326,7 @@ async function createSubAgent(parentCtx: AgentContext) {
//分镜表写入 //分镜表写入
const run_sub_agent_storyboard_table = tool({ const run_sub_agent_storyboard_table = tool({
description: "运行执行subAgent来完成分镜表构建相关任务", description: "运行执行subAgent来完成分镜表构建相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_table.md"); const skill = path.join(u.getPath("skills"), "production_execution_storyboard_table.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -348,7 +350,7 @@ async function createSubAgent(parentCtx: AgentContext) {
const run_sub_agent_supervision = tool({ const run_sub_agent_supervision = tool({
description: "运行监督层subAgent执行独立任务完成后返回结果", description: "运行监督层subAgent执行独立任务完成后返回结果",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "production_agent_supervision.md"); const skill = path.join(u.getPath("skills"), "production_agent_supervision.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");

View File

@ -1,4 +1,4 @@
import { tool, Tool } from "ai"; import { tool, jsonSchema, Tool } from "ai";
import { z } from "zod"; import { z } from "zod";
import _ from "lodash"; import _ from "lodash";
import ResTool from "@/socket/resTool"; import ResTool from "@/socket/resTool";
@ -69,9 +69,13 @@ export default (toolCpnfig: ToolConfig) => {
const tools: Record<string, Tool> = { const tools: Record<string, Tool> = {
get_flowData: tool({ get_flowData: tool({
description: "获取工作区数据", description: "获取工作区数据",
inputSchema: z.object({ inputSchema: jsonSchema<{ key: keyof FlowData }>(
key: keySchema.describe("数据key"), z
}), .object({
key: keySchema.describe("数据key"),
})
.toJSONSchema(),
),
execute: async ({ key }) => { execute: async ({ key }) => {
const thinking = msg.thinking(`正在获取${flowDataKeyLabels[key]}工作区数据...`); const thinking = msg.thinking(`正在获取${flowDataKeyLabels[key]}工作区数据...`);
console.log("[tools] get_flowData", key); console.log("[tools] get_flowData", key);
@ -84,18 +88,22 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
add_deriveAsset: tool({ add_deriveAsset: tool({
description: "新增或更新衍生资产", description: "新增或更新衍生资产",
inputSchema: z.object({ inputSchema: jsonSchema<{ assetsId: number; id: number | null; name: string; desc: string }>(
assetsId: z.number().describe("关联的资产ID"), z
id: z.preprocess( .object({
(val) => { assetsId: z.number().describe("关联的资产ID"),
if (val === "null" || val === "" || val === undefined) return null; id: z.number().nullable().describe("衍生资产ID,如果新增则为空"),
return val; name: z.string().describe("衍生资产名称"),
}, desc: z.string().describe("衍生资产描述"),
z.number().nullable().describe("衍生资产ID,如果新增则为空")), })
name: z.string().describe("衍生资产名称"), .toJSONSchema(),
desc: z.string().describe("衍生资产描述"), ),
}), execute: async (raw) => {
execute: async (deriveAsset) => { // 容错LLM 偶尔传 "null" 字符串或空串,统一规范为 null
const idRaw = raw.id as unknown;
const normalizedId = idRaw === "null" || idRaw === "" || idRaw === undefined ? null : (idRaw as number | null);
const deriveAsset = { ...raw, id: normalizedId };
const thinking = msg.thinking("正在操作资产..."); const thinking = msg.thinking("正在操作资产...");
const { projectId, scriptId } = resTool.data; const { projectId, scriptId } = resTool.data;
const startTime = Date.now(); const startTime = Date.now();
@ -128,10 +136,14 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
del_deriveAsset: tool({ del_deriveAsset: tool({
description: "删除衍生资产", description: "删除衍生资产",
inputSchema: z.object({ inputSchema: jsonSchema<{ assetsId: number; id: number }>(
assetsId: z.number().describe("关联的资产ID"), z
id: z.number().describe("衍生资产ID"), .object({
}), assetsId: z.number().describe("关联的资产ID"),
id: z.number().describe("衍生资产ID"),
})
.toJSONSchema(),
),
execute: async ({ assetsId, id }) => { execute: async ({ assetsId, id }) => {
const thinking = msg.thinking("正在操作资产..."); const thinking = msg.thinking("正在操作资产...");
const { scriptId } = resTool.data; const { scriptId } = resTool.data;
@ -146,9 +158,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
generate_deriveAsset: tool({ generate_deriveAsset: tool({
description: "生成衍生资产图片", description: "生成衍生资产图片",
inputSchema: z.object({ inputSchema: jsonSchema<{ ids: number[] }>(
ids: z.array(z.number()).describe("需要生成的 衍生资产ID"), z
}), .object({
ids: z.array(z.number()).describe("需要生成的 衍生资产ID"),
})
.toJSONSchema(),
),
execute: async ({ ids }) => { execute: async ({ ids }) => {
const thinking = msg.thinking("正在生成衍生资产..."); const thinking = msg.thinking("正在生成衍生资产...");
new Promise((resolve) => socket.emit("generateDeriveAsset", { ids }, (res: any) => resolve(res))) new Promise((resolve) => socket.emit("generateDeriveAsset", { ids }, (res: any) => resolve(res)))
@ -168,9 +184,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
generate_storyboard: tool({ generate_storyboard: tool({
description: "生成分镜图片", description: "生成分镜图片",
inputSchema: z.object({ inputSchema: jsonSchema<{ ids: number[] }>(
ids: z.array(z.number()).describe("必须获取真实的分镜ID支持批量生成"), z
}), .object({
ids: z.array(z.number()).describe("必须获取真实的分镜ID支持批量生成"),
})
.toJSONSchema(),
),
execute: async ({ ids }) => { execute: async ({ ids }) => {
const thinking = msg.thinking("正在生成分镜..."); const thinking = msg.thinking("正在生成分镜...");
new Promise((resolve) => socket.emit("generateStoryboard", { ids }, (res: any) => resolve(res))) new Promise((resolve) => socket.emit("generateStoryboard", { ids }, (res: any) => resolve(res)))

View File

@ -1,5 +1,5 @@
import { Socket } from "socket.io"; import { Socket } from "socket.io";
import { tool } from "ai"; import { tool, jsonSchema } from "ai";
import { z } from "zod"; import { z } from "zod";
import u from "@/utils"; import u from "@/utils";
import Memory from "@/utils/agent/memory"; import Memory from "@/utils/agent/memory";
@ -133,13 +133,15 @@ function createSubAgent(parentCtx: AgentContext) {
return fullResponse; return fullResponse;
} }
const promptInput = z.object({ const promptInput = z
prompt: z.string().describe("交给子Agent的任务简约描述100字以内"), .object({
}); prompt: z.string().describe("交给子Agent的任务简约描述100字以内"),
})
.toJSONSchema();
const run_sub_agent_storySkeleton = tool({ const run_sub_agent_storySkeleton = tool({
description: "运行执行subAgent来完成故事骨架相关任务", description: "运行执行subAgent来完成故事骨架相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "script_execution_skeleton.md"); const skill = path.join(u.getPath("skills"), "script_execution_skeleton.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
@ -159,7 +161,7 @@ function createSubAgent(parentCtx: AgentContext) {
const run_sub_agent_adaptationStrategy = tool({ const run_sub_agent_adaptationStrategy = tool({
description: "运行执行subAgent来完成改编策略相关任务", description: "运行执行subAgent来完成改编策略相关任务",
inputSchema: promptInput, inputSchema: jsonSchema<{ prompt: string }>(promptInput),
execute: async ({ prompt }) => { execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "script_execution_adaptation.md"); const skill = path.join(u.getPath("skills"), "script_execution_adaptation.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");

View File

@ -1,4 +1,4 @@
import { tool, Tool } from "ai"; import { tool, jsonSchema, Tool } from "ai";
import u from "@/utils"; import u from "@/utils";
import { z } from "zod"; import { z } from "zod";
import _ from "lodash"; import _ from "lodash";
@ -33,9 +33,13 @@ export default (toolCpnfig: ToolConfig) => {
const tools: Record<string, Tool> = { const tools: Record<string, Tool> = {
get_novel_events: tool({ get_novel_events: tool({
description: "获取章节事件", description: "获取章节事件",
inputSchema: z.object({ inputSchema: jsonSchema<{ chapterIndexs: number[] }>(
chapterIndexs: z.array(z.number()).describe("章节的编号"), z
}), .object({
chapterIndexs: z.array(z.number()).describe("章节的编号"),
})
.toJSONSchema(),
),
execute: async ({ chapterIndexs }) => { execute: async ({ chapterIndexs }) => {
console.log("[tools] get_novel_events", chapterIndexs); console.log("[tools] get_novel_events", chapterIndexs);
const thinking = msg.thinking("正在查询章节事件..."); const thinking = msg.thinking("正在查询章节事件...");
@ -45,7 +49,7 @@ export default (toolCpnfig: ToolConfig) => {
.select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState") .select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState")
.whereIn("chapterIndex", chapterIndexs); .whereIn("chapterIndex", chapterIndexs);
thinking.appendText("正在查询章节编号: " + chapterIndexs.join(",")); thinking.appendText("正在查询章节编号: " + chapterIndexs.join(","));
const eventString = data.map((i: any) => [`${i.index}章,标题${i.chapter},事件:${i.event}`].join("\n")).join("\n"); const eventString = data.map((i: any) => [`${i.index}章,标题:${i.chapter},事件:${i.event}`].join("\n")).join("\n");
thinking.appendText("查询结果:\n" + eventString); thinking.appendText("查询结果:\n" + eventString);
thinking.updateTitle("查询章节事件完成"); thinking.updateTitle("查询章节事件完成");
thinking.complete(); thinking.complete();
@ -54,9 +58,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
get_planData: tool({ get_planData: tool({
description: "获取工作区数据", description: "获取工作区数据",
inputSchema: z.object({ inputSchema: jsonSchema<{ key: keyof planData }>(
key: keySchema.describe("数据key"), z
}), .object({
key: keySchema.describe("数据key"),
})
.toJSONSchema(),
),
execute: async ({ key }) => { execute: async ({ key }) => {
console.log("[tools] get_planData", key); console.log("[tools] get_planData", key);
const thinking = msg.thinking(`正在获取${planDataKeyLabels[key]}工作区数据...`); const thinking = msg.thinking(`正在获取${planDataKeyLabels[key]}工作区数据...`);
@ -69,9 +77,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
get_novel_text: tool({ get_novel_text: tool({
description: "获取小说章节原始文本内容", description: "获取小说章节原始文本内容",
inputSchema: z.object({ inputSchema: jsonSchema<{ chapterIndex: string }>(
chapterIndex: z.string().describe("章节编号"), z
}), .object({
chapterIndex: z.string().describe("章节编号"),
})
.toJSONSchema(),
),
execute: async ({ chapterIndex }) => { execute: async ({ chapterIndex }) => {
console.log("[tools] get_novel_text", "[tools] get_novel_text", chapterIndex); console.log("[tools] get_novel_text", "[tools] get_novel_text", chapterIndex);
const thinking = msg.thinking(`正在获取小说章节原文...`); const thinking = msg.thinking(`正在获取小说章节原文...`);
@ -85,9 +97,13 @@ export default (toolCpnfig: ToolConfig) => {
}), }),
get_script_content: tool({ get_script_content: tool({
description: "获取剧本本内容", description: "获取剧本本内容",
inputSchema: z.object({ inputSchema: jsonSchema<{ ids: string[] }>(
ids: z.array(z.string()).describe("脚本id"), z
}), .object({
ids: z.array(z.string()).describe("脚本id"),
})
.toJSONSchema(),
),
execute: async ({ ids }) => { execute: async ({ ids }) => {
console.log("[tools] get_script_content", "[tools] get_script_content", ids); console.log("[tools] get_script_content", "[tools] get_script_content", ids);
const thinking = msg.thinking(`正在获取脚本内容...`); const thinking = msg.thinking(`正在获取脚本内容...`);

View File

@ -3,7 +3,7 @@ import u from "@/utils";
import { z } from "zod"; import { z } from "zod";
import { success } from "@/lib/responseFormat"; import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware"; import { validateFields } from "@/middleware/middleware";
import { tool } from "ai"; import { tool, jsonSchema } from "ai";
const router = express.Router(); const router = express.Router();
// 获取资产 // 获取资产
@ -22,26 +22,37 @@ export default router.post(
.whereIn("id", assetsIds) .whereIn("id", assetsIds)
.andWhere("projectId", projectId) .andWhere("projectId", projectId)
.select("id", "name", "describe"); .select("id", "name", "describe");
console.log("%c Line:20 🍎 assetsData", "background:#b03734", assetsData);
const audioData = await u.db("o_assets").where("type", "audio").whereNull("assetsId").andWhere("projectId", projectId).select("id", "name", "describe"); const audioData = await u
console.log("%c Line:26 🍋 audioData", "background:#ea7e5c", audioData); .db("o_assets")
.where("type", "audio")
.whereNull("assetsId")
.andWhere("projectId", projectId)
.select("id", "name", "describe");
async function processGroup() { async function processGroup() {
try { try {
const resultTool = tool({ const resultTool = tool({
description: "返回结果时必须调用这个工具", description: "返回结果时必须调用这个工具",
inputSchema: z.object({ inputSchema: jsonSchema<{ result: { id: number; audioIds: number[] }[] }>(
result: z.array(z.object({ z
id: z.number(), .object({
audioIds: z.array(z.number()).describe("适配的音频id 无适配内容可以为 空数组") result: z
})).describe("适配的音色列表id为资产idaudioIds为适配的音频id 无适配内容可以为 空数组") .array(
}), z.object({
id: z.number(),
audioIds: z.array(z.number()).describe("适配的音频id 无适配内容可以为 空数组"),
}),
)
.describe("适配的音色列表id为资产idaudioIds为适配的音频id 无适配内容可以为 空数组"),
})
.toJSONSchema(),
),
execute: async ({ result }) => { execute: async ({ result }) => {
console.log("[tools] extractAssets result", result); console.log("[tools] extractAssets result", result);
for (const item of result) { for (const item of result) {
await u.db("o_assetsRole2Audio").where("assetsRoleId", item.id).delete() await u.db("o_assetsRole2Audio").where("assetsRoleId", item.id).delete();
if (item.audioIds.length) if (item.audioIds.length)
await u.db("o_assetsRole2Audio").insert(item.audioIds.map(i => ({ assetsRoleId: item.id, assetsAudioId: i }))) await u.db("o_assetsRole2Audio").insert(item.audioIds.map((i) => ({ assetsRoleId: item.id, assetsAudioId: i })));
} }
return "无需回复用户任何内容"; return "无需回复用户任何内容";
}, },
@ -55,21 +66,20 @@ export default router.post(
}, },
{ {
role: "user", role: "user",
content: `音频内容:${audioData.map(i => `Id:${i.id},音色名称:${i.name},描述:${i.describe}`).join("\n")}\n\n content: `音频内容:${audioData.map((i) => `Id:${i.id},音色名称:${i.name},描述:${i.describe}`).join("\n")}\n\n
${assetsData.map(i => `ID:${i.id},名称:${i.name},描述:${i.describe}`).join("\n")}\n\n ${assetsData.map((i) => `ID:${i.id},名称:${i.name},描述:${i.describe}`).join("\n")}\n\n
`, `,
}, },
], ],
tools: { resultTool }, tools: { resultTool },
}); });
console.log("%c Line:44 🍞 text", "background:#f5ce50", text); console.log("%c Line:44 🍞 text", "background:#f5ce50", text);
} catch (e) { } catch (e) {
console.error(`提取失败:`, e); console.error(`提取失败:`, e);
return; return;
} }
} }
await processGroup() await processGroup();
res.status(200).send(success()); res.status(200).send(success());
}, },
); );

View File

@ -4,7 +4,7 @@ import { z } from "zod";
import { error, success } from "@/lib/responseFormat"; import { error, success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware"; import { validateFields } from "@/middleware/middleware";
import { useSkill } from "@/utils/agent/skillsTools"; import { useSkill } from "@/utils/agent/skillsTools";
import { tool } from "ai"; import { tool, jsonSchema } from "ai";
import { o_script } from "@/types/database"; import { o_script } from "@/types/database";
const router = express.Router(); const router = express.Router();
@ -131,10 +131,7 @@ export default router.post(
} }
// 去重:相同 scriptId + assetId 只保留一条 // 去重:相同 scriptId + assetId 只保留一条
const uniqueRows = [ const uniqueRows = [...new Map(scriptAssetRows.map((r) => [`${r.scriptId}_${r.assetId}`, r])).values()];
...new Map(scriptAssetRows.map((r) => [`${r.scriptId}_${r.assetId}`, r])).values(),
];
// 先删除本批 scriptId 的旧关联,再插入新的 // 先删除本批 scriptId 的旧关联,再插入新的
await u.db("o_scriptAssets").whereIn("scriptId", batchScriptIds).delete(); await u.db("o_scriptAssets").whereIn("scriptId", batchScriptIds).delete();
@ -188,16 +185,19 @@ export default router.post(
try { try {
const resultTool = tool({ const resultTool = tool({
description: "返回结果时必须调用这个工具", description: "返回结果时必须调用这个工具",
inputSchema: z.object({ inputSchema: jsonSchema<{ newAssets: NewAsset[]; existingAssetRefs: ExistingAssetRef[] }>(
newAssets: z z
.array(NewAssetSchema) .object({
.describe("新发现的资产列表(不在已有资产列表中的),需要完整的 prompt、name、desc、type 和使用该资产的 scriptIds"), newAssets: z
existingAssetRefs: z .array(NewAssetSchema)
.array(ExistingAssetRefSchema) .describe("新发现的资产列表(不在已有资产列表中的),需要完整的 prompt、name、desc、type 和使用该资产的 scriptIds"),
.describe("已有资产的引用列表(在已有资产列表中已存在的),只需给出资产名称和使用该资产的 scriptIds"), existingAssetRefs: z
}), .array(ExistingAssetRefSchema)
.describe("已有资产的引用列表(在已有资产列表中已存在的),只需给出资产名称和使用该资产的 scriptIds"),
})
.toJSONSchema(),
),
execute: async ({ newAssets, existingAssetRefs }) => { execute: async ({ newAssets, existingAssetRefs }) => {
if (newAssets?.length) collectedNew = newAssets; if (newAssets?.length) collectedNew = newAssets;
if (existingAssetRefs?.length) collectedExisting = existingAssetRefs; if (existingAssetRefs?.length) collectedExisting = existingAssetRefs;
return "无需回复用户任何内容"; return "无需回复用户任何内容";

View File

@ -6,23 +6,29 @@ const router = express.Router();
export default router.post("/", async (req, res) => { export default router.post("/", async (req, res) => {
const data = await u.db("o_vendorConfig").select("*"); const data = await u.db("o_vendorConfig").select("*");
const list = await Promise.all( const list = (
data.map(async (item) => { await Promise.all(
const vendor = u.vendor.getVendor(item.id!); data.map(async (item) => {
return { const vendor = u.vendor.getVendor(item.id!);
...item, if (!vendor) {
inputValues: JSON.parse(item.inputValues ?? "{}"), await u.db("o_vendorConfig").where("id", item.id).delete();
models: await u.vendor.getModelList(item.id!), return null
code: u.vendor.getCode(item.id!), };
description: vendor.description, return {
inputs: vendor.inputs, ...item,
author: vendor.author, inputValues: JSON.parse(item.inputValues ?? "{}"),
name: vendor.name, models: await u.vendor.getModelList(item.id!),
version: vendor.version ?? "1.0", code: u.vendor.getCode(item.id!),
}; description: vendor.description ?? "",
}), inputs: vendor.inputs,
); author: vendor.author,
name: vendor.name,
version: vendor.version ?? "1.0",
};
}),
)
).filter((i) => Boolean(i));
list.sort((a, b) => (a.id === "toonflow" ? -1 : b.id === "toonflow" ? 1 : 0)); list.sort((a, b) => (a!.id === "toonflow" ? -1 : b!.id === "toonflow" ? 1 : 0));
res.status(200).send(success(list)); res.status(200).send(success(list));
}); });

View File

@ -3,7 +3,7 @@ import { success, error } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware"; import { validateFields } from "@/middleware/middleware";
import u from "@/utils"; import u from "@/utils";
import { z } from "zod"; import { z } from "zod";
import { tool } from "ai"; import { tool, jsonSchema } from "ai";
const router = express.Router(); const router = express.Router();
// 检查语言模型 // 检查语言模型
@ -57,9 +57,13 @@ export default router.post(
const getWeatherTool = tool({ const getWeatherTool = tool({
description: "Get the weather in a location", description: "Get the weather in a location",
inputSchema: z.object({ inputSchema: jsonSchema<{ location: string }>(
location: z.string().describe("The location to get the weather for"), z
}), .object({
location: z.string().describe("The location to get the weather for"),
})
.toJSONSchema(),
),
execute: async ({ location }) => { execute: async ({ location }) => {
return { return {
location, location,

View File

@ -1,4 +1,4 @@
// @db-hash 88c167ba73e2771e7b043419ca5089dd // @db-hash 5364c2db0bf42b520761b813ce040489
//该文件由脚本自动生成,请勿手动修改 //该文件由脚本自动生成,请勿手动修改
export interface memories { export interface memories {
@ -23,6 +23,7 @@ export interface o_agentDeploy {
'modelName'?: string | null; 'modelName'?: string | null;
'name'?: string | null; 'name'?: string | null;
'temperature'?: number | null; 'temperature'?: number | null;
'topP'?: number | null;
'type'?: string | null; 'type'?: string | null;
'vendorId'?: string | null; 'vendorId'?: string | null;
} }
@ -210,7 +211,6 @@ export interface o_user {
'password'?: string | null; 'password'?: string | null;
} }
export interface o_vendorConfig { export interface o_vendorConfig {
'code'?: string | null;
'enable'?: number | null; 'enable'?: number | null;
'id'?: string; 'id'?: string;
'inputValues'?: string | null; 'inputValues'?: string | null;

View File

@ -2,7 +2,7 @@ import u from "@/utils";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { getEmbedding, cosineSimilarity } from "./embedding"; import { getEmbedding, cosineSimilarity } from "./embedding";
import type { memories as MemoryRow } from "@/types/database"; import type { memories as MemoryRow } from "@/types/database";
import { tool } from "ai"; import { tool, jsonSchema } from "ai";
import { z } from "zod"; import { z } from "zod";
// ── 可调配置默认值 ── // ── 可调配置默认值 ──
@ -201,9 +201,13 @@ class Memory {
return { return {
deepRetrieve: tool({ deepRetrieve: tool({
description: "深度检索记忆:当你需要回忆与某个关键词相关的详细历史信息时使用此工具", description: "深度检索记忆:当你需要回忆与某个关键词相关的详细历史信息时使用此工具",
inputSchema: z.object({ inputSchema: jsonSchema<{ keyword: string }>(
keyword: z.string().describe("要检索的关键词"), z
}), .object({
keyword: z.string().describe("要检索的关键词"),
})
.toJSONSchema(),
),
execute: async ({ keyword }) => { execute: async ({ keyword }) => {
const results = await this.deepRetrieve(keyword); const results = await this.deepRetrieve(keyword);
if (results.length === 0) return { found: false, message: "未找到相关记忆" }; if (results.length === 0) return { found: false, message: "未找到相关记忆" };

View File

@ -1,5 +1,5 @@
import { tool } from "ai";
import { z } from "zod"; import { z } from "zod";
import { tool, jsonSchema } from "ai";
import path from "path"; import path from "path";
import isPathInside from "is-path-inside"; import isPathInside from "is-path-inside";
import getPath from "@/utils/getPath"; import getPath from "@/utils/getPath";
@ -185,9 +185,13 @@ export function createSkillTools(skills: { name: string; description: string }[]
return { return {
activate_skill: tool({ activate_skill: tool({
description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skillNames.join(", ")}`, description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skillNames.join(", ")}`,
inputSchema: z.object({ inputSchema: jsonSchema<{ name: string }>(
name: z.enum(skillNames as [string, ...string[]]).describe("要激活的技能名称"), z
}), .object({
name: z.enum(skillNames as [string, ...string[]]).describe("要激活的技能名称"),
})
.toJSONSchema(),
),
execute: async ({ name }) => { execute: async ({ name }) => {
if (activated.has(name)) { if (activated.has(name)) {
console.log(`⚡[主技能] 技能 "${name}" 已激活,跳过重复注入`); console.log(`⚡[主技能] 技能 "${name}" 已激活,跳过重复注入`);
@ -222,9 +226,13 @@ export function createSkillTools(skills: { name: string; description: string }[]
}), }),
read_skill_file: tool({ read_skill_file: tool({
description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。", description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。",
inputSchema: z.object({ inputSchema: jsonSchema<{ filePath: string }>(
filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"), z
}), .object({
filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"),
})
.toJSONSchema(),
),
execute: async ({ filePath }) => { execute: async ({ filePath }) => {
const normalizedInputPath = toUnixPath(filePath).trim(); const normalizedInputPath = toUnixPath(filePath).trim();
if (!normalizedInputPath) { if (!normalizedInputPath) {

View File

@ -25,6 +25,7 @@ export async function getModelList(id: string): Promise<Array<any>> {
const code = getCode(id); const code = getCode(id);
const jsCode = transform(code, { transforms: ["typescript"] }).code; const jsCode = transform(code, { transforms: ["typescript"] }).code;
const vendorData = u.vm(jsCode); const vendorData = u.vm(jsCode);
if(!vendorData || !vendorData.vendor || !vendorData.vendor.models) return [];
const combined = [...JSON.parse(JSON.stringify(vendorData.vendor.models)), ...JSON.parse(models?.models ?? "[]")]; const combined = [...JSON.parse(JSON.stringify(vendorData.vendor.models)), ...JSON.parse(models?.models ?? "[]")];
const map = new Map<string, any>(); const map = new Map<string, any>();
for (const m of combined) { for (const m of combined) {