ellm-proxy 0.0.3 → 0.0.6

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.
Files changed (53) hide show
  1. package/README.md +60 -60
  2. package/bin/commands/autostart.js +37 -213
  3. package/bin/commands/config.js +8 -8
  4. package/bin/commands/pm2.js +28 -28
  5. package/bin/commands/service.js +107 -107
  6. package/bin/ellm-proxy.js +61 -61
  7. package/dist/server.js +33 -2
  8. package/dist/web-ui/assets/DashboardView-BHG4xIdN.css +1 -0
  9. package/dist/web-ui/assets/{DashboardView-B71Dy3px.js → DashboardView-C3Hw-HpG.js} +1 -1
  10. package/dist/web-ui/assets/LayoutView-BUA8-IzK.css +1 -0
  11. package/dist/web-ui/assets/LayoutView-C9phf7in.js +1 -0
  12. package/dist/web-ui/assets/LoginView-DrhfslIY.css +1 -0
  13. package/dist/web-ui/assets/LoginView-es6pSPgt.js +1 -0
  14. package/dist/web-ui/assets/{LogsView-B2ctrpv4.js → LogsView-BlXITEJH.js} +1 -1
  15. package/dist/web-ui/assets/ProcessView-Bgf7-MLQ.js +1 -0
  16. package/dist/web-ui/assets/ProcessView-DK0TvDpR.css +1 -0
  17. package/dist/web-ui/assets/{ProvidersView-mb0KU16n.js → ProvidersView-f8aZkMdw.js} +1 -1
  18. package/dist/web-ui/assets/SettingsView-BkwyoOUb.css +1 -0
  19. package/dist/web-ui/assets/SettingsView-GvAA2vLp.js +1 -0
  20. package/dist/web-ui/assets/config-Cs3_z63C.js +1 -0
  21. package/dist/web-ui/assets/{index-dPeMJjrz.js → index-GohB4JDa.js} +2 -2
  22. package/dist/web-ui/assets/{providers-Dmz2p4Wd.js → providers-Djtdy2tv.js} +1 -1
  23. package/dist/web-ui/assets/{useApi-C1Ngt_o5.js → useApi-D_iJ2Mca.js} +1 -1
  24. package/dist/web-ui/assets/useManagerApi-C1_COxKg.js +1 -0
  25. package/dist/web-ui/assets/{useToast-B3quFZyv.js → useToast-C0UMTbkx.js} +1 -1
  26. package/dist/web-ui/config.js +10 -10
  27. package/dist/web-ui/index.html +13 -13
  28. package/docs/DESIGN.md +99 -99
  29. package/manager/api/harness/backup.js +20 -20
  30. package/manager/api/harness/claude-code.js +54 -54
  31. package/manager/api/harness/codex.js +123 -123
  32. package/manager/api/harness/dsh.js +175 -175
  33. package/manager/api/harness/index.js +118 -118
  34. package/manager/api/service-settings.js +98 -0
  35. package/manager/api/service.js +3 -13
  36. package/manager/index.js +182 -170
  37. package/package.json +4 -1
  38. package/utils/autostart.js +219 -0
  39. package/utils/interpreter.js +14 -14
  40. package/utils/paths.js +45 -44
  41. package/utils/pm2.js +130 -130
  42. package/utils/service.js +205 -189
  43. package/utils/updater.js +210 -0
  44. package/dist/web-ui/assets/DashboardView-qrQrkgLA.css +0 -1
  45. package/dist/web-ui/assets/LayoutView-C03GVB3X.js +0 -1
  46. package/dist/web-ui/assets/LoginView-Dg0mB6ok.css +0 -1
  47. package/dist/web-ui/assets/LoginView-XTF5dHvd.js +0 -1
  48. package/dist/web-ui/assets/ProcessView-DmbVABQV.js +0 -1
  49. package/dist/web-ui/assets/ProcessView-DzMqBJmd.css +0 -1
  50. package/dist/web-ui/assets/SettingsView-BDkymsrS.css +0 -1
  51. package/dist/web-ui/assets/SettingsView-BgLQVC2H.js +0 -1
  52. package/dist/web-ui/assets/config-BRaXq0LL.js +0 -1
  53. package/dist/web-ui/assets/useManagerApi-BDWJz5o6.js +0 -1
