panrouter 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.mjs +13 -6
- package/package.json +3 -4
- package/pool-worker.mjs +16 -13
- package/setup-9router.cjs +192 -0
- package/9router-init.mjs +0 -127
package/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import fs from "node:fs";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
|
-
|
|
10
|
+
const ROUTER_PORT = 20128;
|
|
11
11
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
@@ -112,10 +112,13 @@ function openLogs() {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
async function startServer() {
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
const setupScript = path.join(__dirname, "setup-9router.cjs");
|
|
116
|
+
if (fs.existsSync(setupScript)) {
|
|
117
|
+
execSync(`node "${setupScript}"`, { stdio: "inherit", timeout: 180000 });
|
|
118
|
+
} else {
|
|
119
|
+
spawn("9router", [], { detached: true, stdio: "ignore" }).unref();
|
|
120
|
+
}
|
|
117
121
|
|
|
118
|
-
log("..", "正在启动 9router 代理...", "yellow");
|
|
119
122
|
for (let i = 0; i < 15; i++) {
|
|
120
123
|
if (await isPortOpen()) break;
|
|
121
124
|
await new Promise(rs => setTimeout(rs, 1000));
|
|
@@ -126,8 +129,12 @@ async function startServer() {
|
|
|
126
129
|
async function startTray() {
|
|
127
130
|
const psPath = path.join(__dirname, "tray-daemon.ps1");
|
|
128
131
|
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
const setupScript = path.join(__dirname, "setup-9router.cjs");
|
|
133
|
+
if (fs.existsSync(setupScript)) {
|
|
134
|
+
execSync(`node "${setupScript}"`, { stdio: "inherit", timeout: 180000 });
|
|
135
|
+
} else {
|
|
136
|
+
spawn("9router", [], { detached: true, stdio: "ignore" }).unref();
|
|
137
|
+
}
|
|
131
138
|
log("..", "正在后台启动代理...", "yellow");
|
|
132
139
|
|
|
133
140
|
let ok = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panrouter",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"cli.mjs",
|
|
11
11
|
"pool-worker.mjs",
|
|
12
|
-
"9router
|
|
12
|
+
"setup-9router.cjs",
|
|
13
13
|
"tray-daemon.ps1"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"ws": "^8.18.0"
|
|
17
|
-
"9router": "^1.0.0"
|
|
16
|
+
"ws": "^8.18.0"
|
|
18
17
|
},
|
|
19
18
|
"license": "MIT"
|
|
20
19
|
}
|
package/pool-worker.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import fs from "node:fs";
|
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import crypto from "node:crypto";
|
|
20
20
|
import WebSocket from "ws";
|
|
21
|
-
|
|
21
|
+
const ROUTER_PORT = 20128;
|
|
22
22
|
|
|
23
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
const HOME_DIR = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
@@ -404,29 +404,32 @@ function isPortOpen(port) {
|
|
|
404
404
|
// ─── 确保 9router 在运行 ─────────────────────────────────────────────────
|
|
405
405
|
|
|
406
406
|
async function ensureServer() {
|
|
407
|
-
// 如果已经在监听,先检查是不是 9router
|
|
408
407
|
if (await isPortOpen(SERVER_PORT)) {
|
|
409
408
|
log(`端口 ${SERVER_PORT} 已就绪,跳过启动`, "OK");
|
|
410
409
|
return true;
|
|
411
410
|
}
|
|
412
411
|
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
412
|
+
// 第一次运行 / 升级后 → 运行你的完整安装脚本(检查、安装、注入 DB、启动)
|
|
413
|
+
const setupScript = path.join(__dirname, "setup-9router.cjs");
|
|
414
|
+
if (fs.existsSync(setupScript)) {
|
|
415
|
+
log("正在执行 9router 部署脚本...");
|
|
416
|
+
try {
|
|
417
|
+
execSync(`node "${setupScript}"`, { stdio: "inherit", timeout: 180000 });
|
|
418
|
+
} catch (e) {
|
|
419
|
+
log(`9router 部署脚本执行出错: ${e.message},尝试直接启动`, "WARN");
|
|
420
|
+
}
|
|
419
421
|
}
|
|
420
422
|
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
spawn9router();
|
|
424
|
-
|
|
425
|
-
for (let i = 0; i < 20; i++) {
|
|
423
|
+
// 如果脚本没拉起 9router,手动拉一次
|
|
424
|
+
for (let i = 0; i < 15; i++) {
|
|
426
425
|
if (await isPortOpen(SERVER_PORT)) {
|
|
427
426
|
log(`9router 已就绪 (端口 ${SERVER_PORT})`, "OK");
|
|
428
427
|
return true;
|
|
429
428
|
}
|
|
429
|
+
if (i === 3) {
|
|
430
|
+
log("尝试直接启动 9router...");
|
|
431
|
+
spawn("9router", [], { detached: true, stdio: "ignore" }).unref();
|
|
432
|
+
}
|
|
430
433
|
await new Promise((r) => setTimeout(r, 1000));
|
|
431
434
|
}
|
|
432
435
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// 9Router 全自动部署脚本 (官方架构 1:1 复刻原版直写,零进程唤醒)
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const { execSync } = require("child_process");
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// 核心配置 (可根据需要自行修改)
|
|
12
|
+
// ============================================================================
|
|
13
|
+
const CONFIG = {
|
|
14
|
+
port: 20128,
|
|
15
|
+
password: "123456",
|
|
16
|
+
provider: {
|
|
17
|
+
provider: "deepseek",
|
|
18
|
+
authType: "apikey",
|
|
19
|
+
name: "1",
|
|
20
|
+
apiKey: "1",
|
|
21
|
+
priority: 1
|
|
22
|
+
},
|
|
23
|
+
combo: {
|
|
24
|
+
name: "combo",
|
|
25
|
+
models: ["oc/minimax-m3-free", "mmf/mimo-auto", "oc/deepseek-v4-flash-free"]
|
|
26
|
+
},
|
|
27
|
+
aliases: [
|
|
28
|
+
["deepseek-v4-flash-free", "oc/deepseek-v4-flash-free"],
|
|
29
|
+
["minimax-m3-free", "oc/minimax-m3-free"],
|
|
30
|
+
]
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const IS_WIN = process.platform === "win32";
|
|
34
|
+
const HOME = os.homedir();
|
|
35
|
+
const DATA_DIR = IS_WIN ? path.join(process.env.APPDATA || path.join(HOME, "AppData", "Roaming"), "9router") : path.join(HOME, ".9router");
|
|
36
|
+
const DB_PATH = path.join(DATA_DIR, "db", "data.sqlite");
|
|
37
|
+
|
|
38
|
+
function newId() {
|
|
39
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
40
|
+
const r = (Math.random() * 16) | 0;
|
|
41
|
+
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// 新增:炫酷的动态加载进度条辅助函数
|
|
47
|
+
// ============================================================================
|
|
48
|
+
const { spawn } = require("child_process");
|
|
49
|
+
|
|
50
|
+
function installWithProgress(pkgName) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const child = spawn("npm", ["install", "-g", pkgName, "--prefer-online"], {
|
|
53
|
+
shell: true,
|
|
54
|
+
stdio: "ignore"
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
58
|
+
let i = 0;
|
|
59
|
+
const startTime = Date.now();
|
|
60
|
+
|
|
61
|
+
const timer = setInterval(() => {
|
|
62
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
63
|
+
process.stdout.write(`\r [~] 拼命下载安装中 ${frames[i]} (耗时: ${elapsed}s)... `);
|
|
64
|
+
i = (i + 1) % frames.length;
|
|
65
|
+
}, 100);
|
|
66
|
+
|
|
67
|
+
child.on("close", (code) => {
|
|
68
|
+
clearInterval(timer);
|
|
69
|
+
process.stdout.write("\r" + " ".repeat(50) + "\r"); // 清理进度条残留
|
|
70
|
+
if (code === 0) resolve();
|
|
71
|
+
else reject(new Error(`Exit code ${code}`));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// 主流程
|
|
78
|
+
// ============================================================================
|
|
79
|
+
async function main() {
|
|
80
|
+
console.log(`\n=========================================`);
|
|
81
|
+
console.log(` 🚀 9Router 全自动部署工具 (官方图纸复刻版)`);
|
|
82
|
+
console.log(`=========================================\n`);
|
|
83
|
+
|
|
84
|
+
// 0. 检查 Node.js 版本
|
|
85
|
+
const nodeVer = process.versions.node.split('.').map(Number);
|
|
86
|
+
if (nodeVer[0] < 22 || (nodeVer[0] === 22 && nodeVer[1] < 5)) {
|
|
87
|
+
console.error(`[X] 你的 Node.js 版本过低 (${process.version}),需要 v22.5.0+ 以启用原生 SQLite。`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 1. 自动检查并安装 9router
|
|
92
|
+
try {
|
|
93
|
+
console.log(`[~] 正在检查 9router...`);
|
|
94
|
+
execSync("9router --version", { stdio: "ignore" });
|
|
95
|
+
console.log(`[OK] 9router 已经安装在系统中。`);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.log(`[~] 发现未安装 9router,正在自动调用 npm 安装...`);
|
|
98
|
+
try {
|
|
99
|
+
await installWithProgress("9router");
|
|
100
|
+
console.log(`[OK] 9router 安装成功!`);
|
|
101
|
+
} catch (installErr) {
|
|
102
|
+
console.error(`[X] 安装 9router 失败!请手动执行 npm i -g 9router。`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 2. 暴力初始化数据库与写入配置 (100% 官方结构)
|
|
108
|
+
let db;
|
|
109
|
+
try {
|
|
110
|
+
console.log(`[~] 正在根据官方图纸生成数据库结构...`);
|
|
111
|
+
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
|
112
|
+
|
|
113
|
+
const { DatabaseSync } = require("node:sqlite");
|
|
114
|
+
db = new DatabaseSync(DB_PATH);
|
|
115
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
116
|
+
|
|
117
|
+
// 【核心】11张官方表结构全量注入 (加上 IF NOT EXISTS 确保安全)
|
|
118
|
+
const schemaSql = `
|
|
119
|
+
CREATE TABLE IF NOT EXISTS _meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
120
|
+
CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL);
|
|
121
|
+
CREATE TABLE IF NOT EXISTS providerConnections (id TEXT PRIMARY KEY, provider TEXT NOT NULL, authType TEXT NOT NULL, name TEXT, email TEXT, priority INTEGER, isActive INTEGER DEFAULT 1, data TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL);
|
|
122
|
+
CREATE TABLE IF NOT EXISTS providerNodes (id TEXT PRIMARY KEY, type TEXT, name TEXT, data TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL);
|
|
123
|
+
CREATE TABLE IF NOT EXISTS proxyPools (id TEXT PRIMARY KEY, isActive INTEGER DEFAULT 1, testStatus TEXT, data TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL);
|
|
124
|
+
CREATE TABLE IF NOT EXISTS apiKeys (id TEXT PRIMARY KEY, key TEXT UNIQUE NOT NULL, name TEXT, machineId TEXT, isActive INTEGER DEFAULT 1, createdAt TEXT NOT NULL);
|
|
125
|
+
CREATE TABLE IF NOT EXISTS combos (id TEXT PRIMARY KEY, name TEXT UNIQUE NOT NULL, kind TEXT, models TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL);
|
|
126
|
+
CREATE TABLE IF NOT EXISTS kv (scope TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (scope, key));
|
|
127
|
+
CREATE TABLE IF NOT EXISTS usageHistory (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, provider TEXT, model TEXT, connectionId TEXT, apiKey TEXT, endpoint TEXT, promptTokens INTEGER DEFAULT 0, completionTokens INTEGER DEFAULT 0, cost REAL DEFAULT 0, status TEXT, tokens TEXT, meta TEXT);
|
|
128
|
+
CREATE TABLE IF NOT EXISTS usageDaily (dateKey TEXT PRIMARY KEY, data TEXT NOT NULL);
|
|
129
|
+
CREATE TABLE IF NOT EXISTS requestDetails (id TEXT PRIMARY KEY, timestamp TEXT NOT NULL, provider TEXT, model TEXT, connectionId TEXT, status TEXT, data TEXT NOT NULL);
|
|
130
|
+
`;
|
|
131
|
+
db.exec(schemaSql);
|
|
132
|
+
|
|
133
|
+
// 3. 写入核心配置
|
|
134
|
+
db.exec("BEGIN TRANSACTION");
|
|
135
|
+
|
|
136
|
+
const now = new Date().toISOString();
|
|
137
|
+
const p = CONFIG.provider;
|
|
138
|
+
const c = CONFIG.combo;
|
|
139
|
+
|
|
140
|
+
// 写入 Provider (包含新增的 email 字段,赋空值)
|
|
141
|
+
const stmtProvider = db.prepare("INSERT OR REPLACE INTO providerConnections (id, provider, authType, name, email, priority, isActive, data, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)");
|
|
142
|
+
stmtProvider.run(newId(), p.provider, p.authType, p.name, null, p.priority, JSON.stringify({ apiKey: p.apiKey, testStatus: "unknown", providerSpecificData: {} }), now, now);
|
|
143
|
+
|
|
144
|
+
// 写入 Combo (包含新增的 kind 字段,赋空值)
|
|
145
|
+
const checkCombo = db.prepare("SELECT id FROM combos WHERE name = ?").get(c.name);
|
|
146
|
+
if (!checkCombo) {
|
|
147
|
+
const stmtCombo = db.prepare("INSERT INTO combos (id, name, kind, models, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)");
|
|
148
|
+
stmtCombo.run(newId(), c.name, null, JSON.stringify(c.models), now, now);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 写入 Aliases
|
|
152
|
+
const stmtAlias = db.prepare("INSERT OR IGNORE INTO kv (scope, key, value) VALUES ('modelAliases', ?, ?)");
|
|
153
|
+
for (const [k, v] of CONFIG.aliases) {
|
|
154
|
+
stmtAlias.run(k, JSON.stringify(v));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 写入基础设置 (密码等) - 官方规定 id 必须是 1
|
|
158
|
+
const stmtSettings = db.prepare("INSERT OR REPLACE INTO settings (id, data) VALUES (1, ?)");
|
|
159
|
+
stmtSettings.run(JSON.stringify({ rtkEnabled: true, authMode: "password" }));
|
|
160
|
+
|
|
161
|
+
db.exec("COMMIT");
|
|
162
|
+
db.close();
|
|
163
|
+
console.log(`[OK] 数据库与配置强写成功!(耗时不足 0.1 秒)`);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
if (db) { try { db.exec("ROLLBACK"); db.close(); } catch(err){} }
|
|
166
|
+
console.error(`[X] 数据库操作失败: ${e.message}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(`\n=========================================`);
|
|
171
|
+
console.log(` 🎉 部署圆满完成!`);
|
|
172
|
+
console.log(`=========================================`);
|
|
173
|
+
console.log(` 🚀 启动命令: 请在终端输入 9router`);
|
|
174
|
+
console.log(` 🌐 Dashboard: http://127.0.0.1:${CONFIG.port}/dashboard`);
|
|
175
|
+
console.log(` 🔑 管理密码: ${CONFIG.password}`);
|
|
176
|
+
console.log(`=========================================\n`);
|
|
177
|
+
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// 新增:全自动唤醒独立 CMD 窗口启动服务
|
|
180
|
+
// ============================================================================
|
|
181
|
+
if (IS_WIN) {
|
|
182
|
+
console.log(`[~] 正在为你自动拉起 9router 独立运行窗口...`);
|
|
183
|
+
const child = spawn("cmd.exe", ["/c", "start", "cmd.exe", "/k", "9router"], {
|
|
184
|
+
detached: true,
|
|
185
|
+
stdio: "ignore"
|
|
186
|
+
});
|
|
187
|
+
child.unref(); // 甩脱父进程的控制,让新窗口独立生存
|
|
188
|
+
console.log(`[OK] 9router 已启动,你可以去畅游 AI 世界了!\n`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
main().catch(e => console.error("[X] 脚本发生未知错误:", e));
|
package/9router-init.mjs
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
// ─── 9Router 数据库初始化(幂等,可重复执行)────────────────────────
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import { execSync, spawn } from "node:child_process";
|
|
6
|
-
|
|
7
|
-
const IS_WIN = process.platform === "win32";
|
|
8
|
-
const HOME = os.homedir();
|
|
9
|
-
const DATA_DIR = IS_WIN
|
|
10
|
-
? path.join(process.env.APPDATA || path.join(HOME, "AppData", "Roaming"), "9router")
|
|
11
|
-
: path.join(HOME, ".9router");
|
|
12
|
-
const DB_PATH = path.join(DATA_DIR, "db", "data.sqlite");
|
|
13
|
-
const ROUTER_PORT = 20128;
|
|
14
|
-
|
|
15
|
-
const CONFIG = {
|
|
16
|
-
password: "123456",
|
|
17
|
-
provider: {
|
|
18
|
-
provider: "deepseek",
|
|
19
|
-
authType: "apikey",
|
|
20
|
-
name: "1",
|
|
21
|
-
apiKey: "1",
|
|
22
|
-
priority: 1,
|
|
23
|
-
},
|
|
24
|
-
combo: {
|
|
25
|
-
name: "combo",
|
|
26
|
-
models: ["oc/minimax-m3-free", "mmf/mimo-auto", "oc/deepseek-v4-flash-free"],
|
|
27
|
-
},
|
|
28
|
-
aliases: [
|
|
29
|
-
["deepseek-v4-flash-free", "oc/deepseek-v4-flash-free"],
|
|
30
|
-
["minimax-m3-free", "oc/minimax-m3-free"],
|
|
31
|
-
],
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function newId() {
|
|
35
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
36
|
-
const r = (Math.random() * 16) | 0;
|
|
37
|
-
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let _initialized = false;
|
|
42
|
-
|
|
43
|
-
export async function ensure9router() {
|
|
44
|
-
if (_initialized) return true;
|
|
45
|
-
|
|
46
|
-
// 1. 检查 9router 是否已安装
|
|
47
|
-
try {
|
|
48
|
-
execSync("9router --version", { stdio: "ignore" });
|
|
49
|
-
} catch {
|
|
50
|
-
console.log("[9router] 未安装,正在自动安装...");
|
|
51
|
-
try {
|
|
52
|
-
execSync("npm install -g 9router", { stdio: "inherit", timeout: 120000 });
|
|
53
|
-
console.log("[9router] 安装成功");
|
|
54
|
-
} catch (e) {
|
|
55
|
-
console.error(`[9router] 安装失败: ${e.message}`);
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 2. 初始化数据库和配置(幂等)
|
|
61
|
-
try {
|
|
62
|
-
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
|
63
|
-
|
|
64
|
-
const { DatabaseSync } = await import("node:sqlite");
|
|
65
|
-
const db = new DatabaseSync(DB_PATH);
|
|
66
|
-
db.exec("PRAGMA journal_mode = WAL");
|
|
67
|
-
|
|
68
|
-
db.exec(`
|
|
69
|
-
CREATE TABLE IF NOT EXISTS _meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
70
|
-
CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL);
|
|
71
|
-
CREATE TABLE IF NOT EXISTS providerConnections (id TEXT PRIMARY KEY, provider TEXT NOT NULL, authType TEXT NOT NULL, name TEXT, email TEXT, priority INTEGER, isActive INTEGER DEFAULT 1, data TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL);
|
|
72
|
-
CREATE TABLE IF NOT EXISTS providerNodes (id TEXT PRIMARY KEY, type TEXT, name TEXT, data TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL);
|
|
73
|
-
CREATE TABLE IF NOT EXISTS proxyPools (id TEXT PRIMARY KEY, isActive INTEGER DEFAULT 1, testStatus TEXT, data TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL);
|
|
74
|
-
CREATE TABLE IF NOT EXISTS apiKeys (id TEXT PRIMARY KEY, key TEXT UNIQUE NOT NULL, name TEXT, machineId TEXT, isActive INTEGER DEFAULT 1, createdAt TEXT NOT NULL);
|
|
75
|
-
CREATE TABLE IF NOT EXISTS combos (id TEXT PRIMARY KEY, name TEXT UNIQUE NOT NULL, kind TEXT, models TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL);
|
|
76
|
-
CREATE TABLE IF NOT EXISTS kv (scope TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (scope, key));
|
|
77
|
-
CREATE TABLE IF NOT EXISTS usageHistory (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, provider TEXT, model TEXT, connectionId TEXT, apiKey TEXT, endpoint TEXT, promptTokens INTEGER DEFAULT 0, completionTokens INTEGER DEFAULT 0, cost REAL DEFAULT 0, status TEXT, tokens TEXT, meta TEXT);
|
|
78
|
-
CREATE TABLE IF NOT EXISTS usageDaily (dateKey TEXT PRIMARY KEY, data TEXT NOT NULL);
|
|
79
|
-
CREATE TABLE IF NOT EXISTS requestDetails (id TEXT PRIMARY KEY, timestamp TEXT NOT NULL, provider TEXT, model TEXT, connectionId TEXT, status TEXT, data TEXT NOT NULL);
|
|
80
|
-
`);
|
|
81
|
-
|
|
82
|
-
const now = new Date().toISOString();
|
|
83
|
-
db.exec("BEGIN TRANSACTION");
|
|
84
|
-
|
|
85
|
-
// Provider
|
|
86
|
-
const p = CONFIG.provider;
|
|
87
|
-
db.prepare("INSERT OR REPLACE INTO providerConnections (id, provider, authType, name, email, priority, isActive, data, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)")
|
|
88
|
-
.run(newId(), p.provider, p.authType, p.name, null, p.priority, JSON.stringify({ apiKey: p.apiKey, testStatus: "unknown", providerSpecificData: {} }), now, now);
|
|
89
|
-
|
|
90
|
-
// Combo
|
|
91
|
-
const existingCombo = db.prepare("SELECT id FROM combos WHERE name = ?").get(CONFIG.combo.name);
|
|
92
|
-
if (!existingCombo) {
|
|
93
|
-
db.prepare("INSERT INTO combos (id, name, kind, models, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)")
|
|
94
|
-
.run(newId(), CONFIG.combo.name, null, JSON.stringify(CONFIG.combo.models), now, now);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Aliases
|
|
98
|
-
for (const [k, v] of CONFIG.aliases) {
|
|
99
|
-
db.prepare("INSERT OR IGNORE INTO kv (scope, key, value) VALUES ('modelAliases', ?, ?)").run(k, JSON.stringify(v));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Settings (password)
|
|
103
|
-
db.prepare("INSERT OR REPLACE INTO settings (id, data) VALUES (1, ?)")
|
|
104
|
-
.run(JSON.stringify({ rtkEnabled: true, authMode: "password" }));
|
|
105
|
-
|
|
106
|
-
db.exec("COMMIT");
|
|
107
|
-
db.close();
|
|
108
|
-
} catch (e) {
|
|
109
|
-
console.error(`[9router] 数据库初始化失败: ${e.message}`);
|
|
110
|
-
// 不退出 — 可能 db 已存在,重试
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
_initialized = true;
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function spawn9router() {
|
|
118
|
-
const child = spawn("9router", [], {
|
|
119
|
-
detached: true,
|
|
120
|
-
stdio: "ignore",
|
|
121
|
-
windowsHide: true,
|
|
122
|
-
});
|
|
123
|
-
child.unref();
|
|
124
|
-
return child;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export { ROUTER_PORT, DATA_DIR, DB_PATH, CONFIG };
|