ellm-proxy 0.0.3 → 0.0.7

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 (58) 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/credentials.js +100 -0
  5. package/bin/commands/pm2.js +28 -28
  6. package/bin/commands/service.js +107 -107
  7. package/bin/ellm-proxy.js +64 -61
  8. package/dist/server.js +141 -4
  9. package/dist/web-ui/assets/DashboardView-BCwrFrE2.js +1 -0
  10. package/dist/web-ui/assets/DashboardView-BHG4xIdN.css +1 -0
  11. package/dist/web-ui/assets/LayoutView-BUA8-IzK.css +1 -0
  12. package/dist/web-ui/assets/LayoutView-BePJfgia.js +1 -0
  13. package/dist/web-ui/assets/LoginView-Dp7NmXtf.js +1 -0
  14. package/dist/web-ui/assets/LoginView-DrhfslIY.css +1 -0
  15. package/dist/web-ui/assets/{LogsView-B2ctrpv4.js → LogsView-Dyy_zkgg.js} +2 -2
  16. package/dist/web-ui/assets/ProcessView-CpPpMtFg.js +1 -0
  17. package/dist/web-ui/assets/ProcessView-DK0TvDpR.css +1 -0
  18. package/dist/web-ui/assets/{ProvidersView-mb0KU16n.js → ProvidersView-CYFs76wJ.js} +2 -2
  19. package/dist/web-ui/assets/SettingsView-BkwyoOUb.css +1 -0
  20. package/dist/web-ui/assets/SettingsView-mPcaVAD2.js +1 -0
  21. package/dist/web-ui/assets/SetupView-DX6S_KpK.js +1 -0
  22. package/dist/web-ui/assets/SetupView-kPJXJ7dX.css +1 -0
  23. package/dist/web-ui/assets/config-CRWaUFQc.js +1 -0
  24. package/dist/web-ui/assets/{index-dPeMJjrz.js → index-X5PdkJlA.js} +28 -28
  25. package/dist/web-ui/assets/{providers-Dmz2p4Wd.js → providers-H_9x778D.js} +1 -1
  26. package/dist/web-ui/assets/useManagerApi-C1_COxKg.js +1 -0
  27. package/dist/web-ui/assets/{useToast-B3quFZyv.js → useToast-MtbkGJiM.js} +1 -1
  28. package/dist/web-ui/config.js +10 -10
  29. package/dist/web-ui/index.html +13 -13
  30. package/docs/DESIGN.md +99 -99
  31. package/ellm.config.example.json +2 -0
  32. package/manager/api/harness/backup.js +20 -20
  33. package/manager/api/harness/claude-code.js +54 -54
  34. package/manager/api/harness/codex.js +123 -123
  35. package/manager/api/harness/dsh.js +175 -175
  36. package/manager/api/harness/index.js +118 -118
  37. package/manager/api/service-settings.js +98 -0
  38. package/manager/api/service.js +3 -13
  39. package/manager/index.js +182 -170
  40. package/package.json +4 -1
  41. package/utils/autostart.js +219 -0
  42. package/utils/interpreter.js +14 -14
  43. package/utils/paths.js +45 -44
  44. package/utils/pm2.js +130 -130
  45. package/utils/service.js +205 -189
  46. package/utils/updater.js +233 -0
  47. package/dist/web-ui/assets/DashboardView-B71Dy3px.js +0 -1
  48. package/dist/web-ui/assets/DashboardView-qrQrkgLA.css +0 -1
  49. package/dist/web-ui/assets/LayoutView-C03GVB3X.js +0 -1
  50. package/dist/web-ui/assets/LoginView-Dg0mB6ok.css +0 -1
  51. package/dist/web-ui/assets/LoginView-XTF5dHvd.js +0 -1
  52. package/dist/web-ui/assets/ProcessView-DmbVABQV.js +0 -1
  53. package/dist/web-ui/assets/ProcessView-DzMqBJmd.css +0 -1
  54. package/dist/web-ui/assets/SettingsView-BDkymsrS.css +0 -1
  55. package/dist/web-ui/assets/SettingsView-BgLQVC2H.js +0 -1
  56. package/dist/web-ui/assets/config-BRaXq0LL.js +0 -1
  57. package/dist/web-ui/assets/useApi-C1Ngt_o5.js +0 -1
  58. 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,64 @@
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
- // unitsystemctl --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 /
5
+ // master-key / account / -h / --version(选项详见 -h)
6
+ //
7
+ // 运行时布局(默认 ~/.ellm-proxy/,均可被选项/环境变量覆盖):
8
+ // 配置: --config > ELLM_CONFIG 环境变量 > ~/.ellm-proxy/ellm.config.json(首次自动创建)
9
+ // 数据: ELLM_DATA_DIR 环境变量 > ~/.ellm-proxy/data
10
+ // 日志: <appDir>/logs/ellm-out.log · ellm-err.log(主服务)+ manager-out.log · manager-err.log(管理服务)
11
+ //
12
+ // 进程管理(跨平台,pm2 JS API 封装见 utils/pm2.js,编排核心见 utils/service.js):
13
+ // start 会一并托管两个进程——主服务 dist/server.js(name=ellm-proxy)与管理服务 manager/index.js
14
+ // (name=ellm-proxy-manager,启停/重启主服务 + Claude Code/Codex 代理开关)。
15
+ // 崩溃自动重启、日志落盘、dump/resurrect(开机自启一并恢复)。服务要求的 Node >=22.13.0 通过
16
+ // interpreter 保证(utils/interpreter.js),pm2 daemon node 版本无关紧要。
17
+ // start/stop 成功后自动 pm2 dump,供开机自启 resurrect 恢复。`ellm-proxy pm2 <args>` 可透传
18
+ // 任意 pm2 子命令(list/logs/monit/...,用包内局部 pm2,无需自装全局)。
19
+ //
20
+ // 开机自启(commands/autostart.js,跨平台):登录时触发、无需管理员——Windows 写启动文件夹
21
+ // .cmdshell:startup)、macOS LaunchAgent plist(launchctl load)、Linux systemd user
22
+ // unit(systemctl --user enable);内容一致 = pm2 resurrect 恢复最后一次 dump。
23
+
24
+ import { cac } from "cac";
25
+ import { pkg } from "../utils/paths.js";
26
+ import { error as uiError } from "../utils/ui.js";
27
+ import * as serviceCmd from "./commands/service.js";
28
+ import * as autostartCmd from "./commands/autostart.js";
29
+ import * as configCmd from "./commands/config.js";
30
+ import * as credentialsCmd from "./commands/credentials.js";
31
+ import * as pm2Cmd from "./commands/pm2.js";
32
+
33
+ const cli = cac("ellm-proxy");
34
+
35
+ /** 统一错误出口:命令实现抛错 → 打印中文错误信息并以退出码 1 结束 */
36
+ async function run(action) {
37
+ try {
38
+ await action();
39
+ } catch (err) {
40
+ console.error();
41
+ uiError(`错误:${err?.message ?? err}`);
42
+ process.exit(1);
43
+ }
44
+ }
45
+
46
+ serviceCmd.register(cli, run);
47
+ autostartCmd.register(cli, run);
48
+ configCmd.register(cli);
49
+ credentialsCmd.register(cli, run);
50
+ pm2Cmd.register(cli, run);
51
+
52
+ // pm2 透传:在 cac 解析前拦截 `ellm-proxy pm2 <...args>`,pm2 的 -- 选项原样可达(cac 会吞掉未知 -- 选项)
53
+ if (process.argv[2] === "pm2") {
54
+ run(() => pm2Cmd.doPassthrough(process.argv.slice(3)));
55
+ } else {
56
+ // 默认命令(无参数):启动服务,已运行则显示服务信息(选项同 start)
57
+ serviceCmd
58
+ .withStartOptions(cli.command("", "启动服务(已运行则显示服务信息,选项同 start)"))
59
+ .action((opts) => run(() => serviceCmd.doStartOrInfo(opts)));
60
+
61
+ cli.help();
62
+ cli.version(pkg.version);
63
+ cli.parse();
64
+ }
package/dist/server.js CHANGED
@@ -10739,7 +10739,14 @@ function saveConfigFile(config3) {
10739
10739
  writeFileSync(CONFIG_PATH, JSON.stringify(config3, null, 2) + "\n", "utf-8");
10740
10740
  }
