ellm-proxy 0.0.9 → 0.0.10

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 (29) hide show
  1. package/dist/cli.js +5 -4
  2. package/dist/server.js +17 -22
  3. package/dist/web-ui/assets/DashboardView-BTtCBA4g.css +1 -0
  4. package/dist/web-ui/assets/DashboardView-BWPFis7P.js +1 -0
  5. package/dist/web-ui/assets/LayoutView-BkUJgLW2.js +1 -0
  6. package/dist/web-ui/assets/LoginView-C8E6nJw2.js +1 -0
  7. package/dist/web-ui/assets/{LogsView-2h2Y0XE8.js → LogsView-CtRfyOCZ.js} +1 -1
  8. package/dist/web-ui/assets/{ProvidersView-bE1Hv9D-.js → ProvidersView-B6fm2FPQ.js} +1 -1
  9. package/dist/web-ui/assets/SettingsView-BHuXlGS1.js +1 -0
  10. package/dist/web-ui/assets/SetupView-BkBbUyaW.css +1 -0
  11. package/dist/web-ui/assets/SetupView-Oau3EJgh.js +1 -0
  12. package/dist/web-ui/assets/{config-nvSp8hHp.js → config-CHaFDQC7.js} +1 -1
  13. package/dist/web-ui/assets/{index-sPrBBnf5.js → index-BmtcBnoW.js} +18 -18
  14. package/dist/web-ui/assets/{providers-di7L8jy_.js → providers-Aw7T7j0r.js} +1 -1
  15. package/dist/web-ui/assets/useManagerApi-PldAaNYo.js +1 -0
  16. package/dist/web-ui/assets/useToast-C9BTyaBD.js +1 -0
  17. package/dist/web-ui/index.html +1 -1
  18. package/package.json +1 -1
  19. package/dist/web-ui/assets/DashboardView-3c_i_3VF.js +0 -1
  20. package/dist/web-ui/assets/DashboardView-BHG4xIdN.css +0 -1
  21. package/dist/web-ui/assets/LayoutView-BhN1f8wo.js +0 -1
  22. package/dist/web-ui/assets/LoginView-J23vsMu2.js +0 -1
  23. package/dist/web-ui/assets/ProcessView-Bn8Ph7rh.css +0 -1
  24. package/dist/web-ui/assets/ProcessView-Bs2k6aAf.js +0 -1
  25. package/dist/web-ui/assets/SettingsView-BmLmC-_v.js +0 -1
  26. package/dist/web-ui/assets/SetupView-Cgj7ubNz.js +0 -1
  27. package/dist/web-ui/assets/SetupView-kPJXJ7dX.css +0 -1
  28. package/dist/web-ui/assets/useManagerApi-qPjxfB0X.js +0 -1
  29. package/dist/web-ui/assets/useToast-D76qyWZg.js +0 -1
package/dist/cli.js CHANGED
@@ -35728,7 +35728,7 @@ ${generated}
35728
35728
  console.log(`\u914D\u7F6E\u6587\u4EF6: ${path7}\uFF08\u540E\u7AEF\u52A8\u6001\u8BFB\u53D6\uFF0C\u65E0\u9700\u91CD\u542F\u5373\u65F6\u751F\u6548\uFF09`);
35729
35729
  })
35730
35730
  );
