完善打包流程
This commit is contained in:
parent
f1389a370d
commit
029b5de84c
2
.gitignore
vendored
2
.gitignore
vendored
@ -36,6 +36,8 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
build/*
|
||||
|
||||
data/serve/
|
||||
|
||||
upload/*
|
||||
uploads/*
|
||||
|
||||
|
||||
93
data/web/css.worker-BvV5MPou.js
Normal file
93
data/web/css.worker-BvV5MPou.js
Normal file
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
470
data/web/html.worker-BLJhxQJQ.js
Normal file
470
data/web/html.worker-BLJhxQJQ.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
58
data/web/json.worker-usMZ-FED.js
Normal file
58
data/web/json.worker-usMZ-FED.js
Normal file
File diff suppressed because one or more lines are too long
67731
data/web/ts.worker-DGHjMaqB.js
Normal file
67731
data/web/ts.worker-DGHjMaqB.js
Normal file
File diff suppressed because one or more lines are too long
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -72,4 +72,4 @@ linux:
|
||||
category: Development
|
||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||
|
||||
publish: null
|
||||
publish: null
|
||||
8246
package-lock.json
generated
Normal file
8246
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -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",
|
||||
@ -27,7 +27,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 +46,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 +58,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",
|
||||
|
||||
@ -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",
|
||||
|
||||
120
scripts/main.ts
120
scripts/main.ts
@ -1,11 +1,87 @@
|
||||
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");
|
||||
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,
|
||||
@ -15,27 +91,29 @@ function createMainWindow(port: any): void {
|
||||
});
|
||||
// 开发环境和生产环境使用不同的路径
|
||||
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());
|
||||
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(false);
|
||||
console.log("%c Line:37 🍺 port", "background:#e41a6a", port);
|
||||
createMainWindow(port);
|
||||
} catch (err) {
|
||||
console.error("[服务启动失败]:", err);
|
||||
// 如果服务启动失败,使用默认端口创建窗口
|
||||
@ -55,5 +133,5 @@ app.on("activate", () => {
|
||||
});
|
||||
|
||||
app.on("before-quit", async (event) => {
|
||||
await closeServe();
|
||||
if (closeServeFn) await closeServeFn();
|
||||
});
|
||||
|
||||
36
src/err.ts
36
src/err.ts
@ -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));
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// @routes-hash 055f8c83508ff9dfc8b98eb5108287f7
|
||||
// @routes-hash 074af9af2c664d3497c2c676a3423399
|
||||
import { Express } from "express";
|
||||
|
||||
import route1 from "./routes/agents/clearMemory";
|
||||
@ -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);
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ export default router.post(
|
||||
}
|
||||
//连接旧数据库
|
||||
db2 = knex({
|
||||
client: "sqlite3",
|
||||
client: "better-sqlite3",
|
||||
connection: {
|
||||
filename: db2Path,
|
||||
},
|
||||
|
||||
28
src/routes/setting/fileManagement/openFolder.ts
Normal file
28
src/routes/setting/fileManagement/openFolder.ts
Normal file
@ -0,0 +1,28 @@
|
||||
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";
|
||||
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 cmd = platform === "win32" ? `explorer "${folderPath}"` : platform === "darwin" ? `open "${folderPath}"` : `xdg-open "${folderPath}"`;
|
||||
exec(cmd, (err) => {
|
||||
if (err) {
|
||||
return res.status(200).send(error(err.message));
|
||||
}
|
||||
res.status(200).send(success("打开文件夹成功"));
|
||||
});
|
||||
},
|
||||
);
|
||||
9
src/types/database.d.ts
vendored
9
src/types/database.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
// @db-hash 47c0e014bdbd44b60c4ebc95f4d99e0e
|
||||
// @db-hash f6a9a8164252ce954394431079615459
|
||||
//该文件由脚本自动生成,请勿手动修改
|
||||
|
||||
export interface memories {
|
||||
@ -32,14 +32,11 @@ export interface o_agentWorkData {
|
||||
'updateTime'?: number | null;
|
||||
}
|
||||
export interface o_artStyle {
|
||||
'fileUrl'?: string | null;
|
||||
'id'?: number;
|
||||
'label'?: string | null;
|
||||
'name'?: string | null;
|
||||
'prompt'?: string | null;
|
||||
'styles'?: string | null;
|
||||
}
|
||||
export interface o_assets {
|
||||
'assetsId'?: number | null;
|
||||
'describe'?: string | null;
|
||||
'id'?: number;
|
||||
'imageId'?: number | null;
|
||||
@ -48,7 +45,9 @@ export interface o_assets {
|
||||
'prompt'?: string | null;
|
||||
'remark'?: string | null;
|
||||
'scriptId'?: number | null;
|
||||
'sonId'?: number | null;
|
||||
'startTime'?: number | null;
|
||||
'state'?: string | null;
|
||||
'type'?: string | null;
|
||||
}
|
||||
export interface o_assets2Storyboard {
|
||||
|
||||
49
src/utils/agent/embedding copy.ts
Normal file
49
src/utils/agent/embedding copy.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { pipeline, env as transformersEnv, FeatureExtractionPipeline } from "@huggingface/transformers";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import getPath from "@/utils/getPath";
|
||||
import db from "@/utils/db";
|
||||
|
||||
// ── 模型配置 ──
|
||||
// const modelOnnxFile = ["all-MiniLM-L6-v2", "onnx", "model_fp16.onnx"]; // 模型文件路径
|
||||
// const modelDtype = "fp16" as const; // 量化类型:fp32
|
||||
let extractor: FeatureExtractionPipeline | null = null;
|
||||
|
||||
export async function initEmbedding(): Promise<void> {
|
||||
if (extractor) return;
|
||||
|
||||
const modelConfigData = await db("o_setting").whereIn("key", ["modelOnnxFile", "modelDtype"]);
|
||||
const modelObj: Record<string, string> = {};
|
||||
Object.entries(modelConfigData).forEach(([key, value]) => {
|
||||
modelObj[key] = value as string;
|
||||
});
|
||||
let modelOnnxFile = modelObj?.modelOnnxFile ? JSON.parse(modelObj.modelOnnxFile) : ["all-MiniLM-L6-v2", "onnx", "model_fp16.onnx"]; // 模型文件路径
|
||||
let modelDtype = modelObj?.modelDtype ?? ("fp16" as const); // 量化类型:fp32
|
||||
const onnxPath = path.join(getPath("models"), ...modelOnnxFile);
|
||||
if (!fs.existsSync(onnxPath)) {
|
||||
throw new Error(`Embedding 模型文件不存在: ${onnxPath}`);
|
||||
}
|
||||
|
||||
transformersEnv.allowRemoteModels = false;
|
||||
transformersEnv.allowLocalModels = true;
|
||||
transformersEnv.localModelPath = getPath("models").replace(/\\/g, "/") + "/";
|
||||
|
||||
const modelFolder = modelOnnxFile[0];
|
||||
// @ts-ignore - pipeline 重载联合类型过于复杂
|
||||
extractor = await pipeline("feature-extraction", modelFolder, { dtype: modelDtype });
|
||||
}
|
||||
|
||||
export async function getEmbedding(text: string): Promise<number[]> {
|
||||
if (!extractor) await initEmbedding();
|
||||
const output = await extractor!(text, { pooling: "mean", normalize: true });
|
||||
return Array.from(output.data as Float32Array);
|
||||
}
|
||||
|
||||
export function cosineSimilarity(a: number[], b: number[]): number {
|
||||
return a.reduce((dot, v, i) => dot + v * b[i], 0);
|
||||
}
|
||||
|
||||
export async function disposeEmbedding(): Promise<void> {
|
||||
await extractor?.dispose?.();
|
||||
extractor = null;
|
||||
}
|
||||
@ -1,3 +1,7 @@
|
||||
import * as ONNX_WEB from "onnxruntime-web";
|
||||
// 强制 @huggingface/transformers 使用 onnxruntime-web 而非 onnxruntime-node
|
||||
(globalThis as any)[Symbol.for("onnxruntime")] = ONNX_WEB;
|
||||
|
||||
import { pipeline, env as transformersEnv, FeatureExtractionPipeline } from "@huggingface/transformers";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
@ -26,7 +26,7 @@ if (!fs.existsSync(dbPath)) {
|
||||
}
|
||||
|
||||
const db = knex({
|
||||
client: "sqlite3",
|
||||
client: "better-sqlite3",
|
||||
connection: {
|
||||
filename: dbPath,
|
||||
},
|
||||
|
||||
@ -18,3 +18,12 @@ export default (fileName?: string[] | string) => {
|
||||
}
|
||||
return dbPath;
|
||||
};
|
||||
|
||||
export function isEletron() {
|
||||
if (typeof process.versions?.electron !== "undefined") {
|
||||
const { app } = require("electron");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user