Merge branch 'develop' of https://github.com/HBAI-Ltd/Toonflow-app into develop
This commit is contained in:
commit
8197cfcdec
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@ -183,15 +183,14 @@ jobs:
|
||||
|
||||
| 操作系统 | 架构 | 文件 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| 🪟 Windows | 通用 | `ToonFlow-*-win-setup.exe` | **推荐**,适用于大多数 Windows 电脑 |
|
||||
| 🪟 Windows | x64 | `ToonFlow-*-win-x64-setup.exe` | 适用于 64 位 Windows 电脑 |
|
||||
| 🪟 Windows | x64 | `ToonFlow-*-win-x64-setup.exe` | **推荐**,适用于大多数 Windows 电脑 |
|
||||
| 🪟 Windows | ARM64 | `ToonFlow-*-win-arm64-setup.exe` | 适用于 ARM 架构 Windows 设备 |
|
||||
| 🍎 macOS | Apple Silicon | `ToonFlow-*-mac-arm64.dmg` | 适用于 M1/M2/M3/M4 芯片的 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 设备 |
|
||||
|
||||
> 💡 **不确定选哪个?** Windows 用户通常选 **win-setup.exe**;Mac 用户查看「关于本机」:M 系列芯片选 **arm64.dmg**,Intel 选 **x64.dmg**。
|
||||
> 💡 **不确定选哪个?** Windows 用户通常选 **win-x64-setup.exe**;Mac 用户查看「关于本机」:M 系列芯片选 **arm64.dmg**,Intel 选 **x64.dmg**。
|
||||
|
||||
## 🚀 安装说明
|
||||
|
||||
|
||||
4
data/vendor/klingai.ts
vendored
4
data/vendor/klingai.ts
vendored
@ -465,7 +465,7 @@ const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<str
|
||||
const isStartEndRequired = currentMode.includes("startEndRequired");
|
||||
const isEndFrameOptional = currentMode.includes("endFrameOptional");
|
||||
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");
|
||||
@ -635,4 +635,4 @@ exports.videoRequest = videoRequest;
|
||||
exports.ttsRequest = ttsRequest;
|
||||
|
||||
// 这行代码用于确保当前文件被识别为模块,避免全局变量冲突
|
||||
export {};
|
||||
export {};
|
||||
|
||||
427
data/vendor/yunwu.ts
vendored
427
data/vendor/yunwu.ts
vendored
@ -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 图片链接 
|
||||
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;
|
||||
@ -1 +1 @@
|
||||
1.1.5
|
||||
1.1.6
|
||||
1698
data/web/index.html
1698
data/web/index.html
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "toonflow",
|
||||
"version": "1.1.5",
|
||||
"version": "1.1.6",
|
||||
"description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。",
|
||||
"author": "HBAI-Ltd <ltlctools@outlook.com>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Socket } from "socket.io";
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
import { tool, jsonSchema } from "ai";
|
||||
import u from "@/utils";
|
||||
import Memory from "@/utils/agent/memory";
|
||||
import { createSkillTools, parseFrontmatter, scanSkills, useSkill } from "@/utils/agent/skillsTools";
|
||||
@ -138,9 +138,11 @@ async function createSubAgent(parentCtx: AgentContext) {
|
||||
return fullResponse;
|
||||
}
|
||||
|
||||
const promptInput = z.object({
|
||||
prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"),
|
||||
});
|
||||
const promptInput = z
|
||||
.object({
|
||||
prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"),
|
||||
})
|
||||
.toJSONSchema();
|
||||
|
||||
const projectInfo = await u.db("o_project").where("id", resTool.data.projectId).first();
|
||||
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({
|
||||
description: "运行执行subAgent来完成衍生资产分析与信息写入相关任务",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "production_execution_derive_assets.md");
|
||||
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({
|
||||
description: "运行执行subAgent来完成衍生资产图片生成相关任务",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "production_execution_generate_assets.md");
|
||||
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({
|
||||
description: "运行执行subAgent来完成导演规划相关任务",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "production_execution_director_plan.md");
|
||||
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({
|
||||
description: "运行执行subAgent来完成分镜图生成相关任务",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_gen.md");
|
||||
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({
|
||||
description: "运行执行subAgent来完成分镜面板写入相关任务",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_panel.md");
|
||||
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({
|
||||
description: "运行执行subAgent来完成分镜表构建相关任务",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_table.md");
|
||||
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
|
||||
@ -348,7 +350,7 @@ async function createSubAgent(parentCtx: AgentContext) {
|
||||
|
||||
const run_sub_agent_supervision = tool({
|
||||
description: "运行监督层subAgent执行独立任务,完成后返回结果",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "production_agent_supervision.md");
|
||||
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { tool, Tool } from "ai";
|
||||
import { tool, jsonSchema, Tool } from "ai";
|
||||
import { z } from "zod";
|
||||
import _ from "lodash";
|
||||
import ResTool from "@/socket/resTool";
|
||||
@ -69,9 +69,13 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
const tools: Record<string, Tool> = {
|
||||
get_flowData: tool({
|
||||
description: "获取工作区数据",
|
||||
inputSchema: z.object({
|
||||
key: keySchema.describe("数据key"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ key: keyof FlowData }>(
|
||||
z
|
||||
.object({
|
||||
key: keySchema.describe("数据key"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ key }) => {
|
||||
const thinking = msg.thinking(`正在获取${flowDataKeyLabels[key]}工作区数据...`);
|
||||
console.log("[tools] get_flowData", key);
|
||||
@ -84,18 +88,22 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
}),
|
||||
add_deriveAsset: tool({
|
||||
description: "新增或更新衍生资产",
|
||||
inputSchema: z.object({
|
||||
assetsId: z.number().describe("关联的资产ID"),
|
||||
id: z.preprocess(
|
||||
(val) => {
|
||||
if (val === "null" || val === "" || val === undefined) return null;
|
||||
return val;
|
||||
},
|
||||
z.number().nullable().describe("衍生资产ID,如果新增则为空")),
|
||||
name: z.string().describe("衍生资产名称"),
|
||||
desc: z.string().describe("衍生资产描述"),
|
||||
}),
|
||||
execute: async (deriveAsset) => {
|
||||
inputSchema: jsonSchema<{ assetsId: number; id: number | null; name: string; desc: string }>(
|
||||
z
|
||||
.object({
|
||||
assetsId: z.number().describe("关联的资产ID"),
|
||||
id: z.number().nullable().describe("衍生资产ID,如果新增则为空"),
|
||||
name: z.string().describe("衍生资产名称"),
|
||||
desc: z.string().describe("衍生资产描述"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async (raw) => {
|
||||
// 容错: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 { projectId, scriptId } = resTool.data;
|
||||
const startTime = Date.now();
|
||||
@ -128,10 +136,14 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
}),
|
||||
del_deriveAsset: tool({
|
||||
description: "删除衍生资产",
|
||||
inputSchema: z.object({
|
||||
assetsId: z.number().describe("关联的资产ID"),
|
||||
id: z.number().describe("衍生资产ID"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ assetsId: number; id: number }>(
|
||||
z
|
||||
.object({
|
||||
assetsId: z.number().describe("关联的资产ID"),
|
||||
id: z.number().describe("衍生资产ID"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ assetsId, id }) => {
|
||||
const thinking = msg.thinking("正在操作资产...");
|
||||
const { scriptId } = resTool.data;
|
||||
@ -146,9 +158,13 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
}),
|
||||
generate_deriveAsset: tool({
|
||||
description: "生成衍生资产图片",
|
||||
inputSchema: z.object({
|
||||
ids: z.array(z.number()).describe("需要生成的 衍生资产ID"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ ids: number[] }>(
|
||||
z
|
||||
.object({
|
||||
ids: z.array(z.number()).describe("需要生成的 衍生资产ID"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ ids }) => {
|
||||
const thinking = msg.thinking("正在生成衍生资产...");
|
||||
new Promise((resolve) => socket.emit("generateDeriveAsset", { ids }, (res: any) => resolve(res)))
|
||||
@ -168,9 +184,13 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
}),
|
||||
generate_storyboard: tool({
|
||||
description: "生成分镜图片",
|
||||
inputSchema: z.object({
|
||||
ids: z.array(z.number()).describe("必须获取真实的分镜ID,支持批量生成"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ ids: number[] }>(
|
||||
z
|
||||
.object({
|
||||
ids: z.array(z.number()).describe("必须获取真实的分镜ID,支持批量生成"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ ids }) => {
|
||||
const thinking = msg.thinking("正在生成分镜...");
|
||||
new Promise((resolve) => socket.emit("generateStoryboard", { ids }, (res: any) => resolve(res)))
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Socket } from "socket.io";
|
||||
import { tool } from "ai";
|
||||
import { tool, jsonSchema } from "ai";
|
||||
import { z } from "zod";
|
||||
import u from "@/utils";
|
||||
import Memory from "@/utils/agent/memory";
|
||||
@ -133,13 +133,15 @@ function createSubAgent(parentCtx: AgentContext) {
|
||||
return fullResponse;
|
||||
}
|
||||
|
||||
const promptInput = z.object({
|
||||
prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"),
|
||||
});
|
||||
const promptInput = z
|
||||
.object({
|
||||
prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"),
|
||||
})
|
||||
.toJSONSchema();
|
||||
|
||||
const run_sub_agent_storySkeleton = tool({
|
||||
description: "运行执行subAgent来完成故事骨架相关任务",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "script_execution_skeleton.md");
|
||||
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
|
||||
@ -159,7 +161,7 @@ function createSubAgent(parentCtx: AgentContext) {
|
||||
|
||||
const run_sub_agent_adaptationStrategy = tool({
|
||||
description: "运行执行subAgent来完成改编策略相关任务",
|
||||
inputSchema: promptInput,
|
||||
inputSchema: jsonSchema<{ prompt: string }>(promptInput),
|
||||
execute: async ({ prompt }) => {
|
||||
const skill = path.join(u.getPath("skills"), "script_execution_adaptation.md");
|
||||
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { tool, Tool } from "ai";
|
||||
import { tool, jsonSchema, Tool } from "ai";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import _ from "lodash";
|
||||
@ -33,9 +33,13 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
const tools: Record<string, Tool> = {
|
||||
get_novel_events: tool({
|
||||
description: "获取章节事件",
|
||||
inputSchema: z.object({
|
||||
chapterIndexs: z.array(z.number()).describe("章节的编号"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ chapterIndexs: number[] }>(
|
||||
z
|
||||
.object({
|
||||
chapterIndexs: z.array(z.number()).describe("章节的编号"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ chapterIndexs }) => {
|
||||
console.log("[tools] get_novel_events", chapterIndexs);
|
||||
const thinking = msg.thinking("正在查询章节事件...");
|
||||
@ -45,7 +49,7 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
.select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState")
|
||||
.whereIn("chapterIndex", chapterIndexs);
|
||||
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.updateTitle("查询章节事件完成");
|
||||
thinking.complete();
|
||||
@ -54,9 +58,13 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
}),
|
||||
get_planData: tool({
|
||||
description: "获取工作区数据",
|
||||
inputSchema: z.object({
|
||||
key: keySchema.describe("数据key"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ key: keyof planData }>(
|
||||
z
|
||||
.object({
|
||||
key: keySchema.describe("数据key"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ key }) => {
|
||||
console.log("[tools] get_planData", key);
|
||||
const thinking = msg.thinking(`正在获取${planDataKeyLabels[key]}工作区数据...`);
|
||||
@ -69,9 +77,13 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
}),
|
||||
get_novel_text: tool({
|
||||
description: "获取小说章节原始文本内容",
|
||||
inputSchema: z.object({
|
||||
chapterIndex: z.string().describe("章节编号"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ chapterIndex: string }>(
|
||||
z
|
||||
.object({
|
||||
chapterIndex: z.string().describe("章节编号"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ chapterIndex }) => {
|
||||
console.log("[tools] get_novel_text", "[tools] get_novel_text", chapterIndex);
|
||||
const thinking = msg.thinking(`正在获取小说章节原文...`);
|
||||
@ -85,9 +97,13 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
}),
|
||||
get_script_content: tool({
|
||||
description: "获取剧本本内容",
|
||||
inputSchema: z.object({
|
||||
ids: z.array(z.string()).describe("脚本id"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ ids: string[] }>(
|
||||
z
|
||||
.object({
|
||||
ids: z.array(z.string()).describe("脚本id"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ ids }) => {
|
||||
console.log("[tools] get_script_content", "[tools] get_script_content", ids);
|
||||
const thinking = msg.thinking(`正在获取脚本内容...`);
|
||||
|
||||
@ -3,7 +3,7 @@ import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { tool } from "ai";
|
||||
import { tool, jsonSchema } from "ai";
|
||||
const router = express.Router();
|
||||
|
||||
// 获取资产
|
||||
@ -22,26 +22,37 @@ export default router.post(
|
||||
.whereIn("id", assetsIds)
|
||||
.andWhere("projectId", projectId)
|
||||
.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");
|
||||
console.log("%c Line:26 🍋 audioData", "background:#ea7e5c", audioData);
|
||||
const audioData = await u
|
||||
.db("o_assets")
|
||||
.where("type", "audio")
|
||||
.whereNull("assetsId")
|
||||
.andWhere("projectId", projectId)
|
||||
.select("id", "name", "describe");
|
||||
async function processGroup() {
|
||||
try {
|
||||
const resultTool = tool({
|
||||
description: "返回结果时必须调用这个工具",
|
||||
inputSchema: z.object({
|
||||
result: z.array(z.object({
|
||||
id: z.number(),
|
||||
audioIds: z.array(z.number()).describe("适配的音频id 无适配内容可以为 空数组")
|
||||
})).describe("适配的音色列表,id为资产id,audioIds为适配的音频id 无适配内容可以为 空数组")
|
||||
}),
|
||||
inputSchema: jsonSchema<{ result: { id: number; audioIds: number[] }[] }>(
|
||||
z
|
||||
.object({
|
||||
result: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
audioIds: z.array(z.number()).describe("适配的音频id 无适配内容可以为 空数组"),
|
||||
}),
|
||||
)
|
||||
.describe("适配的音色列表,id为资产id,audioIds为适配的音频id 无适配内容可以为 空数组"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ result }) => {
|
||||
console.log("[tools] extractAssets result", 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)
|
||||
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 "无需回复用户任何内容";
|
||||
},
|
||||
@ -55,21 +66,20 @@ export default router.post(
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
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
|
||||
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
|
||||
请根据提供的资产内容描述 与 对应已有的音色 进行匹配,返回适配的音色`,
|
||||
},
|
||||
],
|
||||
tools: { resultTool },
|
||||
});
|
||||
console.log("%c Line:44 🍞 text", "background:#f5ce50", text);
|
||||
|
||||
} catch (e) {
|
||||
console.error(`提取失败:`, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await processGroup()
|
||||
await processGroup();
|
||||
res.status(200).send(success());
|
||||
},
|
||||
);
|
||||
|
||||
@ -4,7 +4,7 @@ import { z } from "zod";
|
||||
import { error, success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { useSkill } from "@/utils/agent/skillsTools";
|
||||
import { tool } from "ai";
|
||||
import { tool, jsonSchema } from "ai";
|
||||
import { o_script } from "@/types/database";
|
||||
|
||||
const router = express.Router();
|
||||
@ -131,10 +131,7 @@ export default router.post(
|
||||
}
|
||||
|
||||
// 去重:相同 scriptId + assetId 只保留一条
|
||||
const uniqueRows = [
|
||||
...new Map(scriptAssetRows.map((r) => [`${r.scriptId}_${r.assetId}`, r])).values(),
|
||||
];
|
||||
|
||||
const uniqueRows = [...new Map(scriptAssetRows.map((r) => [`${r.scriptId}_${r.assetId}`, r])).values()];
|
||||
|
||||
// 先删除本批 scriptId 的旧关联,再插入新的
|
||||
await u.db("o_scriptAssets").whereIn("scriptId", batchScriptIds).delete();
|
||||
@ -188,16 +185,19 @@ export default router.post(
|
||||
try {
|
||||
const resultTool = tool({
|
||||
description: "返回结果时必须调用这个工具",
|
||||
inputSchema: z.object({
|
||||
newAssets: z
|
||||
.array(NewAssetSchema)
|
||||
.describe("新发现的资产列表(不在已有资产列表中的),需要完整的 prompt、name、desc、type 和使用该资产的 scriptIds"),
|
||||
existingAssetRefs: z
|
||||
.array(ExistingAssetRefSchema)
|
||||
.describe("已有资产的引用列表(在已有资产列表中已存在的),只需给出资产名称和使用该资产的 scriptIds"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ newAssets: NewAsset[]; existingAssetRefs: ExistingAssetRef[] }>(
|
||||
z
|
||||
.object({
|
||||
newAssets: z
|
||||
.array(NewAssetSchema)
|
||||
.describe("新发现的资产列表(不在已有资产列表中的),需要完整的 prompt、name、desc、type 和使用该资产的 scriptIds"),
|
||||
existingAssetRefs: z
|
||||
.array(ExistingAssetRefSchema)
|
||||
.describe("已有资产的引用列表(在已有资产列表中已存在的),只需给出资产名称和使用该资产的 scriptIds"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ newAssets, existingAssetRefs }) => {
|
||||
|
||||
if (newAssets?.length) collectedNew = newAssets;
|
||||
if (existingAssetRefs?.length) collectedExisting = existingAssetRefs;
|
||||
return "无需回复用户任何内容";
|
||||
|
||||
@ -6,23 +6,29 @@ const router = express.Router();
|
||||
export default router.post("/", async (req, res) => {
|
||||
const data = await u.db("o_vendorConfig").select("*");
|
||||
|
||||
const list = await Promise.all(
|
||||
data.map(async (item) => {
|
||||
const vendor = u.vendor.getVendor(item.id!);
|
||||
return {
|
||||
...item,
|
||||
inputValues: JSON.parse(item.inputValues ?? "{}"),
|
||||
models: await u.vendor.getModelList(item.id!),
|
||||
code: u.vendor.getCode(item.id!),
|
||||
description: vendor.description,
|
||||
inputs: vendor.inputs,
|
||||
author: vendor.author,
|
||||
name: vendor.name,
|
||||
version: vendor.version ?? "1.0",
|
||||
};
|
||||
}),
|
||||
);
|
||||
const list = (
|
||||
await Promise.all(
|
||||
data.map(async (item) => {
|
||||
const vendor = u.vendor.getVendor(item.id!);
|
||||
if (!vendor) {
|
||||
await u.db("o_vendorConfig").where("id", item.id).delete();
|
||||
return null
|
||||
};
|
||||
return {
|
||||
...item,
|
||||
inputValues: JSON.parse(item.inputValues ?? "{}"),
|
||||
models: await u.vendor.getModelList(item.id!),
|
||||
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));
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import { success, error } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { tool } from "ai";
|
||||
import { tool, jsonSchema } from "ai";
|
||||
const router = express.Router();
|
||||
|
||||
// 检查语言模型
|
||||
@ -57,9 +57,13 @@ export default router.post(
|
||||
|
||||
const getWeatherTool = tool({
|
||||
description: "Get the weather in a location",
|
||||
inputSchema: z.object({
|
||||
location: z.string().describe("The location to get the weather for"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ location: string }>(
|
||||
z
|
||||
.object({
|
||||
location: z.string().describe("The location to get the weather for"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ location }) => {
|
||||
return {
|
||||
location,
|
||||
|
||||
4
src/types/database.d.ts
vendored
4
src/types/database.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
// @db-hash 88c167ba73e2771e7b043419ca5089dd
|
||||
// @db-hash 5364c2db0bf42b520761b813ce040489
|
||||
//该文件由脚本自动生成,请勿手动修改
|
||||
|
||||
export interface memories {
|
||||
@ -23,6 +23,7 @@ export interface o_agentDeploy {
|
||||
'modelName'?: string | null;
|
||||
'name'?: string | null;
|
||||
'temperature'?: number | null;
|
||||
'topP'?: number | null;
|
||||
'type'?: string | null;
|
||||
'vendorId'?: string | null;
|
||||
}
|
||||
@ -210,7 +211,6 @@ export interface o_user {
|
||||
'password'?: string | null;
|
||||
}
|
||||
export interface o_vendorConfig {
|
||||
'code'?: string | null;
|
||||
'enable'?: number | null;
|
||||
'id'?: string;
|
||||
'inputValues'?: string | null;
|
||||
|
||||
@ -2,7 +2,7 @@ import u from "@/utils";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { getEmbedding, cosineSimilarity } from "./embedding";
|
||||
import type { memories as MemoryRow } from "@/types/database";
|
||||
import { tool } from "ai";
|
||||
import { tool, jsonSchema } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
// ── 可调配置默认值 ──
|
||||
@ -201,9 +201,13 @@ class Memory {
|
||||
return {
|
||||
deepRetrieve: tool({
|
||||
description: "深度检索记忆:当你需要回忆与某个关键词相关的详细历史信息时使用此工具",
|
||||
inputSchema: z.object({
|
||||
keyword: z.string().describe("要检索的关键词"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ keyword: string }>(
|
||||
z
|
||||
.object({
|
||||
keyword: z.string().describe("要检索的关键词"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ keyword }) => {
|
||||
const results = await this.deepRetrieve(keyword);
|
||||
if (results.length === 0) return { found: false, message: "未找到相关记忆" };
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
import { tool, jsonSchema } from "ai";
|
||||
import path from "path";
|
||||
import isPathInside from "is-path-inside";
|
||||
import getPath from "@/utils/getPath";
|
||||
@ -185,9 +185,13 @@ export function createSkillTools(skills: { name: string; description: string }[]
|
||||
return {
|
||||
activate_skill: tool({
|
||||
description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skillNames.join(", ")}`,
|
||||
inputSchema: z.object({
|
||||
name: z.enum(skillNames as [string, ...string[]]).describe("要激活的技能名称"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ name: string }>(
|
||||
z
|
||||
.object({
|
||||
name: z.enum(skillNames as [string, ...string[]]).describe("要激活的技能名称"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ name }) => {
|
||||
if (activated.has(name)) {
|
||||
console.log(`⚡[主技能] ℹ️ 技能 "${name}" 已激活,跳过重复注入`);
|
||||
@ -222,9 +226,13 @@ export function createSkillTools(skills: { name: string; description: string }[]
|
||||
}),
|
||||
read_skill_file: tool({
|
||||
description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。",
|
||||
inputSchema: z.object({
|
||||
filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"),
|
||||
}),
|
||||
inputSchema: jsonSchema<{ filePath: string }>(
|
||||
z
|
||||
.object({
|
||||
filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"),
|
||||
})
|
||||
.toJSONSchema(),
|
||||
),
|
||||
execute: async ({ filePath }) => {
|
||||
const normalizedInputPath = toUnixPath(filePath).trim();
|
||||
if (!normalizedInputPath) {
|
||||
|
||||
@ -25,6 +25,7 @@ export async function getModelList(id: string): Promise<Array<any>> {
|
||||
const code = getCode(id);
|
||||
const jsCode = transform(code, { transforms: ["typescript"] }).code;
|
||||
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 map = new Map<string, any>();
|
||||
for (const m of combined) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user