This commit is contained in:
liaibo
2025-12-06 16:47:17 +08:00
parent 15921bfeb7
commit 9ef8e06d67
75 changed files with 559237 additions and 93 deletions
+120 -2
View File
@@ -16,12 +16,67 @@ const { spawn } = require("child_process");
const path = require("path");
const net = require("net");
const { URL } = require("url");
const fs = require("fs");
const rootDir = path.resolve(__dirname, "..");
const frontendDir = path.join(rootDir, "wolai-frontend");
const backendDir = path.join(rootDir, "wolai-backend");
const mineruDir = path.join(rootDir, "services", "mineru");
const baseEnv = { ...process.env };
const frontendEnv = { ...baseEnv };
const backendEnv = { ...baseEnv };
const mineruEnv = { ...baseEnv };
const pythonBin = process.env.PYTHON_BIN || "python";
function loadEnvFile(filePath, targetEnv, { overrideSupabase = false } = {}) {
if (!fs.existsSync(filePath)) {
return;
}
const content = fs.readFileSync(filePath, "utf8");
content.split(/\r?\n/).forEach((line) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) return;
const eqIndex = trimmed.indexOf("=");
if (eqIndex <= 0) return;
const key = trimmed.slice(0, eqIndex).trim();
const value = trimmed.slice(eqIndex + 1).trim();
const hasValue = Boolean(targetEnv[key]);
const shouldForce =
overrideSupabase && key.startsWith("SUPABASE_") && (!hasValue || targetEnv[key].includes("supabase.co"));
if (!hasValue || shouldForce) {
targetEnv[key] = value;
}
});
}
// 强制后端优先使用本地 .env 中的 Supabase 配置,避免系统环境变量指向线上实例。
loadEnvFile(path.join(backendDir, ".env"), backendEnv, { overrideSupabase: true });
loadEnvFile(path.join(rootDir, ".env.local"), backendEnv);
loadEnvFile(path.join(rootDir, ".env.local"), frontendEnv);
loadEnvFile(path.join(rootDir, ".env.local"), mineruEnv);
function resolvePythonBin() {
const suffixes =
process.platform === "win32"
? [
["venv", "Scripts", "python.exe"],
[".venv", "Scripts", "python.exe"],
]
: [
["venv", "bin", "python3"],
["venv", "bin", "python"],
[".venv", "bin", "python3"],
[".venv", "bin", "python"],
];
const candidates = suffixes.map((parts) => path.join(backendDir, ...parts));
for (const candidate of candidates) {
if (fs.existsSync(candidate)) {
return candidate;
}
}
return null;
}
const pythonBin = process.env.PYTHON_BIN || resolvePythonBin() || "python";
const celeryBin = process.env.CELERY_BIN || "celery";
const skipCelery =
(process.env.SKIP_CELERY || "").toLowerCase() === "1" ||
@@ -34,6 +89,7 @@ const tasks = [
name: "frontend",
command: process.env.FRONTEND_CMD || "pnpm dev",
cwd: frontendDir,
env: frontendEnv,
},
{
name: "backend",
@@ -41,9 +97,59 @@ const tasks = [
process.env.BACKEND_CMD ||
`${pythonBin} -m uvicorn app.main:app --reload --port 8000`,
cwd: backendDir,
env: backendEnv,
},
];
function resolveMineru() {
// 优先使用带 ROCm 的全局 venv,找不到再回退 services/mineru/.venv
const rocmPython = path.join("F:\\", "rocm", ".venv-rocm312", "Scripts", "python.exe");
const localVenvPython = path.join(mineruDir, ".venv", "Scripts", "python.exe");
const pythonCandidates = [rocmPython, localVenvPython].filter((p) => fs.existsSync(p));
const venvPython = pythonCandidates[0];
const srcDir = path.join(mineruDir, "src");
if (!venvPython || !fs.existsSync(srcDir)) {
logPrefix(
"mineru",
"未找到 MinerU 虚拟环境或源码,已跳过自动启动。请先运行 services/mineru/setup.ps1",
);
return null;
}
const endpoint = mineruEnv.MINERU_ENDPOINT || "http://127.0.0.1:18888";
const url = new URL(endpoint);
const host = url.hostname || "127.0.0.1";
const port = url.port || "18888";
const command =
process.env.MINERU_CMD ||
`${venvPython} -m uvicorn mineru.cli.fast_api:app --host ${host} --port ${port}`;
const modelPath = path.join(mineruDir, "models", "MinerU2.5-2509-1.2B");
const configPath = path.join(mineruDir, "mineru.json");
return {
name: "mineru",
command,
cwd: srcDir,
env: {
...mineruEnv,
PYTHONPATH: srcDir,
MINERU_ENDPOINT: endpoint,
// 默认使用本地 VLM 模型
MINERU_TOOLS_CONFIG_JSON: configPath,
MINERU_MODEL_SOURCE: "local",
MINERU_MODEL_PATH: modelPath,
MINERU_DEFAULT_BACKEND: "vlm-transformers",
MINERU_TIMEOUT_SECONDS: mineruEnv.MINERU_TIMEOUT_SECONDS || "600",
},
};
}
const mineruTask = resolveMineru();
if (mineruTask) {
tasks.unshift(mineruTask);
backendEnv.MINERU_ENDPOINT = mineruTask.env.MINERU_ENDPOINT;
}
console.log(`[system] Python 解释器:${pythonBin}`);
const children = [];
let shuttingDown = false;
@@ -57,7 +163,7 @@ function startTask(task) {
cwd: task.cwd,
stdio: "inherit",
shell: true,
env: { ...process.env },
env: task.env ? { ...task.env } : { ...process.env },
});
child.on("exit", (code, signal) => {
@@ -133,6 +239,7 @@ async function checkRedisReachable(urlString, timeoutMs = 2000) {
}
async function main() {
let celeryScheduled = false;
if (!skipCelery) {
const celeryTask = {
name: "celery",
@@ -142,8 +249,10 @@ async function main() {
if (celeryCmdFromEnv) {
tasks.push(celeryTask);
celeryScheduled = true;
} else if (await checkRedisReachable(redisUrl)) {
tasks.push(celeryTask);
celeryScheduled = true;
} else {
// Redis 未就绪时直接跳过 Celery,避免热调试流程整体退出。
logPrefix(
@@ -153,6 +262,15 @@ async function main() {
}
}
if (!celeryScheduled) {
backendEnv.CELERY_TASK_ALWAYS_EAGER = "1";
backendEnv.CELERY_TASK_EAGER_PROPAGATES = backendEnv.CELERY_TASK_EAGER_PROPAGATES || "1";
logPrefix(
"backend",
"未启动 Celery,自动启用 task_always_eager 模式用于同步执行后台任务。",
);
}
if (tasks.length === 0) {
console.error("未配置任何可运行的任务,检查环境变量设置。");
process.exit(1);