# Conflicts:
#	src/router.ts
This commit is contained in:
zhishi 2026-03-24 23:00:39 +08:00
commit 9ae2933d43
23 changed files with 92346 additions and 3269 deletions

2
.gitignore vendored
View File

@ -36,6 +36,8 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
build/*
data/serve/
upload/*
uploads/*

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

67731
data/web/ts.worker-DGHjMaqB.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -58,6 +58,7 @@ RUN apk add --no-cache nginx supervisor && \
# 复制后端文件
COPY --from=builder /app/build ./build
COPY --from=builder /app/data/serve ./data/serve
COPY --from=builder /app/package.json ./
COPY --from=builder /app/yarn.lock ./
@ -100,7 +101,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:app]
command=pm2-runtime start build/app.js --name app
command=pm2-runtime start data/serve/app.js --name app
directory=/app
autostart=true
autorestart=true

View File

@ -32,6 +32,7 @@ RUN apk add --no-cache nginx supervisor && \
# 复制后端文件
COPY --from=builder /app/build ./build
COPY --from=builder /app/data/serve ./data/serve
COPY --from=builder /app/package.json ./
COPY --from=builder /app/yarn.lock ./
@ -74,7 +75,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:app]
command=pm2-runtime start build/app.js --name app
command=pm2-runtime start data/serve/app.js --name app
directory=/app
autostart=true
autorestart=true

View File

@ -22,6 +22,15 @@ files:
asar: true
extraResources:
- from: data
to: data
filter:
- "**/*"
- "!db2.sqlite"
- "!logs/**"
- "!oss/**"
win:
target:
- target: nsis
@ -72,4 +81,4 @@ linux:
category: Development
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
publish: null
publish: null

8246
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "toonflow-app",
"version": "1.0.7",
"name": "toonflow",
"version": "1.0.8",
"description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。",
"author": "HBAI-Ltd <ltlctools@outlook.com>",
"homepage": "https://github.com/HBAI-Ltd/Toonflow-app#readme",
@ -20,6 +20,7 @@
"scripts": {
"dev": "nodemon --inspect --exec tsx src/app.ts",
"dev:gui": "electronmon -r tsx scripts/main.ts",
"dev:gui-vite": "cross-env VITE_DEV=1 electronmon -r tsx scripts/main.ts",
"lint": "tsc --noEmit",
"build": "cross-env NODE_ENV=prod tsx scripts/build.ts",
"pack": "electron-builder --dir",
@ -27,7 +28,7 @@
"dist:win": "yarn build && electron-builder --win",
"dist:mac": "yarn build && electron-builder --mac",
"dist:linux": "yarn build && electron-builder --linux",
"test": "cross-env NODE_ENV=prod node build/app.js",
"test": "cross-env NODE_ENV=prod node data/serve/app.js",
"docker:build": "docker-compose -f docker/docker-compose.yml up -d --build",
"docker:local": "docker-compose -f docker/docker-compose.local.yml up -d --build",
"debug:ai": "npx @ai-sdk/devtools",
@ -46,7 +47,7 @@
"ai": "^6.0.67",
"axios": "^1.13.2",
"axios-retry": "^4.5.0",
"better-sqlite3": "^12.6.2",
"better-sqlite3": "^12.8.0",
"compressing": "^2.1.0",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
@ -58,14 +59,13 @@
"is-path-inside": "^4.0.0",
"js-md5": "^0.8.3",
"jsonwebtoken": "^9.0.3",
"knex": "^3.1.0",
"knex": "^3.2.5",
"lodash": "^4.17.23",
"morgan": "^1.10.1",
"qwen-ai-provider-v5": "^2.1.0",
"serialize-error": "^13.0.1",
"sharp": "^0.34.5",
"socket.io": "^4.8.3",
"sqlite3": "^5.1.7",
"sucrase": "^3.35.1",
"uuid": "^13.0.0",
"vm2": "^3.10.5",
@ -89,4 +89,4 @@
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
}

View File