35731
- cli.command("account [username] [password]", "\u67E5\u770B/\u8BBE\u7F6E\u7BA1\u7406\u53F0\u767B\u5F55\u8D26\u53F7\uFF08--reset \u91CD\u7F6E\u4E3A admin/admin\uFF1B\u6539\u540E\u65E7\u767B\u5F55\u6001\u7ACB\u5373\u5931\u6548\uFF09").option("--reset", "\u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u8D26\u53F7 admin/admin").option("--config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 ~/.ellm-proxy/ellm.config.json\uFF09").action(
35731
+ cli.command("account [username] [password]", "\u67E5\u770B/\u8BBE\u7F6E\u7BA1\u7406\u53F0\u767B\u5F55\u8D26\u53F7\uFF08--reset \u6E05\u9664\u8D26\u53F7\u8F6C\u4E3A\u514D\u767B\u5F55\uFF1B\u6539\u540E\u65E7\u767B\u5F55\u6001\u7ACB\u5373\u5931\u6548\uFF09").option("--reset", "\u6E05\u9664\u8D26\u53F7\uFF1A\u7BA1\u7406\u53F0\u514D\u767B\u5F55\u8BBF\u95EE\uFF08\u5982\u9700\u91CD\u65B0\u542F\u7528\u767B\u5F55\uFF0C\u518D\u6267\u884C account <\u7528\u6237\u540D> <\u5BC6\u7801>\uFF09").option("--config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 ~/.ellm-proxy/ellm.config.json\uFF09").action(
35732
35732
  (username, password, opts) => run(() => {
35733
35733
  ensureConfig(opts.config);
35734
35734
  const path7 = configPath(opts.config);
@@ -35737,7 +35737,7 @@ cli.command("account [username] [password]", "\u67E5\u770B/\u8BBE\u7F6E\u7BA1\u7
35737
35737
  const webUi = typeof config["web-ui"] === "object" && config["web-ui"] !== null ? config["web-ui"] : {};
35738
35738
  if (opts.reset) {
35739
35739
  saveConfig({ ...config, "web-ui": { ...webUi, "login-account": {} } });
35740
- console.log("\u5DF2\u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u8D26\u53F7 admin/admin\uFF08\u65E7\u767B\u5F55 token \u7ACB\u5373\u5931\u6548\uFF09\u3002");
35740
+ console.log("\u5DF2\u6E05\u9664\u8D26\u53F7\uFF1A\u7BA1\u7406\u53F0\u514D\u767B\u5F55\u8BBF\u95EE\uFF08\u65E7\u767B\u5F55 token \u7ACB\u5373\u5931\u6548\uFF09\u3002");
35741
35741
  } else if (username && password) {
35742
35742
  const name = String(username).trim();
35743
35743
  const pass = String(password);
@@ -35749,12 +35749,13 @@ cli.command("account [username] [password]", "\u67E5\u770B/\u8BBE\u7F6E\u7BA1\u7
35749
35749
  const account = webUi["login-account"] ?? {};
35750
35750
  const name = typeof account.username === "string" && account.username.trim() !== "" ? account.username.trim() : "";
35751
35751
  if (name === "") {
35752
- console.log("\u5F53\u524D\u672A\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E\u8D26\u53F7\uFF1A\u4F7F\u7528 admin/admin\uFF08\u6216 WEBUI_USERNAME/WEBUI_PASSWORD \u73AF\u5883\u53D8\u91CF\uFF09\u3002");
35752
+ console.log("\u5F53\u524D\u672A\u8BBE\u7F6E\u8D26\u53F7\uFF1A\u7BA1\u7406\u53F0\u514D\u767B\u5F55\u8BBF\u95EE\uFF08\u7EAF\u672C\u673A\u4F7F\u7528\uFF09\u3002");
35753
+ console.log("\u542F\u7528\u767B\u5F55\uFF1Aellm-proxy account <username> <password>\uFF0C\u6216\u8BBE\u7F6E WEBUI_USERNAME/WEBUI_PASSWORD \u73AF\u5883\u53D8\u91CF\u3002");
35753
35754
  } else {
35754
35755
  console.log(`\u5F53\u524D\u8D26\u53F7: ${name}`);
35755
35756
  console.log(`\u5F53\u524D\u5BC6\u7801: ${String(account.password)}`);
35756
35757
  }
35757
- console.log("\u8BBE\u7F6E\u65B9\u5F0F\uFF1Aellm-proxy account <username> <password>\uFF0C\u91CD\u7F6E\uFF1Aellm-proxy account --reset");
35758
+ console.log("\u6E05\u9664\u8D26\u53F7\uFF08\u8F6C\u514D\u767B\u5F55\uFF09\uFF1Aellm-proxy account --reset");
35758
35759
  }
35759
35760
  console.log(`\u914D\u7F6E\u6587\u4EF6: ${path7}\uFF08\u540E\u7AEF\u52A8\u6001\u8BFB\u53D6\uFF0C\u65E0\u9700\u91CD\u542F\u5373\u65F6\u751F\u6548\uFF09`);
35760
35761
  })
package/dist/server.js CHANGED
@@ -18946,9 +18946,15 @@ function resolveLoginAccount() {
18946
18946
  }
18947
18947
  const fileUsername = typeof account?.username === "string" && account.username.trim() !== "" ? account.username.trim() : void 0;
18948
18948
  const filePassword = typeof account?.password === "string" && account.password !== "" ? account.password : void 0;
18949
- const envUsername = process.env["WEBUI_USERNAME"] || "admin";
18950
- const envPassword = process.env["WEBUI_PASSWORD"] || "admin";
18951
- return { username: fileUsername ?? envUsername, password: filePassword ?? envPassword };
18949
+ if (fileUsername !== void 0 && filePassword !== void 0) {
18950
+ return { required: true, username: fileUsername, password: filePassword };
18951
+ }
18952
+ const envUsername = process.env["WEBUI_USERNAME"];
18953
+ const envPassword = process.env["WEBUI_PASSWORD"];
18954
+ if (envUsername && envPassword) {
18955
+ return { required: true, username: envUsername, password: envPassword };
18956
+ }
18957
+ return { required: false, username: "", password: "" };
18952
18958
  }
18953
18959
  function resolveSetupState() {
18954
18960
  let setup;
@@ -19734,8 +19740,11 @@ function tokenOf(account) {
19734
19740
  return Buffer.from(`${account.username}:${account.password}`).toString("base64");
19735
19741
  }
19736
19742
  app.post("/login", async (c) => {
19737
- const { username, password } = await c.req.json();
19738
19743
  const account = resolveLoginAccount();
19744
+ if (!account.required) {
19745
+ return c.json({ error: "\u5F53\u524D\u672A\u8BBE\u7F6E\u767B\u5F55\u8D26\u53F7\uFF0C\u7BA1\u7406\u53F0\u514D\u767B\u5F55\u8BBF\u95EE\uFF08\u5982\u9700\u542F\u7528\u8BF7\u5148\u8BBE\u7F6E\u8D26\u53F7\uFF09" }, 400);
19746
+ }
19747
+ const { username, password } = await c.req.json();
19739
19748
  if ((username ?? "").trim() !== account.username || password !== account.password) {
19740
19749
  return c.json({ error: "\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF" }, 401);
19741
19750
  }
@@ -19743,6 +19752,7 @@ app.post("/login", async (c) => {
19743
19752
  });
19744
19753
  async function authMiddleware(c, next) {
19745
19754
  if (c.req.path === "/api/auth/login") return await next();
19755
+ if (!resolveLoginAccount().required) return await next();
19746
19756
  const authHeader = c.req.header("Authorization");
19747
19757
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
19748
19758
  return c.json({ error: "\u672A\u767B\u5F55" }, 401);
@@ -19765,7 +19775,8 @@ function createSetupApp() {
19765
19775
  return c.json({
19766
19776
  initialized: state.completed,
19767
19777
  deployment: state.deployment,
19768
- masterKeySet: resolveMasterKey() !== ""
19778
+ masterKeySet: resolveMasterKey() !== "",
19779
+ authRequired: resolveLoginAccount().required
19769
19780
  });
19770
19781
  });
19771
19782
  app3.post("/master-key", async (c) => {
@@ -20563,22 +20574,6 @@ function createServiceRoutes(ctx) {
20563
20574
  ]
20564
20575
  });
20565
20576
  });
20566
- app3.post(
20567
- "/start",
20568
- authMiddleware,
20569
- (c) => c.json({ ok: true, action: "start", note: "\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\uFF08\u80FD\u6536\u5230\u672C\u8BF7\u6C42\u5373\u5DF2\u542F\u52A8\uFF09" })
20570
- );
20571
- app3.post("/stop", authMiddleware, (c) => {
20572
- setTimeout(() => {
20573
- clearRunInfo(ctx.appDir);
20574
- process.exit(0);
20575
- }, 300);
20576
- return c.json({ ok: true, action: "stop", note: "\u670D\u52A1\u5373\u5C06\u505C\u6B62\uFF08\u91CD\u542F\u8BF7\u7528\u91CD\u542F\u6309\u94AE\u6216 ellm-proxy start\uFF09" });
20577
- });
20578
- app3.post("/restart", authMiddleware, (c) => {
20579
- setTimeout(() => selfRespawn({ serverJs: ctx.serverJs, appDir: ctx.appDir }), 300);
20580
- return c.json({ ok: true, action: "restart", note: "\u91CD\u542F\u4E2D\uFF0C\u754C\u9762\u5C06\u5728\u6570\u79D2\u540E\u6062\u590D" });
20581
- });
20582
20577
  app3.get("/runinfo", authMiddleware, (c) => {
20583
20578
  const info = readRunInfo(ctx.appDir);
20584
20579
  return c.json({ ok: true, runInfo: info });
@@ -22403,7 +22398,7 @@ function createApp(runtime2, admin) {
22403
22398
  (c) => c.json(
22404
22399
  {
22405
22400
  ok: false,
22406
- error: `\u672A\u77E5\u63A5\u53E3: ${c.req.method} ${c.req.path}\uFF08\u53EF\u7528: GET /\uFF08\u7BA1\u7406\u754C\u9762\uFF09, /v1/*\uFF08\u7F51\u5173\uFF09, /api/*\uFF08\u7BA1\u7406\u53F0\uFF09, GET /status, GET /processes, POST /start|stop|restart, GET|POST /autostart, GET /update, POST /update/enabled|check|apply, GET /harness, POST /harness/claude-code|codex|dsh\uFF09`
22401
+ error: `\u672A\u77E5\u63A5\u53E3: ${c.req.method} ${c.req.path}\uFF08\u53EF\u7528: GET /\uFF08\u7BA1\u7406\u754C\u9762\uFF09, /v1/*\uFF08\u7F51\u5173\uFF09, /api/*\uFF08\u7BA1\u7406\u53F0\uFF09, GET /status, GET /processes, GET|POST /autostart, GET /update, POST /update/enabled|check|apply, GET /harness, POST /harness/claude-code|codex|dsh\uFF09`
22407
22402
  },
22408
22403
  404
22409
22404
  )
@@ -0,0 +1 @@
1
+ .copy-text[data-v-ff795a0a]{color:#409eff;cursor:pointer}.copy-text[data-v-ff795a0a]:hover{text-decoration:underline}
@@ -0,0 +1 @@
1
+ import{d as te,k as ae,q as le,e as w,g as o,w as s,c as S,i as U,r as p,A as V,a as m,j as oe,o as c,b as se,t as r,f as l,h as _,F as ne,v as ue,m as $,_ as re}from"./index-BmtcBnoW.js";import{m as de}from"./useManagerApi-PldAaNYo.js";import{u as ie}from"./providers-Aw7T7j0r.js";import{u as ce}from"./useToast-C9BTyaBD.js";import"./config-CHaFDQC7.js";const ve={class:"flex-col-gap"},pe=["onClick"],me={key:1},_e={class:"flex-between"},ge={class:"flex-gap-8"},fe={class:"usage-grid"},he={class:"usage-cell"},ye={class:"usage-num orange"},we={class:"usage-cell"},be={class:"usage-num blue"},ke={class:"usage-cell"},De={class:"usage-num green"},xe={class:"usage-cell"},Ce={class:"usage-num purple"},Me={class:"usage-cell"},Se={class:"usage-num purple"},Ve={key:0,class:"usage-cell"},Pe={class:"usage-num gray"},$e={key:0},Te=["onClick"],Ae=te({__name:"DashboardView",setup(Be){const D=ie(),{toast:P}=ce(),d=p(null),b=p(!0),x=p(""),v=p(null),k=p("today"),C=t=>{const e=t.getFullYear(),a=String(t.getMonth()+1).padStart(2,"0"),i=String(t.getDate()).padStart(2,"0");return`${e}-${a}-${i}`},R=t=>{const e=new Date;let a,i=e;if(t==="today")a=e;else if(t==="yesterday")a=new Date(e),a.setDate(e.getDate()-1),i=a;else if(t==="beforeYesterday")a=new Date(e),a.setDate(e.getDate()-2),i=a;else if(t==="last3days")a=new Date(e),a.setDate(e.getDate()-2);else if(t==="week"){a=new Date(e);const f=(e.getDay()+6)%7;a.setDate(e.getDate()-f)}else if(t==="month")a=new Date(e.getFullYear(),e.getMonth(),1);else return;k.value=t,v.value=[C(a),C(i)],y()},K=t=>{t?R(t):(k.value="",v.value=null,y())},L=()=>{k.value="",y()},j=$(()=>{var e;const t=new Set;for(const a of D.suppliers)for(const i of a.models)t.add(i.modelName);if((e=d.value)!=null&&e.models)for(const a of d.value.models)t.add(a.model);return[...t].sort()}),y=async()=>{var t;b.value=!0;try{const e=new URLSearchParams;x.value&&e.set("model",x.value),((t=v.value)==null?void 0:t.length)===2&&(e.set("startDate",v.value[0]),e.set("endDate",v.value[1]));const a=e.toString();d.value=await V.get(`/api/usage${a?`?${a}`:""}`)}catch(e){console.error(e)}finally{b.value=!1}},q=async()=>{var t;try{const[e]=await Promise.all([V.get("/api/usage"),D.loadProviders(),V.get("/api/status").then(a=>M.value=a.listenPort).catch(()=>M.value)]);if(d.value=e,!((t=v.value)!=null&&t.length)){const a=new Date;v.value=[C(a),C(a)],await y()}}catch(e){console.error(e)}finally{b.value=!1}},E=window.location.hostname,M=p(Number(window.location.port)||null),O=$(()=>`http://${E}${M.value?`:${M.value}`:""}`),T=p(null),A=p(null),B=p(Date.now()),I=async()=>{try{const[t,e]=await Promise.all([de.processes(),V.get("/api/config").catch(()=>null)]);T.value=t[0]??null,A.value=(e==null?void 0:e.masterKey)??"",B.value=Date.now()}catch{}},z=t=>{if(!t.startedAt)return"-";let e=Math.max(0,Math.floor((B.value-t.startedAt)/1e3));const a=Math.floor(e/86400);e-=a*86400;const i=Math.floor(e/3600);e-=i*3600;const f=Math.floor(e/60);return e-=f*60,[a?`${a}天`:"",i?`${i}时`:"",f?`${f}分`:"",`${e}秒`].filter(Boolean).join(" ")},G=t=>t?t>=1024**3?(t/1024**3).toFixed(1)+"G":t>=1024**2?(t/1024**2).toFixed(1)+"M":Math.round(t/1024)+"K":"-",H=$(()=>{const t=[{key:"API 地址",value:O.value},{key:"API 密钥",value:A.value||"未设置(/v1 免鉴权)"},{key:"供应商数量",value:String(D.providerCount)},{key:"模型总数",value:String(D.totalModelCount)}],e=T.value;return e&&t.push({key:"运行状态",value:e.status==="online"?"运行中":e.status==="errored"?"异常":"已停止"},{key:"PID",value:String(e.pid??"-")},{key:"运行时长",value:z(e)},{key:"内存占用",value:G(e.memory)}),t});let Y;const g=t=>t.toLocaleString(),J=t=>t==null||t<=0?"-":t>=1e3?(t/1e3).toFixed(2)+"s":Math.round(t)+"ms",F=async t=>{try{await navigator.clipboard.writeText(t),P("已复制到剪贴板","success")}catch{const e=document.createElement("textarea");e.value=t,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select();try{document.execCommand("copy"),P("已复制到剪贴板","success")}catch{P("复制失败","error")}document.body.removeChild(e)}};return ae(()=>{q(),I(),Y=window.setInterval(I,1e4)}),le(()=>window.clearInterval(Y)),(t,e)=>{const a=m("el-table-column"),i=m("el-table"),f=m("el-card"),h=m("el-option"),N=m("el-select"),Q=m("el-date-picker"),W=m("el-button"),X=m("el-tag"),Z=m("el-empty"),ee=oe("loading");return c(),w("div",ve,[o(f,{shadow:"never"},{header:s(()=>[...e[3]||(e[3]=[l("span",{class:"fw-600"},"服务信息",-1)])]),default:s(()=>[se((c(),S(i,{border:"",data:H.value,"show-header":!1,class:"full-width"},{default:s(()=>[o(a,{prop:"key",width:"160"}),o(a,null,{default:s(({row:n})=>[n.key==="API 地址"||n.key==="API 密钥"?(c(),w("span",{key:0,class:"copy-text",onClick:u=>F(n.value)},r(n.value),9,pe)):(c(),w("span",me,r(n.value),1))]),_:1})]),_:1},8,["data"])),[[ee,b.value]])]),_:1}),d.value?(c(),S(f,{key:0,shadow:"never"},{header:s(()=>[l("div",_e,[e[5]||(e[5]=l("span",{class:"fw-600"},"用量统计",-1)),l("div",ge,[o(N,{modelValue:x.value,"onUpdate:modelValue":e[0]||(e[0]=n=>x.value=n),placeholder:"全部模型",clearable:"",filterable:"",style:{width:"220px"},onChange:y},{default:s(()=>[(c(!0),w(ne,null,ue(j.value,n=>(c(),S(h,{key:n,label:n,value:n},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),o(N,{modelValue:k.value,"onUpdate:modelValue":e[1]||(e[1]=n=>k.value=n),placeholder:"时间范围",clearable:"",style:{width:"130px"},onChange:K},{default:s(()=>[o(h,{label:"今天",value:"today"}),o(h,{label:"昨天",value:"yesterday"}),o(h,{label:"前天",value:"beforeYesterday"}),o(h,{label:"近三天",value:"last3days"}),o(h,{label:"本周",value:"week"}),o(h,{label:"本月",value:"month"})]),_:1},8,["modelValue"]),o(Q,{modelValue:v.value,"onUpdate:modelValue":e[2]||(e[2]=n=>v.value=n),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",style:{width:"260px"},onChange:L},null,8,["modelValue"]),o(W,{loading:b.value,onClick:y},{default:s(()=>[...e[4]||(e[4]=[_("刷新",-1)])]),_:1},8,["loading"])]),o(X,{type:"info"},{default:s(()=>[_(r(d.value.total.calls)+" 次调用",1)]),_:1})])]),default:s(()=>{var n;return[l("div",fe,[l("div",he,[l("div",ye,r(g(d.value.total.total_tokens)),1),e[6]||(e[6]=l("div",{class:"usage-label"},"总 Token",-1))]),l("div",we,[l("div",be,r(g(d.value.total.input_tokens)),1),e[7]||(e[7]=l("div",{class:"usage-label"},"输入 Token",-1))]),l("div",ke,[l("div",De,r(g(d.value.total.output_tokens)),1),e[8]||(e[8]=l("div",{class:"usage-label"},"输出 Token",-1))]),l("div",xe,[l("div",Ce,r(g(d.value.total.cache_read_tokens)),1),e[9]||(e[9]=l("div",{class:"usage-label"},"缓存读取",-1))]),l("div",Me,[l("div",Se,r(d.value.total.cache_rate!=null?d.value.total.cache_rate+"%":"-"),1),e[10]||(e[10]=l("div",{class:"usage-label"},"缓存命中率",-1))]),d.value.logStorageMB!=null?(c(),w("div",Ve,[l("div",Pe,r(d.value.logStorageMB),1),e[11]||(e[11]=l("div",{class:"usage-label"},"日志占用 (MB)",-1))])):U("",!0)]),(n=d.value.models)!=null&&n.length?(c(),w("div",$e,[e[12]||(e[12]=l("div",{class:"section-title"},"模型用量",-1)),o(i,{data:d.value.models,border:"",stripe:"",class:"full-width"},{default:s(()=>[o(a,{prop:"model",label:"模型","min-width":"200"},{default:s(({row:u})=>[l("span",{class:"copy-text",onClick:Ie=>F(u.model)},r(u.model),9,Te)]),_:1}),o(a,{label:"调用次数",width:"90",align:"center"},{default:s(({row:u})=>[_(r(u.calls),1)]),_:1}),o(a,{label:"平均耗时",width:"110",align:"center"},{default:s(({row:u})=>[_(r(J(u.avg_duration)),1)]),_:1}),o(a,{label:"总 Token",width:"140",align:"center"},{default:s(({row:u})=>[_(r(g(u.total_tokens)),1)]),_:1}),o(a,{label:"输入 Token",width:"140",align:"center"},{default:s(({row:u})=>[_(r(g(u.input_tokens)),1)]),_:1}),o(a,{label:"输出 Token",width:"140",align:"center"},{default:s(({row:u})=>[_(r(g(u.output_tokens)),1)]),_:1}),o(a,{label:"缓存读取",width:"130",align:"center"},{default:s(({row:u})=>[_(r(g(u.cache_read_tokens)),1)]),_:1}),o(a,{label:"缓存命中率",width:"110",align:"center"},{default:s(({row:u})=>[_(r(u.cache_rate!=null?u.cache_rate+"%":"-"),1)]),_:1})]),_:1},8,["data"])])):(c(),S(Z,{key:1,description:"暂无调用记录","image-size":60}))]}),_:1})):U("",!0)])}}}),Ke=re(Ae,[["__scopeId","data-v-ff795a0a"]]);export{Ke as default};
@@ -0,0 +1 @@
1
+ import{d as T,u as R,c as k,w as o,r as g,a as r,o as v,b as z,e as B,F,f as c,g as l,h as u,t as $,i as x,E as I,j as L,_ as W,k as q,l as j,m as A,n as O,p as G,s as H}from"./index-BmtcBnoW.js";import{m as D,w as J}from"./useManagerApi-PldAaNYo.js";import{u as K}from"./useToast-C9BTyaBD.js";const P={class:"check-body"},Q={class:"version-row"},X={class:"version-row"},Y=T({__name:"UpdateCheckDialog",props:{modelValue:{type:Boolean,default:!1},modelModifiers:{}},emits:["update:modelValue"],setup(S){const{toast:w}=K(),y=R(S,"modelValue"),p=g(!1),m=g(!1),a=g(null),f=g(null),i=g(null),h=async()=>{a.value=null,f.value=null,i.value=null,p.value=!0;try{a.value=await D.checkUpdate()}catch(e){f.value=e.message||"检查更新失败"}finally{p.value=!1}},n=async()=>{var e;if(a.value){try{await I.confirm(`确定更新到 ${a.value.latestVersion}?服务将自动重启并在启动时安装新版本(期间界面请求会短暂失败,属正常现象)。`,"更新确认",{confirmButtonText:"立即更新",cancelButtonText:"取消",type:"warning"})}catch{return}m.value=!0;try{const t=await D.update(),d=await D.applyUpdate();if(!d.updated){i.value=`已是最新版本 ${d.toVersion}`,a.value={hasUpdate:!1,currentVersion:d.toVersion,latestVersion:d.toVersion,checkedAt:Date.now()};return}i.value=`正在重启服务并安装 ${d.toVersion},请稍候(预计 1~3 分钟)...`;const s=await J({targetVersion:d.toVersion,sinceAppliedAt:((e=t.lastApply)==null?void 0:e.appliedAt)??null});s&&!s.error?(w(`已更新到 ${s.toVersion}`,"success"),i.value=`已更新 ${s.fromVersion} → ${s.toVersion}`,a.value={hasUpdate:!1,currentVersion:s.toVersion,latestVersion:s.toVersion,checkedAt:Date.now()}):s!=null&&s.error?(w(`更新失败:${s.error}`,"error"),i.value=`更新失败:${s.error}(服务已恢复旧版本)`):i.value="更新耗时较长,请稍后刷新页面查看结果"}catch(t){w(t.message||"更新失败","error")}finally{m.value=!1}}};return(e,t)=>{const d=r("el-tag"),s=r("el-alert"),V=r("el-button"),U=r("el-empty"),b=r("el-dialog"),C=L("loading");return v(),k(b,{modelValue:y.value,"onUpdate:modelValue":t[2]||(t[2]=_=>y.value=_),title:"检查更新",width:"420px","close-on-click-modal":!m.value,onOpen:h},{footer:o(()=>{var _;return[(_=a.value)!=null&&_.hasUpdate?(v(),k(V,{key:0,type:"primary",loading:m.value,onClick:n},{default:o(()=>[u($(m.value?"更新中...":"立即更新"),1)]),_:1},8,["loading"])):x("",!0),l(V,{onClick:t[1]||(t[1]=M=>y.value=!1)},{default:o(()=>[...t[6]||(t[6]=[u("关闭",-1)])]),_:1})]}),default:o(()=>[z((v(),B("div",P,[a.value?(v(),B(F,{key:0},[c("div",Q,[t[3]||(t[3]=c("span",{class:"version-label"},"当前版本",-1)),l(d,{size:"small"},{default:o(()=>[u($(a.value.currentVersion),1)]),_:1})]),c("div",X,[t[4]||(t[4]=c("span",{class:"version-label"},"最新版本",-1)),l(d,{type:a.value.hasUpdate?"warning":"success",size:"small","disable-transitions":""},{default:o(()=>[u($(a.value.latestVersion),1)]),_:1},8,["type"])]),l(s,{title:a.value.hasUpdate?`发现新版本 ${a.value.latestVersion}`:"已是最新版本",type:a.value.hasUpdate?"warning":"success","show-icon":"",closable:!1,class:"mt-12"},null,8,["title","type"])],64)):!p.value&&f.value?(v(),k(U,{key:1,description:f.value},{default:o(()=>[l(V,{type:"primary",onClick:h},{default:o(()=>[...t[5]||(t[5]=[u("重试",-1)])]),_:1})]),_:1},8,["description"])):x("",!0)])),[[C,p.value]]),i.value?(v(),k(s,{key:0,title:i.value,type:"info","show-icon":"",class:"mt-12",onClose:t[0]||(t[0]=_=>i.value=null)},null,8,["title"])):x("",!0)]),_:1},8,["modelValue","close-on-click-modal"])}}}),Z=W(Y,[["__scopeId","data-v-eaed444e"]]),ee={class:"layout"},te={class:"header-nav"},oe={class:"header-actions"},le={class:"main"},se={class:"page"},ue=T({__name:"LayoutView",setup(S){const w=G(),y=O(),p=g(!1);q(()=>{j()});const m=A(()=>{var n;return((n=H.value)==null?void 0:n.authRequired)!==!1}),a=A(()=>{if(!m.value)return"免登录";const n=localStorage.getItem("webui_token")||"";try{return atob(n).split(":")[0]||"用户"}catch{return"用户"}}),f=A(()=>{const n=w.path;return n==="/"?"/":n.startsWith("/providers")?"/providers":n.startsWith("/settings")?"/settings":n.startsWith("/logs")?"/logs":"/"}),i=()=>{localStorage.removeItem("webui_token"),y.push("/login")},h=n=>{n==="logout"&&i(),n==="check-update"&&(p.value=!0)};return(n,e)=>{const t=r("el-menu-item"),d=r("el-menu"),s=r("ArrowDown"),V=r("el-icon"),U=r("el-button"),b=r("el-dropdown-item"),C=r("el-dropdown-menu"),_=r("el-dropdown"),M=r("el-header"),E=r("router-view");return v(),B("div",ee,[l(M,{class:"header"},{default:o(()=>[e[7]||(e[7]=c("h1",null,[c("span",null,"E"),u("LLM")],-1)),c("nav",te,[l(d,{mode:"horizontal","default-active":f.value,ellipsis:!1,router:"",class:"header-menu"},{default:o(()=>[l(t,{index:"/"},{default:o(()=>[...e[1]||(e[1]=[u("首页",-1)])]),_:1}),l(t,{index:"/providers"},{default:o(()=>[...e[2]||(e[2]=[u("供应商管理",-1)])]),_:1}),l(t,{index:"/logs"},{default:o(()=>[...e[3]||(e[3]=[u("日志",-1)])]),_:1}),l(t,{index:"/settings"},{default:o(()=>[...e[4]||(e[4]=[u("设置",-1)])]),_:1})]),_:1},8,["default-active"])]),c("div",oe,[l(_,{trigger:"click",onCommand:h,class:"ml-12"},{dropdown:o(()=>[l(C,null,{default:o(()=>[l(b,{command:"check-update"},{default:o(()=>[...e[5]||(e[5]=[u("检查更新",-1)])]),_:1}),m.value?(v(),k(b,{key:0,command:"logout",divided:""},{default:o(()=>[...e[6]||(e[6]=[u("退出",-1)])]),_:1})):x("",!0)]),_:1})]),default:o(()=>[l(U,null,{default:o(()=>[u($(a.value)+" ",1),l(V,{class:"el-icon--right"},{default:o(()=>[l(s)]),_:1})]),_:1})]),_:1})])]),_:1}),c("main",le,[c("div",se,[l(E)])]),l(Z,{modelValue:p.value,"onUpdate:modelValue":e[0]||(e[0]=N=>p.value=N)},null,8,["modelValue"])])}}});export{ue 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,a0 as x,a6 as f,c as C,i as L,h as B,r as i,A as N,n as I,_ as K}from"./index-BmtcBnoW.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};
@@ -1,4 +1,4 @@
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,A,_ as X,p as pe,q as ce,a3 as _e,l as ve}from"./index-sPrBBnf5.js";import{u as me}from"./providers-di7L8jy_.js";import{u as Z}from"./useToast-D76qyWZg.js";import"./config-nvSp8hHp.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,a2 as m,b as Q,F as P,f as d,i as j,r as y,A,_ as X,k as pe,v as ce,a6 as _e,m as ve}from"./index-BmtcBnoW.js";import{u as me}from"./providers-Aw7T7j0r.js";import{u as Z}from"./useToast-C9BTyaBD.js";import"./config-CHaFDQC7.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
 
@@ -1,4 +1,4 @@
1
- import{N as Kn,s as Mt,v as He,x as cr,y as Lt,z as jo,B as Ts,C as ia,D as Os,G as aa,H as Is,I as Kt,J as sa,K as xi,L as Ns,M as Cs,O as As,P as xs,Q as Ps,R as Xr,S as Ds,T as Kr,U as Rs,V as Ms,W as Ls,X as ws,Y as gn,A as Yt,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,Z as fo,$ as Vs,k as q,h as ae,i as tt,g as P,a0 as Fs,l as Ht,r as Pe,_ as cn,a1 as Xo,a2 as Rt,p as la,a3 as $s,E as Ao,a4 as Us,a5 as js,a6 as Bs,a7 as ks}from"./index-sPrBBnf5.js";import{u as un}from"./useToast-D76qyWZg.js";import{u as Ko}from"./providers-di7L8jy_.js";import"./config-nvSp8hHp.js";var yr={exports:{}},po={exports:{}},ho={};/**
1
+ import{N as Kn,x as Mt,y as He,z as cr,B as Lt,C as jo,D as Ts,G as ia,H as Os,I as aa,J as Is,K as Kt,L as sa,M as xi,O as Ns,P as Cs,Q as As,R as xs,S as Ps,T as Xr,U as Ds,V as Kr,W as Rs,X as Ms,Y as Ls,Z as ws,$ as gn,A as Yt,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,v as ko,a0 as fo,a1 as Vs,a2 as q,h as ae,i as tt,g as P,a3 as Fs,m as Ht,r as Pe,_ as cn,a4 as Xo,a5 as Rt,k as la,a6 as $s,E as Ao,a7 as Us,a8 as js,a9 as Bs,aa as ks}from"./index-BmtcBnoW.js";import{u as un}from"./useToast-C9BTyaBD.js";import{u as Ko}from"./providers-Aw7T7j0r.js";import"./config-CHaFDQC7.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
+ import{u as Y}from"./useToast-C9BTyaBD.js";import{d as X,k as q,b as G,o as m,c as E,w as n,e as x,f as s,t as h,i as I,h as i,g as o,F as M,r,E as W,a as v,j as J,_ as Z,m as j,A as ee}from"./index-BmtcBnoW.js";import{u as te}from"./config-CHaFDQC7.js";import{m as S,w as le}from"./useManagerApi-PldAaNYo.js";const se={class:"flex-between"},ae={class:"muted-sm mb-8"},oe={class:"switch-row"},ne={class:"switch-info"},ie={class:"switch-title"},ue={class:"switch-row"},de={class:"switch-info"},re={class:"switch-title"},ce={class:"switch-row"},ve={class:"switch-info"},pe={class:"switch-title"},me=X({__name:"HarnessSwitches",setup(Q){const{toast:g}=Y(),$=r(!0),T=r(!1),p=r(null),V=r(null),u=r(null),P=r(null),w=r("success"),b=async _=>{_||(p.value?T.value=!0:$.value=!0);try{p.value=await S.harness(),V.value=null}catch(a){_||(V.value=a.message)}finally{_||($.value=!1,T.value=!1)}};q(()=>b(!1));const U={claudeCode:{label:"Claude Code",file:"~/.claude/settings.json",toggle:S.setClaudeCode},codex:{label:"Codex CLI",file:"~/.codex/config.toml",toggle:S.setCodex},dsh:{label:"DeepSeek Harness",file:"~/.dsh/settings.yaml",toggle:S.setDsh}},y=async(_,a)=>{if(!p.value||u.value)return;const C=U[_],D=a?`确定开启 ${C.label} 代理?将改写 ${C.file}(开启前自动备份,关闭时从备份还原)。`:`确定关闭 ${C.label} 代理?将从备份还原 ${C.file}(无备份时移除代理相关配置)。`;try{await W.confirm(D,a?"开启确认":"关闭确认",{confirmButtonText:a?"开启":"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}u.value=_;try{const c=await C.toggle(a);P.value=c.note??null,w.value=a?"success":"info",g(a?"已开启代理":"已关闭代理","success")}catch(c){g(c.message||"操作失败","error")}finally{u.value=null,await b(!0)}};return(_,a)=>{const C=v("el-button"),D=v("el-alert"),c=v("el-tag"),t=v("el-switch"),z=v("el-divider"),R=v("el-empty"),L=v("el-card"),B=J("loading");return G((m(),E(L,{shadow:"never"},{header:n(()=>[s("div",se,[a[7]||(a[7]=s("span",{class:"fw-600"},"代理开关",-1)),o(C,{loading:T.value,onClick:a[0]||(a[0]=A=>b(!1))},{default:n(()=>[...a[6]||(a[6]=[i("刷新",-1)])]),_:1},8,["loading"])])]),default:n(()=>[p.value?(m(),x(M,{key:0},[s("div",ae," 将本机 CLI 工具的流量一键指向 ellm-proxy 网关("+h(p.value.gatewayBase)+")。 开启前自动备份原配置为 *.ellm-bak(保留最早的原始备份),关闭时从备份还原。 ",1),P.value?(m(),E(D,{key:0,title:P.value,type:w.value,"show-icon":"",class:"mb-8",onClose:a[1]||(a[1]=A=>P.value=null)},null,8,["title","type"])):I("",!0),s("div",oe,[s("div",ne,[s("div",ie,[a[8]||(a[8]=i(" Claude Code ",-1)),o(c,{type:p.value.claudeCode?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[i(h(p.value.claudeCode?"已开启":"未开启"),1)]),_:1},8,["type"])]),a[9]||(a[9]=s("div",{class:"switch-desc"},[i(" 写入 "),s("code",null,"~/.claude/settings.json"),i(" 的 env.ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN, 鉴权取网关配置的 masterKey(未设置将开启失败);其余字段原样保留,重启 Claude Code 会话后生效。 ")],-1))]),o(t,{"model-value":p.value.claudeCode,loading:u.value==="claudeCode",onChange:a[2]||(a[2]=A=>y("claudeCode",!!A))},null,8,["model-value","loading"])]),o(z,{class:"switch-divider"}),s("div",ue,[s("div",de,[s("div",re,[a[10]||(a[10]=i(" Codex CLI ",-1)),o(c,{type:p.value.codex?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[i(h(p.value.codex?"已开启":"未开启"),1)]),_:1},8,["type"])]),a[11]||(a[11]=s("div",{class:"switch-desc"},[i(" 写入 "),s("code",null,"~/.codex/config.toml"),i(" 的 model_provider 与 [model_providers.ellm-proxy] 段 (保留用户注释与排版);开启后需按提示设置环境变量 ELLM_PROXY_API_KEY 供 Codex 鉴权。 ")],-1))]),o(t,{"model-value":p.value.codex,loading:u.value==="codex",onChange:a[3]||(a[3]=A=>y("codex",!!A))},null,8,["model-value","loading"])]),o(z,{class:"switch-divider"}),s("div",ce,[s("div",ve,[s("div",pe,[a[12]||(a[12]=i(" DeepSeek Harness ",-1)),o(c,{type:p.value.dsh?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[i(h(p.value.dsh?"已开启":"未开启"),1)]),_:1},8,["type"])]),a[13]||(a[13]=s("div",{class:"switch-desc"},[i(" 写入 "),s("code",null,"~/.dsh/settings.yaml"),i(" 的 llm-deepseek.baseURL 与 "),s("code",null,"~/.dsh/.credentials.yaml"),i(" 的 DEEPSEEK_API_KEY(取网关 masterKey,未设置则写占位值);模型请在 dsh 内选择与网关部署名一致的模型。 dsh 为 developer preview,配置格式可能随版本变化。 ")],-1))]),o(t,{"model-value":p.value.dsh,loading:u.value==="dsh",onChange:a[4]||(a[4]=A=>y("dsh",!!A))},null,8,["model-value","loading"])])],64)):(m(),E(R,{key:1,description:V.value||"暂无开关状态"},{default:n(()=>[o(C,{type:"primary",onClick:a[5]||(a[5]=A=>b(!1))},{default:n(()=>[...a[14]||(a[14]=[i("重试",-1)])]),_:1})]),_:1},8,["description"]))]),_:1})),[[B,$.value]])}}}),fe=Z(me,[["__scopeId","data-v-65e65156"]]),_e={class:"service-settings"},ge={class:"flex-between"},ye={key:0,class:"switch-row"},we={class:"switch-info"},be={class:"switch-title"},Ce={key:0,class:"switch-row"},ke={class:"switch-info"},$e={class:"switch-title"},he={class:"switch-desc"},xe={key:0,class:"version-block"},Ve={class:"version-row"},Ae={class:"muted-sm"},Te={key:0,class:"muted-sm"},Ue={class:"version-actions"},Ee=X({__name:"ServiceUpdateSettings",setup(Q){const{toast:g}=Y(),$=r(!0),T=r(!1),p=r(null),V=r(null),u=r(null),P=r(null),w=r(null),b=r(!1),U=r(!1),y=r(null),_=async l=>{var e,d;if(!l){if(T.value)return;T.value=!0}try{const[f,K]=await Promise.allSettled([S.autostart(),S.update()]);p.value=f.status==="fulfilled"?f.value:null,V.value=f.status==="rejected"?(e=f.reason)==null?void 0:e.message:null,u.value=K.status==="fulfilled"?K.value:null,P.value=K.status==="rejected"?(d=K.reason)==null?void 0:d.message:null}finally{$.value=!1,T.value=!1}};q(()=>_(!1));const a=j(()=>{var l,e;return((e=(l=u.value)==null?void 0:l.lastCheck)==null?void 0:e.currentVersion)??null}),C=j(()=>{var l,e;return((e=(l=u.value)==null?void 0:l.lastCheck)==null?void 0:e.latestVersion)??null}),D=j(()=>{var l,e;return((e=(l=u.value)==null?void 0:l.lastCheck)==null?void 0:e.hasUpdate)===!0}),c=j(()=>{var e,d;const l=(d=(e=u.value)==null?void 0:e.lastCheck)==null?void 0:d.checkedAt;return l?new Date(l).toLocaleString():null}),t=j(()=>{var d;const l=(d=u.value)==null?void 0:d.lastApply;if(!l)return null;const e=l.appliedAt?new Date(l.appliedAt).toLocaleString():"";return`${l.fromVersion} → ${l.toVersion}${e?`(${e})`:""}`}),z=l=>l?new Date(l).toLocaleString():"",R=async l=>{if(!p.value||w.value)return;const e=l?"确定开启开机自启?将注册登录触发的自启项(登录时自动恢复服务)。":"确定关闭开机自启?将移除自启项,且此后首次启动不再自动注册。";try{await W.confirm(e,l?"开启确认":"关闭确认",{confirmButtonText:l?"开启":"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}w.value="autostart";try{const d=await S.setAutostart(l);d.note?g(d.note,"success"):g(l?"开机自启已开启":"开机自启已关闭","success")}catch(d){g(d.message||"操作失败","error")}finally{w.value=null,await _(!0)}},L=async l=>{if(!u.value||w.value)return;const e=l?"确定开启自动更新?将每小时检查私有源,发现新版本自动安装并重启服务。":"确定关闭自动更新?不再自动检查(手动检查更新不受影响)。";try{await W.confirm(e,l?"开启确认":"关闭确认",{confirmButtonText:l?"开启":"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}w.value="updateEnabled";try{const d=await S.setUpdateEnabled(l);d.note&&g(d.note,"success")}catch(d){g(d.message||"操作失败","error")}finally{w.value=null,await _(!0)}},B=async()=>{b.value=!0;try{const l=await S.checkUpdate();y.value=l.hasUpdate?`发现新版本 ${l.latestVersion}(当前 ${l.currentVersion}),可点击「立即更新」安装`:`已是最新版本 ${l.currentVersion}(检查于 ${z(l.checkedAt)})`,await _(!0)}catch(l){g(l.message||"检查更新失败","error")}finally{b.value=!1}},A=async()=>{var l;try{await W.confirm(`确定更新到 ${C.value}?服务将自动重启并在启动时安装新版本(期间界面请求会短暂失败,属正常现象)。`,"更新确认",{confirmButtonText:"立即更新",cancelButtonText:"取消",type:"warning"})}catch{return}U.value=!0;try{const e=await S.update(),d=await S.applyUpdate();if(!d.updated){y.value=`已是最新版本 ${d.toVersion}`;return}y.value=`正在重启服务并安装 ${d.toVersion},请稍候(预计 1~3 分钟)...`;const f=await le({targetVersion:d.toVersion,sinceAppliedAt:((l=e.lastApply)==null?void 0:l.appliedAt)??null});f&&!f.error?(g(`已更新到 ${f.toVersion}`,"success"),y.value=`已更新 ${f.fromVersion} → ${f.toVersion}`):f!=null&&f.error?(g(`更新失败:${f.error}`,"error"),y.value=`更新失败:${f.error}(服务已恢复旧版本)`):y.value="更新耗时较长,请稍后刷新页面查看结果"}catch(e){g(e.message||"更新失败","error")}finally{U.value=!1,await _(!0)}};return(l,e)=>{const d=v("el-button"),f=v("el-tag"),K=v("el-switch"),N=v("el-empty"),O=v("el-card"),F=v("el-alert"),k=J("loading");return G((m(),x("div",_e,[o(O,{shadow:"never",class:"mb-16"},{header:n(()=>[s("div",ge,[e[7]||(e[7]=s("span",{class:"fw-600"},"开机自启",-1)),o(d,{loading:T.value,onClick:e[0]||(e[0]=H=>_(!1))},{default:n(()=>[...e[6]||(e[6]=[i("刷新",-1)])]),_:1},8,["loading"])])]),default:n(()=>[p.value?(m(),x("div",ye,[s("div",we,[s("div",be,[e[8]||(e[8]=i(" 登录时自动启动服务 ",-1)),o(f,{type:p.value.enabled?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[i(h(p.value.enabled?"已开启":"未开启"),1)]),_:1},8,["type"])]),e[9]||(e[9]=s("div",{class:"switch-desc"},[i(" 登录操作系统时自动启动服务(无需管理员权限)。 关闭后首次启动 "),s("code",null,"ellm-proxy"),i(" 也不会再自动注册,重新开启即可解除。 ")],-1))]),o(K,{"model-value":p.value.enabled,loading:w.value==="autostart",onChange:e[1]||(e[1]=H=>R(!!H))},null,8,["model-value","loading"])])):(m(),E(N,{key:1,description:V.value||"暂无自启状态"},{default:n(()=>[o(d,{type:"primary",onClick:e[2]||(e[2]=H=>_(!1))},{default:n(()=>[...e[10]||(e[10]=[i("重试",-1)])]),_:1})]),_:1},8,["description"]))]),_:1}),o(O,{shadow:"never",class:"mb-16"},{header:n(()=>[...e[11]||(e[11]=[s("div",{class:"flex-between"},[s("span",{class:"fw-600"},"自动更新")],-1)])]),default:n(()=>[u.value?(m(),x("div",Ce,[s("div",ke,[s("div",$e,[e[12]||(e[12]=i(" 自动检查并安装新版本 ",-1)),o(f,{type:u.value.enabled?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[i(h(u.value.enabled?"已开启":"未开启"),1)]),_:1},8,["type"])]),s("div",he,[u.value.supported?(m(),x(M,{key:1},[i(" 每小时检查 npm 源,发现新版本自动重启服务并在启动时安装(更新期间服务短暂中断); 关闭后仍可在下方手动检查与更新。 ")],64)):(m(),x(M,{key:0},[i(" 当前为源码运行模式,更新功能不可用(仅 npm 全局安装的包支持自动更新)。 ")],64))])]),u.value.supported?(m(),E(K,{key:0,"model-value":u.value.enabled,loading:w.value==="updateEnabled",onChange:e[3]||(e[3]=H=>L(!!H))},null,8,["model-value","loading"])):I("",!0)])):(m(),E(N,{key:1,description:P.value||"暂无更新状态"},{default:n(()=>[o(d,{type:"primary",onClick:e[4]||(e[4]=H=>_(!1))},{default:n(()=>[...e[13]||(e[13]=[i("重试",-1)])]),_:1})]),_:1},8,["description"]))]),_:1}),o(O,{shadow:"never"},{header:n(()=>[...e[14]||(e[14]=[s("div",{class:"flex-between"},[s("span",{class:"fw-600"},"版本信息")],-1)])]),default:n(()=>[u.value?(m(),x("div",xe,[s("div",Ve,[e[17]||(e[17]=s("span",{class:"version-label"},"当前版本",-1)),o(f,{size:"small"},{default:n(()=>[i(h(a.value||"未知"),1)]),_:1}),C.value?(m(),x(M,{key:0},[e[16]||(e[16]=s("span",{class:"version-label"},"最新版本",-1)),o(f,{type:D.value?"warning":"success",size:"small","disable-transitions":""},{default:n(()=>[i(h(C.value),1)]),_:1},8,["type"]),D.value?(m(),E(f,{key:0,type:"warning",size:"small","disable-transitions":""},{default:n(()=>[...e[15]||(e[15]=[i("发现新版本",-1)])]),_:1})):I("",!0)],64)):I("",!0)]),s("div",Ae,[i(" 上次检查:"+h(c.value||"尚未检查")+" ",1),u.value.updating?(m(),x(M,{key:0},[i(" · 更新执行中...")],64)):I("",!0)]),t.value?(m(),x("div",Te,"上次更新:"+h(t.value),1)):I("",!0),s("div",Ue,[o(d,{loading:b.value,disabled:u.value.updating,onClick:B},{default:n(()=>[i(h(b.value?"检查中...":"检查更新"),1)]),_:1},8,["loading","disabled"]),D.value?(m(),E(d,{key:0,type:"primary",loading:U.value,disabled:u.value.updating,onClick:A},{default:n(()=>[i(h(U.value?"更新中...":`立即更新到 ${C.value}`),1)]),_:1},8,["loading","disabled"])):I("",!0)]),y.value?(m(),E(F,{key:1,title:y.value,type:"info","show-icon":"",class:"mt-8",onClose:e[5]||(e[5]=H=>y.value=null)},null,8,["title"])):I("",!0)])):(m(),E(N,{key:1,description:"更新状态不可用"}))]),_:1})])),[[k,$.value]])}}}),Se=Z(Ee,[["__scopeId","data-v-9366cd99"]]),Be={class:"flex-col-gap"},Ie={class:"flex-between"},Pe={class:"muted-sm"},De={key:0},Ke={class:"flex-end"},Re=X({__name:"SettingsView",setup(Q){const{toast:g}=Y(),$=te(),T=r("basic"),p=r(!0),V=r(!1),u=r(""),P=async()=>{if(u.value)try{await navigator.clipboard.writeText(u.value),g("已复制到剪贴板","success")}catch{g("复制失败","error")}},w=r(29770),b=r(null),U=r("passthrough"),y=r(!1),_=r(""),a=r(""),C=async()=>{var c;try{await $.load(),u.value=$.config.masterKey||"",w.value=$.config.port??29770,U.value=$.config.compression==="plain"?"plain":"passthrough",y.value=$.config.logContent===!0;const t=(c=$.config["web-ui"])==null?void 0:c["login-account"];_.value=(t==null?void 0:t.username)||"",a.value=(t==null?void 0:t.password)||"";try{b.value=await ee.get("/api/status").then(z=>z.listenPort)}catch{b.value=null}}catch(t){g(t.message,"error")}finally{p.value=!1}},D=async()=>{V.value=!0;try{await $.update(c=>{var t;c.masterKey=u.value.trim(),c.port=w.value,c.compression=U.value,c.logContent=y.value,c["web-ui"]??(c["web-ui"]={}),(t=c["web-ui"])["login-account"]??(t["login-account"]={}),c["web-ui"]["login-account"].username=_.value.trim(),c["web-ui"]["login-account"].password=a.value}),g("设置已保存并生效","success")}catch(c){g(c.message,"error")}finally{V.value=!1}};return q(C),(c,t)=>{const z=v("el-tag"),R=v("el-button"),L=v("el-input"),B=v("el-form-item"),A=v("el-input-number"),l=v("el-option"),e=v("el-select"),d=v("el-switch"),f=v("el-form"),K=v("el-card"),N=v("el-tab-pane"),O=v("el-tabs"),F=J("loading");return m(),E(O,{modelValue:T.value,"onUpdate:modelValue":t[6]||(t[6]=k=>T.value=k),class:"settings-tabs"},{default:n(()=>[o(N,{label:"基本设置",name:"basic"},{default:n(()=>[G((m(),x("div",Be,[o(K,{shadow:"never"},{header:n(()=>[s("div",Ie,[t[8]||(t[8]=s("span",{class:"fw-600"},"基本设置",-1)),o(z,{type:"success"},{default:n(()=>[...t[7]||(t[7]=[i("运行中",-1)])]),_:1})])]),default:n(()=>[o(f,{"label-position":"top"},{default:n(()=>[o(B,{label:"API 密钥 (masterKey)"},{default:n(()=>[o(L,{modelValue:u.value,"onUpdate:modelValue":t[0]||(t[0]=k=>u.value=k),placeholder:"sk-1234",clearable:""},{append:n(()=>[o(R,{onClick:P},{default:n(()=>[...t[9]||(t[9]=[i("复制",-1)])]),_:1})]),_:1},8,["modelValue"]),t[10]||(t[10]=s("div",{class:"muted-sm"}," 网关 API 认证密钥(/v1/* 请求需携带 Bearer <masterKey>;留空 = 本地放行) ",-1))]),_:1}),o(B,{label:"监听端口 (port)"},{default:n(()=>[o(A,{modelValue:w.value,"onUpdate:modelValue":t[1]||(t[1]=k=>w.value=k),min:1,max:65535,step:1,class:"full-width"},null,8,["modelValue"]),s("div",Pe,[t[12]||(t[12]=i(" 网关与管理台 API 的监听端口,保存后下次重启生效 ",-1)),b.value!==null?(m(),x(M,{key:0},[t[11]||(t[11]=i(" ;当前实际监听 ",-1)),s("b",null,h(b.value),1),b.value!==w.value?(m(),x("span",De,"(由 PORT 环境变量覆盖,此配置值不生效)")):I("",!0)],64)):I("",!0)])]),_:1}),o(B,{label:"压缩语义 (compression)"},{default:n(()=>[o(e,{modelValue:U.value,"onUpdate:modelValue":t[2]||(t[2]=k=>U.value=k),class:"full-width"},{default:n(()=>[o(l,{label:"透传 (passthrough)",value:"passthrough"}),o(l,{label:"明文 (plain)",value:"plain"})]),_:1},8,["modelValue"]),t[13]||(t[13]=s("div",{class:"muted-sm"},"上游响应压缩处理方式,保存后热更新生效",-1))]),_:1}),o(B,{label:"日志内容捕获 (logContent)"},{default:n(()=>[o(d,{modelValue:y.value,"onUpdate:modelValue":t[3]||(t[3]=k=>y.value=k)},null,8,["modelValue"]),t[14]||(t[14]=s("div",{class:"muted-sm"}," 记录完整请求/响应(含上游原始与转换后内容)进日志详情,用于排查定位问题;开启后有少量内存与存储开销,保存后热更新生效 ",-1))]),_:1}),o(B,{label:"管理台用户名 (web-ui.login-account.username)"},{default:n(()=>[o(L,{modelValue:_.value,"onUpdate:modelValue":t[4]||(t[4]=k=>_.value=k),placeholder:"请输入用户名"},null,8,["modelValue"]),t[15]||(t[15]=s("div",{class:"muted-sm"}," 管理台登录用户名,留空回退 WEBUI_USERNAME/WEBUI_PASSWORD 环境变量(两者都设置才生效) ",-1))]),_:1}),o(B,{label:"管理台密码 (web-ui.login-account.password)"},{default:n(()=>[o(L,{modelValue:a.value,"onUpdate:modelValue":t[5]||(t[5]=k=>a.value=k),type:"password",placeholder:"请输入密码(至少 6 位)","show-password":""},null,8,["modelValue"]),t[16]||(t[16]=s("div",{class:"muted-sm"}," 用户名与密码需同时填写才启用登录;都未提供 = 管理台免登录直接访问(纯本机使用)。修改保存后立即生效,需用新账号重新登录 ",-1))]),_:1})]),_:1})]),_:1}),s("div",Ke,[o(R,{type:"primary",loading:V.value,disabled:V.value,onClick:D},{default:n(()=>[i(h(V.value?"保存中...":"保存设置"),1)]),_:1},8,["loading","disabled"])])])),[[F,p.value]])]),_:1}),o(N,{label:"代理开关",name:"harness",lazy:""},{default:n(()=>[o(fe)]),_:1}),o(N,{label:"服务与更新",name:"service",lazy:""},{default:n(()=>[o(Se)]),_:1})]),_:1},8,["modelValue"])}}});export{Re as default};
@@ -0,0 +1 @@
1
+ .setup-wrap[data-v-631b405c]{min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f5f7fa}.setup-card[data-v-631b405c]{width:520px;max-width:92vw}.setup-title[data-v-631b405c]{text-align:center;margin-bottom:20px;color:#3b82f6}.account-row[data-v-631b405c]{display:flex;gap:12px;width:100%}.label-hint[data-v-631b405c]{display:block;font-size:12px;font-weight:400;color:#909399;line-height:1.4}.full-width[data-v-631b405c]{width:100%}.mb-16[data-v-631b405c]{margin-bottom:16px}
@@ -0,0 +1 @@
1
+ import{d as M,k as N,l as E,e as I,g as l,w as t,r as d,a as u,o as y,f,h as m,c as x,i as C,a0 as T,ac as w,A as h,ad as L,n as P,_ as R}from"./index-BmtcBnoW.js";const $={class:"setup-wrap"},j={class:"account-row"},q=M({__name:"SetupView",setup(z){const S=P(),r=d(!1),i=d(""),c=d(""),p=d(""),_=d(!1),o=d("");N(async()=>{(await E()).masterKeySet||(i.value=A())});function A(){const s=new Uint8Array(32);crypto.getRandomValues(s);let e="";for(const a of s)e+=String.fromCharCode(a);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}async function B(){try{await navigator.clipboard.writeText(i.value),w.success("已复制到剪贴板")}catch{w.error("复制失败,请手动选择复制")}}async function V(){o.value="";const s=r.value?"server":"local",e=i.value.trim(),a=c.value.trim()!==""||p.value!=="";if(r.value&&e===""){o.value="服务器部署必须设置主密钥,防止 /v1 网关被他人盗用";return}if(r.value&&!a){o.value="服务器部署必须设置登录账号密码,防止管理台被陌生人登录";return}if(a){if(c.value.trim()===""||p.value===""){o.value="用户名和密码需同时填写";return}if(p.value.length<6){o.value="密码至少 6 位";return}}_.value=!0;try{e!==""&&await h.post("/api/setup/master-key",{mode:"custom",masterKey:e}),await h.post("/api/setup/finish",{deployment:s,...a?{username:c.value.trim(),password:p.value}:{}}),L(s,a),w.success(a?"初始化完成,请使用新账号登录":"初始化完成,免登录进入管理台"),await S.push(a?"/login":"/")}catch(v){o.value=v.message||"保存失败"}finally{_.value=!1}}return(s,e)=>{const a=u("el-checkbox"),v=u("el-alert"),g=u("el-button"),b=u("el-input"),k=u("el-form-item"),K=u("el-form"),U=u("el-card");return y(),I("div",$,[l(U,{shadow:"hover",class:"setup-card"},{default:t(()=>[e[9]||(e[9]=f("h2",{class:"setup-title"},"初始化 ELLM",-1)),l(a,{modelValue:r.value,"onUpdate:modelValue":e[0]||(e[0]=n=>r.value=n),class:"mb-16"},{default:t(()=>[...e[4]||(e[4]=[m(" 允许其他设备访问本服务(局域网 / 公网部署) ",-1)])]),_:1},8,["modelValue"]),r.value?(y(),x(v,{key:0,type:"warning","show-icon":"",closable:!1,title:"服务将对网络中的其他设备可见,请务必保留主密钥并修改默认账号",class:"mb-16"})):C("",!0),l(K,{"label-position":"top",onSubmit:T(V,["prevent"])},{default:t(()=>[l(k,null,{label:t(()=>[...e[5]||(e[5]=[m(" API 主密钥 ",-1),f("span",{class:"label-hint"},"调用 /v1/* 时的 Bearer Token;清空则不启用鉴权(仅建议纯本机使用)",-1)])]),default:t(()=>[l(b,{modelValue:i.value,"onUpdate:modelValue":e[1]||(e[1]=n=>i.value=n),placeholder:"已设置主密钥,留空保持不变",clearable:""},{append:t(()=>[l(g,{disabled:!i.value,onClick:B},{default:t(()=>[...e[6]||(e[6]=[m("复制",-1)])]),_:1},8,["disabled"])]),_:1},8,["modelValue"])]),_:1}),l(k,null,{label:t(()=>[...e[7]||(e[7]=[m(" 管理台登录(可选) ",-1),f("span",{class:"label-hint"},"不填 = 免登录直接进管理台(纯本机使用);勾选上方后必须填写",-1)])]),default:t(()=>[f("div",j,[l(b,{modelValue:c.value,"onUpdate:modelValue":e[2]||(e[2]=n=>c.value=n),placeholder:"用户名",clearable:""},null,8,["modelValue"]),l(b,{modelValue:p.value,"onUpdate:modelValue":e[3]||(e[3]=n=>p.value=n),type:"password","show-password":"",placeholder:"密码(至少 6 位)"},null,8,["modelValue"])])]),_:1}),o.value?(y(),x(v,{key:0,title:o.value,type:"error","show-icon":"",closable:!1,class:"mb-16"},null,8,["title"])):C("",!0),l(g,{type:"primary",class:"full-width",loading:_.value,onClick:V},{default:t(()=>[...e[8]||(e[8]=[m(" 完成初始化 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1})])}}}),F=R(q,[["__scopeId","data-v-631b405c"]]);export{F as default};
@@ -1 +1 @@
1
- import{X as p,r as u,l as n,A as i}from"./index-sPrBBnf5.js";const y=p("config",()=>{const e=u({}),o=u(!1),t=n(()=>Array.isArray(e.value.providers)?e.value.providers:[]),l=n(()=>t.value.length),c=n(()=>t.value.reduce((a,d)=>{var s;return a+(((s=d.models)==null?void 0:s.length)??0)},0));async function f(){o.value=!0;try{e.value=await i.get("/api/config")}finally{o.value=!1}}async function r(){return await i.put("/api/config",e.value)}async function v(a){return a(e.value),await r()}return{config:e,loading:o,providers:t,providerCount:l,totalModelCount:c,load:f,save:r,update:v}});export{y as u};
1
+ import{Z as p,r as u,m as n,A as i}from"./index-BmtcBnoW.js";const y=p("config",()=>{const e=u({}),o=u(!1),t=n(()=>Array.isArray(e.value.providers)?e.value.providers:[]),l=n(()=>t.value.length),c=n(()=>t.value.reduce((a,d)=>{var s;return a+(((s=d.models)==null?void 0:s.length)??0)},0));async function f(){o.value=!0;try{e.value=await i.get("/api/config")}finally{o.value=!1}}async function r(){return await i.put("/api/config",e.value)}async function v(a){return a(e.value),await r()}return{config:e,loading:o,providers:t,providerCount:l,totalModelCount:c,load:f,save:r,update:v}});export{y as u};