Merge branch 'develop'

This commit is contained in:
ACT丶流星雨 2026-04-14 18:48:23 +08:00
commit d9198e2148
69 changed files with 2466 additions and 1951 deletions

View File

@ -17,8 +17,17 @@ env:
jobs:
# ==================== Windows 构建 ====================
build-Windows:
strategy:
fail-fast: false
matrix:
include:
- arch: x64
name: x64
- arch: arm64
name: ARM64
runs-on: windows-latest
name: 构建 Windows
name: 构建 Windows (${{ matrix.name }})
steps:
- name: 检出代码
@ -33,14 +42,14 @@ jobs:
run: yarn install --frozen-lockfile
- name: 打包 Windows 安装程序
run: yarn dist:win
run: yarn dist:win --${{ matrix.arch }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 上传构建产物
uses: actions/upload-artifact@v4
with:
name: windows
name: windows-${{ matrix.arch }}
path: dist/*.exe
retention-days: 7

View File

@ -114,10 +114,10 @@ Toonflow 是面向短剧生产的 AI 工作台,围绕“策划 → 编剧 →
## 📺 视频教程
https://www.bilibili.com/video/BV1na6wB6Ea2
[![Toonflow 8 分钟快速上手 AI 视频](./docs/videoCover.png)](https://www.bilibili.com/video/BV1oXD7BqEqJ)
https://www.bilibili.com/video/BV1oXD7BqEqJ
[![Toonflow 12 分钟快速上手 AI 视频](./docs/videoCover.jpg)](https://www.bilibili.com/video/BV1oXD7BqEqJ)
**Toonflow 8 分钟快速上手 AI 视频**
**Toonflow 12 分钟快速上手 AI 视频**
👉 [点击观看](https://www.bilibili.com/video/BV1oXD7BqEqJ)
📱 手机微信扫码观看

View File

@ -327,7 +327,7 @@ const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<str
const lowerName = model.modelName.toLowerCase();
// 当前激活的单一 VideoMode取第一个非数组模式或数组模式
const activeMode = config.mode[0];
const activeMode = config.mode;
const imageRefs = (config.referenceList ?? []).filter((r) => r.type === "image").map((r) => r.base64);
const videoRefs = (config.referenceList ?? []).filter((r) => r.type === "video").map((r) => r.base64);
const audioRefs = (config.referenceList ?? []).filter((r) => r.type === "audio").map((r) => r.base64);

View File

@ -133,11 +133,10 @@ declare const exports: {
const vendor: VendorConfig = {
id: "volcengine",
version: "2.1",
version: "2.3",
author: "leeqi",
name: "火山引擎(豆包)",
description:
"火山引擎豆包大模型,支持文本、图片生成、视频生成等能力。\n\n需要在[火山引擎控制台](https://console.volcengine.com/ark)获取API密钥。",
description: "火山引擎豆包大模型,支持文本、图片生成、视频生成等能力。\n\n需要在[火山引擎控制台](https://console.volcengine.com/ark)获取API密钥。",
icon: "",
inputs: [
{ key: "apiKey", label: "API密钥", type: "password", required: true, placeholder: "火山引擎API Key" },
@ -301,10 +300,10 @@ const textRequest = (model: TextModel, think: boolean, thinkLevel: 0 | 1 | 2 | 3
3: "high",
};
return createOpenAI({
return createOpenAICompatible({
name: "volcengine",
baseURL: getBaseUrl(),
apiKey,
compatibility: "compatible",
fetch: async (url: string, options?: RequestInit) => {
const rawBody = JSON.parse((options?.body as string) ?? "{}");
const modifiedBody = {
@ -319,7 +318,7 @@ const textRequest = (model: TextModel, think: boolean, thinkLevel: 0 | 1 | 2 | 3
body: JSON.stringify(modifiedBody),
});
},
}).chat(model.modelName);
}).chatModel(model.modelName);
};
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
@ -455,10 +454,8 @@ const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<str
content.push({ type: "text", text: config.prompt });
}
const activeMode = config.mode && config.mode.length > 0 ? config.mode[0] : "text";
if (typeof activeMode === "string") {
switch (activeMode) {
if (typeof config.mode === "string") {
switch (config.mode) {
case "singleImage": {
const firstImage = config.referenceList?.find((r) => r.type === "image");
if (firstImage) {
@ -526,13 +523,13 @@ const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<str
default:
break;
}
} else if (Array.isArray(activeMode)) {
} else if (Array.isArray(config.mode)) {
// 多模态参考模式:按类型分别提取并添加
const imageRefs = config.referenceList?.filter((r) => r.type === "image") ?? [];
const videoRefs = config.referenceList?.filter((r) => r.type === "video") ?? [];
const audioRefs = config.referenceList?.filter((r) => r.type === "audio") ?? [];
for (const refDef of activeMode) {
for (const refDef of config.mode) {
if (typeof refDef === "string") {
if (refDef.startsWith("imageReference:")) {
const maxCount = parseInt(refDef.split(":")[1], 10);
@ -618,7 +615,7 @@ const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<str
}
},
10000,
600000,
600000 * 3,
);
if (result.error) {

View File

@ -1 +1 @@
1.1.3
1.1.5

File diff suppressed because one or more lines are too long

View File

@ -107,11 +107,11 @@ With Toonflow, you can complete the entire workflow from text to final video wit
## 📺 Video Tutorial
[https://www.bilibili.com/video/BV1na6wB6Ea2](https://www.bilibili.com/video/BV1na6wB6Ea2)
[![Toonflow 8-Minute AI Video Quick Start](./videoCover.png)](https://www.bilibili.com/video/BV1na6wB6Ea2)
[https://www.bilibili.com/video/BV1oXD7BqEqJ](https://www.bilibili.com/video/BV1oXD7BqEqJ)
[![Toonflow 12-Minute AI Video Quick Start](./videoCover.jpg)](https://www.bilibili.com/video/BV1oXD7BqEqJ)
**Toonflow: 8-Minute AI Video Quick Start**
👉 [Click to Watch](https://www.bilibili.com/video/BV1na6wB6Ea2/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
👉 [Click to Watch](https://www.bilibili.com/video/BV1oXD7BqEqJ/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
📱 Scan the QR code to watch on mobile

View File

@ -101,11 +101,11 @@ Toonflow は、AI技術を活用して小説を自動的に脚本へ変換し、
## 📺 動画チュートリアル
[https://www.bilibili.com/video/BV1na6wB6Ea2](https://www.bilibili.com/video/BV1na6wB6Ea2)
[![Toonflow 8分でわかるAI動画作成](./videoCover.png)](https://www.bilibili.com/video/BV1na6wB6Ea2)
[https://www.bilibili.com/video/BV1oXD7BqEqJ](https://www.bilibili.com/video/BV1oXD7BqEqJ)
[![Toonflow 12分でわかるAI動画作成](./videoCover.jpg)](https://www.bilibili.com/video/BV1oXD7BqEqJ)
**Toonflow 8分でわかるクイックスタート AI動画作成**
👉 [クリックして視聴](https://www.bilibili.com/video/BV1na6wB6Ea2/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
**Toonflow 12分でわかるクイックスタート AI動画作成**
👉 [クリックして視聴](https://www.bilibili.com/video/BV1oXD7BqEqJ/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
📱 QRコードをスキャンして視聴

View File

@ -105,11 +105,11 @@ Toonflow — это мощный ИИ-инструмент для создани
## 📺 Видеоуроки
[https://www.bilibili.com/video/BV1na6wB6Ea2](https://www.bilibili.com/video/BV1na6wB6Ea2)
[![Toonflow: Быстрый старт за 8 минут](./videoCover.png)](https://www.bilibili.com/video/BV1na6wB6Ea2)
[https://www.bilibili.com/video/BV1oXD7BqEqJ](https://www.bilibili.com/video/BV1oXD7BqEqJ)
[![Toonflow: Быстрый старт за 12 минут](./videoCover.jpg)](https://www.bilibili.com/video/BV1oXD7BqEqJ)
**Toonflow: Быстрый старт в AI-видео за 8 минут**
👉 [Нажмите для просмотра](https://www.bilibili.com/video/BV1na6wB6Ea2/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
**Toonflow: Быстрый старт в AI-видео за 12 минут**
👉 [Нажмите для просмотра](https://www.bilibili.com/video/BV1oXD7BqEqJ/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
📱 Отсканируйте QR-код для просмотра видео на телефоне

View File

@ -102,11 +102,11 @@ Toonflow เป็นเครื่องมือ AI สำหรับสร
## 📺 วิดีโอสอนการใช้งาน
[https://www.bilibili.com/video/BV1na6wB6Ea2](https://www.bilibili.com/video/BV1na6wB6Ea2)
[![เริ่มต้นสร้างวิดีโอ AI กับ Toonflow ใน 8 นาที](./videoCover.png)](https://www.bilibili.com/video/BV1na6wB6Ea2)
[https://www.bilibili.com/video/BV1oXD7BqEqJ](https://www.bilibili.com/video/BV1oXD7BqEqJ)
[![เริ่มต้นสร้างวิดีโอ AI กับ Toonflow ใน 12 นาที](./videoCover.jpg)](https://www.bilibili.com/video/BV1oXD7BqEqJ)
**เริ่มต้นสร้างวิดีโอ AI กับ Toonflow ใน 8 นาที**
👉 [คลิกเพื่อรับชม](https://www.bilibili.com/video/BV1na6wB6Ea2/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
**เริ่มต้นสร้างวิดีโอ AI กับ Toonflow ใน 12 นาที**
👉 [คลิกเพื่อรับชม](https://www.bilibili.com/video/BV1oXD7BqEqJ/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
📱 **สแกน QR Code เพื่อรับชมวิดีโอบนมือถือ**

View File

@ -97,11 +97,11 @@ Toonflow là công cụ AI chuyên tạo phim ngắn và truyện tranh, có kh
## 📺 Hướng dẫn bằng Video
[https://www.bilibili.com/video/BV1na6wB6Ea2](https://www.bilibili.com/video/BV1na6wB6Ea2)
[![Toonflow - 8 phút làm quen với Video AI](./videoCover.png)](https://www.bilibili.com/video/BV1na6wB6Ea2)
[https://www.bilibili.com/video/BV1oXD7BqEqJ](https://www.bilibili.com/video/BV1oXD7BqEqJ)
[![Toonflow - 12 phút làm quen với Video AI](./videoCover.jpg)](https://www.bilibili.com/video/BV1oXD7BqEqJ)
**Toonflow - 8 phút làm quen nhanh với Video AI**
👉 [Nhấn để xem](https://www.bilibili.com/video/BV1na6wB6Ea2/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
**Toonflow - 12 phút làm quen nhanh với Video AI**
👉 [Nhấn để xem](https://www.bilibili.com/video/BV1oXD7BqEqJ/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
📱 **Quét mã QR để xem video trên điện thoại**
<img src="./videoQR.png" alt="Quét mã QR để xem video" width="150"/>

View File

@ -104,11 +104,11 @@ Toonflow 是一款 AI 短劇與漫畫創作工具,能夠利用 AI 技術將小
## 📺 影片教學
[https://www.bilibili.com/video/BV1na6wB6Ea2](https://www.bilibili.com/video/BV1na6wB6Ea2)
[![Toonflow 8 分鐘快速上手 AI 影片](./videoCover.png)](https://www.bilibili.com/video/BV1na6wB6Ea2)
[https://www.bilibili.com/video/BV1oXD7BqEqJ](https://www.bilibili.com/video/BV1oXD7BqEqJ)
[![Toonflow 12 分鐘快速上手 AI 影片](./videoCover.jpg)](https://www.bilibili.com/video/BV1oXD7BqEqJ)
**Toonflow 8 分鐘快速上手 AI 影片**
👉 [點擊觀看](https://www.bilibili.com/video/BV1na6wB6Ea2/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
**Toonflow 12 分鐘快速上手 AI 影片**
👉 [點擊觀看](https://www.bilibili.com/video/BV1oXD7BqEqJ/?share_source=copy_web&vd_source=5b718c25439a901a34c7bc0c1d35b38e)
📱 使用手機掃描 QR Code 觀看

BIN
docs/videoCover.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@ -37,9 +37,6 @@ extraResources:
win:
target:
- target: nsis
arch:
- x64
- arm64
icon: ./scripts/logo.ico
nsis:

View File

@ -1,6 +1,6 @@
{
"name": "toonflow",
"version": "1.1.4",
"version": "1.1.5",
"description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。",
"author": "HBAI-Ltd <ltlctools@outlook.com>",
"license": "Apache-2.0",
@ -47,7 +47,7 @@
"ai": "^6.0.67",
"axios": "^1.13.2",
"axios-retry": "^4.5.0",
"better-sqlite3": "^12.8.0",
"better-sqlite3": "^12.9.0",
"compressing": "^2.1.0",
"cors": "^2.8.5",
"dotenv": "^17.2.3",

View File

@ -17,6 +17,11 @@ export interface AgentContext {
abortSignal?: AbortSignal;
resTool: ResTool;
msg: ReturnType<ResTool["newMessage"]>;
messages?: { role: "user" | "assistant" | "system"; content: string }[];
thinkConfig: {
think: boolean;
thinlLevel: 0 | 1 | 2 | 3;
};
}
function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): string {
@ -35,7 +40,7 @@ function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): string {
return `## Memory\n以下是你对用户的记忆可作为参考但不要主动提及\n${memoryContext}`;
}
export async function decisionAI(ctx: AgentContext) {
export async function runDecisionAI(ctx: AgentContext) {
const { isolationKey, text, abortSignal } = ctx;
const memory = new Memory("productionAgent", isolationKey);
await memory.add("user", text);
@ -45,17 +50,24 @@ export async function decisionAI(ctx: AgentContext) {
const projectInfo = await u.db("o_project").where("id", ctx.resTool.data.projectId).first();
if (!projectInfo) throw new Error(`项目不存在ID: ${ctx.resTool.data.projectId}`);
const [_, imageModelName] = projectInfo.imageModel!.split(":");
const [id, videoModelName] = projectInfo.videoModel!.split(":");
const [_, imageModelName] = projectInfo.imageModel!.split(/:(.+)/);
const [id, videoModelName] = projectInfo.videoModel!.split(/:(.+)/);
const models = await u.vendor.getModelList(id);
if (!models.length) throw new Error(`项目使用的模型不存在ID: ${projectInfo.videoModel}`);
const findData = models.find((i: any) => i.modelName == videoModelName);
const isRef = findData.mode.every((i: any) => Array.isArray(i));
let videoMode = "";
try {
videoMode = JSON.parse(projectInfo.mode ?? "");
} catch (e) {
videoMode = projectInfo.mode ?? "";
}
const isRef = Array.isArray(videoMode) ? true : false;
// const findData = models.find((i: any) => i.modelName == videoModelName);
// const isRef = findData.mode.every((i: any) => Array.isArray(i));
const modelInfo = `项目使用的模型如下:\n图像模型${imageModelName}\n视频模型${videoModelName}\n多参${isRef ? "是" : "否"}`;
const mem = buildMemPrompt(await memory.get(text));
const { textStream } = await u.Ai.Text("productionAgent").stream({
const { fullStream } = await u.Ai.Text("productionAgent:decisionAgent", ctx.thinkConfig.think, ctx.thinkConfig.thinlLevel).stream({
messages: [
{ role: "system", content: prompt },
{ role: "assistant", content: mem + "\n" + modelInfo },
@ -72,13 +84,20 @@ export async function decisionAI(ctx: AgentContext) {
},
});
return textStream;
let currentMsg = ctx.msg;
await consumeFullStream(fullStream, currentMsg, () => {
if (ctx.msg === currentMsg) return currentMsg;
currentMsg.complete();
currentMsg = ctx.msg;
return currentMsg;
});
}
async function createSubAgent(parentCtx: AgentContext) {
const { resTool, abortSignal } = parentCtx;
const memory = new Memory("productionAgent", parentCtx.isolationKey);
async function runAgent({
key,
prompt,
system,
name,
@ -86,6 +105,7 @@ async function createSubAgent(parentCtx: AgentContext) {
tools: extraTools,
messages,
}: {
key: `${string}:${string}`;
prompt: string;
system: string;
name: string;
@ -95,29 +115,15 @@ async function createSubAgent(parentCtx: AgentContext) {
}) {
parentCtx.msg.complete();
const subMsg = resTool.newMessage("assistant", name);
const text = subMsg.text();
let fullResponse = "";
const { textStream } = await u.Ai.Text("productionAgent").stream({
const { fullStream } = await u.Ai.Text(key, parentCtx.thinkConfig.think, parentCtx.thinkConfig.thinlLevel).stream({
system,
messages: messages ?? [{ role: "user", content: prompt }],
abortSignal,
tools: { ...extraTools, ...useTools({ resTool, msg: subMsg }) },
});
try {
for await (const chunk of textStream) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), 1));
text.append(chunk);
fullResponse += chunk;
}
text.complete();
subMsg.complete();
} catch (err: any) {
text.complete();
subMsg.stop();
throw err;
}
const fullResponse = await consumeFullStream(fullStream, subMsg);
if (fullResponse.trim()) {
await memory.add(memoryKey, removeAllXmlTags(fullResponse), {
@ -138,8 +144,8 @@ async function createSubAgent(parentCtx: AgentContext) {
if (!projectInfo) throw new Error(`项目不存在ID: ${resTool.data.projectId}`);
const artSkills = await createArtSkills(projectInfo?.artStyle!, projectInfo?.directorManual!);
const [_, imageModelName] = projectInfo.imageModel!.split(":");
const [id, videoModelName] = projectInfo.videoModel!.split(":");
const [_, imageModelName] = projectInfo.imageModel!.split(/:(.+)/);
const [id, videoModelName] = projectInfo.videoModel!.split(/:(.+)/);
const models = await u.vendor.getModelList(id);
if (!models.length) throw new Error(`项目使用的模型不存在ID: ${projectInfo.videoModel}`);
const findData = models.find((i: any) => i.modelName == videoModelName);
@ -184,6 +190,7 @@ async function createSubAgent(parentCtx: AgentContext) {
const skill = path.join(u.getPath("skills"), "production_execution_derive_assets.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
key: "productionAgent:deriveAssetsAgent",
prompt,
system: systemPrompt,
name: "执行导演",
@ -205,6 +212,7 @@ async function createSubAgent(parentCtx: AgentContext) {
const skill = path.join(u.getPath("skills"), "production_execution_generate_assets.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
key: "productionAgent:generateAssetsAgent",
prompt,
system: systemPrompt,
name: "执行导演",
@ -229,6 +237,7 @@ async function createSubAgent(parentCtx: AgentContext) {
const addPrompt = "\n你必须使用如下XML格式写入工作区\n```\n<scriptPlan>内容</scriptPlan>\n```";
return runAgent({
key: "productionAgent:directorPlanAgent",
prompt,
system: systemPrompt + addPrompt,
name: "执行导演",
@ -250,6 +259,7 @@ async function createSubAgent(parentCtx: AgentContext) {
const skill = path.join(u.getPath("skills"), "production_execution_storyboard_gen.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
key: "productionAgent:storyboardGenAgent",
prompt,
system: systemPrompt,
name: "执行导演",
@ -287,6 +297,7 @@ async function createSubAgent(parentCtx: AgentContext) {
"\n你必须使用如下XML格式写入工作区\n```\n<storyboardItem videoDesc='视频描述' prompt=提示词内容 track='分组' duration='视频推荐时间' associateAssetsIds='[该分镜所需的资产ID列表]'></storyboardItem>\n```";
return runAgent({
key: "productionAgent:storyboardPanelAgent",
prompt,
system: systemPrompt + addPrompt,
name: "执行导演",
@ -311,6 +322,7 @@ async function createSubAgent(parentCtx: AgentContext) {
const addPrompt = "\n你必须使用如下XML格式写入工作区\n```\n<storyboardTable>内容</storyboardTable>\n```";
return runAgent({
key: "productionAgent:storyboardTableAgent",
prompt,
system: systemPrompt + addPrompt,
name: "执行导演",
@ -331,6 +343,7 @@ async function createSubAgent(parentCtx: AgentContext) {
const skill = path.join(u.getPath("skills"), "production_agent_supervision.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
key: "productionAgent:supervisionAgent",
prompt,
system: systemPrompt,
name: "监制",
@ -370,7 +383,57 @@ ${buildSkillPrompt(mainSkills)}`,
};
return res;
}
async function consumeFullStream(
fullStream: AsyncIterable<any>,
initialMsg: ReturnType<ResTool["newMessage"]>,
syncMsg?: () => ReturnType<ResTool["newMessage"]>,
): Promise<string> {
let msg = initialMsg;
let text = msg.text();
let thinking: ReturnType<typeof msg.thinking> | null = null;
let thinkTime = 0;
let fullResponse = "";
try {
for await (const chunk of fullStream) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), 1));
if (syncMsg) {
const newMsg = syncMsg();
if (newMsg !== msg) {
msg = newMsg;
text = msg.text();
}
}
if (chunk.type === "reasoning-start") {
thinkTime = Date.now();
thinking = msg.thinking("思考中...");
} else if (chunk.type === "reasoning-delta") {
thinking?.append(chunk.text);
} else if (chunk.type === "reasoning-end") {
thinkTime = Date.now() - thinkTime;
thinking?.updateTitle(`思考完毕(${(thinkTime / 1000).toFixed(1)} 秒)`);
thinking?.complete();
thinking = null;
} else if (chunk.type === "text-delta") {
text.append(chunk.text);
fullResponse += chunk.text;
} else if (chunk.type === "error") {
throw chunk.error;
}
}
text.complete();
msg.complete();
} catch (err: any) {
thinking?.complete();
const errMsg = err?.message ?? String(err);
text.append(errMsg);
text.error();
msg.error();
throw err;
}
return fullResponse;
}
function removeAllXmlTags(text: string): string {
text = text.replace(/<([a-zA-Z][\w-]*)(\s+[^>]*)?>([\s\S]*?)<\/\1>/g, "");
text = text.replace(/<([a-zA-Z][\w-]*)(\s+[^>]*)?\/>/g, "");

View File

@ -16,6 +16,10 @@ export interface AgentContext {
abortSignal?: AbortSignal;
resTool: ResTool;
msg: ReturnType<ResTool["newMessage"]>;
thinkConfig: {
think: boolean;
thinlLevel: 0 | 1 | 2 | 3;
};
}
function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): string {
@ -34,7 +38,7 @@ function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): string {
return `## Memory\n以下是你对用户的记忆可作为参考但不要主动提及\n${memoryContext}`;
}
export async function decisionAI(ctx: AgentContext) {
export async function runDecisionAI(ctx: AgentContext) {
const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx;
const memory = new Memory("scriptAgent", isolationKey);
@ -59,7 +63,7 @@ export async function decisionAI(ctx: AgentContext) {
`章节数量:${novelData.length}`,
].join("\n");
const { textStream } = await u.Ai.Text("scriptAgent").stream({
const { fullStream } = await u.Ai.Text("scriptAgent:decisionAgent", ctx.thinkConfig.think, ctx.thinkConfig.thinlLevel).stream({
messages: [
{ role: "system", content: prompt },
{ role: "assistant", content: projectInfo + "\n" + mem },
@ -76,7 +80,13 @@ export async function decisionAI(ctx: AgentContext) {
},
});
return textStream;
let currentMsg = ctx.msg;
await consumeFullStream(fullStream, currentMsg, () => {
if (ctx.msg === currentMsg) return currentMsg;
currentMsg.complete();
currentMsg = ctx.msg;
return currentMsg;
});
}
function createSubAgent(parentCtx: AgentContext) {
@ -84,6 +94,7 @@ function createSubAgent(parentCtx: AgentContext) {
const memory = new Memory("scriptAgent", parentCtx.isolationKey);
async function runAgent({
key,
prompt,
system,
name,
@ -91,6 +102,7 @@ function createSubAgent(parentCtx: AgentContext) {
tools: extraTools,
messages,
}: {
key: `${string}:${string}`;
prompt: string;
system: string;
name: string;
@ -100,29 +112,15 @@ function createSubAgent(parentCtx: AgentContext) {
}) {
parentCtx.msg.complete();
const subMsg = resTool.newMessage("assistant", name);
const text = subMsg.text();
let fullResponse = "";
const { textStream } = await u.Ai.Text("scriptAgent").stream({
const { fullStream } = await u.Ai.Text(key, parentCtx.thinkConfig.think, parentCtx.thinkConfig.thinlLevel).stream({
system,
messages: messages ?? [{ role: "user", content: prompt }],
abortSignal,
tools: { ...extraTools, ...useTools({ resTool, msg: subMsg }) },
});
try {
for await (const chunk of textStream) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), 1));
text.append(chunk);
fullResponse += chunk;
}
text.complete();
subMsg.complete();
} catch (err: any) {
text.complete();
subMsg.stop();
throw err;
}
const fullResponse = await consumeFullStream(fullStream, subMsg);
if (fullResponse.trim()) {
await memory.add(memoryKey, removeAllXmlTags(fullResponse), {
@ -149,6 +147,7 @@ function createSubAgent(parentCtx: AgentContext) {
const formatPrompt = "\n你必须使用如下XML格式写入工作区\n<storySkeleton>故事骨架内容</storySkeleton>";
return runAgent({
key: "scriptAgent:storySkeletonAgent",
prompt,
system: systemPrompt + formatPrompt,
name: "编剧",
@ -168,6 +167,7 @@ function createSubAgent(parentCtx: AgentContext) {
const formatPrompt = "\n你必须使用如下XML格式写入工作区\n<adaptationStrategy>改编策略内容</adaptationStrategy>";
return runAgent({
key: "scriptAgent:adaptationStrategyAgent",
prompt,
system: systemPrompt + formatPrompt,
name: "编剧",
@ -194,6 +194,7 @@ function createSubAgent(parentCtx: AgentContext) {
const formatPrompt = `\n你必须使用如下XML格式写入工作区\nXML不得添加任何额外标签<scriptItem name="剧本名称">剧本内容</scriptItem><scriptItem name="剧本名称">剧本内容</scriptItem><scriptItem name="剧本名称">剧本内容</scriptItem>`;
return runAgent({
key: "scriptAgent:scriptAgent",
prompt,
system: systemPrompt + formatPrompt,
messages: [
@ -214,6 +215,7 @@ function createSubAgent(parentCtx: AgentContext) {
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
key: "scriptAgent:supervisionAgent",
prompt,
system: systemPrompt,
name: "编辑",
@ -230,6 +232,58 @@ function createSubAgent(parentCtx: AgentContext) {
};
}
async function consumeFullStream(
fullStream: AsyncIterable<any>,
initialMsg: ReturnType<ResTool["newMessage"]>,
syncMsg?: () => ReturnType<ResTool["newMessage"]>,
): Promise<string> {
let msg = initialMsg;
let text = msg.text();
let thinking: ReturnType<typeof msg.thinking> | null = null;
let thinkTime = 0;
let fullResponse = "";
try {
for await (const chunk of fullStream) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), 1));
if (syncMsg) {
const newMsg = syncMsg();
if (newMsg !== msg) {
msg = newMsg;
text = msg.text();
}
}
if (chunk.type === "reasoning-start") {
thinkTime = Date.now();
thinking = msg.thinking("思考中...");
} else if (chunk.type === "reasoning-delta") {
thinking?.append(chunk.text);
} else if (chunk.type === "reasoning-end") {
thinkTime = Date.now() - thinkTime;
thinking?.updateTitle(`思考完毕(${(thinkTime / 1000).toFixed(1)} 秒)`);
thinking?.complete();
thinking = null;
} else if (chunk.type === "text-delta") {
text.append(chunk.text);
fullResponse += chunk.text;
} else if (chunk.type === "error") {
throw chunk.error;
}
}
text.complete();
msg.complete();
} catch (err: any) {
thinking?.complete();
const errMsg = err?.message ?? String(err);
text.append(errMsg);
text.error();
msg.error();
throw err;
}
return fullResponse;
}
function removeAllXmlTags(text: string): string {
text = text.replace(/<([a-zA-Z][\w-]*)(\s+[^>]*)?>([\s\S]*?)<\/\1>/g, "");
text = text.replace(/<([a-zA-Z][\w-]*)(\s+[^>]*)?\/>/g, "");

View File

@ -8,15 +8,43 @@ import expressWs from "express-ws";
import logger from "morgan";
import cors from "cors";
import buildRoute from "@/core";
import path from "path";
import fs from "fs";
import u from "@/utils";
import jwt from "jsonwebtoken";
import socketInit from "@/socket/index";
import { isEletron } from "@/utils/getPath";
const app = express();
const server = http.createServer(app);
async function checkPermissions() {
if (!isEletron()) return true;
const userDataPath = u.getPath();
try {
fs.mkdirSync(userDataPath, { recursive: true });
const testFile = path.join(userDataPath, ".access_test");
fs.writeFileSync(testFile, "test");
fs.unlinkSync(testFile);
} catch (e) {
const { dialog, app } = require("electron");
const { response } = await dialog.showMessageBox({
type: "warning",
title: "权限不足",
message: "应用无法访问数据目录",
detail: `无法读写以下目录:\n${userDataPath}\n\n请联系管理员授予权限或以管理员身份运行本程序。`,
buttons: ["确认退出"],
defaultId: 0,
});
if (response === 0) {
app.quit();
}
}
}
export default async function startServe(randomPort: Boolean = false) {
await checkPermissions();
await u.writeVersion();
const io = new Server(server, { cors: { origin: "*" } });
socketInit(io);

File diff suppressed because one or more lines are too long

View File

@ -71,6 +71,8 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.text("vendorId");
table.string("desc");
table.string("name");
table.integer("temperature");
table.integer("maxOutputTokens");
table.boolean("disabled").defaultTo(false);
table.primary(["id"]);
table.unique(["id"]);
@ -113,6 +115,150 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
desc: "根据剧本内容生成角色配音,支持多种声音风格和情绪",
disabled: true,
},
{
model: "",
modelName: "",
vendorId: null,
key: "scriptAgent:decisionAgent",
name: "剧本Agent:决策层",
desc: "决策层",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "scriptAgent:supervisionAgent",
name: "剧本Agent:监督层",
desc: "监督层",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "scriptAgent:storySkeletonAgent",
name: "剧本Agent:故事骨架",
desc: "故事骨架生成",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "scriptAgent:adaptationStrategyAgent",
name: "剧本Agent:改编策略",
desc: "改编策略生成",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "scriptAgent:scriptAgent",
name: "剧本Agent:剧本生成",
desc: "剧本生成",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "productionAgent:decisionAgent",
name: "生产Agent:决策层",
desc: "决策层",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "productionAgent:supervisionAgent",
name: "生产Agent:监督层",
desc: "监督层",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "productionAgent:deriveAssetsAgent",
name: "生产Agent:衍生资产",
desc: "衍生资产",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "productionAgent:generateAssetsAgent",
name: "生产Agent:生成资产",
desc: "生成资产",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "productionAgent:directorPlanAgent",
name: "生产Agent:导演规划",
desc: "导演规划",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "productionAgent:storyboardGenAgent",
name: "生产Agent:分镜生成",
desc: "分镜生成",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "productionAgent:storyboardPanelAgent",
name: "生产Agent:分镜面板",
desc: "分镜面板生成",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
{
model: "",
modelName: "",
vendorId: null,
key: "productionAgent:storyboardTableAgent",
name: "生产Agent:分镜表格",
desc: "分镜表格生成",
temperature: 1,
maxOutputTokens: 0,
disabled: false,
},
]);
},
},
@ -220,6 +366,19 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
]);
},
},
//模型绑定提示词表
{
name: "o_modelPrompt",
builder: (table) => {
table.integer("id").notNullable();
table.string("vendorId");
table.string("model");
table.text("prompt");
table.primary(["id"]);
table.unique(["id"]);
},
initData: async (knex) => { },
},
//小说原文表
{
name: "o_novel",
@ -261,29 +420,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.unique(["id"]);
},
},
//大纲表
{
name: "o_outline",
builder: (table) => {
table.integer("id").notNullable();
table.integer("episode");
table.text("data");
table.integer("projectId");
table.primary(["id"]);
table.unique(["id"]);
},
},
//大纲-原文表
{
name: "o_outlineNovel",
builder: (table) => {
table.integer("id").notNullable();
table.integer("outlineId").unsigned().references("id").inTable("o_outline");
table.integer("novelId").unsigned().references("id").inTable("o_novel");
table.primary(["id"]);
table.unique(["id"]);
},
},
//剧本
{
name: "o_script",
@ -424,38 +560,38 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
await knex("o_vendorConfig").insert([
{
id: "toonflow",
inputValues: "",
models: [],
inputValues: "{}",
models: "[]",
enable: 0,
},
{
id: "volcengine",
inputValues: "",
models: [],
inputValues: "{}",
models: "[]",
enable: 0,
},
{
id: "minimax",
inputValues: "",
models: [],
inputValues: "{}",
models: "[]",
enable: 0,
},
{
id: "openai",
inputValues: "",
models: [],
inputValues: "{}",
models: "[]",
enable: 0,
},
{
id: "klingai",
inputValues: "",
models: [],
inputValues: "{}",
models: "[]",
enable: 0,
},
{
id: "vidu",
inputValues: "",
models: [],
inputValues: "{}",
models: "[]",
enable: 0,
},
]);

10
src/lib/vendor.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
// @routes-hash 62534cff632db5d31442f1bca1932925
// @routes-hash 341ae5b09b9564f4ea847d0edead3c77
import { Express } from "express";
import route1 from "./routes/agents/clearMemory";
@ -34,116 +34,126 @@ import route30 from "./routes/general/generalStatistics";
import route31 from "./routes/general/getSingleProject";
import route32 from "./routes/general/updateProject";
import route33 from "./routes/login/login";
import route34 from "./routes/migrate/migrateData";
import route35 from "./routes/modelSelect/getModelDetail";
import route36 from "./routes/modelSelect/getModelList";
import route37 from "./routes/novel/addNovel";
import route38 from "./routes/novel/batchDeleteNovel";
import route39 from "./routes/novel/delNovel";
import route40 from "./routes/novel/event/batchDeleteEvent";
import route41 from "./routes/novel/event/deletEvent";
import route42 from "./routes/novel/event/generateEvents";
import route43 from "./routes/novel/event/getEvent";
import route44 from "./routes/novel/getNovel";
import route45 from "./routes/novel/getNovelData";
import route46 from "./routes/novel/getNovelEventState";
import route47 from "./routes/novel/getNovelIndex";
import route48 from "./routes/novel/updateNovel";
import route49 from "./routes/other/deleteAllData";
import route50 from "./routes/other/getVersion";
import route51 from "./routes/production/assets/batchGenerateAssetsImage";
import route52 from "./routes/production/assets/deleteAssetsDireve";
import route53 from "./routes/production/assets/getAssetsData";
import route54 from "./routes/production/assets/pollingImage";
import route55 from "./routes/production/assets/updateAssetsUrl";
import route56 from "./routes/production/editImage/generateFlowImage";
import route57 from "./routes/production/editImage/getImageDefaultModle";
import route58 from "./routes/production/editImage/getImageFlow";
import route59 from "./routes/production/editImage/saveImageFlow";
import route60 from "./routes/production/editImage/updateImageFlow";
import route61 from "./routes/production/editImage/uploadImage";
import route62 from "./routes/production/getFlowData";
import route63 from "./routes/production/getStoryboardData";
import route64 from "./routes/production/saveFlowData";
import route65 from "./routes/production/storyboard/addStoryboard";
import route66 from "./routes/production/storyboard/batchAddStoryboardInfo";
import route67 from "./routes/production/storyboard/batchGenerateImage";
import route68 from "./routes/production/storyboard/downPreviewImage";
import route69 from "./routes/production/storyboard/editStoryboardInfo";
import route70 from "./routes/production/storyboard/getStoryboardData";
import route71 from "./routes/production/storyboard/pollingImage";
import route72 from "./routes/production/storyboard/previewImage";
import route73 from "./routes/production/storyboard/removeFrame";
import route74 from "./routes/production/storyboard/updateStoryboardUrl";
import route75 from "./routes/production/workbench/addTrack";
import route34 from "./routes/modelSelect/getModelDetail";
import route35 from "./routes/modelSelect/getModelList";
import route36 from "./routes/novel/addNovel";
import route37 from "./routes/novel/batchDeleteNovel";
import route38 from "./routes/novel/delNovel";
import route39 from "./routes/novel/event/batchDeleteEvent";
import route40 from "./routes/novel/event/deletEvent";
import route41 from "./routes/novel/event/generateEvents";
import route42 from "./routes/novel/event/getEvent";
import route43 from "./routes/novel/getNovel";
import route44 from "./routes/novel/getNovelData";
import route45 from "./routes/novel/getNovelEventState";
import route46 from "./routes/novel/getNovelIndex";
import route47 from "./routes/novel/updateNovel";
import route48 from "./routes/other/deleteAllData";
import route49 from "./routes/other/getVersion";
import route50 from "./routes/production/assets/batchGenerateAssetsImage";
import route51 from "./routes/production/assets/deleteAssetsDireve";
import route52 from "./routes/production/assets/getAssetsData";
import route53 from "./routes/production/assets/pollingImage";
import route54 from "./routes/production/assets/updateAssetsUrl";
import route55 from "./routes/production/editImage/generateFlowImage";
import route56 from "./routes/production/editImage/getImageDefaultModle";
import route57 from "./routes/production/editImage/getImageFlow";
import route58 from "./routes/production/editImage/saveImageFlow";
import route59 from "./routes/production/editImage/updateImageFlow";
import route60 from "./routes/production/editImage/uploadImage";
import route61 from "./routes/production/getFlowData";
import route62 from "./routes/production/getStoryboardData";
import route63 from "./routes/production/saveFlowData";
import route64 from "./routes/production/storyboard/addStoryboard";
import route65 from "./routes/production/storyboard/batchAddStoryboardInfo";
import route66 from "./routes/production/storyboard/batchGenerateImage";
import route67 from "./routes/production/storyboard/downPreviewImage";
import route68 from "./routes/production/storyboard/editStoryboardInfo";
import route69 from "./routes/production/storyboard/getStoryboardData";
import route70 from "./routes/production/storyboard/pollingImage";
import route71 from "./routes/production/storyboard/previewImage";
import route72 from "./routes/production/storyboard/removeFrame";
import route73 from "./routes/production/storyboard/updateStoryboardUrl";
import route74 from "./routes/production/workbench/addTrack";
import route75 from "./routes/production/workbench/checkVideoStateList";
import route76 from "./routes/production/workbench/deleteTrack";
import route77 from "./routes/production/workbench/delVideo";
import route78 from "./routes/production/workbench/generateVideo";
import route79 from "./routes/production/workbench/generateVideoPrompt";
import route80 from "./routes/production/workbench/getGenerateData";
import route81 from "./routes/production/workbench/getVideoList";
import route82 from "./routes/production/workbench/selectVideo";
import route83 from "./routes/production/workbench/updateVideoPrompt";
import route84 from "./routes/project/addDirectorManual";
import route85 from "./routes/project/addProject";
import route86 from "./routes/project/addVisualManual";
import route87 from "./routes/project/deleteDirectorManual";
import route88 from "./routes/project/deleteVisualManual";
import route89 from "./routes/project/delProject";
import route90 from "./routes/project/editDirectorlManual";
import route91 from "./routes/project/editProject";
import route92 from "./routes/project/editVisualManual";
import route93 from "./routes/project/getProject";
import route94 from "./routes/project/getVisualManual";
import route95 from "./routes/project/queryDirectorManual";
import route96 from "./routes/project/visualManual";
import route97 from "./routes/script/addScript";
import route98 from "./routes/script/batchAddScript";
import route99 from "./routes/script/delScript";
import route100 from "./routes/script/exportScript";
import route101 from "./routes/script/extractAssets";
import route102 from "./routes/script/getScrptApi";
import route103 from "./routes/script/pollScriptAssets";
import route104 from "./routes/script/updateScript";
import route105 from "./routes/scriptAgent/getPlanData";
import route106 from "./routes/scriptAgent/setPlanData";
import route107 from "./routes/scriptAgent/updateData";
import route108 from "./routes/setting/about/checkUpdate";
import route109 from "./routes/setting/about/downloadApp";
import route110 from "./routes/setting/agentDeploy/agentSetKey";
import route111 from "./routes/setting/agentDeploy/deployAgentModel";
import route112 from "./routes/setting/agentDeploy/getAgentDeploy";
import route113 from "./routes/setting/dbConfig/clearData";
import route114 from "./routes/setting/dev/getSwitchAiDevTool";
import route115 from "./routes/setting/dev/updateSwitchAiDevTool";
import route116 from "./routes/setting/fileManagement/openFolder";
import route117 from "./routes/setting/getTextModel";
import route118 from "./routes/setting/loginConfig/getUser";
import route119 from "./routes/setting/loginConfig/updateUserPwd";
import route120 from "./routes/setting/memoryConfig/delAllMemory";
import route121 from "./routes/setting/memoryConfig/getMemory";
import route122 from "./routes/setting/memoryConfig/sureMemory";
import route123 from "./routes/setting/promptManage/getPrompt";
import route124 from "./routes/setting/promptManage/updatePrompt";
import route125 from "./routes/setting/skillManagement/getSkillContent";
import route126 from "./routes/setting/skillManagement/getSkillList";
import route127 from "./routes/setting/skillManagement/saveSkillContent";
import route128 from "./routes/setting/vendorConfig/addVendor";
import route129 from "./routes/setting/vendorConfig/addVendorModel";
import route130 from "./routes/setting/vendorConfig/deleteVendor";
import route131 from "./routes/setting/vendorConfig/delVendorModel";
import route132 from "./routes/setting/vendorConfig/enableVendor";
import route133 from "./routes/setting/vendorConfig/getCodeByLink";
import route134 from "./routes/setting/vendorConfig/getVendorList";
import route135 from "./routes/setting/vendorConfig/modelTest";
import route136 from "./routes/setting/vendorConfig/updateCode";
import route137 from "./routes/setting/vendorConfig/updateVendorInputs";
import route138 from "./routes/setting/vendorConfig/upVendorModel";
import route139 from "./routes/task/getProject";
import route140 from "./routes/task/getTaskApi";
import route141 from "./routes/task/getTaskCategories";
import route142 from "./routes/task/taskDetails";
import route143 from "./routes/test/test";
import route80 from "./routes/production/workbench/getFileUrl";
import route81 from "./routes/production/workbench/getGenerateData";
import route82 from "./routes/production/workbench/getVideoList";
import route83 from "./routes/production/workbench/selectVideo";
import route84 from "./routes/production/workbench/updateVideoDuration";
import route85 from "./routes/production/workbench/updateVideoPrompt";
import route86 from "./routes/project/addDirectorManual";
import route87 from "./routes/project/addProject";
import route88 from "./routes/project/addVisualManual";
import route89 from "./routes/project/deleteDirectorManual";
import route90 from "./routes/project/deleteVisualManual";
import route91 from "./routes/project/delProject";
import route92 from "./routes/project/editDirectorlManual";
import route93 from "./routes/project/editProject";
import route94 from "./routes/project/editVisualManual";
import route95 from "./routes/project/getModelDetails";
import route96 from "./routes/project/getProject";
import route97 from "./routes/project/getVisualManual";
import route98 from "./routes/project/queryDirectorManual";
import route99 from "./routes/project/visualManual";
import route100 from "./routes/script/addScript";
import route101 from "./routes/script/batchAddScript";
import route102 from "./routes/script/delScript";
import route103 from "./routes/script/exportScript";
import route104 from "./routes/script/extractAssets";
import route105 from "./routes/script/getAiRegex";
import route106 from "./routes/script/getScrptApi";
import route107 from "./routes/script/pollScriptAssets";
import route108 from "./routes/script/updateScript";
import route109 from "./routes/scriptAgent/getPlanData";
import route110 from "./routes/scriptAgent/setPlanData";
import route111 from "./routes/scriptAgent/updateData";
import route112 from "./routes/setting/about/checkUpdate";
import route113 from "./routes/setting/about/downloadApp";
import route114 from "./routes/setting/agentDeploy/agentSetKey";
import route115 from "./routes/setting/agentDeploy/deployAgentModel";
import route116 from "./routes/setting/agentDeploy/getAgentDeploy";
import route117 from "./routes/setting/dbConfig/clearData";
import route118 from "./routes/setting/dbConfig/clearTable";
import route119 from "./routes/setting/dbConfig/dbInfo";
import route120 from "./routes/setting/dbConfig/exportData";
import route121 from "./routes/setting/dbConfig/importData";
import route122 from "./routes/setting/dev/getSwitchAiDevTool";
import route123 from "./routes/setting/dev/updateSwitchAiDevTool";
import route124 from "./routes/setting/fileManagement/openFolder";
import route125 from "./routes/setting/getTextModel";
import route126 from "./routes/setting/loginConfig/getUser";
import route127 from "./routes/setting/loginConfig/updateUserPwd";
import route128 from "./routes/setting/memoryConfig/delAllMemory";
import route129 from "./routes/setting/memoryConfig/getMemory";
import route130 from "./routes/setting/memoryConfig/sureMemory";
import route131 from "./routes/setting/modelMap/bindingPrompt";
import route132 from "./routes/setting/modelMap/getImageAndVideoModel";
import route133 from "./routes/setting/promptManage/getPrompt";
import route134 from "./routes/setting/promptManage/updatePrompt";
import route135 from "./routes/setting/skillManagement/getSkillContent";
import route136 from "./routes/setting/skillManagement/getSkillList";
import route137 from "./routes/setting/skillManagement/saveSkillContent";
import route138 from "./routes/setting/vendorConfig/addVendor";
import route139 from "./routes/setting/vendorConfig/addVendorModel";
import route140 from "./routes/setting/vendorConfig/deleteVendor";
import route141 from "./routes/setting/vendorConfig/delVendorModel";
import route142 from "./routes/setting/vendorConfig/enableVendor";
import route143 from "./routes/setting/vendorConfig/getCodeByLink";
import route144 from "./routes/setting/vendorConfig/getVendorList";
import route145 from "./routes/setting/vendorConfig/modelTest";
import route146 from "./routes/setting/vendorConfig/updateCode";
import route147 from "./routes/setting/vendorConfig/updateVendorInputs";
import route148 from "./routes/setting/vendorConfig/upVendorModel";
import route149 from "./routes/task/getProject";
import route150 from "./routes/task/getTaskApi";
import route151 from "./routes/task/getTaskCategories";
import route152 from "./routes/task/taskDetails";
import route153 from "./routes/test/test";
export default async (app: Express) => {
app.use("/api/agents/clearMemory", route1);
@ -179,114 +189,124 @@ export default async (app: Express) => {
app.use("/api/general/getSingleProject", route31);
app.use("/api/general/updateProject", route32);
app.use("/api/login/login", route33);
app.use("/api/migrate/migrateData", route34);
app.use("/api/modelSelect/getModelDetail", route35);
app.use("/api/modelSelect/getModelList", route36);
app.use("/api/novel/addNovel", route37);
app.use("/api/novel/batchDeleteNovel", route38);
app.use("/api/novel/delNovel", route39);
app.use("/api/novel/event/batchDeleteEvent", route40);
app.use("/api/novel/event/deletEvent", route41);
app.use("/api/novel/event/generateEvents", route42);
app.use("/api/novel/event/getEvent", route43);
app.use("/api/novel/getNovel", route44);
app.use("/api/novel/getNovelData", route45);
app.use("/api/novel/getNovelEventState", route46);
app.use("/api/novel/getNovelIndex", route47);
app.use("/api/novel/updateNovel", route48);
app.use("/api/other/deleteAllData", route49);
app.use("/api/other/getVersion", route50);
app.use("/api/production/assets/batchGenerateAssetsImage", route51);
app.use("/api/production/assets/deleteAssetsDireve", route52);
app.use("/api/production/assets/getAssetsData", route53);
app.use("/api/production/assets/pollingImage", route54);
app.use("/api/production/assets/updateAssetsUrl", route55);
app.use("/api/production/editImage/generateFlowImage", route56);
app.use("/api/production/editImage/getImageDefaultModle", route57);
app.use("/api/production/editImage/getImageFlow", route58);
app.use("/api/production/editImage/saveImageFlow", route59);
app.use("/api/production/editImage/updateImageFlow", route60);
app.use("/api/production/editImage/uploadImage", route61);
app.use("/api/production/getFlowData", route62);
app.use("/api/production/getStoryboardData", route63);
app.use("/api/production/saveFlowData", route64);
app.use("/api/production/storyboard/addStoryboard", route65);
app.use("/api/production/storyboard/batchAddStoryboardInfo", route66);
app.use("/api/production/storyboard/batchGenerateImage", route67);
app.use("/api/production/storyboard/downPreviewImage", route68);
app.use("/api/production/storyboard/editStoryboardInfo", route69);
app.use("/api/production/storyboard/getStoryboardData", route70);
app.use("/api/production/storyboard/pollingImage", route71);
app.use("/api/production/storyboard/previewImage", route72);
app.use("/api/production/storyboard/removeFrame", route73);
app.use("/api/production/storyboard/updateStoryboardUrl", route74);
app.use("/api/production/workbench/addTrack", route75);
app.use("/api/modelSelect/getModelDetail", route34);
app.use("/api/modelSelect/getModelList", route35);
app.use("/api/novel/addNovel", route36);
app.use("/api/novel/batchDeleteNovel", route37);
app.use("/api/novel/delNovel", route38);
app.use("/api/novel/event/batchDeleteEvent", route39);
app.use("/api/novel/event/deletEvent", route40);
app.use("/api/novel/event/generateEvents", route41);
app.use("/api/novel/event/getEvent", route42);
app.use("/api/novel/getNovel", route43);
app.use("/api/novel/getNovelData", route44);
app.use("/api/novel/getNovelEventState", route45);
app.use("/api/novel/getNovelIndex", route46);
app.use("/api/novel/updateNovel", route47);
app.use("/api/other/deleteAllData", route48);
app.use("/api/other/getVersion", route49);
app.use("/api/production/assets/batchGenerateAssetsImage", route50);
app.use("/api/production/assets/deleteAssetsDireve", route51);
app.use("/api/production/assets/getAssetsData", route52);
app.use("/api/production/assets/pollingImage", route53);
app.use("/api/production/assets/updateAssetsUrl", route54);
app.use("/api/production/editImage/generateFlowImage", route55);
app.use("/api/production/editImage/getImageDefaultModle", route56);
app.use("/api/production/editImage/getImageFlow", route57);
app.use("/api/production/editImage/saveImageFlow", route58);
app.use("/api/production/editImage/updateImageFlow", route59);
app.use("/api/production/editImage/uploadImage", route60);
app.use("/api/production/getFlowData", route61);
app.use("/api/production/getStoryboardData", route62);
app.use("/api/production/saveFlowData", route63);
app.use("/api/production/storyboard/addStoryboard", route64);
app.use("/api/production/storyboard/batchAddStoryboardInfo", route65);
app.use("/api/production/storyboard/batchGenerateImage", route66);
app.use("/api/production/storyboard/downPreviewImage", route67);
app.use("/api/production/storyboard/editStoryboardInfo", route68);
app.use("/api/production/storyboard/getStoryboardData", route69);
app.use("/api/production/storyboard/pollingImage", route70);
app.use("/api/production/storyboard/previewImage", route71);
app.use("/api/production/storyboard/removeFrame", route72);
app.use("/api/production/storyboard/updateStoryboardUrl", route73);
app.use("/api/production/workbench/addTrack", route74);
app.use("/api/production/workbench/checkVideoStateList", route75);
app.use("/api/production/workbench/deleteTrack", route76);
app.use("/api/production/workbench/delVideo", route77);
app.use("/api/production/workbench/generateVideo", route78);
app.use("/api/production/workbench/generateVideoPrompt", route79);
app.use("/api/production/workbench/getGenerateData", route80);
app.use("/api/production/workbench/getVideoList", route81);
app.use("/api/production/workbench/selectVideo", route82);
app.use("/api/production/workbench/updateVideoPrompt", route83);
app.use("/api/project/addDirectorManual", route84);
app.use("/api/project/addProject", route85);
app.use("/api/project/addVisualManual", route86);
app.use("/api/project/deleteDirectorManual", route87);
app.use("/api/project/deleteVisualManual", route88);
app.use("/api/project/delProject", route89);
app.use("/api/project/editDirectorlManual", route90);
app.use("/api/project/editProject", route91);
app.use("/api/project/editVisualManual", route92);
app.use("/api/project/getProject", route93);
app.use("/api/project/getVisualManual", route94);
app.use("/api/project/queryDirectorManual", route95);
app.use("/api/project/visualManual", route96);
app.use("/api/script/addScript", route97);
app.use("/api/script/batchAddScript", route98);
app.use("/api/script/delScript", route99);
app.use("/api/script/exportScript", route100);
app.use("/api/script/extractAssets", route101);
app.use("/api/script/getScrptApi", route102);
app.use("/api/script/pollScriptAssets", route103);
app.use("/api/script/updateScript", route104);
app.use("/api/scriptAgent/getPlanData", route105);
app.use("/api/scriptAgent/setPlanData", route106);
app.use("/api/scriptAgent/updateData", route107);
app.use("/api/setting/about/checkUpdate", route108);
app.use("/api/setting/about/downloadApp", route109);
app.use("/api/setting/agentDeploy/agentSetKey", route110);
app.use("/api/setting/agentDeploy/deployAgentModel", route111);
app.use("/api/setting/agentDeploy/getAgentDeploy", route112);
app.use("/api/setting/dbConfig/clearData", route113);
app.use("/api/setting/dev/getSwitchAiDevTool", route114);
app.use("/api/setting/dev/updateSwitchAiDevTool", route115);
app.use("/api/setting/fileManagement/openFolder", route116);
app.use("/api/setting/getTextModel", route117);
app.use("/api/setting/loginConfig/getUser", route118);
app.use("/api/setting/loginConfig/updateUserPwd", route119);
app.use("/api/setting/memoryConfig/delAllMemory", route120);
app.use("/api/setting/memoryConfig/getMemory", route121);
app.use("/api/setting/memoryConfig/sureMemory", route122);
app.use("/api/setting/promptManage/getPrompt", route123);
app.use("/api/setting/promptManage/updatePrompt", route124);
app.use("/api/setting/skillManagement/getSkillContent", route125);
app.use("/api/setting/skillManagement/getSkillList", route126);
app.use("/api/setting/skillManagement/saveSkillContent", route127);
app.use("/api/setting/vendorConfig/addVendor", route128);
app.use("/api/setting/vendorConfig/addVendorModel", route129);
app.use("/api/setting/vendorConfig/deleteVendor", route130);
app.use("/api/setting/vendorConfig/delVendorModel", route131);
app.use("/api/setting/vendorConfig/enableVendor", route132);
app.use("/api/setting/vendorConfig/getCodeByLink", route133);
app.use("/api/setting/vendorConfig/getVendorList", route134);
app.use("/api/setting/vendorConfig/modelTest", route135);
app.use("/api/setting/vendorConfig/updateCode", route136);
app.use("/api/setting/vendorConfig/updateVendorInputs", route137);
app.use("/api/setting/vendorConfig/upVendorModel", route138);
app.use("/api/task/getProject", route139);
app.use("/api/task/getTaskApi", route140);
app.use("/api/task/getTaskCategories", route141);
app.use("/api/task/taskDetails", route142);
app.use("/api/test/test", route143);
app.use("/api/production/workbench/getFileUrl", route80);
app.use("/api/production/workbench/getGenerateData", route81);
app.use("/api/production/workbench/getVideoList", route82);
app.use("/api/production/workbench/selectVideo", route83);
app.use("/api/production/workbench/updateVideoDuration", route84);
app.use("/api/production/workbench/updateVideoPrompt", route85);
app.use("/api/project/addDirectorManual", route86);
app.use("/api/project/addProject", route87);
app.use("/api/project/addVisualManual", route88);
app.use("/api/project/deleteDirectorManual", route89);
app.use("/api/project/deleteVisualManual", route90);
app.use("/api/project/delProject", route91);
app.use("/api/project/editDirectorlManual", route92);
app.use("/api/project/editProject", route93);
app.use("/api/project/editVisualManual", route94);
app.use("/api/project/getModelDetails", route95);
app.use("/api/project/getProject", route96);
app.use("/api/project/getVisualManual", route97);
app.use("/api/project/queryDirectorManual", route98);
app.use("/api/project/visualManual", route99);
app.use("/api/script/addScript", route100);
app.use("/api/script/batchAddScript", route101);
app.use("/api/script/delScript", route102);
app.use("/api/script/exportScript", route103);
app.use("/api/script/extractAssets", route104);
app.use("/api/script/getAiRegex", route105);
app.use("/api/script/getScrptApi", route106);
app.use("/api/script/pollScriptAssets", route107);
app.use("/api/script/updateScript", route108);
app.use("/api/scriptAgent/getPlanData", route109);
app.use("/api/scriptAgent/setPlanData", route110);
app.use("/api/scriptAgent/updateData", route111);
app.use("/api/setting/about/checkUpdate", route112);
app.use("/api/setting/about/downloadApp", route113);
app.use("/api/setting/agentDeploy/agentSetKey", route114);
app.use("/api/setting/agentDeploy/deployAgentModel", route115);
app.use("/api/setting/agentDeploy/getAgentDeploy", route116);
app.use("/api/setting/dbConfig/clearData", route117);
app.use("/api/setting/dbConfig/clearTable", route118);
app.use("/api/setting/dbConfig/dbInfo", route119);
app.use("/api/setting/dbConfig/exportData", route120);
app.use("/api/setting/dbConfig/importData", route121);
app.use("/api/setting/dev/getSwitchAiDevTool", route122);
app.use("/api/setting/dev/updateSwitchAiDevTool", route123);
app.use("/api/setting/fileManagement/openFolder", route124);
app.use("/api/setting/getTextModel", route125);
app.use("/api/setting/loginConfig/getUser", route126);
app.use("/api/setting/loginConfig/updateUserPwd", route127);
app.use("/api/setting/memoryConfig/delAllMemory", route128);
app.use("/api/setting/memoryConfig/getMemory", route129);
app.use("/api/setting/memoryConfig/sureMemory", route130);
app.use("/api/setting/modelMap/bindingPrompt", route131);
app.use("/api/setting/modelMap/getImageAndVideoModel", route132);
app.use("/api/setting/promptManage/getPrompt", route133);
app.use("/api/setting/promptManage/updatePrompt", route134);
app.use("/api/setting/skillManagement/getSkillContent", route135);
app.use("/api/setting/skillManagement/getSkillList", route136);
app.use("/api/setting/skillManagement/saveSkillContent", route137);
app.use("/api/setting/vendorConfig/addVendor", route138);
app.use("/api/setting/vendorConfig/addVendorModel", route139);
app.use("/api/setting/vendorConfig/deleteVendor", route140);
app.use("/api/setting/vendorConfig/delVendorModel", route141);
app.use("/api/setting/vendorConfig/enableVendor", route142);
app.use("/api/setting/vendorConfig/getCodeByLink", route143);
app.use("/api/setting/vendorConfig/getVendorList", route144);
app.use("/api/setting/vendorConfig/modelTest", route145);
app.use("/api/setting/vendorConfig/updateCode", route146);
app.use("/api/setting/vendorConfig/updateVendorInputs", route147);
app.use("/api/setting/vendorConfig/upVendorModel", route148);
app.use("/api/task/getProject", route149);
app.use("/api/task/getTaskApi", route150);
app.use("/api/task/getTaskCategories", route151);
app.use("/api/task/taskDetails", route152);
app.use("/api/test/test", route153);
}

View File

@ -141,7 +141,7 @@ export default router.post("/", validateFields(requestSchema), async (req, res)
state: "已完成",
filePath: imagePath,
type: item.type,
model: model.split(":")[1],
model: model.split(/:(.+)/)[1],
resolution,
});

View File

@ -27,22 +27,6 @@ interface NovelChapter {
type ItemType = "characters" | "props" | "scenes";
interface ResultItem {
type: ItemType;
name: string;
chapterRange: number[];
}
function findItemByName(items: ResultItem[], name: string, type?: ItemType): ResultItem | undefined {
return items.find((item) => (!type || item.type === type) && item.name === name);
}
function mergeNovelText(novelData: NovelChapter[]): string {
if (!Array.isArray(novelData)) return "";
return novelData
.map((chap) => {
return `${chap.chapter.trim()}\n\n${chap.chapterData.trim().replace(/\r?\n/g, "\n")}\n`;
})
.join("\n");
}
//润色提示词
export default router.post(
"/",
@ -66,23 +50,6 @@ export default router.post(
if (!project) return res.status(500).send(success({ message: "项目为空" }));
// 预加载公共数据
const allOutlineDataList: { data: string }[] = await u.db("o_outline").where("projectId", projectId).select("data");
const itemMap: Record<string, ResultItem> = {};
if (allOutlineDataList.length > 0)
allOutlineDataList.forEach((row) => {
const data: OutlineData = JSON.parse(row?.data || "{}");
(["characters", "props", "scenes"] as ItemType[]).forEach((type) => {
(data[type] || []).forEach((item) => {
const key = `${type}-${item.name}`;
if (!itemMap[key]) {
itemMap[key] = { type, name: item.name, chapterRange: [...(data.chapterRange || [])] };
} else {
itemMap[key].chapterRange = Array.from(new Set([...itemMap[key].chapterRange, ...(data.chapterRange || [])]));
}
});
});
});
const result: ResultItem[] = Object.values(itemMap);
const assetsIds = items.map((item: { assetsId: number }) => item.assetsId);
//查询所有资产,用于判断每个资产是否是衍生资产
const assetsDataList = await u.db("o_assets").whereIn("id", assetsIds).select("id", "assetsId");
@ -132,7 +99,6 @@ export default router.post(
await u.db("o_assets").where("id", item.assetsId).update({ promptState: "生成失败", promptErrorReason: "视觉手册未定义" });
return;
}
findItemByName(result, item.name, config.itemType);
const systemPrompt = visualManual;
try {
const { _output } = (await u.Ai.Text("universalAi").invoke({

View File

@ -123,7 +123,7 @@ export default router.post("/", validateFields(requestSchema), async (req, res)
state: "已完成",
filePath: imagePath,
type,
model: model.split(":")[1],
model: model.split(/:(.+)/)[1],
resolution,
});

View File

@ -4,36 +4,10 @@ import * as zod from "zod";
import { error, success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
const router = express.Router();
interface OutlineItem {
description: string;
name: string;
}
interface OutlineData {
chapterRange: number[];
characters?: OutlineItem[];
props?: OutlineItem[];
scenes?: OutlineItem[];
}
interface NovelChapter {
id: number;
reel: string;
chapter: string;
chapterData: string;
projectId: number;
}
type ItemType = "characters" | "props" | "scenes";
interface ResultItem {
type: ItemType;
name: string;
chapterRange: number[];
}
function findItemByName(items: ResultItem[], name: string, type?: ItemType): ResultItem | undefined {
return items.find((item) => (!type || item.type === type) && item.name === name);
}
//润色提示词
export default router.post(
"/",
@ -51,31 +25,8 @@ export default router.post(
//如果没有找到对应的项目,返回错误
if (!project) return res.status(500).send(success({ message: "项目为空" }));
const allOutlineDataList: { data: string }[] = await u.db("o_outline").where("projectId", projectId).select("data");
await u.db("o_assets").where("id", assetsId).update({ promptState: "生成中" });
const itemMap: Record<string, ResultItem> = {};
if (allOutlineDataList.length > 0)
allOutlineDataList.forEach((row) => {
const data: OutlineData = JSON.parse(row?.data || "{}");
(["characters", "props", "scenes"] as ItemType[]).forEach((type) => {
(data[type] || []).forEach((item) => {
const key = `${type}-${item.name}`;
if (!itemMap[key]) {
itemMap[key] = {
type,
name: item.name,
chapterRange: [...(data.chapterRange || [])],
};
} else {
itemMap[key].chapterRange = Array.from(new Set([...itemMap[key].chapterRange, ...(data.chapterRange || [])]));
}
});
});
});
const result: ResultItem[] = Object.values(itemMap);
//查询资产是否是衍生资产
const assetsData = await u.db("o_assets").where("id", assetsId).select("assetsId").first();
if (!assetsData) return { code: 500, message: "资产不存在" };
@ -109,7 +60,6 @@ export default router.post(
//获取到视觉手册
const visualManual = await u.getArtPrompt(project.artStyle as string, "art_skills", config.visualManual);
if (!visualManual) return res.status(500).send(error("视觉手册未定义"));
findItemByName(result, name, config.itemType);
const systemPrompt = visualManual;
try {
const { _output } = (await u.Ai.Text("universalAi").invoke({

View File

@ -40,7 +40,6 @@ export default router.post(
data.map((i:any) => i.id!),
)
.select( "o_assets.id", "o_assets.name");
console.log("%c Line:36 🍎 assets2AudioData", "background:#3f7cff", assets2AudioData);
const repleAssets:Record<number,{id:number;name:string}[]> = {};
assets2AudioData.forEach((item) => {
if (!repleAssets[item.id]) repleAssets[item.id] = [item];

View File

@ -1,133 +0,0 @@
import express from "express";
import { success } from "@/lib/responseFormat";
import db from "@/utils/db";
import type { DB } from "@/types/database";
import knex from "knex";
import path from "path";
import fs from "fs";
import { tr } from "zod/locales";
const router = express.Router();
// 迁移数据
export default router.post(
"/",
async (req, res) => {
// return res.status(200).send({
// success: true,
// message: '数据迁移功能已关闭,建议手动迁移数据后删除旧数据库文件'
// });
//连接旧数据库,读取数据
try {
let db2: knex.Knex | null = null;
//读取旧数据库路径
let db2Path: string;
if (typeof process.versions?.electron !== "undefined") {
const { app } = require("electron");
const userDataDir: string = app.getPath("userData");
db2Path = path.join(userDataDir, "db2.sqlite");
} else {
db2Path = path.join(process.cwd(), "db2.sqlite");
}
const dbDir = path.dirname(db2Path);
// 确保数据库目录存在
if (!fs.existsSync(dbDir)) {
fs.mkdirSync(dbDir, { recursive: true });
}
if (!fs.existsSync(db2Path)) {
return res.status(404).send({
success: false,
message: `源数据库文件不存在: ${db2Path}`
});
}
//连接旧数据库
db2 = knex({
client: "better-sqlite3",
connection: {
filename: db2Path,
},
useNullAsDefault: true,
});
//需要迁移的旧数据表
const db2TableNames = [
't_project',
't_assets',
't_event',
't_image',
't_novel',
't_outline',
't_script',
't_storyboard',
't_video',
]
//新数据库的表
const dbTableNames = [
'o_project',
'o_assets',
'o_event',
'o_eventChapter',
'o_image',
'o_novel',
'o_outline',
'o_outlineNovel',
'o_script',
'o_scriptAssets',
'o_scriptOutline',
'o_storyboard',
'o_storyboardScript',
'o_video',
]
for (const tableName of db2TableNames) {
try {
// 从 db2 读取数据
const sourceData = await db2(tableName).select('*');
for (const item of sourceData) {
//迁移项目表
if (tableName === 't_project') {
// await db("o_project").insert({
// name: item.name,
// intro: item.intro,
// type: item.type,
// artStyle: item.artStyle,
// videoRatio: item.videoRatio,
// createTime: item.createTime,
// userId: item.userId,
// projectType: "基于小说原文"
// })
}
//迁移资产表
if (tableName === 't_assets') {
}
//迁移事件表
if (tableName === 't_event') { }
//迁移图片表
if (tableName === 't_image') { }
//迁移小说表
if (tableName === 't_novel') { }
//迁移大纲表
if (tableName === 't_outline') { }
//迁移脚本表
if (tableName === 't_script') { }
//迁移分镜面板
if (tableName === 't_storyboard') { }
//迁移视频表
if (tableName === 't_video') { }
}
// // 将数据插入到 db 中
// const targetTableName = dbTableNames[db2TableNames.indexOf(tableName)];
// await db(targetTableName).insert(sourceData);
// console.log(`成功迁移表 ${tableName} 的数据到 ${targetTableName}`);
} catch (error) {
console.error(`连接旧数据库失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
} catch (error) {
console.error('连接旧数据库失败:', error);
}
return res.status(200).send({
success: true,
message: '数据迁移功能已关闭,建议手动迁移数据后删除旧数据库文件'
});
}
);

View File

@ -12,7 +12,7 @@ export default router.post(
}),
async (req, res) => {
const { modelId } = req.body;
const [id, name] = modelId.split(":");
const [id, name] = modelId.split(/:(.+)/);
const models = await u.vendor.getModelList(id);
const findData = models.find((i: any) => i.modelName == name);
res.status(200).send(success(findData));

View File

@ -5,7 +5,6 @@ import sharp from "sharp";
import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
import { Output } from "ai";
import { urlToBase64 } from "@/utils/vm";
const router = express.Router();
export default router.post(
@ -28,14 +27,6 @@ export default router.post(
.leftJoin("o_image", "o_assets.imageId", "o_image.id")
.whereIn("o_assets.id", parentIds as number[])
.select("o_assets.id", "o_image.filePath", "o_assets.describe");
const assetsSrcArr = await Promise.all(
parentAssetsData.map(async (item) => {
return {
src: await u.oss.getFileUrl(item.filePath),
id: item.id,
};
}),
);
assetsDataArr.forEach((i: any) => {
const parent = parentAssetsData.find((item) => item.id === i.assetsId);
if (parent) {
@ -43,8 +34,8 @@ export default router.post(
}
});
const imageUrlRecord: Record<number, string> = {};
assetsSrcArr.forEach((item) => {
imageUrlRecord[item.id] = item.src;
parentAssetsData.forEach((item) => {
if (item.filePath) imageUrlRecord[item.id] = item.filePath;
});
const rolePrompt = u.getArtPrompt(projectSettingData!.artStyle!, "art_skills", "art_character_derivative");
const toolPrompt = u.getArtPrompt(projectSettingData!.artStyle!, "art_skills", "art_prop_derivative");
@ -91,8 +82,9 @@ export default router.post(
},
],
});
await u.db("o_assets").where("id", item.id).update({ prompt: text });
const imageBase64 = imageUrlRecord[item.assetsId!] ? await urlToBase64(imageUrlRecord[item.assetsId!]) : null;
const imageBase64 = imageUrlRecord[item.assetsId!] ? await u.oss.getImageBase64(imageUrlRecord[item.assetsId!]) : null;
try {
const repeloadObj = {
prompt: text,
@ -113,7 +105,6 @@ export default router.post(
);
const savePath = `/${projectId}/assets/${scriptId}/${item.type}/${u.uuid()}.jpg`;
await imageCls.save(savePath);
await u.db("o_assets").where("id", item.id).update({ prompt: text });
await u.db("o_image").where({ id: imageId }).update({ state: "已完成", filePath: savePath });
return {
id: item.id!,

View File

@ -13,6 +13,11 @@ export default router.post(
}),
async (req, res) => {
const { id, projectId } = req.body;
const assetsFirstData = await u.db("o_assets").where("id", id).first();
if (!assetsFirstData) {
return res.status(404).send({ error: "资源未找到" });
}
if (assetsFirstData?.flowId) await u.db("o_imageFlow").where("id", assetsFirstData?.flowId).delete();
await u.db("o_assets").where("id", id).delete();
await u.db("o_assets2Storyboard").where("assetId", id).delete();
res.status(200).send(success({ message: "视频删除成功" }));

View File

@ -7,6 +7,9 @@ import axios from "axios";
const router = express.Router();
async function urlToBase64(imageUrl: string): Promise<string> {
if (imageUrl.startsWith("/oss/")) {
return await u.oss.getImageBase64(u.replaceUrl(imageUrl));
}
const response = await axios.get(imageUrl, { responseType: "arraybuffer" });
const contentType = response.headers["content-type"] || "image/png";
const base64 = Buffer.from(response.data, "binary").toString("base64");

View File

@ -21,6 +21,13 @@ export default router.post(
node.data.image = node.data.image ? await u.oss.getFileUrl(node.data.image) : "";
} else if (node.type === "generated") {
node.data.generatedImage = node.data.generatedImage ? await u.oss.getFileUrl(node.data.generatedImage) : "";
console.log("%c Line:25 🍋 node.data.references", "background:#42b983", node.data.references);
node.data.references = await Promise.all(node.data.references.map(async (item: { image: string }) => {
return {
image: await u.oss.getFileUrl(item.image)
}
}));
}
}),
);

View File

@ -14,12 +14,16 @@ export default router.post(
async (req, res) => {
const { edges, nodes } = req.body;
nodes.forEach((node: any) => {
console.log("%c Line:17 🌮 node", "background:#465975", node);
if (node.type == "upload") {
node.data.image = node.data.image ? u.replaceUrl(node.data.image) : "";
}
if (node.type == "generated") {
node.data.generatedImage = node.data.generatedImage ? u.replaceUrl(node.data.generatedImage) : "";
node.data.references.forEach((item: { image: string }) => {
item.image = item.image ? u.replaceUrl(item.image) : "";
});
}
});
const [insertFlowId] = await u.db("o_imageFlow").insert({

View File

@ -21,6 +21,9 @@ export default router.post(
if (node.type == "generated") {
node.data.generatedImage = node.data.generatedImage ? u.replaceUrl(node.data.generatedImage) : "";
node.data.references.forEach((item: { image: string }) => {
item.image = item.image ? u.replaceUrl(item.image) : "";
});
}
});

View File

@ -86,6 +86,7 @@ export default router.post(
} else {
try {
const storyboardData = await u.db("o_storyboard").where("scriptId", episodesId);
await Promise.all(
storyboardData.map(async (i) => {
if (i.filePath) {

View File

@ -25,7 +25,8 @@ export default router.post(
} = req.body;
const sqlData = await u.db("o_agentWorkData").where("projectId", String(projectId)).andWhere("episodesId", String(episodesId)).first();
const filterDatas = data.storyboard.filter((i) => !i.id);
if (data.storyboard && data.storyboard.length && !filterDatas.length)
if (data.storyboard && data.storyboard.length && !filterDatas.length) {
try {
await Promise.all(
data.storyboard
.filter((i) => i.id)
@ -35,6 +36,12 @@ export default router.post(
});
}),
);
} catch (error) {
console.error("更新分镜排序失败", error)
}
}
if (!sqlData) {
await u.db("o_agentWorkData").insert({
projectId,

View File

@ -91,7 +91,7 @@ export default router.post(
const storyboardData = await Promise.all(
lastStoryboard.map(async (i) => {
return {
associateAssetsIds: await u.db("o_assets2Storyboard").where("storyboardId", i.id).select("assetId").pluck("assetId"),
associateAssetsIds: await u.db("o_assets2Storyboard").where("storyboardId", i.id).orderBy("rowid").select("assetId").pluck("assetId"),
src: i.filePath ? await u.oss.getFileUrl(i.filePath) : "",
id: i.id,
trackId: i.trackId,
@ -100,6 +100,7 @@ export default router.post(
state: i.state,
scriptId: i.scriptId,
reason: i.reason,
videoDesc: i.videoDesc
};
}),
);

View File

@ -5,7 +5,6 @@ import sharp from "sharp";
import { error, success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
import { Output, tool } from "ai";
import { urlToBase64 } from "@/utils/vm";
import { assetItemSchema } from "@/agents/productionAgent/tools";
const router = express.Router();
export type AssetData = z.infer<typeof assetItemSchema>;
@ -50,18 +49,33 @@ export default router.post(
const projectSettingData = await u.db("o_project").where("id", projectId).select("imageModel", "imageQuality", "artStyle", "videoRatio").first();
const storyboardData = await u.db("o_storyboard").where("scriptId", scriptId).whereIn("id", finalStoryboardIds);
const assetData = await u
.db("o_assets")
.leftJoin("o_assets2Storyboard", "o_assets.id", "o_assets2Storyboard.assetId")
.whereIn("o_assets2Storyboard.storyboardId", finalStoryboardIds)
.select("o_assets2Storyboard.storyboardId", "o_assets.imageId");
// 按 rowid 顺序查出每个 storyboard 关联的 assetId 有序列表
const assets2StoryboardRows = await u
.db("o_assets2Storyboard")
.whereIn("storyboardId", finalStoryboardIds)
.orderBy("rowid")
.select("storyboardId", "assetId");
// 收集所有 assetId批量查对应的 imageId
const allAssetIds = [...new Set(assets2StoryboardRows.map((r: any) => r.assetId))];
const assetImageMap: Record<number, number> = {};
if (allAssetIds.length > 0) {
const assetRows = await u.db("o_assets").whereIn("id", allAssetIds).select("id", "imageId");
assetRows.forEach((row: any) => {
assetImageMap[row.id] = row.imageId;
});
}
// 按 rowid 顺序重建 assetRecord值为有序的 imageId 列表
const assetRecord: Record<number, number[]> = {};
assetData.forEach((item: any) => {
assets2StoryboardRows.forEach((item: any) => {
if (!assetRecord[item.storyboardId]) {
assetRecord[item.storyboardId] = [];
}
assetRecord[item.storyboardId].push(item.imageId);
const imageId = assetImageMap[item.assetId];
if (imageId != null) {
assetRecord[item.storyboardId].push(imageId);
}
});
res.status(200).send(
@ -142,7 +156,7 @@ async function getAssetsImageBase64(imageIds: number[]) {
const filePath = id2Path.get(id);
if (filePath) {
try {
return await urlToBase64(await u.oss.getFileUrl(filePath));
return await u.oss.getImageBase64(filePath);
} catch {
return null;
}

View File

@ -12,8 +12,9 @@ export default router.post(
}),
async (req, res) => {
const { id } = req.body;
const storyboardData = await u.db("o_storyboard").where("id", id).select("id", "track", "trackId").first();
const storyboardData = await u.db("o_storyboard").where("id", id).select("id", "track", "trackId", "flowId").first();
if (!storyboardData) return res.status(400).send(error("未找到该分镜"));
if (storyboardData?.flowId) await u.db("o_imageFlow").where("id", storyboardData?.flowId).delete();
const trackData = await u.db("o_storyboard").where("track", storyboardData.track).select("id");
if (trackData.length == 1) await u.db("o_videoTrack").where("id", storyboardData.trackId).delete();
await u.db("o_storyboard").where("id", id).delete();

View File

@ -13,6 +13,9 @@ export default router.post(
}),
async (req, res) => {
const { projectId, scriptId, duration } = req.body;
const data = await u.db("o_project").where("id", projectId).first();
const video = data?.videoModel?.split(":");
const vemdor = await u.vendor.getModelList(video?.[0]!);
const [id] = await u.db("o_videoTrack").insert({
projectId,
scriptId,

View File

@ -0,0 +1,33 @@
import express from "express";
import u from "@/utils";
import { z } from "zod";
import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
const router = express.Router();
export default router.post(
"/",
validateFields({
projectId: z.number(),
scriptId: z.number(),
videoIds: z.array(z.number()),
}),
async (req, res) => {
const { projectId, scriptId, videoIds } = req.body;
const videoList = await u
.db("o_video")
.whereIn("id", videoIds)
.whereIn("state", ["生成成功", "生成失败"])
.select("id", "state", "errorReason", "filePath");
res.status(200).send(
success(
await Promise.all(
videoList.map(async (s) => ({
...s,
src: s.filePath ? await u.oss.getFileUrl(s.filePath) : "",
})),
),
),
);
},
);

View File

@ -13,6 +13,9 @@ export default router.post(
async (req, res) => {
const { id } = req.body;
await u.db("o_videoTrack").where("id", id).delete();
await u.db("o_storyboard").where("trackId", id).update({
trackId: null,
});
res.status(200).send(success({ message: "视频段删除成功" }));
},
);

View File

@ -117,7 +117,7 @@ export default router.post(
.where("id", videoId)
.update({
state: "生成失败",
errorReason: error instanceof Error ? error.message : "未知错误",
errorReason: u.error(error).message,
});
}
})();

View File

@ -1,7 +1,7 @@
import express from "express";
import u from "@/utils";
import { z } from "zod";
import { success } from "@/lib/responseFormat";
import { success, error } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
import { info } from "node:console";
const router = express.Router();
@ -32,7 +32,7 @@ export default router.post(
.select("videoDesc", "prompt", "track", "duration", "shouldGenerateImage")
.first();
// 查询分镜关联的资产ID
const assetRows = await u.db("o_assets2Storyboard").where("storyboardId", item.id).select("assetId");
const assetRows = await u.db("o_assets2Storyboard").where("storyboardId", item.id).orderBy("rowid").select("assetId");
const associateAssetsIds = assetRows.map((row: any) => row.assetId);
return {
...storyboard,
@ -42,7 +42,12 @@ export default router.post(
}
if (item.sources === "assets") {
// 查询素材
const assetsData = await u.db("o_assets").leftJoin("o_image","o_image.id","o_assets.imageId").where("o_assets.id", item.id).select("o_assets.id", "o_assets.type", "o_assets.name","o_image.filePath").first();
const assetsData = await u
.db("o_assets")
.leftJoin("o_image", "o_image.id", "o_assets.imageId")
.where("o_assets.id", item.id)
.select("o_assets.id", "o_assets.type", "o_assets.name", "o_image.filePath")
.first();
return {
...assetsData,
_type: "assets", // 标记类型
@ -61,7 +66,7 @@ export default router.post(
id: item.id,
type: item.type,
name: item.name,
filePath:item.filePath
filePath: item.filePath,
});
if (item._type === "storyboard")
storyboard.push({
@ -73,7 +78,7 @@ export default router.post(
shouldGenerateImage: item.shouldGenerateImage,
});
}
const [id, modelData] = model.split(":");
const [id, modelData] = model.split(/:(.+)/);
const projectData = await u.db("o_project").select("*").where({ id: projectId }).first();
const videoPrompt = await u.db("o_prompt").where("type", "videoPromptGeneration").first();
let videoPromptGeneration = "" as string | undefined;
@ -86,7 +91,10 @@ export default router.post(
const visualManual = u.getArtPrompt(artStyle, "art_skills", "art_storyboard_video");
const content = `
****${modelData},
****):${assets.filter(i => i.filePath).map((i) => `[${i.id},${i.type},${i.name}]`).join("")},
****):${assets
.filter((i) => i.filePath)
.map((i) => `[${i.id},${i.type},${i.name}]`)
.join("")},
****${storyboard.map(
(i) => `<storyboardItem
videoDesc='${i.videoDesc}'
@ -94,7 +102,6 @@ export default router.post(
></storyboardItem>`,
)},
`;
console.log("%c Line:87 🌮 content", "background:#2eafb0", content);
try {
const { text } = await u.Ai.Text("universalAi").invoke({
@ -114,8 +121,8 @@ export default router.post(
prompt: text,
});
res.status(200).send(success(text));
} catch (error) {
res.status(500).send(error);
} catch (e) {
res.status(400).send(error(u.error(e).message));
}
},
);

View File

@ -0,0 +1,39 @@
import express from "express";
import u from "@/utils";
import { z } from "zod";
import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
import { id } from "zod/locales";
const router = express.Router();
export default router.post(
"/",
validateFields({
items: z.array(z.object({
id: z.number(),
sources: z.string()
}))
}),
async (req, res) => {
const { items } = req.body;
const result: Record<string, string> = {};
const storyboardIds = items.filter((item: any) => item.sources == "storyboard").map((item: any) => item.id)
const totalFilePaths = []
if (storyboardIds.length) {
const storyBoardPaths = await u.db("o_storyboard").whereIn("id", storyboardIds).select("id", "filePath");
totalFilePaths.push(...storyBoardPaths.map(i => ({ id: i.id, filePath: i.filePath, sources: "storyboard" })))
}
const assetsIds = items.filter((item: any) => item.sources == "assets").map((item: any) => item.id)
if (assetsIds.length) {
const assetsPaths = await u.db("o_assets").leftJoin("o_image", "o_image.id", "o_assets.imageId").whereIn("o_assets.id", assetsIds).select("o_assets.id", "o_image.filePath");
totalFilePaths.push(...assetsPaths.map(i => ({ id: i.id, filePath: i.filePath, sources: "assets" })))
}
await Promise.all(
totalFilePaths.map(async (item: { id: string, filePath: string, sources: string }) => {
result[`${item.id}:${item.sources}`] = item.filePath ? await u.oss.getFileUrl(item.filePath) : "";
}))
res.status(200).send(success({ data: result }));
},
);

View File

@ -37,14 +37,18 @@ export default router.post(
}),
async (req, res) => {
const { projectId, scriptId } = req.body;
const projectData = await u.db("o_project").where("id", projectId).select("id", "videoModel").first();
const projectData = await u.db("o_project").where("id", projectId).select("id", "videoModel", "mode").first();
if (!projectData?.videoModel) {
return res.status(400).json(success("项目未配置视频模型"));
}
const [videoId, videoModelName] = projectData.videoModel.split(":");
const models = await u.vendor.getModelList(videoId);
const findData = models.find((i: any) => i.modelName == videoModelName);
const isRef = findData.mode.every((i: any) => Array.isArray(i));
let videoMode = "";
try {
videoMode = JSON.parse(projectData?.mode ?? "");
} catch (e) {
videoMode = projectData?.mode ?? "";
}
const isRef = Array.isArray(videoMode) ? true : false;
const storyboardList = await u.db("o_storyboard").where({ scriptId, projectId }).orderBy("index", "asc");
await Promise.all(
@ -105,7 +109,6 @@ export default router.post(
);
}
const id = await u.db("o_project").where({ id: projectId }).select("id").first();
const trackData = await u.db("o_videoTrack").where({ projectId, scriptId });
const videoList = await u.db("o_video").whereIn(
"videoTrackId",
@ -131,8 +134,8 @@ export default router.post(
seenAssetIds.add(a.id);
return true;
});
const hasImageAssetData = uniqueAssets.filter(i => i.src)
const notHasImageAssetData = uniqueAssets.filter(i => !i.src)
const hasImageAssetData = uniqueAssets.filter((i) => i.src);
const notHasImageAssetData = uniqueAssets.filter((i) => !i.src);
return [...hasImageAssetData, ...storyboardMedias, ...notHasImageAssetData];
})(),
@ -143,6 +146,7 @@ export default router.post(
id: v.id!,
src: v.filePath ? await u.oss.getFileUrl(v.filePath) : "",
state: v.state === "已完成" ? "已完成" : v.state === "生成中" ? "生成中" : v.state === "生成失败" ? "生成失败" : "未生成",
errorReason: v?.errorReason ?? "",
})),
),
});

View File

@ -0,0 +1,20 @@
import express from "express";
import u from "@/utils";
import { z } from "zod";
import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
const router = express.Router();
export default router.post(
"/",
validateFields({
id: z.number(),
duration: z.number().optional(),
}),
async (req, res) => {
const { id, duration } = req.body;
await u.db("o_videoTrack").where("id", id).update({
duration,
});
res.status(200).send(success("更新成功"));
},
);

View File

@ -9,13 +9,11 @@ export default router.post(
validateFields({
id: z.number(),
prompt: z.string().optional(),
duration: z.number().optional(),
}),
async (req, res) => {
const { id, prompt, duration } = req.body;
await u.db("o_videoTrack").where("id", id).update({
prompt,
duration,
});
res.status(200).send(success("更新成功"));
},

View File

@ -18,9 +18,6 @@ export default router.post(
await u.db("o_agentWorkData").where("projectId", id).delete();
const novelData = await u.db("o_novel").where("projectId", id).select("id");
const novelId = novelData.map((item: any) => item.id);
if (novelId.length > 0) {
await u.db("o_outlineNovel").whereIn("novelId", novelId).delete();
}
//删除项目下的原文
await u.db("o_novel").where("projectId", id).delete();
// 删除项目下的剧本信息

View File

@ -0,0 +1,22 @@
import express from "express";
import { success, error } from "@/lib/responseFormat";
import u from "@/utils";
import { z } from "zod";
import { validateFields } from "@/middleware/middleware";
const router = express.Router();
export default router.post(
"/",
validateFields({
key: z.enum(["scriptAgent", "productionAgent"]),
}),
async (req, res) => {
const { key } = req.body;
const data = await u.db("o_agentDeploy").select("o_agentDeploy.*").where("o_agentDeploy.key", key).first();
const [id, modelName] = data ? data.modelName.split(/:(.+)/) : [];
const models = await u.vendor.getModelList(id);
const model = models.find((m) => m.modelName === modelName);
if (!model) return res.status(400).send(error("未找到模型"));
res.status(200).send(success(model));
},
);

View File

@ -0,0 +1,35 @@
import express from "express";
import u from "@/utils";
import { z } from "zod";
import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
const router = express.Router();
export default router.post(
"/",
validateFields({
content: z.string(),
}),
async (req, res) => {
const { content } = req.body;
const systemPrompt = `你是一个正则表达式专家。用户会提供一段剧本文本,你需要分析其中的集/章节分隔模式返回一个JavaScript正则表达式字符串。
1. //scriptName
2. //g/\s*([0-9]+)\s*\s*([^\n\r]*)/g
3. markdown格式
4. `;
const resText = await u.Ai.Text("universalAi").invoke({
system: systemPrompt,
messages: [
{
role: "user",
content: content.slice(0, 2000),
},
],
});
const result = (resText.text || "").trim();
res.status(200).send(success(result));
},
);

View File

@ -25,22 +25,8 @@ export default router.post(
const zip = await axios.get(url, { responseType: "arraybuffer" }).then((res) => res.data);
fs.writeFileSync(`${rootDir}/latest.zip`, zip);
await compressing.zip.uncompress(`${rootDir}/latest.zip`, rootDir);
const tempServerPath = u.getPath(["temp", "serve"]);
if (fs.existsSync(tempServerPath)) {
fs.cpSync(tempServerPath, u.getPath(["serve"]), { recursive: true, force: true });
}
const webPath = u.getPath(["temp", "web"]);
if (fs.existsSync(webPath)) {
fs.cpSync(webPath, u.getPath(["web"]), { recursive: true, force: true });
}
const tempSkillsPath = u.getPath(["temp", "skills"]);
if (fs.existsSync(tempSkillsPath)) {
fs.cpSync(tempSkillsPath, u.getPath(["skills"]), { recursive: true, force: true });
}
const tempModelsPath = u.getPath(["temp", "models"]);
if (fs.existsSync(tempModelsPath)) {
fs.cpSync(tempModelsPath, u.getPath(["models"]), { recursive: true, force: true });
}
const dataDir = u.getPath();
fs.cpSync(rootDir, dataDir, { recursive: true, force: true });
fs.rmSync(rootDir, { recursive: true, force: true });
res.status(200).send(success(`更新${version}成功5秒后重启`));
}

View File

@ -14,10 +14,12 @@ export default router.post(
modelName: z.string(),
vendorId: z.string().nullable(),
desc: z.string(),
temperature: z.number().optional(),
maxOutputTokens: z.number().optional(),
}),
async (req, res) => {
const { id, name, model, modelName, vendorId, desc } = req.body;
await u.db("o_agentDeploy").where({ id }).update({ id, name, model, modelName, vendorId, desc });
const { id, name, model, modelName, vendorId, desc, temperature, maxOutputTokens } = req.body;
await u.db("o_agentDeploy").where({ id }).update({ id, name, model, modelName, vendorId, desc, temperature, maxOutputTokens });
res.status(200).send(success("配置成功"));
},
);

View File

@ -4,6 +4,8 @@ import u from "@/utils";
const router = express.Router();
export default router.post("/", async (req, res) => {
const data = await u.db("o_agentDeploy").leftJoin("o_vendorConfig", "o_vendorConfig.id", "o_agentDeploy.vendorId").select("o_agentDeploy.*");
res.status(200).send(success(data));
const allData = await u.db("o_agentDeploy").leftJoin("o_vendorConfig", "o_vendorConfig.id", "o_agentDeploy.vendorId").select("o_agentDeploy.*");
const qrdinaryData = allData.filter((item: any) => !item.key?.includes(":"));
const advancedData = allData.filter((item: any) => item.key?.includes(":"));
res.status(200).send(success({ qrdinaryData, advancedData }));
});

View File

@ -0,0 +1,29 @@
import express from "express";
import { success, error } from "@/lib/responseFormat";
import { db } from "@/utils/db";
const router = express.Router();
export default router.post("/", async (req, res) => {
try {
const { tableName } = req.body;
if (!tableName || typeof tableName !== "string") {
return res.status(400).send(error("请提供有效的表名"));
}
// 验证表名存在防止SQL注入
const tableExists: { name: string }[] = await db.raw(
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
[tableName],
);
if (tableExists.length === 0) {
return res.status(400).send(error("表不存在"));
}
await db.raw(`DELETE FROM "${tableName}"`);
res.status(200).send(success(`${tableName} 已清空`));
} catch (err: any) {
res.status(500).send(error(err?.message || "清空表失败"));
}
});

View File

@ -0,0 +1,26 @@
import express from "express";
import { success, error } from "@/lib/responseFormat";
import { db } from "@/utils/db";
const router = express.Router();
export default router.get("/", async (req, res) => {
try {
const tables: { name: string }[] = await db.raw(
`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE 'knex_%'`,
);
const tableInfo = [];
for (const table of tables) {
const countResult = await db.raw(`SELECT COUNT(*) as count FROM "${table.name}"`);
tableInfo.push({
name: table.name,
rowCount: countResult[0]?.count ?? 0,
});
}
res.status(200).send(success(tableInfo));
} catch (err: any) {
res.status(500).send(error(err?.message || "获取数据库信息失败"));
}
});

View File

@ -0,0 +1,29 @@
import express from "express";
import { success, error } from "@/lib/responseFormat";
import { db } from "@/utils/db";
const router = express.Router();
export default router.get("/", async (req, res) => {
try {
const tables: { name: string }[] = await db.raw(
`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE 'knex_%'`,
);
const data: Record<string, any[]> = {};
for (const table of tables) {
data[table.name] = await db.raw(`SELECT * FROM "${table.name}"`);
}
const exportData = {
exportTime: Date.now(),
tables: data,
};
res.setHeader("Content-Type", "application/json");
res.setHeader("Content-Disposition", `attachment; filename=toonflow-backup-${Date.now()}.json`);
res.status(200).send(JSON.stringify(exportData, null, 2));
} catch (err: any) {
res.status(500).send(error(err?.message || "导出失败"));
}
});

View File

@ -0,0 +1,55 @@
import express from "express";
import { success, error } from "@/lib/responseFormat";
import { db } from "@/utils/db";
import initDB from "@/lib/initDB";
const router = express.Router();
export default router.post("/", async (req, res) => {
try {
const { tables: importTables } = req.body;
if (!importTables || typeof importTables !== "object") {
return res.status(400).send(error("无效的导入数据格式"));
}
// 删除所有现有表
const existingTables: { name: string }[] = await db.raw(
`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE 'knex_%'`,
);
await db.raw("PRAGMA foreign_keys = OFF");
for (const table of existingTables) {
await db.schema.dropTableIfExists(table.name);
}
await db.raw("PRAGMA foreign_keys = ON");
// 重新初始化表结构
await initDB(db as any);
// 导入数据
await db.raw("PRAGMA foreign_keys = OFF");
for (const [tableName, rows] of Object.entries(importTables)) {
if (!Array.isArray(rows) || rows.length === 0) continue;
// 验证表名合法性防止SQL注入
const tableExists = await db.raw(
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
[tableName],
);
if (tableExists.length === 0) continue;
// 清空表数据后插入导入数据
await db.raw(`DELETE FROM "${tableName}"`);
// 分批插入每批100条
for (let i = 0; i < rows.length; i += 100) {
const batch = rows.slice(i, i + 100);
await db(tableName).insert(batch);
}
}
await db.raw("PRAGMA foreign_keys = ON");
res.status(200).send(success("数据库导入成功"));
} catch (err: any) {
res.status(500).send(error(err?.message || "导入失败"));
}
});

View File

@ -0,0 +1,26 @@
import express from "express";
import { error, success } from "@/lib/responseFormat";
import u from "@/utils";
import { z } from "zod";
import { validateFields } from "@/middleware/middleware";
const router = express.Router();
export default router.post(
"/",
validateFields({
vendorId: z.string(),
model: z.string(),
prompt: z.string(),
}),
async (req, res) => {
const { vendorId, model, prompt } = req.body;
const data = await u.db("o_modelPrompt").where("model", model).andWhere("vendorId", vendorId).select("*").first();
if (data) {
await u.db("o_modelPrompt").where("model", model).andWhere("vendorId", vendorId).update({ prompt });
res.status(200).send(success("绑定成功"));
} else {
await u.db("o_modelPrompt").insert({ vendorId, model, prompt });
res.status(200).send(success("绑定成功"));
}
},
);

View File

@ -0,0 +1,33 @@
import express from "express";
import u from "@/utils";
import { success } from "@/lib/responseFormat";
const router = express.Router();
export default router.post("/", async (req, res) => {
const dataList = await u.db("o_vendorConfig").select("id").where("enable", 1);
if (!dataList || dataList.length === 0) {
return res.status(404).send({ error: "模型未找到" });
}
const data = await Promise.all(
dataList.map(async (item) => {
const vendor = u.vendor.getVendor(item.id!);
const promptList = await u.db("o_modelPrompt").andWhere("vendorId", vendor.id).select("*");
const promptMap = new Map(promptList.map((p) => [p.model, p.prompt]));
const models = await u.vendor.getModelList(item.id!);
const filteredModels = models
.filter((m: any) => m.type === "video")
.map((m: any) => ({
name: m.name,
type: m.type as "image" | "video",
model: m.modelName,
prompt: promptMap.get(m.modelName) ?? "",
}));
return {
id: item.id,
name: vendor.name,
promptList: filteredModels,
};
}),
);
res.status(200).send(success(data));
});

View File

@ -41,6 +41,11 @@ export default (nsp: Namespace) => {
});
let abortController: AbortController | null = null;
const thinkConfig: agent.AgentContext["thinkConfig"] = {
think: false,
thinlLevel: 0,
};
socket.on("updateContext", (data: { isolationKey: string; projectId: number; scriptId: number }, callback) => {
isolationKey = data.isolationKey;
resTool = new ResTool(socket, {
@ -66,52 +71,14 @@ export default (nsp: Namespace) => {
abortSignal: currentController.signal,
resTool,
msg,
thinkConfig,
};
try {
const textStream = await agent.decisionAI(ctx);
let currentMsg = ctx.msg;
let text = currentMsg.text();
const syncCurrentMessage = () => {
if (ctx.msg === currentMsg) return;
text.complete();
currentMsg.complete();
currentMsg = ctx.msg;
text = currentMsg.text();
};
let aborted = false;
try {
for await (const chunk of textStream) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), 1));
syncCurrentMessage();
text.append(chunk);
}
} catch (err: any) {
if (err.name === "AbortError" || currentController.signal.aborted) {
aborted = true;
} else {
throw err;
}
} finally {
syncCurrentMessage();
if (aborted) {
text.append("[已停止]");
text.complete();
currentMsg.stop();
} else {
text.complete();
currentMsg.complete();
}
}
await agent.runDecisionAI(ctx);
} catch (err: any) {
if (err.name !== "AbortError" && !currentController.signal.aborted) {
const errorMsg = u.error(err).message;
console.error("[productionAgent] chat error:", errorMsg);
ctx.msg.text(errorMsg).complete();
ctx.msg.error();
console.error("[productionAgent] chat error:", u.error(err).message);
}
} finally {
if (abortController === currentController) {
@ -120,6 +87,12 @@ export default (nsp: Namespace) => {
}
});
socket.on("updateThinkConfig", (data: { think: boolean; thinlLevel: 0 | 1 | 2 | 3 }) => {
thinkConfig.think = data.think;
thinkConfig.thinlLevel = data.thinlLevel;
console.log("[productionAgent] 更新思考配置:", thinkConfig);
});
socket.on("stop", () => {
abortController?.abort();
abortController = null;

View File

@ -40,6 +40,11 @@ export default (nsp: Namespace) => {
});
let abortController: AbortController | null = null;
const thinkConfig: agent.AgentContext["thinkConfig"] = {
think: false,
thinlLevel: 0,
};
socket.on("chat", async (data: { content: string }) => {
const { content } = data;
abortController?.abort();
@ -55,51 +60,14 @@ export default (nsp: Namespace) => {
abortSignal: currentController.signal,
resTool,
msg,
thinkConfig,
};
try {
const textStream = await agent.decisionAI(ctx);
let currentMsg = ctx.msg;
let text = currentMsg.text();
const syncCurrentMessage = () => {
if (ctx.msg === currentMsg) return;
text.complete();
currentMsg.complete();
currentMsg = ctx.msg;
text = currentMsg.text();
};
let aborted = false;
try {
for await (const chunk of textStream) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), 1));
syncCurrentMessage();
text.append(chunk);
}
} catch (err: any) {
if (err.name === "AbortError" || currentController.signal.aborted) {
aborted = true;
} else {
throw err;
}
} finally {
syncCurrentMessage();
if (aborted) {
text.complete();
currentMsg.stop();
} else {
text.complete();
currentMsg.complete();
}
}
await agent.runDecisionAI(ctx);
} catch (err: any) {
if (err.name !== "AbortError" && !currentController.signal.aborted) {
const errorMsg = u.error(err).message;
console.error("[scriptAgent] chat error:", errorMsg);
ctx.msg.text(errorMsg).complete();
ctx.msg.error();
console.error("[scriptAgent] chat error:", u.error(err).message);
}
} finally {
if (abortController === currentController) {
@ -108,6 +76,12 @@ export default (nsp: Namespace) => {
}
});
socket.on("updateThinkConfig", (data: { think: boolean; thinlLevel: 0 | 1 | 2 | 3 }) => {
thinkConfig.think = data.think;
thinkConfig.thinlLevel = data.thinlLevel;
console.log("[scriptAgent] 更新思考配置:", thinkConfig);
});
socket.on("stop", () => {
abortController?.abort();
abortController = null;

View File

@ -1,4 +1,4 @@
// @db-hash 9248d7bcfe0a1bc57e5b9bc33d8c7d83
// @db-hash 88c167ba73e2771e7b043419ca5089dd
//该文件由脚本自动生成,请勿手动修改
export interface memories {
@ -18,9 +18,12 @@ export interface o_agentDeploy {
'disabled'?: boolean | null;
'id'?: number;
'key'?: string | null;
'maxOutputTokens'?: number | null;
'model'?: string | null;
'modelName'?: string | null;
'name'?: string | null;
'temperature'?: number | null;
'type'?: string | null;
'vendorId'?: string | null;
}
export interface o_agentWorkData {
@ -88,6 +91,12 @@ export interface o_imageFlow {
'flowData': string;
'id'?: number;
}
export interface o_modelPrompt {
'id'?: number;
'model'?: string | null;
'prompt'?: string | null;
'vendorId'?: string | null;
}
export interface o_novel {
'chapter'?: string | null;
'chapterData'?: string | null;
@ -201,6 +210,7 @@ export interface o_user {
'password'?: string | null;
}
export interface o_vendorConfig {
'code'?: string | null;
'enable'?: number | null;
'id'?: string;
'inputValues'?: string | null;
@ -240,6 +250,7 @@ export interface DB {
"o_eventChapter": o_eventChapter;
"o_image": o_image;
"o_imageFlow": o_imageFlow;
"o_modelPrompt": o_modelPrompt;
"o_novel": o_novel;
"o_outline": o_outline;
"o_outlineNovel": o_outlineNovel;

View File

@ -4,26 +4,82 @@ import axios from "axios";
import { transform } from "sucrase";
import u from "@/utils";
type AiType = "scriptAgent" | "productionAgent" | "universalAi";
type AiType =
| "scriptAgent"
| "productionAgent"
| "universalAi"
| "scriptAgent:decisionAgent"
| "scriptAgent:supervisionAgent"
| "scriptAgent:storySkeletonAgent"
| "scriptAgent:adaptationStrategyAgent"
| "scriptAgent:scriptAgent"
| "productionAgent:decisionAgent"
| "productionAgent:supervisionAgent"
| "productionAgent:deriveAssetsAgent"
| "productionAgent:generateAssetsAgent"
| "productionAgent:directorPlanAgent"
| "productionAgent:storyboardGenAgent"
| "productionAgent:storyboardPanelAgent"
| "productionAgent:storyboardTableAgent";
type FnName = "textRequest" | "imageRequest" | "videoRequest" | "ttsRequest";
const AiTypeValues: AiType[] = ["scriptAgent", "productionAgent", "universalAi"];
const AiTypeValues: AiType[] = [
"scriptAgent",
"productionAgent",
"universalAi",
"scriptAgent:decisionAgent",
"scriptAgent:supervisionAgent",
"scriptAgent:storySkeletonAgent",
"scriptAgent:adaptationStrategyAgent",
"scriptAgent:scriptAgent",
"productionAgent:decisionAgent",
"productionAgent:supervisionAgent",
"productionAgent:deriveAssetsAgent",
"productionAgent:generateAssetsAgent",
"productionAgent:directorPlanAgent",
"productionAgent:storyboardGenAgent",
"productionAgent:storyboardPanelAgent",
"productionAgent:storyboardTableAgent",
"universalAi",
];
async function resolveModelName(value: AiType | `${string}:${string}`): Promise<`${string}:${string}`> {
if (AiTypeValues.includes(value as AiType)) {
const agentDeployData = await u.db("o_agentDeploy").where("key", value).first();
if (!agentDeployData?.modelName) throw new Error(`${value}模型未配置`);
return agentDeployData.modelName as `${number}:${string}`;
let modelName = null;
if (!agentDeployData?.modelName) {
const [mainly] = agentDeployData!.key!.split(/:(.+)/);
const mainlyData = await u.db("o_agentDeploy").where("key", mainly).first();
if (!mainlyData?.modelName) throw new Error(`未找到部署配置 ${value}`);
modelName = mainlyData.modelName;
}
modelName = agentDeployData?.modelName || modelName;
return modelName as `${number}:${string}`;
}
return value as `${number}:${string}`;
}
async function getModelConfig(value: AiType | `${string}:${string}`) {
if (AiTypeValues.includes(value as AiType)) {
const agentDeployData = await u.db("o_agentDeploy").where("key", value).first();
if (!agentDeployData?.modelName) {
const [mainly] = agentDeployData!.key!.split(/:(.+)/);
const mainlyData = await u.db("o_agentDeploy").where("key", mainly).first();
if (!mainlyData?.modelName) throw new Error(`未找到部署配置 ${value}`);
return mainlyData;
}
return agentDeployData;
}
return null;
}
async function getVendorTemplateFn(
fnName: "textRequest",
modelName: `${string}:${string}`,
): Promise<(think?: boolean, thinkLevel?: 0 | 1 | 2 | 3) => any>;
async function getVendorTemplateFn(fnName: Exclude<FnName, "textRequest">, modelName: `${string}:${string}`): Promise<(input: any) => any>;
async function getVendorTemplateFn(fnName: FnName, modelName: `${string}:${string}`): Promise<any> {
const [id, name] = modelName.split(":");
const [id, name] = modelName.split(/:(.+)/);
const vendorConfigData = await u.db("o_vendorConfig").where("id", id).first();
if (!vendorConfigData) throw new Error(`未找到供应商配置 id=${id}`);
const modelList = await u.vendor.getModelList(id);
@ -55,7 +111,7 @@ async function withTaskRecord<T>(
fn: (modelName: `${string}:${string}`, think: Boolean, thinkLevel: 0 | 1 | 2 | 3) => Promise<T>,
): Promise<T> {
const modelName = await resolveModelName(modelKey);
const [id, model] = modelName.split(":");
const [_, model] = modelName.split(/:(.+)/);
const taskRecord = await u.task(projectId, taskClass, model, { describe: describe, content: relatedObjects });
try {
const result = await fn(modelName, false, 0);
@ -89,46 +145,37 @@ class AiText {
this.think = think;
this.thinkLevel = thinkLevel;
}
async invoke(input: Omit<Parameters<typeof generateText>[0], "model">) {
private async resolveModel(middleware?: any | any[]) {
const switchAiDevTool = await u.db("o_setting").where("key", "switchAiDevTool").first();
const modelName = await resolveModelName(this.AiType);
const sdkFn = await getVendorTemplateFn("textRequest", modelName);
const baseModel = await sdkFn(this.think, this.thinkLevel);
const mws = [
...(switchAiDevTool?.value === "1" ? [devToolsMiddleware()] : []),
...(middleware ? (Array.isArray(middleware) ? middleware : [middleware]) : []),
];
return mws.length > 0 ? wrapLanguageModel({ model: baseModel, middleware: mws.length === 1 ? mws[0] : mws }) : baseModel;
}
async invoke(input: Omit<Parameters<typeof generateText>[0], "model">) {
const config = await getModelConfig(this.AiType);
return generateText({
...(input.tools && { stopWhen: stepCountIs(Object.keys(input.tools).length * 50) }),
...input,
model:
switchAiDevTool?.value === "1"
? wrapLanguageModel({
model: await sdkFn(this.think, this.thinkLevel),
middleware: devToolsMiddleware(),
})
: await sdkFn(this.think, this.thinkLevel),
model: await this.resolveModel(),
...(config?.temperature && { temperature: config.temperature }),
...(config?.maxOutputTokens && { maxOutputTokens: config.maxOutputTokens }),
} as Parameters<typeof generateText>[0]);
}
async stream(input: Omit<Parameters<typeof streamText>[0], "model">) {
const switchAiDevTool = await u.db("o_setting").where("key", "switchAiDevTool").first();
const modelName = await resolveModelName(this.AiType);
const sdkFn = await getVendorTemplateFn("textRequest", modelName);
const config = await getModelConfig(this.AiType);
return streamText({
...(input.tools && { stopWhen: stepCountIs(Object.keys(input.tools).length * 50) }),
...input,
model:
switchAiDevTool?.value == "1"
? wrapLanguageModel({
model: sdkFn(this.think, this.thinkLevel),
middleware: [
devToolsMiddleware(),
extractReasoningMiddleware({
tagName: "reasoning_content",
}),
],
})
: wrapLanguageModel({
model: sdkFn(this.think, this.thinkLevel),
middleware: extractReasoningMiddleware({
tagName: "reasoning_content",
}),
}),
model: await this.resolveModel(extractReasoningMiddleware({ tagName: "reasoning_content", separator: "\n" })),
...(config?.temperature && { temperature: config.temperature }),
...(config?.maxOutputTokens && { maxOutputTokens: config.maxOutputTokens }),
} as Parameters<typeof streamText>[0]);
}
}
@ -168,7 +215,7 @@ class AiImage {
const modelName = await resolveModelName(this.key);
const exec = async (mn: `${string}:${string}`) => {
const fn = await getVendorTemplateFn("imageRequest", mn);
await referenceList2imageBase642(mn.split(":")[0], input);
await referenceList2imageBase642(mn.split(/:(.+)/)[0], input);
this.result = await fn(input);
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
return this;
@ -212,7 +259,8 @@ class AiVideo {
const modelName = await resolveModelName(this.key);
const exec = async (mn: `${string}:${string}`) => {
const fn = await getVendorTemplateFn("videoRequest", mn);
await referenceList2imageBase642(mn.split(":")[0], input);
await referenceList2imageBase642(mn.split(/:(.+)/)[0], input);
this.result = await fn(input);
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
return this;
@ -237,7 +285,7 @@ class AiAudio {
const modelName = await resolveModelName(this.key);
const exec = async (mn: `${string}:${string}`) => {
const fn = await getVendorTemplateFn("ttsRequest", mn);
await referenceList2imageBase642(mn.split(":")[0], input);
await referenceList2imageBase642(mn.split(/:(.+)/)[0], input);
this.result = await fn(input);
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
return this;

View File

@ -50,7 +50,9 @@ class OSS {
await this.ensureInit();
const safePath = normalizeUserPath(userRelPath);
// URL 始终使用 /,所以这里需要将系统分隔符转回 /
let url = `http://127.0.0.1:10588/${prefix}/`;
let url = `/${prefix}/`;
if (process.env.ossURL && process.env.ossURL !== "") url = process.env.ossURL + `/${prefix}/`;
if (process.env.NODE_ENV == "dev") url = `http://localhost:10588/${prefix}/`;
if (isEletron()) url = `http://localhost:${process.env.PORT}/${prefix}/`;
return `${url}${safePath.split(path.sep).join("/")}`;
}

View File

@ -54,8 +54,8 @@ export default function runCode(code: string, vendor?: Record<string, any>) {
return exports as Record<string, any>;
}
export function logger(logstring: string) {
console.log("【VM】" + logstring);
export function logger(logstring: any) {
console.log("【VM】" + JSON.stringify(logstring));
}
/**
* size

View File

@ -1338,10 +1338,10 @@ basic-auth@~2.0.1:
dependencies:
safe-buffer "5.1.2"
better-sqlite3@^12.8.0:
version "12.8.0"
resolved "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-12.8.0.tgz#ec9ccd4a426a35f3b9355c147af6c92a6ddd6862"
integrity sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==
better-sqlite3@^12.9.0:
version "12.9.0"
resolved "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-12.9.0.tgz#32498c99ba3fb36f604fbb5c70667c5f68c00414"
integrity sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==
dependencies:
bindings "^1.5.0"
prebuild-install "^7.1.1"