panrouter 4.0.0 → 4.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 +48 -43
- package/package.json +1 -1
- package/server.mjs +2 -0
- package/tray-daemon.ps1 +58 -50
package/cli.mjs
CHANGED
|
@@ -66,43 +66,31 @@ async function isPortOpen() {
|
|
|
66
66
|
function stopAll() {
|
|
67
67
|
log("..", "正在停止所有 Pan Router 进程...", "yellow");
|
|
68
68
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
execSync("taskkill /f /im powershell.exe >nul 2>&1", { stdio: "pipe" });
|
|
73
|
-
} catch {}
|
|
74
|
-
try {
|
|
75
|
-
const out = execSync(
|
|
76
|
-
'wmic process where "name=\'node.exe\'" get ProcessId,CommandLine /format:csv 2>nul',
|
|
77
|
-
{ encoding: "utf8", windowsHide: true, timeout: 5000 }
|
|
78
|
-
);
|
|
79
|
-
for (const line of out.split("\n")) {
|
|
80
|
-
if (line.includes("server.mjs")) {
|
|
81
|
-
const m = line.match(/(\d+),.*?server\.mjs/);
|
|
82
|
-
if (m) try { process.kill(parseInt(m[1]), "SIGKILL"); } catch {}
|
|
83
|
-
}
|
|
69
|
+
if (process.platform === "win32") {
|
|
70
|
+
execSync('wmic process where "name=\'node.exe\' and CommandLine like \'%server.mjs%\'" call terminate >nul 2>&1', { stdio: "pipe" });
|
|
71
|
+
execSync('wmic process where "name=\'powershell.exe\' and CommandLine like \'%tray-daemon.ps1%\'" call terminate >nul 2>&1', { stdio: "pipe" });
|
|
84
72
|
}
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
log("OK", "已停止所有进程", "green");
|
|
74
|
+
} catch (e) {
|
|
75
|
+
log("!!", "停止进程时遇到问题", "red");
|
|
76
|
+
}
|
|
87
77
|
}
|
|
88
78
|
|
|
89
79
|
function showStatus() {
|
|
90
80
|
console.log(`\n\x1b[36m=== Pan Router 状态 ===\x1b[0m\n`);
|
|
91
81
|
try {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const m = line.match(/(\d+),/);
|
|
100
|
-
if (m) nodePids.push(m[1]);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (nodePids.length > 0) log("OK", `代理服务 (Node): 运行中 [PID: ${nodePids.join(", ")}]`, "green");
|
|
82
|
+
const nodeOut = execSync('wmic process where "name=\'node.exe\' and CommandLine like \'%server.mjs%\'" get ProcessId 2>nul').toString();
|
|
83
|
+
const psOut = execSync('wmic process where "name=\'powershell.exe\' and CommandLine like \'%tray-daemon.ps1%\'" get ProcessId 2>nul').toString();
|
|
84
|
+
|
|
85
|
+
const nodePids = nodeOut.match(/\d+/g) || [];
|
|
86
|
+
const psPids = psOut.match(/\d+/g) || [];
|
|
87
|
+
|
|
88
|
+
if (nodePids.length > 0) log("OK", `代理服务 (Node): 运行中 [PID: ${nodePids.join(', ')}]`, "green");
|
|
104
89
|
else log("!!", "代理服务 (Node): 未运行", "red");
|
|
105
|
-
|
|
90
|
+
|
|
91
|
+
if (psPids.length > 0) log("OK", `系统托盘 (PowerShell): 运行中 [PID: ${psPids.join(', ')}]`, "green");
|
|
92
|
+
else log("!!", "系统托盘 (PowerShell): 未运行", "red");
|
|
93
|
+
} catch (e) {
|
|
106
94
|
log("!!", "无法获取状态", "red");
|
|
107
95
|
}
|
|
108
96
|
console.log("");
|
|
@@ -112,6 +100,7 @@ function openLogs() {
|
|
|
112
100
|
const logFile = path.join(process.env.TEMP, "panrouter_tray.log");
|
|
113
101
|
if (fs.existsSync(logFile)) {
|
|
114
102
|
execSync(`start notepad "${logFile}"`);
|
|
103
|
+
log("OK", "日志已在记事本中打开", "green");
|
|
115
104
|
} else {
|
|
116
105
|
log("!!", "暂无托盘日志文件", "red");
|
|
117
106
|
}
|
|
@@ -122,7 +111,11 @@ async function startServer() {
|
|
|
122
111
|
stopAll();
|
|
123
112
|
|
|
124
113
|
log("..", "正在启动代理...", "yellow");
|
|
125
|
-
|
|
114
|
+
if (process.platform === "win32") {
|
|
115
|
+
execSync(`start "Pan Router" cmd /c "node ${serverPath} & pause"`, { stdio: "pipe" });
|
|
116
|
+
} else {
|
|
117
|
+
spawn("node", [serverPath], { cwd: __dirname, stdio: "ignore", detached: true }).unref();
|
|
118
|
+
}
|
|
126
119
|
|
|
127
120
|
for (let i = 0; i < 15; i++) {
|
|
128
121
|
if (await isPortOpen()) break;
|
|
@@ -138,17 +131,20 @@ async function startTray() {
|
|
|
138
131
|
stopAll();
|
|
139
132
|
log("..", "正在后台启动代理...", "yellow");
|
|
140
133
|
|
|
141
|
-
// UTF-8 BOM
|
|
134
|
+
// 追加 UTF-8 BOM 修复乱码
|
|
142
135
|
try {
|
|
143
136
|
const psContent = fs.readFileSync(psPath, "utf8");
|
|
144
|
-
if (psContent.
|
|
137
|
+
if (!psContent.startsWith("")) {
|
|
145
138
|
const bom = Buffer.from([0xEF, 0xBB, 0xBF]);
|
|
146
139
|
fs.writeFileSync(psPath, Buffer.concat([bom, Buffer.from(psContent, "utf8")]));
|
|
147
140
|
}
|
|
148
|
-
} catch {}
|
|
141
|
+
} catch (e) { }
|
|
149
142
|
|
|
150
143
|
const srv = spawn(process.execPath, [serverPath], {
|
|
151
|
-
cwd: __dirname,
|
|
144
|
+
cwd: __dirname,
|
|
145
|
+
stdio: "ignore",
|
|
146
|
+
windowsHide: true,
|
|
147
|
+
detached: true
|
|
152
148
|
});
|
|
153
149
|
srv.unref();
|
|
154
150
|
|
|
@@ -161,15 +157,20 @@ async function startTray() {
|
|
|
161
157
|
if (ok) log("OK", "代理服务已就绪!(端口 50816)", "green");
|
|
162
158
|
else log("!!", "服务启动超时,但仍将尝试加载托盘", "red");
|
|
163
159
|
|
|
164
|
-
log("..", "
|
|
160
|
+
log("..", "正在加载系统托盘与控制台引擎...", "yellow");
|
|
165
161
|
|
|
166
162
|
const tray = spawn("powershell.exe", [
|
|
167
|
-
"-NoProfile",
|
|
168
|
-
"-
|
|
163
|
+
"-NoProfile",
|
|
164
|
+
"-STA",
|
|
165
|
+
"-ExecutionPolicy", "Bypass",
|
|
166
|
+
"-WindowStyle", "Hidden",
|
|
167
|
+
"-File", `"${psPath}"`
|
|
169
168
|
], {
|
|
170
|
-
cwd: __dirname,
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
cwd: __dirname,
|
|
170
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
171
|
+
windowsHide: true,
|
|
172
|
+
shell: true,
|
|
173
|
+
env: { ...process.env, PANROUTER_NODE: process.execPath }
|
|
173
174
|
});
|
|
174
175
|
|
|
175
176
|
let psOutput = "";
|
|
@@ -193,10 +194,11 @@ function printHelp() {
|
|
|
193
194
|
|
|
194
195
|
| 指令 | 功能 |
|
|
195
196
|
|------|------|
|
|
196
|
-
| \x1b[33mpanrouter\x1b[0m | 自动检测 Claude Code → 安装(如需要) → 配路由 → 启托盘 |
|
|
197
|
+
| \x1b[33mpanrouter\x1b[0m | 🔥 自动检测 Claude Code → 安装(如需要) → 配路由 → 启托盘 |
|
|
197
198
|
| \x1b[33mpanrouter --setup\x1b[0m | 只配路由(恢复配置用) |
|
|
198
199
|
| \x1b[33mpanrouter --status\x1b[0m | 查看运行状态 + PID |
|
|
199
200
|
| \x1b[33mpanrouter --stop\x1b[0m | 停止所有进程 |
|
|
201
|
+
| \x1b[33mpanrouter --restart\x1b[0m | 重启托盘 |
|
|
200
202
|
| \x1b[33mpanrouter --server\x1b[0m | 前台窗口模式 |
|
|
201
203
|
| \x1b[33mpanrouter --logs\x1b[0m | 打开日志 |
|
|
202
204
|
| \x1b[33mpanrouter --version\x1b[0m | 版本号 |
|
|
@@ -208,7 +210,7 @@ async function main() {
|
|
|
208
210
|
const args = process.argv.slice(2);
|
|
209
211
|
const cmd = args[0];
|
|
210
212
|
|
|
211
|
-
switch
|
|
213
|
+
switch(cmd) {
|
|
212
214
|
case "--help":
|
|
213
215
|
case "-h":
|
|
214
216
|
printHelp();
|
|
@@ -229,6 +231,9 @@ async function main() {
|
|
|
229
231
|
case "--logs":
|
|
230
232
|
openLogs();
|
|
231
233
|
break;
|
|
234
|
+
case "--restart":
|
|
235
|
+
await startTray();
|
|
236
|
+
break;
|
|
232
237
|
case "--server":
|
|
233
238
|
await startServer();
|
|
234
239
|
break;
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -305,6 +305,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
305
305
|
}
|
|
306
306
|
} catch(e) {}
|
|
307
307
|
|
|
308
|
+
// 按时间倒序,返回最近 100 条给前端展示
|
|
308
309
|
history.sort((a,b) => b.ts - a.ts);
|
|
309
310
|
const recent = history.slice(0, 100);
|
|
310
311
|
|
|
@@ -380,6 +381,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
380
381
|
}
|
|
381
382
|
});
|
|
382
383
|
|
|
384
|
+
// 请求结束时记录统计数据
|
|
383
385
|
upstream.stream.on("end", () => {
|
|
384
386
|
if (state.model) {
|
|
385
387
|
const inTokens = state.usage?.input_tokens || 0;
|
package/tray-daemon.ps1
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Pan Router 托盘守护脚本 (原生桌面控制台版)
|
|
3
|
+
Pan Router 托盘守护脚本 (原生桌面控制台版 - 作用域修复)
|
|
4
4
|
#>
|
|
5
5
|
$ErrorActionPreference = "Stop"
|
|
6
6
|
|
|
@@ -68,8 +68,13 @@ try {
|
|
|
68
68
|
$notifyIcon.Icon = [System.Drawing.SystemIcons]::Shield
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
# ======
|
|
71
|
+
# ====== 【原生 WinForms 数据面板 (修复变量回收 Bug)】 ======
|
|
72
72
|
$global:dashForm = $null
|
|
73
|
+
$script:lblReq = $null
|
|
74
|
+
$script:lblIn = $null
|
|
75
|
+
$script:lblOut = $null
|
|
76
|
+
$script:cmbPeriod = $null
|
|
77
|
+
$script:lv = $null
|
|
73
78
|
|
|
74
79
|
function Show-Dashboard {
|
|
75
80
|
if ($global:dashForm -ne $null -and -not $global:dashForm.IsDisposed) {
|
|
@@ -93,25 +98,25 @@ try {
|
|
|
93
98
|
$grpSummary.Size = New-Object System.Drawing.Size(515, 65)
|
|
94
99
|
$form.Controls.Add($grpSummary)
|
|
95
100
|
|
|
96
|
-
$lblReq = New-Object System.Windows.Forms.Label
|
|
97
|
-
$lblReq.Location = New-Object System.Drawing.Point(20, 28)
|
|
98
|
-
$lblReq.Size = New-Object System.Drawing.Size(140, 20)
|
|
99
|
-
$lblReq.Text = "请求总数: -"
|
|
100
|
-
$grpSummary.Controls.Add($lblReq)
|
|
101
|
-
|
|
102
|
-
$lblIn = New-Object System.Windows.Forms.Label
|
|
103
|
-
$lblIn.Location = New-Object System.Drawing.Point(160, 28)
|
|
104
|
-
$lblIn.Size = New-Object System.Drawing.Size(160, 20)
|
|
105
|
-
$lblIn.Text = "输入 Token: -"
|
|
106
|
-
$lblIn.ForeColor = [System.Drawing.Color]::MediumBlue
|
|
107
|
-
$grpSummary.Controls.Add($lblIn)
|
|
108
|
-
|
|
109
|
-
$lblOut = New-Object System.Windows.Forms.Label
|
|
110
|
-
$lblOut.Location = New-Object System.Drawing.Point(340, 28)
|
|
111
|
-
$lblOut.Size = New-Object System.Drawing.Size(160, 20)
|
|
112
|
-
$lblOut.Text = "输出 Token: -"
|
|
113
|
-
$lblOut.ForeColor = [System.Drawing.Color]::ForestGreen
|
|
114
|
-
$grpSummary.Controls.Add($lblOut)
|
|
101
|
+
$script:lblReq = New-Object System.Windows.Forms.Label
|
|
102
|
+
$script:lblReq.Location = New-Object System.Drawing.Point(20, 28)
|
|
103
|
+
$script:lblReq.Size = New-Object System.Drawing.Size(140, 20)
|
|
104
|
+
$script:lblReq.Text = "请求总数: -"
|
|
105
|
+
$grpSummary.Controls.Add($script:lblReq)
|
|
106
|
+
|
|
107
|
+
$script:lblIn = New-Object System.Windows.Forms.Label
|
|
108
|
+
$script:lblIn.Location = New-Object System.Drawing.Point(160, 28)
|
|
109
|
+
$script:lblIn.Size = New-Object System.Drawing.Size(160, 20)
|
|
110
|
+
$script:lblIn.Text = "输入 Token: -"
|
|
111
|
+
$script:lblIn.ForeColor = [System.Drawing.Color]::MediumBlue
|
|
112
|
+
$grpSummary.Controls.Add($script:lblIn)
|
|
113
|
+
|
|
114
|
+
$script:lblOut = New-Object System.Windows.Forms.Label
|
|
115
|
+
$script:lblOut.Location = New-Object System.Drawing.Point(340, 28)
|
|
116
|
+
$script:lblOut.Size = New-Object System.Drawing.Size(160, 20)
|
|
117
|
+
$script:lblOut.Text = "输出 Token: -"
|
|
118
|
+
$script:lblOut.ForeColor = [System.Drawing.Color]::ForestGreen
|
|
119
|
+
$grpSummary.Controls.Add($script:lblOut)
|
|
115
120
|
|
|
116
121
|
$lblFilter = New-Object System.Windows.Forms.Label
|
|
117
122
|
$lblFilter.Text = "时间筛选:"
|
|
@@ -119,55 +124,58 @@ try {
|
|
|
119
124
|
$lblFilter.AutoSize = $true
|
|
120
125
|
$form.Controls.Add($lblFilter)
|
|
121
126
|
|
|
122
|
-
$cmbPeriod = New-Object System.Windows.Forms.ComboBox
|
|
123
|
-
$cmbPeriod.Items.AddRange(@("最近 1 天", "最近 7 天", "最近 30 天", "全部时间"))
|
|
124
|
-
$cmbPeriod.SelectedIndex = 3
|
|
125
|
-
$cmbPeriod.Location = New-Object System.Drawing.Point(80, 85)
|
|
126
|
-
$cmbPeriod.Size = New-Object System.Drawing.Size(120, 20)
|
|
127
|
-
$cmbPeriod.DropDownStyle = 'DropDownList'
|
|
128
|
-
$form.Controls.Add($cmbPeriod)
|
|
129
|
-
|
|
130
|
-
$lv = New-Object System.Windows.Forms.ListView
|
|
131
|
-
$lv.Location = New-Object System.Drawing.Point(15, 115)
|
|
132
|
-
$lv.Size = New-Object System.Drawing.Size(515, 310)
|
|
133
|
-
$lv.View = 'Details'
|
|
134
|
-
$lv.FullRowSelect = $true
|
|
135
|
-
$lv.GridLines = $true
|
|
136
|
-
$lv.Columns.Add("时间", 135) | Out-Null
|
|
137
|
-
$lv.Columns.Add("模型", 185) | Out-Null
|
|
138
|
-
$lv.Columns.Add("输入", 85) | Out-Null
|
|
139
|
-
$lv.Columns.Add("输出", 85) | Out-Null
|
|
140
|
-
$form.Controls.Add($lv)
|
|
127
|
+
$script:cmbPeriod = New-Object System.Windows.Forms.ComboBox
|
|
128
|
+
$script:cmbPeriod.Items.AddRange(@("最近 1 天", "最近 7 天", "最近 30 天", "全部时间"))
|
|
129
|
+
$script:cmbPeriod.SelectedIndex = 3
|
|
130
|
+
$script:cmbPeriod.Location = New-Object System.Drawing.Point(80, 85)
|
|
131
|
+
$script:cmbPeriod.Size = New-Object System.Drawing.Size(120, 20)
|
|
132
|
+
$script:cmbPeriod.DropDownStyle = 'DropDownList'
|
|
133
|
+
$form.Controls.Add($script:cmbPeriod)
|
|
134
|
+
|
|
135
|
+
$script:lv = New-Object System.Windows.Forms.ListView
|
|
136
|
+
$script:lv.Location = New-Object System.Drawing.Point(15, 115)
|
|
137
|
+
$script:lv.Size = New-Object System.Drawing.Size(515, 310)
|
|
138
|
+
$script:lv.View = 'Details'
|
|
139
|
+
$script:lv.FullRowSelect = $true
|
|
140
|
+
$script:lv.GridLines = $true
|
|
141
|
+
$script:lv.Columns.Add("时间", 135) | Out-Null
|
|
142
|
+
$script:lv.Columns.Add("模型", 185) | Out-Null
|
|
143
|
+
$script:lv.Columns.Add("输入", 85) | Out-Null
|
|
144
|
+
$script:lv.Columns.Add("输出", 85) | Out-Null
|
|
145
|
+
$form.Controls.Add($script:lv)
|
|
141
146
|
|
|
142
147
|
$updateData = {
|
|
148
|
+
if ($null -eq $script:cmbPeriod -or $null -eq $script:cmbPeriod.SelectedItem) { return }
|
|
149
|
+
|
|
143
150
|
$map = @{"最近 1 天"="1"; "最近 7 天"="7"; "最近 30 天"="30"; "全部时间"="all"}
|
|
144
|
-
$p = $map[$cmbPeriod.SelectedItem.ToString()]
|
|
151
|
+
$p = $map[$script:cmbPeriod.SelectedItem.ToString()]
|
|
152
|
+
|
|
145
153
|
try {
|
|
146
154
|
$data = Invoke-RestMethod -Uri "http://127.0.0.1:50816/api/stats?period=$p" -Method Get -ErrorAction Stop
|
|
147
155
|
|
|
148
|
-
$lblReq.Text = "请求总数: {0:N0}" -f $data.totalReq
|
|
149
|
-
$lblIn.Text = "输入 Token: {0:N0}" -f $data.totalIn
|
|
150
|
-
$lblOut.Text = "输出 Token: {0:N0}" -f $data.totalOut
|
|
156
|
+
$script:lblReq.Text = "请求总数: {0:N0}" -f $data.totalReq
|
|
157
|
+
$script:lblIn.Text = "输入 Token: {0:N0}" -f $data.totalIn
|
|
158
|
+
$script:lblOut.Text = "输出 Token: {0:N0}" -f $data.totalOut
|
|
151
159
|
|
|
152
|
-
$lv.Items.Clear()
|
|
160
|
+
$script:lv.Items.Clear()
|
|
153
161
|
if ($data.recent) {
|
|
154
|
-
$lv.BeginUpdate()
|
|
162
|
+
$script:lv.BeginUpdate()
|
|
155
163
|
foreach ($r in $data.recent) {
|
|
156
164
|
$dt = [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddMilliseconds($r.ts))
|
|
157
165
|
$item = New-Object System.Windows.Forms.ListViewItem($dt.ToString("yyyy-MM-dd HH:mm:ss"))
|
|
158
166
|
$item.SubItems.Add($r.m) | Out-Null
|
|
159
167
|
$item.SubItems.Add(("{0:N0} ↑" -f $r.i)) | Out-Null
|
|
160
168
|
$item.SubItems.Add(("{0:N0} ↓" -f $r.o)) | Out-Null
|
|
161
|
-
$lv.Items.Add($item) | Out-Null
|
|
169
|
+
$script:lv.Items.Add($item) | Out-Null
|
|
162
170
|
}
|
|
163
|
-
$lv.EndUpdate()
|
|
171
|
+
$script:lv.EndUpdate()
|
|
164
172
|
}
|
|
165
173
|
} catch {
|
|
166
|
-
$lblReq.Text = "
|
|
174
|
+
$script:lblReq.Text = "无法连接统计接口,请检查服务状态"
|
|
167
175
|
}
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
$cmbPeriod.Add_SelectedIndexChanged($updateData)
|
|
178
|
+
$script:cmbPeriod.Add_SelectedIndexChanged($updateData)
|
|
171
179
|
& $updateData
|
|
172
180
|
|
|
173
181
|
$form.Show()
|