@@ -1,107 +1,107 @@
1
- // 服务生命周期 CLI 命令:start / stop / restart(restart = stop + start,保证最新参数生效)。
2
- // 进程编排核心在 utils/service.js,本文件只做命令装配与人读输出(摘要文案)。
3
- import { join } from "node:path";
4
- import { APP_NAME, MANAGER_APP_NAME, pkgRoot, defaultAppDir, absolutize } from "../../utils/paths.js";
5
- import { withPm2, findApp } from "../../utils/pm2.js";
6
- import { doStart, doStop, resolvePort } from "../../utils/service.js";
7
- import { ICONS, say, field } from "../../utils/ui.js";
8
-
9
- /** 摘要块(start/restart 成功 与 默认命令已运行 共用的输出形态,图标 + 对齐标签) */
10
- function printInfo({ appDir, port, managerPort, logsHint }) {
11
- if (port) {
12
- field(ICONS.gateway, "网关端点", `http://localhost:${port}`);
13
- field(ICONS.api, "管理台 API", `http://localhost:${port}/api/*`);
14
- if (managerPort) field(ICONS.webui, "管理界面", `http://127.0.0.1:${managerPort}/`);
15
- } else {
16
- field(
17
- ICONS.question,
18
- "访问入口",
19
- "端口未知(请检查配置):网关 = http://localhost:<端口> · 管理界面 = http://127.0.0.1:<主端口+1>/",
20
- );
21
- }
22
- field(
23
- ICONS.api,
24
- "管理服务",
25
- `${managerPort ? `http://127.0.0.1:${managerPort}` : "端口未知(= 网关端口 + 1)"}(启停/代理开关接口,进程名 ${MANAGER_APP_NAME})`,
26
- );
27
- field(ICONS.folder, "应用目录", appDir);
28
- field(ICONS.status, "运行状态", `pm2 ls(进程名 ${APP_NAME})· 日志 ${logsHint ?? "<应用目录>/logs/"}`);
29
- }
30
-
31
- /** 启动摘要(start/restart 成功后输出) */
32
- function printSummary(appDir, config) {
33
- printInfo({
34
- appDir,
35
- port: resolvePort(process.env.ELLM_PORT, config),
36
- managerPort: process.env.ELLM_MANAGER_PORT,
37
- });
38
- }
39
-
40
- /** 运行中服务的信息(默认命令:ellm-proxy 不带参数时已运行则展示此信息)。
41
- * 端口/路径以 pm2 记录的进程 env 为准(与当前 CLI 会话 env 无关) */
42
- function printRunningInfo(proc) {
43
- const pmEnv = proc.pm2_env?.env ?? {};
44
- const appDir = pmEnv.ELLM_APP_DIR || proc.pm2_env?.pm_cwd || defaultAppDir;
45
- const uptime = proc.pm2_env?.pm_uptime ? new Date(proc.pm2_env.pm_uptime).toLocaleString() : "";
46
- say(`${APP_NAME} 已在运行(pid ${proc.pid}${uptime ? ` · 自 ${uptime} 起` : ""})`, ICONS.running);
47
- printInfo({
48
- appDir,
49
- port: resolvePort(pmEnv.ELLM_PORT, pmEnv.ELLM_CONFIG),
50
- managerPort: pmEnv.ELLM_MANAGER_PORT,
51
- logsHint: join(appDir, "logs"),
52
- });
53
- }
54
-
55
- /** start 成功后的统一输出 */
56
- function printStarted({ proc, managerProc, appDir, config }) {
57
- console.log();
58
- say(`启动完成(主服务 pid ${proc.pid}${managerProc ? ` · 管理服务 pid ${managerProc.pid}` : ""})`, ICONS.success);
59
- console.log();
60
- printSummary(appDir, config);
61
- }
62
-
63
- /** 默认命令(ellm-proxy 不带参数):已运行则打印服务信息,未运行则启动(同 start) */
64
- export async function doStartOrInfo(opts) {
65
- const existing = await withPm2(() => findApp());
66
- if (existing) return printRunningInfo(existing);
67
- printStarted(await doStart(absolutize(opts)));
68
- }
69
-
70
- /** start/restart/默认命令共用的选项集 */
71
- export function withStartOptions(cmd) {
72
- return cmd
73
- .option("--port <n>", "监听端口(也可用 ELLM_PORT 环境变量)")
74
- .option("--config <path>", "配置文件路径(也可用 ELLM_CONFIG 环境变量)")
75
- .option("--data-dir <path>", "数据目录(也可用 ELLM_DATA_DIR 环境变量;默认 <appDir>/data)")
76
- .option("--working-dir <path>", "应用目录(配置/数据/日志基目录 ELLM_APP_DIR,默认 ~/.ellm-proxy)")
77
- .option("--server-js <path>", "显式指定 dist/server.js 路径(默认本包 dist/server.js)");
78
- }
79
-
80
- export function register(cli, run) {
81
- withStartOptions(cli.command("start", "pm2 后台守护启动(崩溃自动重启;首次自动创建配置)")).action((opts) =>
82
- run(() => doStart(absolutize(opts)).then(printStarted)),
83
- );
84
-
85
- withStartOptions(cli.command("restart", "重启服务(先 stop 后 start,保证最新参数生效,选项同 start)")).action(
86
- (opts) =>
87
- run(async () => {
88
- const stopped = await doStop();
89
- if (!stopped.main && !stopped.manager) say(`${APP_NAME} 未在运行(可能已停止)。`, ICONS.idle);
90
- printStarted(await doStart(absolutize(opts)));
91
- }),
92
- );
93
-
94
- cli.command("stop", "停止服务(pm2 删除守护进程,不影响其他 node 进程)").action(() =>
95
- run(async () => {
96
- const stopped = await doStop();
97
- if (!stopped.main && !stopped.manager) {
98
- say(`${APP_NAME} 未在运行(可能已停止)。`, ICONS.idle);
99
- return;
100
- }
101
- say(
102
- stopped.manager ? `${APP_NAME} 已停止(管理服务 ${MANAGER_APP_NAME} 一并停止)。` : `${APP_NAME} 已停止。`,
103
- ICONS.stopped,
104
- );
105
- }),
106
- );
107
- }
1
+ // 服务生命周期 CLI 命令:start / stop / restart(restart = stop + start,保证最新参数生效)。
2
+ // 进程编排核心在 utils/service.js,本文件只做命令装配与人读输出(摘要文案)。
3
+ import { join } from "node:path";
4
+ import { APP_NAME, MANAGER_APP_NAME, pkgRoot, defaultAppDir, absolutize } from "../../utils/paths.js";
5
+ import { withPm2, findApp } from "../../utils/pm2.js";
6
+ import { doStart, doStop, resolvePort } from "../../utils/service.js";
7
+ import { ICONS, say, field } from "../../utils/ui.js";
8
+
9
+ /** 摘要块(start/restart 成功 与 默认命令已运行 共用的输出形态,图标 + 对齐标签) */
10
+ function printInfo({ appDir, port, managerPort, logsHint }) {
11
+ if (port) {
12
+ field(ICONS.gateway, "网关端点", `http://localhost:${port}`);
13
+ field(ICONS.api, "管理台 API", `http://localhost:${port}/api/*`);
14
+ if (managerPort) field(ICONS.webui, "管理界面", `http://127.0.0.1:${managerPort}/`);
15
+ } else {
16
+ field(
17
+ ICONS.question,
18
+ "访问入口",
19
+ "端口未知(请检查配置):网关 = http://localhost:<端口> · 管理界面 = http://127.0.0.1:<主端口+1>/",
20
+ );
21
+ }
22
+ field(
23
+ ICONS.api,
24
+ "管理服务",
25
+ `${managerPort ? `http://127.0.0.1:${managerPort}` : "端口未知(= 网关端口 + 1)"}(启停/代理开关接口,进程名 ${MANAGER_APP_NAME})`,
26
+ );
27
+ field(ICONS.folder, "应用目录", appDir);
28
+ field(ICONS.status, "运行状态", `pm2 ls(进程名 ${APP_NAME})· 日志 ${logsHint ?? "<应用目录>/logs/"}`);
29
+ }
30
+
31
+ /** 启动摘要(start/restart 成功后输出) */
32
+ function printSummary(appDir, config) {
33
+ printInfo({
34
+ appDir,
35
+ port: resolvePort(process.env.ELLM_PORT, config),
36
+ managerPort: process.env.ELLM_MANAGER_PORT,
37
+ });
38
+ }
39
+
40
+ /** 运行中服务的信息(默认命令:ellm-proxy 不带参数时已运行则展示此信息)。
41
+ * 端口/路径以 pm2 记录的进程 env 为准(与当前 CLI 会话 env 无关) */
42
+ function printRunningInfo(proc) {
43
+ const pmEnv = proc.pm2_env?.env ?? {};
44
+ const appDir = pmEnv.ELLM_APP_DIR || proc.pm2_env?.pm_cwd || defaultAppDir;
45
+ const uptime = proc.pm2_env?.pm_uptime ? new Date(proc.pm2_env.pm_uptime).toLocaleString() : "";
46
+ say(`${APP_NAME} 已在运行(pid ${proc.pid}${uptime ? ` · 自 ${uptime} 起` : ""})`, ICONS.running);
47
+ printInfo({
48
+ appDir,
49
+ port: resolvePort(pmEnv.ELLM_PORT, pmEnv.ELLM_CONFIG),
50
+ managerPort: pmEnv.ELLM_MANAGER_PORT,
51
+ logsHint: join(appDir, "logs"),
52
+ });
53
+ }
54
+
55
+ /** start 成功后的统一输出 */
56
+ function printStarted({ proc, managerProc, appDir, config }) {
57
+ console.log();
58
+ say(`启动完成(主服务 pid ${proc.pid}${managerProc ? ` · 管理服务 pid ${managerProc.pid}` : ""})`, ICONS.success);
59
+ console.log();
60
+ printSummary(appDir, config);
61
+ }
62
+
63
+ /** 默认命令(ellm-proxy 不带参数):已运行则打印服务信息,未运行则启动(同 start) */
64
+ export async function doStartOrInfo(opts) {
65
+ const existing = await withPm2(() => findApp());
66
+ if (existing) return printRunningInfo(existing);
67
+ printStarted(await doStart(absolutize(opts)));
68
+ }
69
+
70
+ /** start/restart/默认命令共用的选项集 */
71
+ export function withStartOptions(cmd) {
72
+ return cmd
73
+ .option("--port <n>", "监听端口(也可用 ELLM_PORT 环境变量)")
74
+ .option("--config <path>", "配置文件路径(也可用 ELLM_CONFIG 环境变量)")
75
+ .option("--data-dir <path>", "数据目录(也可用 ELLM_DATA_DIR 环境变量;默认 <appDir>/data)")
76
+ .option("--working-dir <path>", "应用目录(配置/数据/日志基目录 ELLM_APP_DIR,默认 ~/.ellm-proxy)")
77
+ .option("--server-js <path>", "显式指定 dist/server.js 路径(默认本包 dist/server.js)");
78
+ }
79
+
80
+ export function register(cli, run) {
81
+ withStartOptions(cli.command("start", "pm2 后台守护启动(崩溃自动重启;首次自动创建配置)")).action((opts) =>
82
+ run(() => doStart(absolutize(opts)).then(printStarted)),
83
+ );
84
+
85
+ withStartOptions(cli.command("restart", "重启服务(先 stop 后 start,保证最新参数生效,选项同 start)")).action(
86
+ (opts) =>
87
+ run(async () => {
88
+ const stopped = await doStop();
89
+ if (!stopped.main && !stopped.manager) say(`${APP_NAME} 未在运行(可能已停止)。`, ICONS.idle);
90
+ printStarted(await doStart(absolutize(opts)));
91
+ }),
92
+ );
93
+
94
+ cli.command("stop", "停止服务(pm2 删除守护进程,不影响其他 node 进程)").action(() =>
95
+ run(async () => {
96
+ const stopped = await doStop();
97
+ if (!stopped.main && !stopped.manager) {
98
+ say(`${APP_NAME} 未在运行(可能已停止)。`, ICONS.idle);
99
+ return;
100
+ }
101
+ say(
102
+ stopped.manager ? `${APP_NAME} 已停止(管理服务 ${MANAGER_APP_NAME} 一并停止)。` : `${APP_NAME} 已停止。`,
103
+ ICONS.stopped,
104
+ );
105
+ }),
106
+ );
107
+ }
package/bin/ellm-proxy.js CHANGED
@@ -1,61 +1,61 @@
1
- #!/usr/bin/env node
2
- // ellm-proxy CLI 入口(bin,cac 框架):仅做命令装配,实现在 commands/(命令)与 utils/(共享工具)。
3
- //
4
- // 命令:(无参数 = 默认启动)start / stop / restart / pm2 <args...> / autostart on|off / config / -h / --version(选项详见 -h)
5
- //
6
- // 运行时布局(默认 ~/.ellm-proxy/,均可被选项/环境变量覆盖):
7
- // 配置: --config > ELLM_CONFIG 环境变量 > ~/.ellm-proxy/ellm.config.json(首次自动创建)
8
- // 数据: ELLM_DATA_DIR 环境变量 > ~/.ellm-proxy/data
9
- // 日志: <appDir>/logs/ellm-out.log · ellm-err.log(主服务)+ manager-out.log · manager-err.log(管理服务)
10
- //
11
- // 进程管理(跨平台,pm2 JS API 封装见 utils/pm2.js,编排核心见 utils/service.js):
12
- // start 会一并托管两个进程——主服务 dist/server.js(name=ellm-proxy)与管理服务 manager/index.js
13
- // (name=ellm-proxy-manager,启停/重启主服务 + Claude Code/Codex 代理开关)。
14
- // 崩溃自动重启、日志落盘、dump/resurrect(开机自启一并恢复)。服务要求的 Node >=22.13.0 通过
15
- // interpreter 保证(utils/interpreter.js),pm2 daemon 的 node 版本无关紧要。
16
- // start/stop 成功后自动 pm2 dump,供开机自启 resurrect 恢复。`ellm-proxy pm2 <args>` 可透传
17
- // 任意 pm2 子命令(list/logs/monit/...,用包内局部 pm2,无需自装全局)。
18
- //
19
- // 开机自启(commands/autostart.js,跨平台):登录时触发、无需管理员——Windows 写启动文件夹
20
- // .cmd(shell:startup)、macOS 写 LaunchAgent plist(launchctl load)、Linux 写 systemd user
21
- // unit(systemctl --user enable);内容一致 = pm2 resurrect 恢复最后一次 dump。
22
-
23
- import { cac } from "cac";
24
- import { pkg } from "../utils/paths.js";
25
- import { error as uiError } from "../utils/ui.js";
26
- import * as serviceCmd from "./commands/service.js";
27
- import * as autostartCmd from "./commands/autostart.js";
28
- import * as configCmd from "./commands/config.js";
29
- import * as pm2Cmd from "./commands/pm2.js";
30
-
31
- const cli = cac("ellm-proxy");
32
-
33
- /** 统一错误出口:命令实现抛错 → 打印中文错误信息并以退出码 1 结束 */
34
- async function run(action) {
35
- try {
36
- await action();
37
- } catch (err) {
38
- console.error();
39
- uiError(`错误:${err?.message ?? err}`);
40
- process.exit(1);
41
- }
42
- }
43
-
44
- serviceCmd.register(cli, run);
45
- autostartCmd.register(cli, run);
46
- configCmd.register(cli);
47
- pm2Cmd.register(cli, run);
48
-
49
- // pm2 透传:在 cac 解析前拦截 `ellm-proxy pm2 <...args>`,pm2 的 -- 选项原样可达(cac 会吞掉未知 -- 选项)
50
- if (process.argv[2] === "pm2") {
51
- run(() => pm2Cmd.doPassthrough(process.argv.slice(3)));
52
- } else {
53
- // 默认命令(无参数):启动服务,已运行则显示服务信息(选项同 start)
54
- serviceCmd
55
- .withStartOptions(cli.command("", "启动服务(已运行则显示服务信息,选项同 start)"))
56
- .action((opts) => run(() => serviceCmd.doStartOrInfo(opts)));
57
-
58
- cli.help();
59
- cli.version(pkg.version);
60
- cli.parse();
61
- }
1
+ #!/usr/bin/env node
2
+ // ellm-proxy CLI 入口(bin,cac 框架):仅做命令装配,实现在 commands/(命令)与 utils/(共享工具)。
3
+ //
4
+ // 命令:(无参数 = 默认启动)start / stop / restart / pm2 <args...> / autostart on|off / config / -h / --version(选项详见 -h)
5
+ //
6
+ // 运行时布局(默认 ~/.ellm-proxy/,均可被选项/环境变量覆盖):
7
+ // 配置: --config > ELLM_CONFIG 环境变量 > ~/.ellm-proxy/ellm.config.json(首次自动创建)
8
+ // 数据: ELLM_DATA_DIR 环境变量 > ~/.ellm-proxy/data
9
+ // 日志: <appDir>/logs/ellm-out.log · ellm-err.log(主服务)+ manager-out.log · manager-err.log(管理服务)
10
+ //
11
+ // 进程管理(跨平台,pm2 JS API 封装见 utils/pm2.js,编排核心见 utils/service.js):
12
+ // start 会一并托管两个进程——主服务 dist/server.js(name=ellm-proxy)与管理服务 manager/index.js
13
+ // (name=ellm-proxy-manager,启停/重启主服务 + Claude Code/Codex 代理开关)。
14
+ // 崩溃自动重启、日志落盘、dump/resurrect(开机自启一并恢复)。服务要求的 Node >=22.13.0 通过
15
+ // interpreter 保证(utils/interpreter.js),pm2 daemon 的 node 版本无关紧要。
16
+ // start/stop 成功后自动 pm2 dump,供开机自启 resurrect 恢复。`ellm-proxy pm2 <args>` 可透传
17
+ // 任意 pm2 子命令(list/logs/monit/...,用包内局部 pm2,无需自装全局)。
18
+ //
19
+ // 开机自启(commands/autostart.js,跨平台):登录时触发、无需管理员——Windows 写启动文件夹
20
+ // .cmd(shell:startup)、macOS 写 LaunchAgent plist(launchctl load)、Linux 写 systemd user
21
+ // unit(systemctl --user enable);内容一致 = pm2 resurrect 恢复最后一次 dump。
22
+
23
+ import { cac } from "cac";
24
+ import { pkg } from "../utils/paths.js";
25
+ import { error as uiError } from "../utils/ui.js";
26
+ import * as serviceCmd from "./commands/service.js";
27
+ import * as autostartCmd from "./commands/autostart.js";
28
+ import * as configCmd from "./commands/config.js";
29
+ import * as pm2Cmd from "./commands/pm2.js";
30
+
31
+ const cli = cac("ellm-proxy");
32
+
33
+ /** 统一错误出口:命令实现抛错 → 打印中文错误信息并以退出码 1 结束 */
34
+ async function run(action) {
35
+ try {
36
+ await action();
37
+ } catch (err) {
38
+ console.error();
39
+ uiError(`错误:${err?.message ?? err}`);
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ serviceCmd.register(cli, run);
45
+ autostartCmd.register(cli, run);
46
+ configCmd.register(cli);
47
+ pm2Cmd.register(cli, run);
48
+
49
+ // pm2 透传:在 cac 解析前拦截 `ellm-proxy pm2 <...args>`,pm2 的 -- 选项原样可达(cac 会吞掉未知 -- 选项)
50
+ if (process.argv[2] === "pm2") {
51
+ run(() => pm2Cmd.doPassthrough(process.argv.slice(3)));
52
+ } else {
53
+ // 默认命令(无参数):启动服务,已运行则显示服务信息(选项同 start)
54
+ serviceCmd
55
+ .withStartOptions(cli.command("", "启动服务(已运行则显示服务信息,选项同 start)"))
56
+ .action((opts) => run(() => serviceCmd.doStartOrInfo(opts)));
57
+
58
+ cli.help();
59
+ cli.version(pkg.version);
60
+ cli.parse();
61
+ }
package/dist/server.js CHANGED
@@ -11363,6 +11363,37 @@ function redactHeaders(headers) {
11363
11363
  }
11364
11364
  return changed ? out : headers;
11365
11365
  }
11366
+ var REDACTED_QUERY_PARAM_NAMES = /* @__PURE__ */ new Set([
11367
+ "key",
11368
+ "apikey",
11369
+ "accesskey",
11370
+ "accesstoken",
11371
+ "token",
11372
+ "authtoken",
11373
+ "sig",
11374
+ "signature",
11375
+ "secret",
11376
+ "password"
11377
+ ]);
11378
+ function normalizeParamName(name) {
11379
+ return name.toLowerCase().replace(/[-_]/g, "");
11380
+ }
11381
+ function redactUrl(url) {
11382
+ let parsed;
11383
+ try {
11384
+ parsed = new URL(url);
11385
+ } catch {
11386
+ return url;
11387
+ }
11388
+ let changed = false;
11389
+ for (const [name, value] of Array.from(parsed.searchParams.entries())) {
11390
+ if (REDACTED_QUERY_PARAM_NAMES.has(normalizeParamName(name))) {
11391
+ parsed.searchParams.set(name, `***redacted(${String(value.length)})***`);
11392
+ changed = true;
11393
+ }
11394
+ }
11395
+ return changed ? parsed.toString() : url;
11396
+ }
11366
11397
  function createSqliteTraceSink() {
11367
11398
  return {
11368
11399
  write(trace) {
@@ -11382,12 +11413,12 @@ function createSqliteTraceSink() {
11382
11413
  _capture: true,
11383
11414
  // 与 upstream_request 同形(url/headers/body 三件套):"客户端发什么就存什么"
11384
11415
  inbound_request: {
11385
- url: trace.http.url,
11416
+ url: redactUrl(trace.http.url),
11386
11417
  headers: redactHeaders(trace.inbound.headers),
11387
11418
  body: trace.inbound.body ?? null
11388
11419
  },
11389
11420
  upstream_request: upstream.url ? {
11390
- url: upstream.url,
11421
+ url: redactUrl(upstream.url),
11391
11422
  headers: redactHeaders(upstream.requestHeaders),
11392
11423
  body: parseOrRaw(upstream.requestBody)
11393
11424
  } : null,
@@ -0,0 +1 @@
1
+ .copy-text[data-v-de31ace5]{color:#409eff;cursor:pointer}.copy-text[data-v-de31ace5]:hover{text-decoration:underline}
@@ -1 +1 @@
1
- import{A as T}from"./useApi-C1Ngt_o5.js";import{u as H}from"./providers-Dmz2p4Wd.js";import{u as J}from"./useToast-B3quFZyv.js";import{d as Q,i as W,c as h,a as o,w as s,j as V,k as B,l as y,r as _,m as X,o as c,n as Z,u as Y,t as u,b as l,e as p,F as ee,p as te,f as N,_ as ae}from"./index-dPeMJjrz.js";import"./config-BRaXq0LL.js";const le={class:"flex-col-gap"},oe=["onClick"],se={key:1},ne={class:"flex-between"},de={class:"flex-gap-8"},ue={class:"usage-grid"},ie={class:"usage-cell"},re={class:"usage-num orange"},ce={class:"usage-cell"},ve={class:"usage-num blue"},_e={class:"usage-cell"},pe={class:"usage-num green"},ge={class:"usage-cell"},me={class:"usage-num purple"},fe={class:"usage-cell"},he={class:"usage-num purple"},ye={key:0,class:"usage-cell"},be={class:"usage-num gray"},we={key:0},ke=["onClick"],De=Q({__name:"DashboardView",setup(Ce){const k=H(),{toast:M}=J(),i=y(null),b=y(!0),D=y(""),v=y(null),w=y("today"),C=a=>{const e=a.getFullYear(),t=String(a.getMonth()+1).padStart(2,"0"),r=String(a.getDate()).padStart(2,"0");return`${e}-${t}-${r}`},F=a=>{const e=new Date;let t,r=e;if(a==="today")t=e;else if(a==="yesterday")t=new Date(e),t.setDate(e.getDate()-1),r=t;else if(a==="beforeYesterday")t=new Date(e),t.setDate(e.getDate()-2),r=t;else if(a==="last3days")t=new Date(e),t.setDate(e.getDate()-2);else if(a==="week"){t=new Date(e);const S=(e.getDay()+6)%7;t.setDate(e.getDate()-S)}else if(a==="month")t=new Date(e.getFullYear(),e.getMonth(),1);else return;w.value=a,v.value=[C(t),C(r)],f()},U=a=>{a?F(a):(w.value="",v.value=null,f())},A=()=>{w.value="",f()},I=N(()=>{var e;const a=new Set;for(const t of k.suppliers)for(const r of t.models)a.add(r.modelName);if((e=i.value)!=null&&e.models)for(const t of i.value.models)a.add(t.model);return[...a].sort()}),f=async()=>{var a;b.value=!0;try{const e=new URLSearchParams;D.value&&e.set("model",D.value),((a=v.value)==null?void 0:a.length)===2&&(e.set("startDate",v.value[0]),e.set("endDate",v.value[1]));const t=e.toString();i.value=await T.get(`/api/usage${t?`?${t}`:""}`)}catch(e){console.error(e)}finally{b.value=!1}},R=async()=>{var a;try{const[e]=await Promise.all([T.get("/api/usage"),k.loadProviders(),T.get("/api/status").then(t=>x.value=t.listenPort).catch(()=>x.value)]);if(i.value=e,!((a=v.value)!=null&&a.length)){const t=new Date;v.value=[C(t),C(t)],await f()}}catch(e){console.error(e)}finally{b.value=!1}},L=window.location.hostname,x=y(Number(window.location.port)||null),E=N(()=>`http://${L}${x.value?`:${x.value}`:""}`),g=a=>a.toLocaleString(),j=a=>a==null||a<=0?"-":a>=1e3?(a/1e3).toFixed(2)+"s":Math.round(a)+"ms",$=async a=>{try{await navigator.clipboard.writeText(a),M("已复制到剪贴板","success")}catch{const e=document.createElement("textarea");e.value=a,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select();try{document.execCommand("copy"),M("已复制到剪贴板","success")}catch{M("复制失败","error")}document.body.removeChild(e)}};return W(R),(a,e)=>{const t=_("el-table-column"),r=_("el-table"),S=_("el-card"),m=_("el-option"),P=_("el-select"),q=_("el-date-picker"),z=_("el-button"),K=_("el-tag"),O=_("el-empty"),G=X("loading");return c(),h("div",le,[o(S,{shadow:"never"},{header:s(()=>[...e[3]||(e[3]=[l("span",{class:"fw-600"},"服务信息",-1)])]),default:s(()=>[Z((c(),V(r,{border:"",data:[{key:"API 地址",value:E.value},{key:"供应商数量",value:String(Y(k).providerCount)},{key:"模型总数",value:String(Y(k).totalModelCount)}],"show-header":!1,class:"full-width"},{default:s(()=>[o(t,{prop:"key",width:"160"}),o(t,null,{default:s(({row:d})=>[d.key==="API 地址"?(c(),h("span",{key:0,class:"copy-text",onClick:n=>$(d.value)},u(d.value),9,oe)):(c(),h("span",se,u(d.value),1))]),_:1})]),_:1},8,["data"])),[[G,b.value]])]),_:1}),i.value?(c(),V(S,{key:0,shadow:"never"},{header:s(()=>[l("div",ne,[e[5]||(e[5]=l("span",{class:"fw-600"},"用量统计",-1)),l("div",de,[o(P,{modelValue:D.value,"onUpdate:modelValue":e[0]||(e[0]=d=>D.value=d),placeholder:"全部模型",clearable:"",filterable:"",style:{width:"220px"},onChange:f},{default:s(()=>[(c(!0),h(ee,null,te(I.value,d=>(c(),V(m,{key:d,label:d,value:d},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),o(P,{modelValue:w.value,"onUpdate:modelValue":e[1]||(e[1]=d=>w.value=d),placeholder:"时间范围",clearable:"",style:{width:"130px"},onChange:U},{default:s(()=>[o(m,{label:"今天",value:"today"}),o(m,{label:"昨天",value:"yesterday"}),o(m,{label:"前天",value:"beforeYesterday"}),o(m,{label:"近三天",value:"last3days"}),o(m,{label:"本周",value:"week"}),o(m,{label:"本月",value:"month"})]),_:1},8,["modelValue"]),o(q,{modelValue:v.value,"onUpdate:modelValue":e[2]||(e[2]=d=>v.value=d),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",style:{width:"260px"},onChange:A},null,8,["modelValue"]),o(z,{loading:b.value,onClick:f},{default:s(()=>[...e[4]||(e[4]=[p("刷新",-1)])]),_:1},8,["loading"])]),o(K,{type:"info"},{default:s(()=>[p(u(i.value.total.calls)+" 次调用",1)]),_:1})])]),default:s(()=>{var d;return[l("div",ue,[l("div",ie,[l("div",re,u(g(i.value.total.total_tokens)),1),e[6]||(e[6]=l("div",{class:"usage-label"},"总 Token",-1))]),l("div",ce,[l("div",ve,u(g(i.value.total.input_tokens)),1),e[7]||(e[7]=l("div",{class:"usage-label"},"输入 Token",-1))]),l("div",_e,[l("div",pe,u(g(i.value.total.output_tokens)),1),e[8]||(e[8]=l("div",{class:"usage-label"},"输出 Token",-1))]),l("div",ge,[l("div",me,u(g(i.value.total.cache_read_tokens)),1),e[9]||(e[9]=l("div",{class:"usage-label"},"缓存读取",-1))]),l("div",fe,[l("div",he,u(i.value.total.cache_rate!=null?i.value.total.cache_rate+"%":"-"),1),e[10]||(e[10]=l("div",{class:"usage-label"},"缓存命中率",-1))]),i.value.logStorageMB!=null?(c(),h("div",ye,[l("div",be,u(i.value.logStorageMB),1),e[11]||(e[11]=l("div",{class:"usage-label"},"日志占用 (MB)",-1))])):B("",!0)]),(d=i.value.models)!=null&&d.length?(c(),h("div",we,[e[12]||(e[12]=l("div",{class:"section-title"},"模型用量",-1)),o(r,{data:i.value.models,border:"",stripe:"",class:"full-width"},{default:s(()=>[o(t,{prop:"model",label:"模型","min-width":"200"},{default:s(({row:n})=>[l("span",{class:"copy-text",onClick:xe=>$(n.model)},u(n.model),9,ke)]),_:1}),o(t,{label:"调用次数",width:"90",align:"center"},{default:s(({row:n})=>[p(u(n.calls),1)]),_:1}),o(t,{label:"平均耗时",width:"110",align:"center"},{default:s(({row:n})=>[p(u(j(n.avg_duration)),1)]),_:1}),o(t,{label:"总 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.total_tokens)),1)]),_:1}),o(t,{label:"输入 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.input_tokens)),1)]),_:1}),o(t,{label:"输出 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.output_tokens)),1)]),_:1}),o(t,{label:"缓存读取",width:"130",align:"center"},{default:s(({row:n})=>[p(u(g(n.cache_read_tokens)),1)]),_:1}),o(t,{label:"缓存命中率",width:"110",align:"center"},{default:s(({row:n})=>[p(u(n.cache_rate!=null?n.cache_rate+"%":"-"),1)]),_:1})]),_:1},8,["data"])])):(c(),V(O,{key:1,description:"暂无调用记录","image-size":60}))]}),_:1})):B("",!0)])}}}),Pe=ae(De,[["__scopeId","data-v-a652207c"]]);export{Pe as default};
1
+ import{A as T}from"./useApi-D_iJ2Mca.js";import{u as H}from"./providers-Djtdy2tv.js";import{u as J}from"./useToast-C0UMTbkx.js";import{d as Q,p as W,e as h,g as o,w as s,c as V,i as B,r as y,a as _,j as X,o as c,b as Z,k as Y,t as u,f as l,h as p,F as ee,q as te,l as N,_ as ae}from"./index-GohB4JDa.js";import"./config-Cs3_z63C.js";const le={class:"flex-col-gap"},oe=["onClick"],se={key:1},ne={class:"flex-between"},de={class:"flex-gap-8"},ue={class:"usage-grid"},ie={class:"usage-cell"},re={class:"usage-num orange"},ce={class:"usage-cell"},ve={class:"usage-num blue"},_e={class:"usage-cell"},pe={class:"usage-num green"},ge={class:"usage-cell"},me={class:"usage-num purple"},fe={class:"usage-cell"},he={class:"usage-num purple"},ye={key:0,class:"usage-cell"},be={class:"usage-num gray"},we={key:0},ke=["onClick"],De=Q({__name:"DashboardView",setup(Ce){const k=H(),{toast:M}=J(),i=y(null),b=y(!0),D=y(""),v=y(null),w=y("today"),C=a=>{const e=a.getFullYear(),t=String(a.getMonth()+1).padStart(2,"0"),r=String(a.getDate()).padStart(2,"0");return`${e}-${t}-${r}`},F=a=>{const e=new Date;let t,r=e;if(a==="today")t=e;else if(a==="yesterday")t=new Date(e),t.setDate(e.getDate()-1),r=t;else if(a==="beforeYesterday")t=new Date(e),t.setDate(e.getDate()-2),r=t;else if(a==="last3days")t=new Date(e),t.setDate(e.getDate()-2);else if(a==="week"){t=new Date(e);const S=(e.getDay()+6)%7;t.setDate(e.getDate()-S)}else if(a==="month")t=new Date(e.getFullYear(),e.getMonth(),1);else return;w.value=a,v.value=[C(t),C(r)],f()},U=a=>{a?F(a):(w.value="",v.value=null,f())},A=()=>{w.value="",f()},I=N(()=>{var e;const a=new Set;for(const t of k.suppliers)for(const r of t.models)a.add(r.modelName);if((e=i.value)!=null&&e.models)for(const t of i.value.models)a.add(t.model);return[...a].sort()}),f=async()=>{var a;b.value=!0;try{const e=new URLSearchParams;D.value&&e.set("model",D.value),((a=v.value)==null?void 0:a.length)===2&&(e.set("startDate",v.value[0]),e.set("endDate",v.value[1]));const t=e.toString();i.value=await T.get(`/api/usage${t?`?${t}`:""}`)}catch(e){console.error(e)}finally{b.value=!1}},R=async()=>{var a;try{const[e]=await Promise.all([T.get("/api/usage"),k.loadProviders(),T.get("/api/status").then(t=>x.value=t.listenPort).catch(()=>x.value)]);if(i.value=e,!((a=v.value)!=null&&a.length)){const t=new Date;v.value=[C(t),C(t)],await f()}}catch(e){console.error(e)}finally{b.value=!1}},L=window.location.hostname,x=y(Number(window.location.port)||null),q=N(()=>`http://${L}${x.value?`:${x.value}`:""}`),g=a=>a.toLocaleString(),E=a=>a==null||a<=0?"-":a>=1e3?(a/1e3).toFixed(2)+"s":Math.round(a)+"ms",$=async a=>{try{await navigator.clipboard.writeText(a),M("已复制到剪贴板","success")}catch{const e=document.createElement("textarea");e.value=a,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select();try{document.execCommand("copy"),M("已复制到剪贴板","success")}catch{M("复制失败","error")}document.body.removeChild(e)}};return W(R),(a,e)=>{const t=_("el-table-column"),r=_("el-table"),S=_("el-card"),m=_("el-option"),P=_("el-select"),j=_("el-date-picker"),z=_("el-button"),K=_("el-tag"),O=_("el-empty"),G=X("loading");return c(),h("div",le,[o(S,{shadow:"never"},{header:s(()=>[...e[3]||(e[3]=[l("span",{class:"fw-600"},"服务信息",-1)])]),default:s(()=>[Z((c(),V(r,{border:"",data:[{key:"API 地址",value:q.value},{key:"供应商数量",value:String(Y(k).providerCount)},{key:"模型总数",value:String(Y(k).totalModelCount)}],"show-header":!1,class:"full-width"},{default:s(()=>[o(t,{prop:"key",width:"160"}),o(t,null,{default:s(({row:d})=>[d.key==="API 地址"?(c(),h("span",{key:0,class:"copy-text",onClick:n=>$(d.value)},u(d.value),9,oe)):(c(),h("span",se,u(d.value),1))]),_:1})]),_:1},8,["data"])),[[G,b.value]])]),_:1}),i.value?(c(),V(S,{key:0,shadow:"never"},{header:s(()=>[l("div",ne,[e[5]||(e[5]=l("span",{class:"fw-600"},"用量统计",-1)),l("div",de,[o(P,{modelValue:D.value,"onUpdate:modelValue":e[0]||(e[0]=d=>D.value=d),placeholder:"全部模型",clearable:"",filterable:"",style:{width:"220px"},onChange:f},{default:s(()=>[(c(!0),h(ee,null,te(I.value,d=>(c(),V(m,{key:d,label:d,value:d},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),o(P,{modelValue:w.value,"onUpdate:modelValue":e[1]||(e[1]=d=>w.value=d),placeholder:"时间范围",clearable:"",style:{width:"130px"},onChange:U},{default:s(()=>[o(m,{label:"今天",value:"today"}),o(m,{label:"昨天",value:"yesterday"}),o(m,{label:"前天",value:"beforeYesterday"}),o(m,{label:"近三天",value:"last3days"}),o(m,{label:"本周",value:"week"}),o(m,{label:"本月",value:"month"})]),_:1},8,["modelValue"]),o(j,{modelValue:v.value,"onUpdate:modelValue":e[2]||(e[2]=d=>v.value=d),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",style:{width:"260px"},onChange:A},null,8,["modelValue"]),o(z,{loading:b.value,onClick:f},{default:s(()=>[...e[4]||(e[4]=[p("刷新",-1)])]),_:1},8,["loading"])]),o(K,{type:"info"},{default:s(()=>[p(u(i.value.total.calls)+" 次调用",1)]),_:1})])]),default:s(()=>{var d;return[l("div",ue,[l("div",ie,[l("div",re,u(g(i.value.total.total_tokens)),1),e[6]||(e[6]=l("div",{class:"usage-label"},"总 Token",-1))]),l("div",ce,[l("div",ve,u(g(i.value.total.input_tokens)),1),e[7]||(e[7]=l("div",{class:"usage-label"},"输入 Token",-1))]),l("div",_e,[l("div",pe,u(g(i.value.total.output_tokens)),1),e[8]||(e[8]=l("div",{class:"usage-label"},"输出 Token",-1))]),l("div",ge,[l("div",me,u(g(i.value.total.cache_read_tokens)),1),e[9]||(e[9]=l("div",{class:"usage-label"},"缓存读取",-1))]),l("div",fe,[l("div",he,u(i.value.total.cache_rate!=null?i.value.total.cache_rate+"%":"-"),1),e[10]||(e[10]=l("div",{class:"usage-label"},"缓存命中率",-1))]),i.value.logStorageMB!=null?(c(),h("div",ye,[l("div",be,u(i.value.logStorageMB),1),e[11]||(e[11]=l("div",{class:"usage-label"},"日志占用 (MB)",-1))])):B("",!0)]),(d=i.value.models)!=null&&d.length?(c(),h("div",we,[e[12]||(e[12]=l("div",{class:"section-title"},"模型用量",-1)),o(r,{data:i.value.models,border:"",stripe:"",class:"full-width"},{default:s(()=>[o(t,{prop:"model",label:"模型","min-width":"200"},{default:s(({row:n})=>[l("span",{class:"copy-text",onClick:xe=>$(n.model)},u(n.model),9,ke)]),_:1}),o(t,{label:"调用次数",width:"90",align:"center"},{default:s(({row:n})=>[p(u(n.calls),1)]),_:1}),o(t,{label:"平均耗时",width:"110",align:"center"},{default:s(({row:n})=>[p(u(E(n.avg_duration)),1)]),_:1}),o(t,{label:"总 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.total_tokens)),1)]),_:1}),o(t,{label:"输入 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.input_tokens)),1)]),_:1}),o(t,{label:"输出 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.output_tokens)),1)]),_:1}),o(t,{label:"缓存读取",width:"130",align:"center"},{default:s(({row:n})=>[p(u(g(n.cache_read_tokens)),1)]),_:1}),o(t,{label:"缓存命中率",width:"110",align:"center"},{default:s(({row:n})=>[p(u(n.cache_rate!=null?n.cache_rate+"%":"-"),1)]),_:1})]),_:1},8,["data"])])):(c(),V(O,{key:1,description:"暂无调用记录","image-size":60}))]}),_:1})):B("",!0)])}}}),Pe=ae(De,[["__scopeId","data-v-de31ace5"]]);export{Pe as default};
@@ -0,0 +1 @@
1
+ .mt-12[data-v-e326b723]{margin-top:12px}.check-body[data-v-e326b723]{min-height:80px}.version-row[data-v-e326b723]{display:flex;align-items:center;gap:8px;margin-bottom:8px}.version-label[data-v-e326b723]{color:var(--el-text-color-secondary);font-size:13px}
@@ -0,0 +1 @@
1
+ import{d as E,u as N,c as b,w as e,r as f,a,o as _,b as W,e as D,F as z,f as i,g as o,h as u,t as x,i as $,E as I,j as L,_ as S,k as F,l as R,m as j,n as O}from"./index-GohB4JDa.js";import{m as T}from"./useManagerApi-C1_COxKg.js";import{u as q}from"./useToast-C0UMTbkx.js";const G={class:"check-body"},H={class:"version-row"},J={class:"version-row"},K=E({__name:"UpdateCheckDialog",props:{modelValue:{type:Boolean,default:!1},modelModifiers:{}},emits:["update:modelValue"],setup(B){const{toast:w}=q(),g=N(B,"modelValue"),d=f(!1),c=f(!1),n=f(null),v=f(null),p=f(null),r=async()=>{n.value=null,v.value=null,p.value=null,d.value=!0;try{n.value=await T.checkUpdate()}catch(l){v.value=l.message||"检查更新失败"}finally{d.value=!1}},t=async()=>{if(n.value){try{await I.confirm(`确定更新到 ${n.value.latestVersion}?更新期间主服务将短暂中断(停服安装后自动重启),管理服务随后自重启。`,"更新确认",{confirmButtonText:"立即更新",cancelButtonText:"取消",type:"warning"})}catch{return}c.value=!0;try{const l=await T.applyUpdate();p.value=l.note??`已更新 ${l.fromVersion} → ${l.toVersion}`,w("更新完成","success"),n.value={hasUpdate:!1,currentVersion:l.toVersion,latestVersion:l.toVersion,checkedAt:Date.now()}}catch(l){w(l.message||"更新失败","error")}finally{c.value=!1}}};return(l,s)=>{const k=a("el-tag"),V=a("el-alert"),y=a("el-button"),h=a("el-empty"),C=a("el-dialog"),U=L("loading");return _(),b(C,{modelValue:g.value,"onUpdate:modelValue":s[2]||(s[2]=m=>g.value=m),title:"检查更新",width:"420px","close-on-click-modal":!c.value,onOpen:r},{footer:e(()=>{var m;return[(m=n.value)!=null&&m.hasUpdate?(_(),b(y,{key:0,type:"primary",loading:c.value,onClick:t},{default:e(()=>[u(x(c.value?"更新中...":"立即更新"),1)]),_:1},8,["loading"])):$("",!0),o(y,{onClick:s[1]||(s[1]=A=>g.value=!1)},{default:e(()=>[...s[6]||(s[6]=[u("关闭",-1)])]),_:1})]}),default:e(()=>[W((_(),D("div",G,[n.value?(_(),D(z,{key:0},[i("div",H,[s[3]||(s[3]=i("span",{class:"version-label"},"当前版本",-1)),o(k,{size:"small"},{default:e(()=>[u(x(n.value.currentVersion),1)]),_:1})]),i("div",J,[s[4]||(s[4]=i("span",{class:"version-label"},"最新版本",-1)),o(k,{type:n.value.hasUpdate?"warning":"success",size:"small","disable-transitions":""},{default:e(()=>[u(x(n.value.latestVersion),1)]),_:1},8,["type"])]),o(V,{title:n.value.hasUpdate?`发现新版本 ${n.value.latestVersion}`:"已是最新版本",type:n.value.hasUpdate?"warning":"success","show-icon":"",closable:!1,class:"mt-12"},null,8,["title","type"])],64)):!d.value&&v.value?(_(),b(h,{key:1,description:v.value},{default:e(()=>[o(y,{type:"primary",onClick:r},{default:e(()=>[...s[5]||(s[5]=[u("重试",-1)])]),_:1})]),_:1},8,["description"])):$("",!0)])),[[U,d.value]]),p.value?(_(),b(V,{key:0,title:p.value,type:"info","show-icon":"",class:"mt-12",onClose:s[0]||(s[0]=m=>p.value=null)},null,8,["title"])):$("",!0)]),_:1},8,["modelValue","close-on-click-modal"])}}}),P=S(K,[["__scopeId","data-v-e326b723"]]),Q={class:"layout"},X={class:"header-nav"},Y={class:"header-actions"},Z={class:"main"},ee={class:"page"},se=E({__name:"LayoutView",setup(B){const w=O(),g=j(),d=f(!1),c=(()=>{const r=localStorage.getItem("webui_token")||"";try{return atob(r).split(":")[0]||"用户"}catch{return"用户"}})(),n=R(()=>{const r=w.path;return r==="/"?"/":r.startsWith("/providers")?"/providers":r.startsWith("/settings")?"/settings":r.startsWith("/logs")?"/logs":r.startsWith("/process")?"/process":"/"}),v=()=>{localStorage.removeItem("webui_token"),g.push("/login")},p=r=>{r==="logout"&&v(),r==="check-update"&&(d.value=!0)};return(r,t)=>{const l=a("el-menu-item"),s=a("el-menu"),k=a("ArrowDown"),V=a("el-icon"),y=a("el-button"),h=a("el-dropdown-item"),C=a("el-dropdown-menu"),U=a("el-dropdown"),m=a("el-header"),A=a("router-view");return _(),D("div",Q,[o(m,{class:"header"},{default:e(()=>[t[8]||(t[8]=i("h1",null,[i("span",null,"E"),u("LLM")],-1)),i("nav",X,[o(s,{mode:"horizontal","default-active":n.value,ellipsis:!1,router:"",class:"header-menu"},{default:e(()=>[o(l,{index:"/"},{default:e(()=>[...t[1]||(t[1]=[u("首页",-1)])]),_:1}),o(l,{index:"/providers"},{default:e(()=>[...t[2]||(t[2]=[u("供应商管理",-1)])]),_:1}),o(l,{index:"/logs"},{default:e(()=>[...t[3]||(t[3]=[u("日志",-1)])]),_:1}),o(l,{index:"/settings"},{default:e(()=>[...t[4]||(t[4]=[u("设置",-1)])]),_:1}),o(l,{index:"/process"},{default:e(()=>[...t[5]||(t[5]=[u("进程管理",-1)])]),_:1})]),_:1},8,["default-active"])]),i("div",Y,[o(U,{trigger:"click",onCommand:p,class:"ml-12"},{dropdown:e(()=>[o(C,null,{default:e(()=>[o(h,{command:"check-update"},{default:e(()=>[...t[6]||(t[6]=[u("检查更新",-1)])]),_:1}),o(h,{command:"logout",divided:""},{default:e(()=>[...t[7]||(t[7]=[u("退出",-1)])]),_:1})]),_:1})]),default:e(()=>[o(y,null,{default:e(()=>[u(x(F(c))+" ",1),o(V,{class:"el-icon--right"},{default:e(()=>[o(k)]),_:1})]),_:1})]),_:1})])]),_:1}),i("main",Z,[i("div",ee,[o(A)])]),o(P,{modelValue:d.value,"onUpdate:modelValue":t[0]||(t[0]=M=>d.value=M)},null,8,["modelValue"])])}}});export{se as default};
@@ -0,0 +1 @@
1
+ .login-card[data-v-c944978e]{max-width:400px;width:100%}.login-title[data-v-c944978e]{text-align:center;margin-bottom:24px;color:#3b82f6}
@@ -0,0 +1 @@
1
+ import{d as h,e as b,g as o,w as l,a,o as _,f as k,Y as x,a2 as f,c as C,i as L,h as B,r as i,m as N,_ as I}from"./index-GohB4JDa.js";import{A as K}from"./useApi-D_iJ2Mca.js";const A={class:"login-wrap"},E=h({__name:"LoginView",setup(M){const v=N(),n=i(""),s=i(""),t=i(""),d=i(!1);async function r(){if(!n.value){t.value="请输入用户名";return}if(!s.value){t.value="请输入密码";return}d.value=!0,t.value="";try{const u=await K.post("/api/auth/login",{username:n.value.trim(),password:s.value});localStorage.setItem("webui_token",u.token),v.push("/")}catch(u){t.value=u.message||"登录失败"}finally{d.value=!1}}return(u,e)=>{const c=a("el-input"),m=a("el-form-item"),w=a("el-alert"),g=a("el-button"),V=a("el-form"),y=a("el-card");return _(),b("div",A,[o(y,{shadow:"hover",class:"login-card"},{default:l(()=>[e[3]||(e[3]=k("h2",{class:"login-title"},"ELLM",-1)),o(V,{"label-position":"top",onSubmit:x(r,["prevent"])},{default:l(()=>[o(m,{label:"用户名"},{default:l(()=>[o(c,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=p=>n.value=p),placeholder:"请输入用户名",onKeyup:f(r,["enter"])},null,8,["modelValue"])]),_:1}),o(m,{label:"密码"},{default:l(()=>[o(c,{modelValue:s.value,"onUpdate:modelValue":e[1]||(e[1]=p=>s.value=p),type:"password",placeholder:"请输入密码","show-password":"",onKeyup:f(r,["enter"])},null,8,["modelValue"])]),_:1}),t.value?(_(),C(w,{key:0,title:t.value,type:"error","show-icon":"",class:"mb-16"},null,8,["title"])):L("",!0),o(g,{type:"primary",class:"full-width",loading:d.value,onClick:r},{default:l(()=>[...e[2]||(e[2]=[B(" 登录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1})])}}}),P=I(E,[["__scopeId","data-v-c944978e"]]);export{P as default};
@@ -1,4 +1,4 @@
1
- import{d as G,r as b,m as H,o as _,j as q,w as a,c as g,a as e,e as i,t as u,u as m,n as Q,F as P,b as d,k as j,l as y,_ as X,i as pe,p as ce,a0 as _e,f as ve}from"./index-dPeMJjrz.js";import{A}from"./useApi-C1Ngt_o5.js";import{u as me}from"./providers-Dmz2p4Wd.js";import{u as Z}from"./useToast-B3quFZyv.js";import"./config-BRaXq0LL.js";async function ee(p){try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(p),!0}catch{}try{const f=document.createElement("textarea");f.value=p,f.style.position="fixed",f.style.opacity="0",document.body.appendChild(f),f.select();const k=document.execCommand("copy");return document.body.removeChild(f),k}catch{return!1}}const te=p=>{if(!p)return"-";const f=new Date(p),k=w=>String(w).padStart(2,"0");return`${f.getFullYear()}-${k(f.getMonth()+1)}-${k(f.getDate())} ${k(f.getHours())}:${k(f.getMinutes())}:${k(f.getSeconds())}`},V=p=>p?typeof p=="string"?p:JSON.stringify(p,null,2):"-",fe=p=>{if(!p)return"-";try{return JSON.stringify(JSON.parse(p),null,2)}catch{return p}},T=p=>p.toLocaleString(),le=p=>p==null||p<=0?"-":p>=1e3?(p/1e3).toFixed(2)+"s":Math.round(p)+"ms",ae=p=>{const f=p.input_tokens||0,k=p.cache_read_tokens||0;return f<=0?"-":(k/f*100).toFixed(1)+"%"},be={key:0,class:"detail-content"},ge={key:0,class:"ip-link"},ye={class:"ip-pop"},ke={class:"ip-pop-row"},he={key:0,class:"ip-pop-row"},xe={key:1,class:"ip-pop-error"},we={key:2},De=["href"],Ce={key:1},$e={key:1},Ve={class:"error-text"},Se={class:"pane-bar"},Ie={class:"json-block"},qe={class:"pane-bar"},ze={class:"json-block"},Re={class:"pane-bar"},Ue={class:"json-block"},je={class:"pane-bar"},Te={class:"json-block"},Ne={class:"pane-bar"},Le={class:"json-block"},Me={class:"pane-bar"},Ye={class:"json-block"},Ee={class:"pane-bar"},Pe={class:"json-block"},Fe={class:"pane-bar"},Be={class:"json-block"},Je=G({__name:"LogDetailDialog",setup(p,{expose:f}){const{toast:k}=Z(),w=y(!1),t=y(null),S=y("info"),I=y(!1);f({open:async v=>{w.value=!0,t.value=null,S.value="info";try{const l=v.id?`?id=${v.id}`:`?_file=${encodeURIComponent(v._file)}`,c=await A.get("/api/logs/detail"+l);c&&c.detail?t.value={...c.detail,...c.meta}:t.value=c}catch(l){console.error(l)}}});const h=async v=>{await ee(v)?k("已复制到剪贴板","success"):k("复制失败","error")},N=async v=>{if(!(v._ipInfo||v._ipInfoError)){I.value=!0;try{const l=await A.get(`/api/logs/ipinfo?ip=${encodeURIComponent(v.ip)}`);l.error?v._ipInfoError=l.error:v._ipInfo=l}catch{v._ipInfoError="查询失败"}finally{I.value=!1}}},M=v=>[v.country,v.region,v.city].filter(Boolean).join(" · ")||"-";return(v,l)=>{const c=b("el-descriptions-item"),Y=b("el-tag"),F=b("el-divider"),B=b("el-popover"),J=b("el-descriptions"),E=b("el-alert"),x=b("el-tab-pane"),D=b("el-button"),K=b("el-tabs"),L=b("el-dialog"),C=H("loading");return _(),q(L,{modelValue:w.value,"onUpdate:modelValue":l[11]||(l[11]=z=>w.value=z),title:"日志详情",width:"80%",top:"5vh","destroy-on-close":""},{default:a(()=>[t.value?(_(),g("div",be,[e(K,{modelValue:S.value,"onUpdate:modelValue":l[10]||(l[10]=z=>S.value=z)},{default:a(()=>{var z,r,s;return[e(x,{label:"基本信息",name:"info"},{default:a(()=>{var n;return[e(J,{column:2,border:""},{default:a(()=>[e(c,{label:"时间"},{default:a(()=>[i(u(m(te)(t.value.time)),1)]),_:1}),e(c,{label:"模型"},{default:a(()=>[i(u(t.value.model),1)]),_:1}),e(c,{label:"方法"},{default:a(()=>[i(u(t.value.method),1)]),_:1}),e(c,{label:"路径"},{default:a(()=>[i(u(t.value.path),1)]),_:1}),e(c,{label:"状态码"},{default:a(()=>[i(u(t.value.status),1)]),_:1}),e(c,{label:"流式"},{default:a(()=>[e(Y,{type:t.value.stream?"warning":"info"},{default:a(()=>[i(u(t.value.stream?"是":"否"),1)]),_:1},8,["type"])]),_:1}),e(c,{label:"IP"},{default:a(()=>[t.value.ip?(_(),g("span",ge,[e(B,{placement:"right",width:"240",trigger:"click",onShow:l[0]||(l[0]=R=>N(t.value))},{reference:a(()=>[d("span",null,u(t.value.ip),1)]),default:a(()=>[Q((_(),g("div",ye,[t.value._ipInfo?(_(),g(P,{key:0},[d("div",ke,[l[12]||(l[12]=d("span",{class:"ip-pop-label"},"归属地",-1)),d("span",null,u(M(t.value._ipInfo)),1)]),t.value._ipInfo.isp?(_(),g("div",he,[l[13]||(l[13]=d("span",{class:"ip-pop-label"},"运营商",-1)),d("span",null,u(t.value._ipInfo.isp),1)])):j("",!0)],64)):t.value._ipInfoError?(_(),g("div",xe,u(t.value._ipInfoError),1)):(_(),g("span",we,"查询中...")),e(F,{style:{margin:"8px 0"}}),d("a",{class:"ip-pop-link",href:`https://m.ip138.com/iplookup.php?ip=${encodeURIComponent(t.value.ip)}`,target:"_blank"},"在 ip138 查看",8,De)])),[[C,I.value]])]),_:1})])):(_(),g("span",Ce,"-"))]),_:1}),e(c,{label:"Session"},{default:a(()=>[i(u(t.value.session_id||"-"),1)]),_:1}),e(c,{label:"Request ID"},{default:a(()=>[t.value.request_id?(_(),g("span",{key:0,class:"request-id",title:"点击复制(可用于关联控制台日志与检索)",onClick:l[1]||(l[1]=R=>h(t.value.request_id))},u(t.value.request_id),1)):(_(),g("span",$e,"-"))]),_:1}),e(c,{label:"输入 Token(含缓存读取)"},{default:a(()=>[i(u(m(T)(t.value.input_tokens??0)),1)]),_:1}),e(c,{label:"输出 Token"},{default:a(()=>[i(u(m(T)(t.value.output_tokens??0)),1)]),_:1}),e(c,{label:"缓存读取"},{default:a(()=>[i(u(m(T)(t.value.cache_read_tokens??0)),1)]),_:1}),e(c,{label:"请求耗时"},{default:a(()=>[i(u(m(le)(t.value.duration)),1)]),_:1}),e(c,{label:"缓存命中率"},{default:a(()=>[i(u(m(ae)(t.value)),1)]),_:1}),t.value.error?(_(),q(c,{key:0,label:"错误",span:2},{default:a(()=>[d("span",Ve,u(t.value.error),1)]),_:1})):j("",!0)]),_:1}),(n=t.value.response)!=null&&n.truncated?(_(),q(E,{key:0,class:"mt-16",type:"warning",closable:!1,title:"内容超过单请求捕获上限,已截断"})):j("",!0)]}),_:1}),(z=t.value.response)!=null&&z._capture?(_(),g(P,{key:0},[e(x,{label:"客户端请求",name:"inbound"},{default:a(()=>[d("div",Se,[e(D,{size:"small",class:"copy-btn",onClick:l[2]||(l[2]=n=>h(m(V)(t.value.response.inbound_request)))},{default:a(()=>[...l[14]||(l[14]=[i(" 复制 ",-1)])]),_:1})]),d("pre",Ie,u(m(V)(t.value.response.inbound_request)),1)]),_:1}),e(x,{label:"转发上游的请求",name:"upstreamReq"},{default:a(()=>[d("div",qe,[e(D,{size:"small",class:"copy-btn",onClick:l[3]||(l[3]=n=>h(m(V)(t.value.response.upstream_request)))},{default:a(()=>[...l[15]||(l[15]=[i(" 复制 ",-1)])]),_:1})]),d("pre",ze,u(m(V)(t.value.response.upstream_request)),1)]),_:1}),t.value.response.upstream_response_text?(_(),q(x,{key:0,label:"上游响应(原始)",name:"upstreamRes"},{default:a(()=>[d("div",Re,[e(D,{size:"small",class:"copy-btn",onClick:l[4]||(l[4]=n=>h(t.value.response.upstream_response_text))},{default:a(()=>[...l[16]||(l[16]=[i(" 复制 ",-1)])]),_:1})]),d("pre",Ue,u(m(fe)(t.value.response.upstream_response_text)),1)]),_:1})):(r=t.value.response.upstream_stream_frames)!=null&&r.length?(_(),q(x,{key:1,label:`上游响应(原始流,${t.value.response.upstream_stream_frames.length} 帧)`,name:"upstreamStream"},{default:a(()=>[d("div",je,[e(D,{size:"small",class:"copy-btn",onClick:l[5]||(l[5]=n=>h(t.value.response.upstream_stream_frames.join(`
1
+ import{d as G,a as b,j as H,o as _,c as q,w as a,e as g,g as e,h as i,t as u,k as m,b as Q,F as P,f as d,i as j,r as y,_ as X,p as pe,q as ce,a2 as _e,l as ve}from"./index-GohB4JDa.js";import{A}from"./useApi-D_iJ2Mca.js";import{u as me}from"./providers-Djtdy2tv.js";import{u as Z}from"./useToast-C0UMTbkx.js";import"./config-Cs3_z63C.js";async function ee(p){try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(p),!0}catch{}try{const f=document.createElement("textarea");f.value=p,f.style.position="fixed",f.style.opacity="0",document.body.appendChild(f),f.select();const k=document.execCommand("copy");return document.body.removeChild(f),k}catch{return!1}}const te=p=>{if(!p)return"-";const f=new Date(p),k=w=>String(w).padStart(2,"0");return`${f.getFullYear()}-${k(f.getMonth()+1)}-${k(f.getDate())} ${k(f.getHours())}:${k(f.getMinutes())}:${k(f.getSeconds())}`},V=p=>p?typeof p=="string"?p:JSON.stringify(p,null,2):"-",fe=p=>{if(!p)return"-";try{return JSON.stringify(JSON.parse(p),null,2)}catch{return p}},T=p=>p.toLocaleString(),le=p=>p==null||p<=0?"-":p>=1e3?(p/1e3).toFixed(2)+"s":Math.round(p)+"ms",ae=p=>{const f=p.input_tokens||0,k=p.cache_read_tokens||0;return f<=0?"-":(k/f*100).toFixed(1)+"%"},be={key:0,class:"detail-content"},ge={key:0,class:"ip-link"},ye={class:"ip-pop"},ke={class:"ip-pop-row"},he={key:0,class:"ip-pop-row"},xe={key:1,class:"ip-pop-error"},we={key:2},De=["href"],Ce={key:1},$e={key:1},Ve={class:"error-text"},Se={class:"pane-bar"},Ie={class:"json-block"},qe={class:"pane-bar"},ze={class:"json-block"},Re={class:"pane-bar"},Ue={class:"json-block"},je={class:"pane-bar"},Te={class:"json-block"},Ne={class:"pane-bar"},Le={class:"json-block"},Me={class:"pane-bar"},Ye={class:"json-block"},Ee={class:"pane-bar"},Pe={class:"json-block"},Fe={class:"pane-bar"},Be={class:"json-block"},Je=G({__name:"LogDetailDialog",setup(p,{expose:f}){const{toast:k}=Z(),w=y(!1),t=y(null),S=y("info"),I=y(!1);f({open:async v=>{w.value=!0,t.value=null,S.value="info";try{const l=v.id?`?id=${v.id}`:`?_file=${encodeURIComponent(v._file)}`,c=await A.get("/api/logs/detail"+l);c&&c.detail?t.value={...c.detail,...c.meta}:t.value=c}catch(l){console.error(l)}}});const h=async v=>{await ee(v)?k("已复制到剪贴板","success"):k("复制失败","error")},N=async v=>{if(!(v._ipInfo||v._ipInfoError)){I.value=!0;try{const l=await A.get(`/api/logs/ipinfo?ip=${encodeURIComponent(v.ip)}`);l.error?v._ipInfoError=l.error:v._ipInfo=l}catch{v._ipInfoError="查询失败"}finally{I.value=!1}}},M=v=>[v.country,v.region,v.city].filter(Boolean).join(" · ")||"-";return(v,l)=>{const c=b("el-descriptions-item"),Y=b("el-tag"),F=b("el-divider"),B=b("el-popover"),J=b("el-descriptions"),E=b("el-alert"),x=b("el-tab-pane"),D=b("el-button"),K=b("el-tabs"),L=b("el-dialog"),C=H("loading");return _(),q(L,{modelValue:w.value,"onUpdate:modelValue":l[11]||(l[11]=z=>w.value=z),title:"日志详情",width:"80%",top:"5vh","destroy-on-close":""},{default:a(()=>[t.value?(_(),g("div",be,[e(K,{modelValue:S.value,"onUpdate:modelValue":l[10]||(l[10]=z=>S.value=z)},{default:a(()=>{var z,r,s;return[e(x,{label:"基本信息",name:"info"},{default:a(()=>{var n;return[e(J,{column:2,border:""},{default:a(()=>[e(c,{label:"时间"},{default:a(()=>[i(u(m(te)(t.value.time)),1)]),_:1}),e(c,{label:"模型"},{default:a(()=>[i(u(t.value.model),1)]),_:1}),e(c,{label:"方法"},{default:a(()=>[i(u(t.value.method),1)]),_:1}),e(c,{label:"路径"},{default:a(()=>[i(u(t.value.path),1)]),_:1}),e(c,{label:"状态码"},{default:a(()=>[i(u(t.value.status),1)]),_:1}),e(c,{label:"流式"},{default:a(()=>[e(Y,{type:t.value.stream?"warning":"info"},{default:a(()=>[i(u(t.value.stream?"是":"否"),1)]),_:1},8,["type"])]),_:1}),e(c,{label:"IP"},{default:a(()=>[t.value.ip?(_(),g("span",ge,[e(B,{placement:"right",width:"240",trigger:"click",onShow:l[0]||(l[0]=R=>N(t.value))},{reference:a(()=>[d("span",null,u(t.value.ip),1)]),default:a(()=>[Q((_(),g("div",ye,[t.value._ipInfo?(_(),g(P,{key:0},[d("div",ke,[l[12]||(l[12]=d("span",{class:"ip-pop-label"},"归属地",-1)),d("span",null,u(M(t.value._ipInfo)),1)]),t.value._ipInfo.isp?(_(),g("div",he,[l[13]||(l[13]=d("span",{class:"ip-pop-label"},"运营商",-1)),d("span",null,u(t.value._ipInfo.isp),1)])):j("",!0)],64)):t.value._ipInfoError?(_(),g("div",xe,u(t.value._ipInfoError),1)):(_(),g("span",we,"查询中...")),e(F,{style:{margin:"8px 0"}}),d("a",{class:"ip-pop-link",href:`https://m.ip138.com/iplookup.php?ip=${encodeURIComponent(t.value.ip)}`,target:"_blank"},"在 ip138 查看",8,De)])),[[C,I.value]])]),_:1})])):(_(),g("span",Ce,"-"))]),_:1}),e(c,{label:"Session"},{default:a(()=>[i(u(t.value.session_id||"-"),1)]),_:1}),e(c,{label:"Request ID"},{default:a(()=>[t.value.request_id?(_(),g("span",{key:0,class:"request-id",title:"点击复制(可用于关联控制台日志与检索)",onClick:l[1]||(l[1]=R=>h(t.value.request_id))},u(t.value.request_id),1)):(_(),g("span",$e,"-"))]),_:1}),e(c,{label:"输入 Token(含缓存读取)"},{default:a(()=>[i(u(m(T)(t.value.input_tokens??0)),1)]),_:1}),e(c,{label:"输出 Token"},{default:a(()=>[i(u(m(T)(t.value.output_tokens??0)),1)]),_:1}),e(c,{label:"缓存读取"},{default:a(()=>[i(u(m(T)(t.value.cache_read_tokens??0)),1)]),_:1}),e(c,{label:"请求耗时"},{default:a(()=>[i(u(m(le)(t.value.duration)),1)]),_:1}),e(c,{label:"缓存命中率"},{default:a(()=>[i(u(m(ae)(t.value)),1)]),_:1}),t.value.error?(_(),q(c,{key:0,label:"错误",span:2},{default:a(()=>[d("span",Ve,u(t.value.error),1)]),_:1})):j("",!0)]),_:1}),(n=t.value.response)!=null&&n.truncated?(_(),q(E,{key:0,class:"mt-16",type:"warning",closable:!1,title:"内容超过单请求捕获上限,已截断"})):j("",!0)]}),_:1}),(z=t.value.response)!=null&&z._capture?(_(),g(P,{key:0},[e(x,{label:"客户端请求",name:"inbound"},{default:a(()=>[d("div",Se,[e(D,{size:"small",class:"copy-btn",onClick:l[2]||(l[2]=n=>h(m(V)(t.value.response.inbound_request)))},{default:a(()=>[...l[14]||(l[14]=[i(" 复制 ",-1)])]),_:1})]),d("pre",Ie,u(m(V)(t.value.response.inbound_request)),1)]),_:1}),e(x,{label:"转发上游的请求",name:"upstreamReq"},{default:a(()=>[d("div",qe,[e(D,{size:"small",class:"copy-btn",onClick:l[3]||(l[3]=n=>h(m(V)(t.value.response.upstream_request)))},{default:a(()=>[...l[15]||(l[15]=[i(" 复制 ",-1)])]),_:1})]),d("pre",ze,u(m(V)(t.value.response.upstream_request)),1)]),_:1}),t.value.response.upstream_response_text?(_(),q(x,{key:0,label:"上游响应(原始)",name:"upstreamRes"},{default:a(()=>[d("div",Re,[e(D,{size:"small",class:"copy-btn",onClick:l[4]||(l[4]=n=>h(t.value.response.upstream_response_text))},{default:a(()=>[...l[16]||(l[16]=[i(" 复制 ",-1)])]),_:1})]),d("pre",Ue,u(m(fe)(t.value.response.upstream_response_text)),1)]),_:1})):(r=t.value.response.upstream_stream_frames)!=null&&r.length?(_(),q(x,{key:1,label:`上游响应(原始流,${t.value.response.upstream_stream_frames.length} 帧)`,name:"upstreamStream"},{default:a(()=>[d("div",je,[e(D,{size:"small",class:"copy-btn",onClick:l[5]||(l[5]=n=>h(t.value.response.upstream_stream_frames.join(`
2
2
 
3
3
  `)))},{default:a(()=>[...l[17]||(l[17]=[i(" 复制 ",-1)])]),_:1})]),d("pre",Te,u(t.value.response.upstream_stream_frames.join(`
4
4
 
@@ -0,0 +1 @@
1
+ import{d as X,p as Z,a7 as q,b as H,e as D,g as s,w as l,r as i,a as c,j as J,o as w,f as d,Z as Q,h as o,t as r,c as W,E as Y,_ as ee}from"./index-GohB4JDa.js";import{m as E}from"./useManagerApi-C1_COxKg.js";import{u as te}from"./useToast-C0UMTbkx.js";const ae={class:"flex-col-gap"},le={class:"flex-between"},se={class:"proc-name"},ne={class:"proc-sub"},oe=["onClick"],re={key:1,class:"muted-sm"},ie={class:"desc-full"},de=X({__name:"ProcessView",setup(ue){const{toast:b}=te(),v=i(!0),g=i(!1),y=i([]),x=i(null),p=i(null),h=i(Date.now()),I={daemon:"pm2 守护进程",manager:"管理服务",main:"代理服务"},C=e=>I[e.role]??e.role,k={daemon:"pm2 守护进程:托管代理服务与管理服务的后台守护,进程崩溃时自动拉起;自身不监听端口、不处理业务请求,不允许远程操控(停止它将失去自动恢复能力)。",manager:"管理服务(ellm-proxy-manager):独立常驻的轻量服务,提供进程启停接口与代理开关接口;代理服务停止时保持存活,保证还能在本页远程拉起代理服务。",main:"代理服务(ellm-proxy):网关本体,转发 /v1/* 模型请求,同时承载管理台页面与 API;本页的重启操作即作用于该进程,重启期间网关与管理台 API 短暂不可用(代理服务停止时重启等效于启动)。"},P=e=>k[e.role],_=i(!1),T=i(""),M=i(""),A=e=>{T.value=`${C(e)}(${$(e)})`,M.value=k[e.role],_.value=!0},$=e=>e.role==="daemon"?"pm2":e.role==="manager"?"ellm-proxy-manager":"ellm-proxy",O=e=>e.status==="online",N=e=>e.status?e.status==="online"?"running":e.status==="errored"?"abnormal":"stopped":"stopped",V=e=>{switch(e.status){case"online":return{text:"运行中",type:"success"};case"stopped":return{text:"已停止",type:"info"};case"errored":return{text:"异常",type:"danger"};case"waiting restart":case"launching":return{text:"重启中",type:"warning"};default:return{text:e.status||"未知",type:"info"}}},R=e=>{if(!O(e)||!e.startedAt)return"-";let t=Math.max(0,Math.floor((h.value-e.startedAt)/1e3));const m=Math.floor(t/86400);t-=m*86400;const u=Math.floor(t/3600);t-=u*3600;const n=Math.floor(t/60);return t-=n*60,[m?`${m}天`:"",u?`${u}时`:"",n?`${n}分`:"",`${t}秒`].filter(Boolean).join(" ")},S=e=>e?e>=1024**3?(e/1024**3).toFixed(1)+"G":e>=1024**2?(e/1024**2).toFixed(1)+"M":Math.round(e/1024)+"K":"-",f=async e=>{e||(y.value.length?g.value=!0:v.value=!0);try{y.value=await E.processes(),h.value=Date.now(),x.value=null}catch(t){e||(x.value=t.message)}finally{e||(v.value=!1,g.value=!1)}};let B;const j=()=>{p.value||document.hidden||f(!0)};Z(()=>{f(!1),B=window.setInterval(j,5e3)}),q(()=>window.clearInterval(B));const z=async()=>{try{await Y.confirm("确定重启 ellm-proxy 代理服务?重启期间网关与管理台 API 将短暂不可用(代理服务已停止时重启等效于启动)。","重启确认",{confirmButtonText:"重启",cancelButtonText:"取消",type:"warning"})}catch{return}p.value="restart";try{await E.restart(),b("重启完成","success")}catch(e){b(e.message||"操作失败","error")}finally{p.value=null,await f(!0)}};return(e,t)=>{const m=c("el-tag"),u=c("el-button"),n=c("el-table-column"),F=c("el-empty"),L=c("el-table"),U=c("el-card"),G=c("el-dialog"),K=J("loading");return H((w(),D("div",ae,[s(U,{shadow:"never"},{header:l(()=>[d("div",le,[t[6]||(t[6]=d("span",{class:"fw-600"},"进程管理",-1)),d("div",null,[s(m,{type:"info",class:"mr-8"},{default:l(()=>[...t[4]||(t[4]=[o("刷新间隔 5s",-1)])]),_:1}),s(u,{loading:g.value,onClick:t[0]||(t[0]=a=>f(!1))},{default:l(()=>[...t[5]||(t[5]=[o("刷新",-1)])]),_:1},8,["loading"])])])]),default:l(()=>[s(L,{data:y.value,border:"",stripe:"",class:"full-width"},{empty:l(()=>[s(F,{description:x.value||"暂无进程数据"},{default:l(()=>[s(u,{type:"primary",onClick:t[1]||(t[1]=a=>f(!1))},{default:l(()=>[...t[8]||(t[8]=[o("重试",-1)])]),_:1})]),_:1},8,["description"])]),default:l(()=>[s(n,{label:"进程","min-width":"180"},{default:l(({row:a})=>[d("div",se,[d("span",{class:Q(["status-dot",N(a)])},null,2),o(" "+r(C(a))+" ",1),d("span",ne,r($(a)),1)])]),_:1}),s(n,{label:"运行状态",width:"100",align:"center"},{default:l(({row:a})=>[s(m,{type:V(a).type,"disable-transitions":""},{default:l(()=>[o(r(V(a).text),1)]),_:2},1032,["type"])]),_:1}),s(n,{label:"PID",width:"90",align:"center"},{default:l(({row:a})=>[o(r(a.pid??"-"),1)]),_:1}),s(n,{label:"端口",width:"90",align:"center"},{default:l(({row:a})=>[o(r(a.port??"-"),1)]),_:1}),s(n,{label:"运行时长",width:"130",align:"center"},{default:l(({row:a})=>[o(r(R(a)),1)]),_:1}),s(n,{label:"地址","min-width":"170"},{default:l(({row:a})=>[o(r(a.address??"-"),1)]),_:1}),s(n,{label:"内存",width:"90",align:"center"},{default:l(({row:a})=>[o(r(S(a.memory)),1)]),_:1}),s(n,{label:"描述","min-width":"220"},{default:l(({row:a})=>[d("span",{class:"desc-cell",title:"点击查看详情",onClick:ce=>A(a)},r(P(a)),9,oe)]),_:1}),s(n,{label:"操作",width:"90",align:"center"},{default:l(({row:a})=>[a.role==="main"?(w(),W(u,{key:0,type:"primary",size:"small",loading:p.value==="restart",disabled:p.value!==null,onClick:z},{default:l(()=>[...t[7]||(t[7]=[o(" 重启 ",-1)])]),_:1},8,["loading","disabled"])):(w(),D("span",re,"不可操作"))]),_:1})]),_:1},8,["data"]),t[9]||(t[9]=d("div",{class:"muted-sm"}," 进程控制由管理服务(manager)执行,仅代理服务(ellm-proxy)可重启(已停止时重启等效于启动): 重启期间网关与管理台 API 短暂不可用。 pm2 守护进程与管理服务不允许远程操控(停止它们将失去远程恢复能力)。 ",-1))]),_:1}),s(G,{modelValue:_.value,"onUpdate:modelValue":t[3]||(t[3]=a=>_.value=a),title:T.value,width:"520px"},{footer:l(()=>[s(u,{type:"primary",onClick:t[2]||(t[2]=a=>_.value=!1)},{default:l(()=>[...t[10]||(t[10]=[o("知道了",-1)])]),_:1})]),default:l(()=>[d("p",ie,r(M.value),1)]),_:1},8,["modelValue","title"])])),[[K,v.value]])}}}),_e=ee(de,[["__scopeId","data-v-c87eb134"]]);export{_e as default};
@@ -0,0 +1 @@
1
+ @charset "UTF-8";.proc-name[data-v-c87eb134]{display:flex;align-items:center;gap:8px;font-weight:600}.proc-name .proc-sub[data-v-c87eb134]{font-weight:400;color:var(--el-text-color-secondary);font-size:12px}.status-dot[data-v-c87eb134]{display:inline-block;width:8px;height:8px;border-radius:50%}.status-dot.running[data-v-c87eb134]{background:var(--el-color-success);box-shadow:0 0 4px var(--el-color-success)}.status-dot.stopped[data-v-c87eb134]{background:var(--el-color-info)}.status-dot.abnormal[data-v-c87eb134]{background:var(--el-color-danger)}.desc-cell[data-v-c87eb134]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.desc-full[data-v-c87eb134]{margin:0;line-height:1.8}.mr-8[data-v-c87eb134]{margin-right:8px}
@@ -1,4 +1,4 @@
1
- import{N as Kn,q as Mt,s as He,v as cr,x as Lt,y as jo,z as Ts,A as ia,E as Os,B as aa,C as Is,D as Kt,G as sa,H as xi,I as Ns,J as Cs,K as As,L as xs,M as Ps,O as Xr,P as Ds,Q as Kr,R as Rs,S as Ms,T as Ls,U as ws,V as gn,d as Jt,r as J,o as te,j as Me,w as M,b as K,t as Te,c as Be,F as Bo,p as ko,W as fo,X as Vs,u as q,e as ae,k as tt,a as P,Y as Fs,f as Ht,l as Pe,_ as cn,Z as Xo,$ as Rt,i as la,a0 as $s,a1 as Ao,a2 as Us,a3 as js,a4 as Bs,a5 as ks}from"./index-dPeMJjrz.js";import{u as un}from"./useToast-B3quFZyv.js";import{u as Ko}from"./providers-Dmz2p4Wd.js";import{A as Yt}from"./useApi-C1Ngt_o5.js";import"./config-BRaXq0LL.js";var yr={exports:{}},po={exports:{}},ho={};/**
1
+ import{N as Kn,s as Mt,v as He,x as cr,y as Lt,z as jo,A as Ts,B as ia,C as Os,D as aa,G as Is,H as Kt,I as sa,J as xi,K as Ns,L as Cs,M as As,O as xs,P as Ps,Q as Xr,R as Ds,S as Kr,T as Rs,U as Ms,V as Ls,W as ws,X as gn,d as Jt,a as J,o as te,c as Me,w as M,f as K,t as Te,e as Be,F as Bo,q as ko,Y as fo,Z as Vs,k as q,h as ae,i as tt,g as P,$ as Fs,l as Ht,r as Pe,_ as cn,a0 as Xo,a1 as Rt,p as la,a2 as $s,E as Ao,a3 as Us,a4 as js,a5 as Bs,a6 as ks}from"./index-GohB4JDa.js";import{u as un}from"./useToast-C0UMTbkx.js";import{u as Ko}from"./providers-Djtdy2tv.js";import{A as Yt}from"./useApi-D_iJ2Mca.js";import"./config-Cs3_z63C.js";var yr={exports:{}},po={exports:{}},ho={};/**
2
2
  * @vue/compiler-core v3.5.42
3
3
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
4
4
  * @license MIT
@@ -0,0 +1 @@
1
+ .switch-row[data-v-65e65156]{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:4px 0}.switch-title[data-v-65e65156]{display:flex;align-items:center;gap:8px;font-weight:600;margin-bottom:4px}.switch-desc[data-v-65e65156]{color:var(--el-text-color-secondary);font-size:13px;line-height:1.6}.switch-desc code[data-v-65e65156]{background:var(--el-fill-color-light);padding:1px 6px;border-radius:4px;font-size:12px}.switch-divider[data-v-65e65156]{margin:16px 0}.mb-16[data-v-03f482d3]{margin-bottom:16px}.mt-8[data-v-03f482d3]{margin-top:8px}.switch-row[data-v-03f482d3]{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:4px 0}.switch-title[data-v-03f482d3]{display:flex;align-items:center;gap:8px;font-weight:600;margin-bottom:4px}.switch-desc[data-v-03f482d3]{color:var(--el-text-color-secondary);font-size:13px;line-height:1.6}.switch-desc code[data-v-03f482d3]{background:var(--el-fill-color-light);padding:1px 6px;border-radius:4px;font-size:12px}.version-row[data-v-03f482d3]{display:flex;align-items:center;gap:8px;margin-bottom:8px}.version-label[data-v-03f482d3]{color:var(--el-text-color-secondary);font-size:13px}.version-actions[data-v-03f482d3]{margin-top:12px;display:flex;gap:8px}