10741
10741
  function validateConfig(config3) {
10742
- const { masterKey: _masterKey, "web-ui": _webUi, logs: _logs, ...gatewayConfig } = config3;
10742
+ const {
10743
+ masterKey: _masterKey,
10744
+ "web-ui": _webUi,
10745
+ logs: _logs,
10746
+ setup: _setup,
10747
+ autoUpdate: _autoUpdate,
10748
+ ...gatewayConfig
10749
+ } = config3;
10743
10750
  return expandConfig(gatewayConfig);
10744
10751
  }
10745
10752
 
@@ -10763,6 +10770,19 @@ function resolveLoginAccount() {
10763
10770
  const envPassword = process.env["WEBUI_PASSWORD"] || "admin";
10764
10771
  return { username: fileUsername ?? envUsername, password: filePassword ?? envPassword };
10765
10772
  }
10773
+ function resolveSetupState() {
10774
+ let setup;
10775
+ try {
10776
+ setup = loadConfigFile()?.setup;
10777
+ } catch {
10778
+ setup = void 0;
10779
+ }
10780
+ if (setup === void 0) return { completed: true, deployment: void 0 };
10781
+ return {
10782
+ completed: setup.completed === true,
10783
+ deployment: setup.deployment === "server" ? "server" : "local"
10784
+ };
10785
+ }
10766
10786
  function resolveLogRetention() {
10767
10787
  const logs = loadConfigFile()?.["logs"];
10768
10788
  const days = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
@@ -11363,6 +11383,37 @@ function redactHeaders(headers) {
11363
11383
  }
11364
11384
  return changed ? out : headers;
11365
11385
  }