@ -22,6 +22,7 @@ if (!fs.existsSync(envFile)) {
const external = [
"electron",
"@huggingface/transformers",
"onnxruntime-node",
"vm2",
"sqlite3",
"better-sqlite3",
@ -41,7 +42,7 @@ const appBuildConfig: esbuild.BuildOptions = {
minify: false,
format: "cjs",
allowOverwrite: true,
outfile: `build/app.js`,
outfile: `data/serve/app.js`,
platform: "node",
target: "esnext",
tsconfig: "./tsconfig.json",

View File

@ -1,11 +1,80 @@
import { app, BrowserWindow } from "electron";
import path from "path";
import startServe, { closeServe } from "src/app";
import { number } from "zod";
import fs from "fs";
import Module from "module";
// 默认端口配置
const defaultPort = 10588;
/**
* extraResources data
*/
function initializeData(): void {
const srcDir = path.join(process.resourcesPath, "data");
const destDir = path.join(app.getPath("userData"), "data");
if (fs.existsSync(destDir)) return;
copyDirRecursive(srcDir, destDir);
}
function copyDirRecursive(src: string, dest: string): void {
if (!fs.existsSync(src)) return;
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirRecursive(srcPath, destPath);
} else if (!fs.existsSync(destPath)) {
fs.copyFileSync(srcPath, destPath);
}
}
}
//获取全部依赖路径,优先从 unpacked 加载原生模块,其他模块从 asar 加载
function getNodeModulesPaths(): string[] {
const paths: string[] = [];
if (app.isPackaged) {
// external 依赖(原生模块)在 unpacked 目录
const unpackedNodeModules = path.join(process.resourcesPath, "app.asar.unpacked", "node_modules");
if (fs.existsSync(unpackedNodeModules)) {
paths.push(unpackedNodeModules);
}
// 普通依赖在 asar 内
const asarNodeModules = path.join(process.resourcesPath, "app.asar", "node_modules");
paths.push(asarNodeModules);
} else {
paths.push(path.join(process.cwd(), "node_modules"));
}
return paths;
}
//动态加载
function requireWithCustomPaths(modulePath: string): any {
const appNodeModulesPaths = getNodeModulesPaths();
// 保存原始方法
const originalNodeModulePaths = (Module as any)._nodeModulePaths;
// 临时修改模块路径解析
(Module as any)._nodeModulePaths = function (from: string): string[] {
const paths = originalNodeModulePaths.call(this, from);
// 将主程序的 node_modules 添加到前面
for (let i = appNodeModulesPaths.length - 1; i >= 0; i--) {
const p = appNodeModulesPaths[i];
if (!paths.includes(p)) {
paths.unshift(p);
}
}
return paths;
};
try {
// 清除缓存确保加载最新
delete require.cache[require.resolve(modulePath)];
return require(modulePath);
} finally {
// 恢复原始方法
(Module as any)._nodeModulePaths = originalNodeModulePaths;
}
}
function createMainWindow(port: any): void {
const win = new BrowserWindow({
width: 900,
@ -13,29 +82,37 @@ function createMainWindow(port: any): void {
show: true,
autoHideMenuBar: true,
});
// 开发环境和生产环境使用不同的路径
win.webContents.on("did-start-loading", () => {
void win.webContents.executeJavaScript(`window.$electron = true; window.$port = ${port};`);
});
const isDev = process.env.NODE_ENV === "dev" || !app.isPackaged;
const htmlPath = isDev
? path.join(process.cwd(), "scripts", "web", "index.html")
: path.join(app.getAppPath(), "scripts", "web", "index.html");
// 使用实际端口构建地址
const baseUrl = `http://localhost:${port}`;
const wsBaseUrl = `ws://localhost:${port}`;
// 构建带有 query 参数的 URL
const url = new URL(`file://${htmlPath}`);
url.searchParams.set("baseUrl", baseUrl);
url.searchParams.set("wsBaseUrl", wsBaseUrl);
console.log("%c Line:30 🥓 url", "background:#33a5ff", url.toString());
void win.loadURL(url.toString());
if (process.env.VITE_DEV) {
void win.loadURL("http://localhost:50188");
} else {
const htmlPath = isDev ? path.join(process.cwd(), "data", "web", "index.html") : path.join(app.getPath("userData"), "data", "web", "index.html");
void win.loadFile(htmlPath);
}
}
let closeServeFn: (() => Promise<void>) | undefined;
app.whenReady().then(async () => {
try {
const port = await startServe(false);
createMainWindow(10588);
let servePath: string;
if (app.isPackaged) {
// 生产环境:从 extraResources 初始化数据到用户目录,然后从用户目录加载后端服务
initializeData();
servePath = path.join(app.getPath("userData"), "data", "serve", "app.js");
} else {
// 开发环境直接加载源码tsx 通过 -r tsx 注册了 require 钩子)
servePath = path.join(process.cwd(), "src", "app.ts");
}
// 使用自定义路径加载模块
const mod = requireWithCustomPaths(servePath);
closeServeFn = mod.closeServe;
const port = await mod.default(true);
console.log("%c Line:112 🍇 port", "background:#2eafb0", port);
createMainWindow(port);
} catch (err) {
console.error("[服务启动失败]:", err);
// 如果服务启动失败,使用默认端口创建窗口
@ -55,5 +132,5 @@ app.on("activate", () => {
});
app.on("before-quit", async (event) => {
await closeServe();
if (closeServeFn) await closeServeFn();
});

View File

@ -17,6 +17,7 @@ const app = express();
const server = http.createServer(app);
export default async function startServe(randomPort: Boolean = false) {
console.log("%c Line:20 🍰 randomPort", "background:#b03734", randomPort);
const io = new Server(server, { cors: { origin: "*" } });
socketInit(io);

View File

@ -1,30 +1,30 @@
import { serializeError } from "serialize-error";
// 处理未捕获的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
console.error('[未处理的 Promise 拒绝]');
process.on("unhandledRejection", (reason, promise) => {
console.error("[未处理的 Promise 拒绝]");
if (reason instanceof Error) {
console.error('错误名称:', reason.name);
console.error('错误消息:', reason.message);
console.error('堆栈信息:', reason.stack);
console.error('序列化详情:', JSON.stringify(serializeError(reason), null, 2));
console.error("错误名称:", reason.name);
console.error("错误消息:", reason.message);
console.error("堆栈信息:", reason.stack);
console.error("序列化详情:", JSON.stringify(serializeError(reason), null, 2));
} else {
console.error('原因:', reason);
console.error('类型:', typeof reason);
console.error("原因:", reason);
console.error("类型:", typeof reason);
try {
console.error('JSON:', JSON.stringify(reason, null, 2));
} catch {
console.error('(无法序列化)');
console.error("JSON:", JSON.stringify(reason, null, 2));
} catch {
console.error("(无法序列化)");
}
}
console.error('Promise:', promise);
console.error("Promise:", promise);
});
// 处理未捕获的异常
process.on('uncaughtException', (error) => {
console.error('[未捕获的异常]');
console.error('错误名称:', error.name);
console.error('错误消息:', error.message);
console.error('堆栈信息:', error.stack);
console.error('序列化详情:', JSON.stringify(serializeError(error), null, 2));
process.on("uncaughtException", (error) => {
console.error("[未捕获的异常]");
console.error("错误名称:", error.name);
console.error("错误消息:", error.message);
console.error("堆栈信息:", error.stack);
console.error("序列化详情:", JSON.stringify(serializeError(error), null, 2));
});

View File

@ -1,4 +1,4 @@
// @routes-hash 27c3b6ded51fc0de03bec6512e84485b
// @routes-hash 074af9af2c664d3497c2c676a3423399
import { Express } from "express";
import route1 from "./routes/agents/clearMemory";
@ -40,10 +40,10 @@ import route36 from "./routes/novel/updateNovel";
import route37 from "./routes/other/deleteAllData";
import route38 from "./routes/other/getCaptcha";
import route39 from "./routes/production/assets/getAssetsData";
import route40 from "./routes/production/editImage/generateStoryboardImage";
import route41 from "./routes/production/editImage/getStoryboardFlow";
import route42 from "./routes/production/editImage/saveStoryboardFlow";
import route43 from "./routes/production/editImage/updateStoryboardFlow";
import route40 from "./routes/production/editStoryboard/generateStoryboardImage";
import route41 from "./routes/production/editStoryboard/getStoryboardFlow";
import route42 from "./routes/production/editStoryboard/saveStoryboardFlow";
import route43 from "./routes/production/editStoryboard/updateStoryboardFlow";
import route44 from "./routes/production/exportImage";
import route45 from "./routes/production/getFlowData";
import route46 from "./routes/production/getProductionData";
@ -70,20 +70,21 @@ import route66 from "./routes/setting/agentDeploy/agentSetKey";
import route67 from "./routes/setting/agentDeploy/deployAgentModel";
import route68 from "./routes/setting/agentDeploy/getAgentDeploy";
import route69 from "./routes/setting/dbConfig/clearData";
import route70 from "./routes/setting/getTextModel";
import route71 from "./routes/setting/loginConfig/getUser";
import route72 from "./routes/setting/loginConfig/updateUserPwd";
import route73 from "./routes/setting/memoryConfig/getMemory";
import route74 from "./routes/setting/memoryConfig/sureMemory";
import route75 from "./routes/setting/vendorConfig/addVendor";
import route76 from "./routes/setting/vendorConfig/deleteVendor";
import route77 from "./routes/setting/vendorConfig/getVendorList";
import route78 from "./routes/setting/vendorConfig/modelTest";
import route79 from "./routes/setting/vendorConfig/updateVendor";
import route80 from "./routes/task/getTaskApi";
import route81 from "./routes/task/getTaskCategories";
import route82 from "./routes/task/taskDetails";
import route83 from "./routes/test/test";
import route70 from "./routes/setting/fileManagement/openFolder";
import route71 from "./routes/setting/getTextModel";
import route72 from "./routes/setting/loginConfig/getUser";
import route73 from "./routes/setting/loginConfig/updateUserPwd";
import route74 from "./routes/setting/memoryConfig/getMemory";
import route75 from "./routes/setting/memoryConfig/sureMemory";
import route76 from "./routes/setting/vendorConfig/addVendor";
import route77 from "./routes/setting/vendorConfig/deleteVendor";
import route78 from "./routes/setting/vendorConfig/getVendorList";
import route79 from "./routes/setting/vendorConfig/modelTest";
import route80 from "./routes/setting/vendorConfig/updateVendor";
import route81 from "./routes/task/getTaskApi";
import route82 from "./routes/task/getTaskCategories";
import route83 from "./routes/task/taskDetails";
import route84 from "./routes/test/test";
export default async (app: Express) => {
app.use("/api/agents/clearMemory", route1);
@ -125,10 +126,10 @@ export default async (app: Express) => {
app.use("/api/other/deleteAllData", route37);
app.use("/api/other/getCaptcha", route38);
app.use("/api/production/assets/getAssetsData", route39);
app.use("/api/production/editImage/generateStoryboardImage", route40);
app.use("/api/production/editImage/getStoryboardFlow", route41);
app.use("/api/production/editImage/saveStoryboardFlow", route42);
app.use("/api/production/editImage/updateStoryboardFlow", route43);
app.use("/api/production/editStoryboard/generateStoryboardImage", route40);
app.use("/api/production/editStoryboard/getStoryboardFlow", route41);
app.use("/api/production/editStoryboard/saveStoryboardFlow", route42);
app.use("/api/production/editStoryboard/updateStoryboardFlow", route43);
app.use("/api/production/exportImage", route44);
app.use("/api/production/getFlowData", route45);
app.use("/api/production/getProductionData", route46);
@ -155,18 +156,19 @@ export default async (app: Express) => {
app.use("/api/setting/agentDeploy/deployAgentModel", route67);
app.use("/api/setting/agentDeploy/getAgentDeploy", route68);
app.use("/api/setting/dbConfig/clearData", route69);
app.use("/api/setting/getTextModel", route70);
app.use("/api/setting/loginConfig/getUser", route71);
app.use("/api/setting/loginConfig/updateUserPwd", route72);
app.use("/api/setting/memoryConfig/getMemory", route73);
app.use("/api/setting/memoryConfig/sureMemory", route74);
app.use("/api/setting/vendorConfig/addVendor", route75);
app.use("/api/setting/vendorConfig/deleteVendor", route76);
app.use("/api/setting/vendorConfig/getVendorList", route77);
app.use("/api/setting/vendorConfig/modelTest", route78);
app.use("/api/setting/vendorConfig/updateVendor", route79);
app.use("/api/task/getTaskApi", route80);
app.use("/api/task/getTaskCategories", route81);
app.use("/api/task/taskDetails", route82);
app.use("/api/test/test", route83);
app.use("/api/setting/fileManagement/openFolder", route70);
app.use("/api/setting/getTextModel", route71);
app.use("/api/setting/loginConfig/getUser", route72);
app.use("/api/setting/loginConfig/updateUserPwd", route73);
app.use("/api/setting/memoryConfig/getMemory", route74);
app.use("/api/setting/memoryConfig/sureMemory", route75);
app.use("/api/setting/vendorConfig/addVendor", route76);
app.use("/api/setting/vendorConfig/deleteVendor", route77);
app.use("/api/setting/vendorConfig/getVendorList", route78);
app.use("/api/setting/vendorConfig/modelTest", route79);
app.use("/api/setting/vendorConfig/updateVendor", route80);
app.use("/api/task/getTaskApi", route81);
app.use("/api/task/getTaskCategories", route82);
app.use("/api/task/taskDetails", route83);
app.use("/api/test/test", route84);
}

View File

@ -42,7 +42,7 @@ export default router.post(
}
//连接旧数据库
db2 = knex({
client: "sqlite3",
client: "better-sqlite3",
connection: {
filename: db2Path,
},

View File

@ -0,0 +1,32 @@
import express from "express";
import { z } from "zod";
import { exec } from "child_process";
import { success, error } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
import { isEletron } from "@/utils/getPath";
import u from "@/utils";
import path from "path";
const router = express.Router();
export default router.post(
"/",
validateFields({
path: z.string(),
}),
async (req, res) => {
if (!isEletron()) {
return res.status(400).send(error("仅支持客户端打开文件夹"));
}
const { path: folderPath } = req.body;
const platform = process.platform;
const target = u.getPath(folderPath);
console.log("%c Line:23 🎂 target", "background:#fca650", target);
const cmd = platform === "win32" ? `explorer "${target}"` : platform === "darwin" ? `open "${target}"` : `xdg-open "${target}"`;
exec(cmd, (err) => {
if (err) {
return res.status(200).send(error(err.message));
}
res.status(200).send(success("打开文件夹成功"));
});
},
);

View File

@ -1,3 +1,4 @@
import * as ONNX_WEB from "onnxruntime-web";
import { pipeline, env as transformersEnv, FeatureExtractionPipeline } from "@huggingface/transformers";
import path from "path";
import fs from "fs";

View File

@ -26,7 +26,7 @@ if (!fs.existsSync(dbPath)) {
}
const db = knex({
client: "sqlite3",
client: "better-sqlite3",
connection: {
filename: dbPath,
},

View File

@ -1,20 +1,35 @@
import path from "path";
import isPathInside from "is-path-inside";
export default (fileName?: string[] | string) => {
let dbPath: string;
let basePath: string;
if (typeof process.versions?.electron !== "undefined") {
const { app } = require("electron");
const userDataDir: string = app.getPath("userData");
dbPath = path.join(userDataDir, "data");
basePath = path.join(userDataDir, "data");
} else {
dbPath = path.join(process.cwd(), "data");
basePath = path.join(process.cwd(), "data");
}
if (fileName) {
let dbPath: string;
if (Array.isArray(fileName)) {
dbPath = path.join(dbPath, ...fileName);
dbPath = path.resolve(basePath, ...fileName);
} else {
dbPath = path.join(dbPath, fileName);
dbPath = path.resolve(basePath, fileName);
}
if (!isPathInside(dbPath, basePath) && dbPath !== basePath) {
throw new Error("路径逃逸错误,路径必须在数据目录内");
}
return dbPath;
}
return dbPath;
return basePath;
};
export function isEletron() {
if (typeof process.versions?.electron !== "undefined") {
const { app } = require("electron");
return true;
} else {
return false;
}
}

889
yarn.lock

File diff suppressed because it is too large Load Diff