完成动态端口+打包

This commit is contained in:
ACT丶流星雨 2026-03-24 22:52:47 +08:00
parent 029b5de84c
commit 165fce6016
9 changed files with 341 additions and 373 deletions

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,15 @@ files:
asar: true
extraResources:
- from: data
to: data
filter:
- "**/*"
- "!db2.sqlite"
- "!logs/**"
- "!oss/**"
win:
target:
- target: nsis

View File

@ -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",
@ -88,4 +89,4 @@
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
}

View File

@ -12,6 +12,7 @@ const defaultPort = 10588;
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);
}
@ -34,20 +35,12 @@ function getNodeModulesPaths(): string[] {
const paths: string[] = [];
if (app.isPackaged) {
// external 依赖(原生模块)在 unpacked 目录
const unpackedNodeModules = path.join(
process.resourcesPath,
"app.asar.unpacked",
"node_modules"
);
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"
);
const asarNodeModules = path.join(process.resourcesPath, "app.asar", "node_modules");
paths.push(asarNodeModules);
} else {
paths.push(path.join(process.cwd(), "node_modules"));
@ -89,10 +82,16 @@ 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(), "data", "web", "index.html") : path.join(app.getPath("userData"), "data", "web", "index.html");
void win.loadFile(htmlPath);
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;
@ -111,8 +110,8 @@ app.whenReady().then(async () => {
// 使用自定义路径加载模块
const mod = requireWithCustomPaths(servePath);
closeServeFn = mod.closeServe;
const port = await mod.default(false);
console.log("%c Line:37 🍺 port", "background:#e41a6a", port);
const port = await mod.default(true);
console.log("%c Line:112 🍇 port", "background:#2eafb0", port);
createMainWindow(port);
} catch (err) {
console.error("[服务启动失败]:", err);

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

@ -4,6 +4,8 @@ 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(
@ -17,7 +19,9 @@ export default router.post(
}
const { path: folderPath } = req.body;
const platform = process.platform;
const cmd = platform === "win32" ? `explorer "${folderPath}"` : platform === "darwin" ? `open "${folderPath}"` : `xdg-open "${folderPath}"`;
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));

View File

@ -1,49 +0,0 @@
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;
}

View File

@ -1,7 +1,4 @@
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";

View File

@ -1,22 +1,28 @@
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() {