11386
+ var REDACTED_QUERY_PARAM_NAMES = /* @__PURE__ */ new Set([
11387
+ "key",
11388
+ "apikey",
11389
+ "accesskey",
11390
+ "accesstoken",
11391
+ "token",
11392
+ "authtoken",
11393
+ "sig",
11394
+ "signature",
11395
+ "secret",
11396
+ "password"
11397
+ ]);
11398
+ function normalizeParamName(name) {
11399
+ return name.toLowerCase().replace(/[-_]/g, "");
11400
+ }
11401
+ function redactUrl(url) {
11402
+ let parsed;
11403
+ try {
11404
+ parsed = new URL(url);
11405
+ } catch {
11406
+ return url;
11407
+ }
11408
+ let changed = false;
11409
+ for (const [name, value] of Array.from(parsed.searchParams.entries())) {
11410
+ if (REDACTED_QUERY_PARAM_NAMES.has(normalizeParamName(name))) {
11411
+ parsed.searchParams.set(name, `***redacted(${String(value.length)})***`);
11412
+ changed = true;
11413
+ }
11414
+ }
11415
+ return changed ? parsed.toString() : url;
11416
+ }
11366
11417
  function createSqliteTraceSink() {
11367
11418
  return {
11368
11419
  write(trace) {
@@ -11382,12 +11433,12 @@ function createSqliteTraceSink() {
11382
11433
  _capture: true,
11383
11434
  // 与 upstream_request 同形(url/headers/body 三件套):"客户端发什么就存什么"
11384
11435
  inbound_request: {
11385
- url: trace.http.url,
11436
+ url: redactUrl(trace.http.url),
11386
11437
  headers: redactHeaders(trace.inbound.headers),
11387
11438
  body: trace.inbound.body ?? null
11388
11439
  },
11389
11440
  upstream_request: upstream.url ? {
11390
- url: upstream.url,
11441
+ url: redactUrl(upstream.url),
11391
11442
  headers: redactHeaders(upstream.requestHeaders),
11392
11443
  body: parseOrRaw(upstream.requestBody)
11393
11444
  } : null,
@@ -11523,6 +11574,88 @@ async function authMiddleware(c, next) {
11523
11574
  }
11524
11575
  var auth_default = app;
11525
11576
 
11577
+ // ../backend/src/api/setup.ts
11578
+ import { randomBytes } from "node:crypto";
11579
+ var MIN_MASTER_KEY_LENGTH = 8;
11580
+ var MIN_PASSWORD_LENGTH = 6;
11581
+ function createSetupApp() {
11582
+ const app3 = new Hono2();
11583
+ app3.get("/status", (c) => {
11584
+ const state = resolveSetupState();
11585
+ return c.json({
11586
+ initialized: state.completed,
11587
+ deployment: state.deployment,
11588
+ masterKeySet: resolveMasterKey() !== ""
11589
+ });
11590
+ });
11591
+ app3.post("/master-key", async (c) => {
11592
+ if (resolveSetupState().completed) {
11593
+ return c.json({ error: "\u5DF2\u5B8C\u6210\u521D\u59CB\u5316\uFF0C\u8BF7\u5230\u8BBE\u7F6E\u9875\u4FEE\u6539\u4E3B\u5BC6\u94A5" }, 403);
11594
+ }
11595
+ const body = await c.req.json().catch(() => null);
11596
+ if (body === null || typeof body !== "object") {
11597
+ return c.json({ error: "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF" }, 400);
11598
+ }
11599
+ let masterKey;
11600
+ if (body.mode === "generate") {
11601
+ masterKey = randomBytes(32).toString("base64url");
11602
+ } else if (body.mode === "custom") {
11603
+ masterKey = (body.masterKey ?? "").trim();
11604
+ if (masterKey.length < MIN_MASTER_KEY_LENGTH) {
11605
+ return c.json({ error: `\u4E3B\u5BC6\u94A5\u81F3\u5C11 ${String(MIN_MASTER_KEY_LENGTH)} \u4F4D` }, 400);
11606
+ }
11607
+ } else {
11608
+ return c.json({ error: "mode \u5FC5\u987B\u662F generate \u6216 custom" }, 400);
11609
+ }
11610
+ const config3 = loadConfigFile();
11611
+ if (config3 === null) return c.json({ error: "\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728" }, 500);
11612
+ saveConfigFile({ ...config3, masterKey });
11613
+ return c.json({ masterKey });
11614
+ });
11615
+ app3.post("/finish", async (c) => {
11616
+ if (resolveSetupState().completed) {
11617
+ return c.json({ error: "\u5DF2\u5B8C\u6210\u521D\u59CB\u5316\uFF0C\u8BF7\u5230\u8BBE\u7F6E\u9875\u4FEE\u6539" }, 403);
11618
+ }
11619
+ const body = await c.req.json().catch(() => null);
11620
+ if (body === null || typeof body !== "object") {
11621
+ return c.json({ error: "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF" }, 400);
11622
+ }
11623
+ const deployment = body.deployment;
11624
+ if (deployment !== "local" && deployment !== "server") {
11625
+ return c.json({ error: "deployment \u5FC5\u987B\u662F local \u6216 server" }, 400);
11626
+ }
11627
+ if (deployment === "server" && resolveMasterKey() === "") {
11628
+ return c.json({ error: "\u670D\u52A1\u5668\u90E8\u7F72\u5FC5\u987B\u8BBE\u7F6E\u4E3B\u5BC6\u94A5\uFF0C\u9632\u6B62 /v1 \u7F51\u5173\u88AB\u4ED6\u4EBA\u76D7\u7528" }, 400);
11629
+ }
11630
+ const username = (body.username ?? "").trim();
11631
+ const password = body.password ?? "";
11632
+ const hasAccount = username !== "" || password !== "";
11633
+ if (hasAccount) {
11634
+ if (username === "" || password === "") {
11635
+ return c.json({ error: "\u7528\u6237\u540D\u548C\u5BC6\u7801\u9700\u540C\u65F6\u63D0\u4F9B" }, 400);
11636
+ }
11637
+ if (password.length < MIN_PASSWORD_LENGTH) {
11638
+ return c.json({ error: `\u5BC6\u7801\u81F3\u5C11 ${String(MIN_PASSWORD_LENGTH)} \u4F4D` }, 400);
11639
+ }
11640
+ } else if (deployment === "server") {
11641
+ return c.json({ error: "\u670D\u52A1\u5668\u90E8\u7F72\u5FC5\u987B\u8BBE\u7F6E\u767B\u5F55\u8D26\u53F7\u5BC6\u7801\uFF0C\u9632\u6B62\u7BA1\u7406\u53F0\u88AB\u964C\u751F\u4EBA\u767B\u5F55" }, 400);
11642
+ }
11643
+ if (deployment === "server" && username === "admin" && password === "admin") {
11644
+ return c.json({ error: "\u670D\u52A1\u5668\u90E8\u7F72\u4E0D\u80FD\u4F7F\u7528\u9ED8\u8BA4\u7684 admin/admin \u8D26\u53F7\u5BC6\u7801" }, 400);
11645
+ }
11646
+ const config3 = loadConfigFile();
11647
+ if (config3 === null) return c.json({ error: "\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728" }, 500);
11648
+ const next = {
11649
+ ...config3,
11650
+ setup: { completed: true, deployment },
11651
+ ...hasAccount ? { "web-ui": { ...config3["web-ui"], "login-account": { username, password } } } : {}
11652
+ };
11653
+ saveConfigFile(next);
11654
+ return c.json({ message: hasAccount ? "\u521D\u59CB\u5316\u5B8C\u6210\uFF0C\u8BF7\u4F7F\u7528\u65B0\u8D26\u53F7\u767B\u5F55" : "\u521D\u59CB\u5316\u5B8C\u6210" });
11655
+ });
11656
+ return app3;
11657
+ }
11658
+
11526
11659
  // ../backend/src/api/config.ts
11527
11660
  function createConfigApp(holder) {
11528
11661
  const app3 = new Hono2();
@@ -11552,7 +11685,7 @@ function createConfigApp(holder) {
11552
11685
  throw error;
11553
11686
  }
11554
11687
  saveConfigFile(config3);
11555
- const { masterKey: _masterKey, "web-ui": _webUi, ...gatewayConfig } = config3;
11688
+ const { masterKey: _masterKey, "web-ui": _webUi, setup: _setup, autoUpdate: _autoUpdate, ...gatewayConfig } = config3;
11556
11689
  holder.runtime.rebuild(gatewayConfig);
11557
11690
  return c.json({ message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548" });
11558
11691
  } catch (error) {
@@ -12099,6 +12232,7 @@ function createApp(runtime2) {
12099
12232
  if (c.req.header("origin")) c.header("Access-Control-Allow-Credentials", "true");
12100
12233
  });
12101
12234
  app3.route("/api/auth", auth_default);
12235
+ app3.route("/api/setup", createSetupApp());
12102
12236
  app3.use("/api/*", authMiddleware);
12103
12237
  app3.route("/api/config", createConfigApp({ runtime: runtime2 }));
12104
12238
  app3.route("/api/test", createTestApp(runtime2));
@@ -12163,6 +12297,9 @@ serve({ fetch: app2.fetch, port, hostname }, (info) => {
12163
12297
  console.log(` \u5C40\u57DF\u7F51\u8BBF\u95EE: http://${ip}:${String(info.port)}`);
12164
12298
  }
12165
12299
  console.log(` \u6A21\u578B\u6570\u91CF: ${String(runtime.current.expanded.deployments.names.length)}`);
12300
+ if (!resolveSetupState().completed) {
12301
+ console.log(` \u26A0 \u9996\u6B21\u542F\u52A8\uFF1A\u8BF7\u6253\u5F00 http://127.0.0.1:${String(info.port + 1)}/ \u5B8C\u6210\u521D\u59CB\u5316\u5411\u5BFC\uFF08\u8BBE\u7F6E\u4E3B\u5BC6\u94A5\u4E0E\u767B\u5F55\u8D26\u53F7\uFF09`);
12302
+ }
12166
12303
  console.log("========================================");
12167
12304
  });
12168
12305
  function lanAddresses() {
@@ -0,0 +1 @@
1
+ import{d as H,p as J,e as h,g as o,w as s,c as V,i as B,r as y,A as T,a as _,j as Q,o as c,b as W,k as Y,t as u,f as l,h as p,F as X,q as Z,l as N,_ as ee}from"./index-X5PdkJlA.js";import{u as te}from"./providers-H_9x778D.js";import{u as ae}from"./useToast-MtbkGJiM.js";import"./config-CRWaUFQc.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=H({__name:"DashboardView",setup(Ce){const k=te(),{toast:M}=ae(),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 J(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=Q("loading");return c(),h("div",le,[o(S,{shadow:"never"},{header:s(()=>[...e[3]||(e[3]=[l("span",{class:"fw-600"},"服务信息",-1)])]),default:s(()=>[W((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(X,null,Z(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)])}}}),$e=ee(De,[["__scopeId","data-v-de31ace5"]]);export{$e as default};
@@ -0,0 +1 @@
1
+ .copy-text[data-v-de31ace5]{color:#409eff;cursor:pointer}.copy-text[data-v-de31ace5]:hover{text-decoration:underline}
@@ -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-X5PdkJlA.js";import{m as T}from"./useManagerApi-C1_COxKg.js";import{u as q}from"./useToast-MtbkGJiM.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
+ import{d as h,e as b,g as o,w as l,a,o as _,f as k,Z as x,a3 as f,c as C,i as L,h as B,r as i,A as N,m as I,_ as K}from"./index-X5PdkJlA.js";const A={class:"login-wrap"},E=h({__name:"LoginView",setup(M){const v=I(),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 N.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 p=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(p,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=c=>n.value=c),placeholder:"请输入用户名",onKeyup:f(r,["enter"])},null,8,["modelValue"])]),_:1}),o(m,{label:"密码"},{default:l(()=>[o(p,{modelValue:s.value,"onUpdate:modelValue":e[1]||(e[1]=c=>s.value=c),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})])}}}),U=K(E,[["__scopeId","data-v-c944978e"]]);export{U 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}