ezllm 0.0.27 → 0.0.28

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 (26) hide show
  1. package/README.md +12 -0
  2. package/dist/server.js +63 -5
  3. package/dist/web-ui/assets/{DashboardView-DTF4RzgZ.js → DashboardView-ChJpgKis.js} +1 -1
  4. package/dist/web-ui/assets/{LayoutView-CkUPhcPQ.js → LayoutView-C8YPw0x2.js} +1 -1
  5. package/dist/web-ui/assets/{LoginView-CzfNjlvz.js → LoginView-C4hkT-3T.js} +1 -1
  6. package/dist/web-ui/assets/{LogsView-BYA6dgGO.js → LogsView-DdS8qKEs.js} +1 -1
  7. package/dist/web-ui/assets/ProvidersView-BD33caY8.js +14 -0
  8. package/dist/web-ui/assets/ProvidersView-DNqX_AWw.css +1 -0
  9. package/dist/web-ui/assets/{ServiceStatusView-c0jJPnb2.js → ServiceStatusView-BtF40E0V.js} +1 -1
  10. package/dist/web-ui/assets/{SettingsView-BZsuqac5.js → SettingsView-SdL8ednl.js} +1 -1
  11. package/dist/web-ui/assets/{SetupView-DpD5okpe.js → SetupView-BZFBBRSd.js} +1 -1
  12. package/dist/web-ui/assets/{config-C6eC0CWg.js → config-Dbms19Et.js} +1 -1
  13. package/dist/web-ui/assets/{index-DRdS47Vm.js → index-C6wv4UB9.js} +1 -1
  14. package/dist/web-ui/assets/{message-D5w4qfHY.js → message-CEBSyhlN.js} +1 -1
  15. package/dist/web-ui/assets/{providers-BM4fqFJi.js → providers-DkujdEZ8.js} +1 -1
  16. package/dist/web-ui/assets/{router-DjNMXIJT.js → router-BqkHX02I.js} +2 -2
  17. package/dist/web-ui/assets/{useLoadError-qYTOtUW0.js → useLoadError-Yu7YH4ZH.js} +1 -1
  18. package/dist/web-ui/assets/{useManagerApi-COiXXii9.js → useManagerApi-fVrRDZkc.js} +1 -1
  19. package/dist/web-ui/assets/useToast-DJwG-33L.js +1 -0
  20. package/dist/web-ui/index.html +3 -3
  21. package/docs/DESIGN.md +2 -2
  22. package/hook.example.js +176 -0
  23. package/package.json +2 -1
  24. package/dist/web-ui/assets/ProvidersView-DYUaYhO-.js +0 -14
  25. package/dist/web-ui/assets/ProvidersView-ufXnqFGi.css +0 -1
  26. package/dist/web-ui/assets/useToast-DKg4wBxA.js +0 -1
package/README.md CHANGED
@@ -54,4 +54,16 @@ ezllm restart # 重启
54
54
  ezllm install # 装配/修复自带 Node 运行时的环境(幂等;自动迁移旧数据目录)
55
55
  ```
56
56
 
57
+ ## 进阶:改写请求报文(可选)
58
+
59
+ 如果某个客户端每轮都在请求里塞大量固定内容(工具描述里内嵌的 API 手册、注入的记忆索引等),可以在网关侧裁掉,从根上省 token。
60
+
61
+ 把发布包里的 `hook.example.js` 复制到应用目录(默认 `~/.ezllm`)并改名为 **`hook.cjs`**,按注释改里面的规则,然后 `ezllm restart`。
62
+
63
+ - **候选文件名**:`hook.cjs` → `hook.mjs` → `hook.js`,取第一个存在的。**推荐 `hook.cjs`**——`.js` 的解析模式(CJS 还是 ESM)取决于它祖先目录里最近的 `package.json`,不受你控制(比如家目录若有一份 `"type": "module"` 的 package.json,`hook.js` 里的 CJS 写法就会失效);`.cjs` 强制按 CJS 解析,任何环境都稳。
64
+ - **只有请求侧改写**一个钩子(`onModelRequest`):在发往上游前改请求体/请求头,**改不了响应**。
65
+ - **改完必须重启服务**:钩子在启动时加载,不支持运行期热加载。
66
+ - **文件不存在 = 什么都不做**;文件写错也只打一条告警、服务照常启动(不会因为钩子报错而起不来)。
67
+ - **改动的部分要保持内容稳定**(别插时间戳 / 随机 id),否则会打断上游的缓存前缀,命中率下降得不偿失。
68
+
57
69
  > 说明:本工具为个人本地使用设计,非高并发架构,不建议作为商业代理部署。
package/dist/server.js CHANGED
@@ -8,7 +8,7 @@ import crypto from "crypto";
8
8
  import { closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
9
9
  import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from "node:path";
10
10
  import { cwd } from "node:process";
11
- import { fileURLToPath } from "node:url";
11
+ import { fileURLToPath, pathToFileURL } from "node:url";
12
12
  import { createHash, randomBytes, randomUUID } from "node:crypto";
13
13
  import { DatabaseSync } from "node:sqlite";
14
14
  import { brotliCompressSync, brotliDecompressSync, constants as constants$1, gunzipSync } from "node:zlib";
@@ -13242,6 +13242,61 @@ function validateConfig(config) {
13242
13242
  const { masterKey: _masterKey, "web-ui": _webUi, logs: _logs, setup: _setup, autoUpdate: _autoUpdate, allowLan: _allowLan, latencyTest: _latencyTest, ...gatewayConfig } = config;
13243
13243
  return expandConfig(gatewayConfig);
13244
13244
  }
13245
+ /**
13246
+ * 用户钩子文件搜索路径(固定约定,非配置项):`<appDir>/hook.cjs` / `hook.mjs` / `hook.js`
13247
+ * 三选一。数组按优先级排列;实际加载的是其中**第一个存在的**(见 resolveHookPath)。
13248
+ */
13249
+ var USER_HOOK_PATHS = [
13250
+ "hook.cjs",
13251
+ "hook.mjs",
13252
+ "hook.js"
13253
+ ].map((n) => join(APP_DIR, n));
13254
+ /** 取第一个存在的候选路径;都不存在返回 null(= 未配置钩子,静默跳过) */
13255
+ function resolveHookPath() {
13256
+ for (const p of USER_HOOK_PATHS) if (existsSync(p)) return p;
13257
+ return null;
13258
+ }
13259
+ /**
13260
+ * 把 import 结果归一为钩子对象:兼容 esm default 导出与 cjs module.exports。
13261
+ * 优先级 default → module.exports → 裸对象,任一命中即止。
13262
+ */
13263
+ function pickHooks(mod, path) {
13264
+ if (mod === null || typeof mod !== "object") return null;
13265
+ const m = mod;
13266
+ const candidate = m["default"] ?? m["module.exports"] ?? m;
13267
+ if (candidate === null || typeof candidate !== "object") return null;
13268
+ const hooks = candidate;
13269
+ if (typeof hooks.onModelRequest !== "function") {
13270
+ console.warn(`[ezllm] ${path} 未导出 onModelRequest 函数,已忽略(不挂任何钩子)`);
13271
+ return null;
13272
+ }
13273
+ return { onModelRequest: hooks.onModelRequest };
13274
+ }
13275
+ /**
13276
+ * 加载并注册用户钩子(文件不存在 = 静默跳过,按现状运行)。
13277
+ *
13278
+ * **失败只告警、绝不致命**:hook.js 是用户手写文件,一个语法错误不该打死整个网关——
13279
+ * 那会连管理台一起失去,用户无法自救。失败时打印完整原因并保持无钩子运行。
13280
+ *
13281
+ * @param ellm 已构造的网关实例(钩子挂其 HookRunner)
13282
+ */
13283
+ async function registerUserHooks(ellm) {
13284
+ const hookPath = resolveHookPath();
13285
+ if (hookPath === null) return;
13286
+ let mod;
13287
+ try {
13288
+ mod = await import(pathToFileURL(hookPath).href);
13289
+ } catch (error) {
13290
+ console.error(`[ezllm] 加载用户钩子失败,本次按无钩子运行:${hookPath}`);
13291
+ console.error(`[ezllm] 原因:${error instanceof Error ? error.message : String(error)}`);
13292
+ console.error("[ezllm] 提示:模块写法与文件扩展名不匹配。CJS 内容(module.exports)请用 hook.cjs,ESM 内容(export default)请用 hook.mjs——这两个扩展名强制解析模式,不受任何 package.json 的 type 影响");
13293
+ return;
13294
+ }
13295
+ const hooks = pickHooks(mod, hookPath);
13296
+ if (hooks?.onModelRequest === void 0) return;
13297
+ ellm.addHook("onModelRequest", hooks.onModelRequest);
13298
+ console.log(`[ezllm] 已加载用户钩子:${hookPath}(onModelRequest)`);
13299
+ }
13245
13300
  //#endregion
13246
13301
  //#region ../../packages/server/src/services/gateway-runtime.ts
13247
13302
  /**
@@ -13263,12 +13318,13 @@ function validateConfig(config) {
13263
13318
  * @param options - traceSink 可选(观测面落库 sink);缺省时观测数据不落库
13264
13319
  * @returns 可热更新的运行时句柄:rebuild() 热重载配置,proxy 挂载到外层 Hono
13265
13320
  */
13266
- function createRuntime(config, hooks, options = {}) {
13321
+ async function createRuntime(config, hooks, options = {}) {
13267
13322
  const ellm = new Ellm({
13268
13323
  config,
13269
13324
  traceSink: options.traceSink
13270
13325
  });
13271
13326
  hooks.register(ellm);
13327
+ await registerUserHooks(ellm);
13272
13328
  const state = {
13273
13329
  current: ellm,
13274
13330
  port: ellm.expanded.port,
@@ -17957,7 +18013,8 @@ function createQuotaApp() {
17957
18013
  vendors: listQuotaVendors().map((v) => ({
17958
18014
  vendorId: v.vendorId,
17959
18015
  vendorLabel: v.vendorLabel,
17960
- hosts: v.hosts
18016
+ hosts: v.hosts,
18017
+ apiHosts: v.apiHosts ?? []
17961
18018
  })),
17962
18019
  providers: getAllQuotaEntries()
17963
18020
  });
@@ -17968,7 +18025,8 @@ function createQuotaApp() {
17968
18025
  vendors: listQuotaVendors().map((v) => ({
17969
18026
  vendorId: v.vendorId,
17970
18027
  vendorLabel: v.vendorLabel,
17971
- hosts: v.hosts
18028
+ hosts: v.hosts,
18029
+ apiHosts: v.apiHosts ?? []
17972
18030
  }))
17973
18031
  });
17974
18032
  });
@@ -41761,7 +41819,7 @@ if (orphanTmpRemoved > 0) console.log(`[ezllm] spool 启动清理:回收中断
41761
41819
  await recoverSpool();
41762
41820
  var traceSink = createSqliteTraceSink();
41763
41821
  var knownModelNames = () => config.providers.flatMap((p) => p.models.map((m) => m.modelName));
41764
- var runtime = createRuntime(config, {
41822
+ var runtime = await createRuntime(config, {
41765
41823
  authorize(authHeader) {
41766
41824
  const masterKey = resolveMasterKey();
41767
41825
  if (masterKey === "") return true;
@@ -1 +1 @@
1
- import{$ as e,A as t,Et as n,F as r,Ft as i,Gt as a,H as o,I as s,L as c,M as l,N as u,Q as d,T as ee,a as f,at as p,d as m,gt as te,ht as h,j as g,k as _,ot as ne,pn as v,rt as re,tt as y,yt as b}from"./router-DjNMXIJT.js";import{t as x}from"./useManagerApi-COiXXii9.js";import{t as S}from"./providers-BM4fqFJi.js";import{t as C}from"./useToast-DKg4wBxA.js";import{t as w}from"./useLoadError-qYTOtUW0.js";import{t as T}from"./latestRequest-Dk21IcrH.js";import{t as ie}from"./clipboard-CorU9X8e.js";import{a as ae,o as E,r as oe,t as se}from"./format-DrbP4_cY.js";b();function ce(e){let{loadError:t,run:r}=w(e),i=n(null);function a(e){let t=async()=>{let n=await r(e);return n?i.value===t&&(i.value=null):i.value=t,n};return t}function o(e){(i.value??e)()}return{loadError:t,track:a,retry:o}}o(),b(),a();var le={class:`flex-col-gap`},ue=[`onClick`],de={key:1},fe={class:`flex-between`},pe={class:`flex-gap-8`},me={class:`usage-grid`},he={class:`usage-cell`},ge={class:`usage-num orange`},_e={class:`usage-cell`},D={class:`usage-num blue`},O={class:`usage-cell`},k={class:`usage-num green`},A={class:`usage-cell`},j={class:`usage-num purple`},M={class:`usage-cell`},N={class:`usage-num purple`},P={key:0,class:`usage-cell`},F={class:`usage-num gray`},I={key:0},L=[`onClick`],R=m(c({__name:`DashboardView`,setup(a){let o=S(),{toast:c}=C(),{loadError:m,track:b,retry:w}=ce(),R=n(null),z=n(!0),B=n(``),V=n(`today`),H=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}`,U=H(new Date),W=n([U,U]),ve=e=>{let t=new Date,n,r=t;if(e===`today`)n=t;else if(e===`yesterday`)n=new Date(t),n.setDate(t.getDate()-1),r=n;else if(e===`beforeYesterday`)n=new Date(t),n.setDate(t.getDate()-2),r=n;else if(e===`last3days`)n=new Date(t),n.setDate(t.getDate()-2);else if(e===`week`){n=new Date(t);let e=(t.getDay()+6)%7;n.setDate(t.getDate()-e)}else if(e===`month`)n=new Date(t.getFullYear(),t.getMonth(),1);else return;V.value=e,W.value=[H(n),H(r)],G()},ye=e=>{e?ve(e):(V.value=``,W.value=null,G())},be=()=>{V.value=``,G()},xe=_(()=>{let e=new Set;for(let t of o.suppliers)for(let n of t.models)e.add(n.modelName);if(R.value?.models)for(let t of R.value.models)e.add(t.model);return[...e].sort()}),Se=T(),G=b(async()=>{let e=Se();z.value=!0;try{let t=new URLSearchParams;B.value&&t.set(`model`,B.value),W.value?.length===2&&(t.set(`startDate`,W.value[0]),t.set(`endDate`,W.value[1]));let n=t.toString(),r=await f.get(`/api/usage${n?`?${n}`:``}`);if(!e())return;R.value=r}catch(t){if(!e())return;throw t}finally{e()&&(z.value=!1)}}),Ce=T(),K=b(async()=>{let e=Ce();try{await Promise.all([G(),o.loadProviders()])}catch(t){if(!e())return;throw t}finally{e()&&(z.value=!1)}}),we=()=>w(K),Te=window.location.hostname,Ee=Number(window.location.port)||null,De=_(()=>se({protocol:window.location.protocol,hostname:Te,port:Ee})),q=n(null),J=n(null),Y=n(!1),X=n(Date.now()),Z=async()=>{try{let[e,t]=await Promise.all([x.processes(),f.get(`/api/config`).catch(()=>null)]);q.value=e[0]??null,Y.value=t===null,J.value=t?.masterKey??``,X.value=Date.now()}catch{}},Oe=e=>{if(!e.startedAt)return`-`;let t=Math.max(0,Math.floor((X.value-e.startedAt)/1e3)),n=Math.floor(t/86400);t-=n*86400;let r=Math.floor(t/3600);t-=r*3600;let i=Math.floor(t/60);return t-=i*60,[n?`${n}天`:``,r?`${r}时`:``,i?`${i}分`:``,`${t}秒`].filter(Boolean).join(` `)},ke=_(()=>{let e=[{key:`API 地址`,value:De.value},{key:`API 密钥`,value:Y.value?`获取失败,请刷新重试`:J.value||`未设置(/v1 免鉴权)`},{key:`供应商数量`,value:String(o.providerCount)},{key:`模型总数`,value:String(o.totalModelCount)}],t=q.value;return t&&e.push({key:`运行状态`,value:`运行中`},{key:`PID`,value:String(t.pid??`-`)},{key:`运行时长`,value:Oe(t)},{key:`内存占用`,value:ae(t.memory)}),e}),Q,$=async e=>{await ie(e)?c(`已复制到剪贴板`,`success`):c(`复制失败`,`error`)};return d(()=>{K(),Z(),Q=window.setInterval(Z,1e4)}),e(()=>window.clearInterval(Q)),(e,n)=>{let a=p(`el-button`),o=p(`el-alert`),c=p(`el-table-column`),d=p(`el-table`),f=p(`el-card`),_=p(`el-option`),b=p(`el-select`),x=p(`el-date-picker`),S=p(`el-tag`),C=p(`el-empty`),w=ne(`loading`);return y(),u(`div`,le,[i(m)?(y(),g(o,{key:0,title:i(m),type:`error`,"show-icon":``,closable:!1},{default:h(()=>[s(a,{size:`small`,onClick:we},{default:h(()=>[...n[3]||=[r(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):l(``,!0),s(f,{shadow:`never`},{header:h(()=>[...n[4]||=[t(`span`,{class:`fw-600`},`服务信息`,-1)]]),default:h(()=>[te((y(),g(d,{border:``,data:ke.value,"show-header":!1,class:`full-width`},{default:h(()=>[s(c,{prop:`key`,width:`160`}),s(c,null,{default:h(({row:e})=>[e.key===`API 地址`||e.key===`API 密钥`?(y(),u(`span`,{key:0,class:`copy-text`,onClick:t=>$(e.value)},v(e.value),9,ue)):(y(),u(`span`,de,v(e.value),1))]),_:1})]),_:1},8,[`data`])),[[w,z.value]])]),_:1}),R.value?(y(),g(f,{key:1,shadow:`never`},{header:h(()=>[t(`div`,fe,[n[6]||=t(`span`,{class:`fw-600`},`用量统计`,-1),t(`div`,pe,[s(b,{modelValue:B.value,"onUpdate:modelValue":n[0]||=e=>B.value=e,placeholder:`全部模型`,clearable:``,filterable:``,style:{width:`220px`},onChange:i(G)},{default:h(()=>[(y(!0),u(ee,null,re(xe.value,e=>(y(),g(_,{key:e,label:e,value:e},null,8,[`label`,`value`]))),128))]),_:1},8,[`modelValue`,`onChange`]),s(b,{modelValue:V.value,"onUpdate:modelValue":n[1]||=e=>V.value=e,placeholder:`时间范围`,clearable:``,style:{width:`130px`},onChange:ye},{default:h(()=>[s(_,{label:`今天`,value:`today`}),s(_,{label:`昨天`,value:`yesterday`}),s(_,{label:`前天`,value:`beforeYesterday`}),s(_,{label:`近三天`,value:`last3days`}),s(_,{label:`本周`,value:`week`}),s(_,{label:`本月`,value:`month`})]),_:1},8,[`modelValue`]),s(x,{modelValue:W.value,"onUpdate:modelValue":n[2]||=e=>W.value=e,type:`daterange`,"range-separator":`至`,"start-placeholder":`开始日期`,"end-placeholder":`结束日期`,"value-format":`YYYY-MM-DD`,style:{width:`260px`},onChange:be},null,8,[`modelValue`]),s(a,{loading:z.value,onClick:i(G)},{default:h(()=>[...n[5]||=[r(`刷新`,-1)]]),_:1},8,[`loading`,`onClick`])]),s(S,{type:`info`},{default:h(()=>[r(v(R.value.total.calls)+` 次调用`,1)]),_:1})])]),default:h(()=>[t(`div`,me,[t(`div`,he,[t(`div`,ge,v(i(E)(R.value.total.total_tokens)),1),n[7]||=t(`div`,{class:`usage-label`},`总 Token`,-1)]),t(`div`,_e,[t(`div`,D,v(i(E)(R.value.total.input_tokens)),1),n[8]||=t(`div`,{class:`usage-label`},`输入 Token`,-1)]),t(`div`,O,[t(`div`,k,v(i(E)(R.value.total.output_tokens)),1),n[9]||=t(`div`,{class:`usage-label`},`输出 Token`,-1)]),t(`div`,A,[t(`div`,j,v(i(E)(R.value.total.cache_read_tokens)),1),n[10]||=t(`div`,{class:`usage-label`},`缓存读取`,-1)]),t(`div`,M,[t(`div`,N,v(R.value.total.cache_rate==null?`-`:R.value.total.cache_rate+`%`),1),n[11]||=t(`div`,{class:`usage-label`},`缓存命中率`,-1)]),R.value.logStorageMB==null?l(``,!0):(y(),u(`div`,P,[t(`div`,F,v(R.value.logStorageMB),1),n[12]||=t(`div`,{class:`usage-label`},`数据目录占用 (MB)`,-1)]))]),R.value.models?.length?(y(),u(`div`,I,[n[13]||=t(`div`,{class:`section-title`},`模型用量`,-1),s(d,{data:R.value.models,border:``,stripe:``,class:`full-width`},{default:h(()=>[s(c,{prop:`model`,label:`模型`,"min-width":`200`},{default:h(({row:e})=>[t(`span`,{class:`copy-text`,onClick:t=>$(e.model)},v(e.model),9,L)]),_:1}),s(c,{label:`调用次数`,width:`90`,align:`center`},{default:h(({row:e})=>[r(v(e.calls),1)]),_:1}),s(c,{label:`平均耗时`,width:`110`,align:`center`},{default:h(({row:e})=>[r(v(i(oe)(e.avg_duration)),1)]),_:1}),s(c,{label:`总 Token`,width:`140`,align:`center`},{default:h(({row:e})=>[r(v(i(E)(e.total_tokens)),1)]),_:1}),s(c,{label:`输入 Token`,width:`140`,align:`center`},{default:h(({row:e})=>[r(v(i(E)(e.input_tokens)),1)]),_:1}),s(c,{label:`输出 Token`,width:`140`,align:`center`},{default:h(({row:e})=>[r(v(i(E)(e.output_tokens)),1)]),_:1}),s(c,{label:`缓存读取`,width:`130`,align:`center`},{default:h(({row:e})=>[r(v(i(E)(e.cache_read_tokens)),1)]),_:1}),s(c,{label:`缓存命中率`,width:`110`,align:`center`},{default:h(({row:e})=>[r(v(e.cache_rate==null?`-`:e.cache_rate+`%`),1)]),_:1})]),_:1},8,[`data`])])):(y(),g(C,{key:1,description:`暂无调用记录`,"image-size":60}))]),_:1})):l(``,!0)])}}}),[[`__scopeId`,`data-v-e5f81c6f`]]);export{R as default};
1
+ import{$ as e,A as t,Et as n,F as r,Ft as i,Gt as a,H as o,I as s,L as c,M as l,N as u,Q as d,T as ee,a as f,at as p,d as m,gt as te,ht as h,j as g,k as _,ot as ne,pn as v,rt as re,tt as y,yt as b}from"./router-BqkHX02I.js";import{t as x}from"./useManagerApi-fVrRDZkc.js";import{t as S}from"./providers-DkujdEZ8.js";import{t as C}from"./useToast-DJwG-33L.js";import{t as w}from"./useLoadError-Yu7YH4ZH.js";import{t as T}from"./latestRequest-Dk21IcrH.js";import{t as ie}from"./clipboard-CorU9X8e.js";import{a as ae,o as E,r as oe,t as se}from"./format-DrbP4_cY.js";b();function ce(e){let{loadError:t,run:r}=w(e),i=n(null);function a(e){let t=async()=>{let n=await r(e);return n?i.value===t&&(i.value=null):i.value=t,n};return t}function o(e){(i.value??e)()}return{loadError:t,track:a,retry:o}}o(),b(),a();var le={class:`flex-col-gap`},ue=[`onClick`],de={key:1},fe={class:`flex-between`},pe={class:`flex-gap-8`},me={class:`usage-grid`},he={class:`usage-cell`},ge={class:`usage-num orange`},_e={class:`usage-cell`},D={class:`usage-num blue`},O={class:`usage-cell`},k={class:`usage-num green`},A={class:`usage-cell`},j={class:`usage-num purple`},M={class:`usage-cell`},N={class:`usage-num purple`},P={key:0,class:`usage-cell`},F={class:`usage-num gray`},I={key:0},L=[`onClick`],R=m(c({__name:`DashboardView`,setup(a){let o=S(),{toast:c}=C(),{loadError:m,track:b,retry:w}=ce(),R=n(null),z=n(!0),B=n(``),V=n(`today`),H=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}`,U=H(new Date),W=n([U,U]),ve=e=>{let t=new Date,n,r=t;if(e===`today`)n=t;else if(e===`yesterday`)n=new Date(t),n.setDate(t.getDate()-1),r=n;else if(e===`beforeYesterday`)n=new Date(t),n.setDate(t.getDate()-2),r=n;else if(e===`last3days`)n=new Date(t),n.setDate(t.getDate()-2);else if(e===`week`){n=new Date(t);let e=(t.getDay()+6)%7;n.setDate(t.getDate()-e)}else if(e===`month`)n=new Date(t.getFullYear(),t.getMonth(),1);else return;V.value=e,W.value=[H(n),H(r)],G()},ye=e=>{e?ve(e):(V.value=``,W.value=null,G())},be=()=>{V.value=``,G()},xe=_(()=>{let e=new Set;for(let t of o.suppliers)for(let n of t.models)e.add(n.modelName);if(R.value?.models)for(let t of R.value.models)e.add(t.model);return[...e].sort()}),Se=T(),G=b(async()=>{let e=Se();z.value=!0;try{let t=new URLSearchParams;B.value&&t.set(`model`,B.value),W.value?.length===2&&(t.set(`startDate`,W.value[0]),t.set(`endDate`,W.value[1]));let n=t.toString(),r=await f.get(`/api/usage${n?`?${n}`:``}`);if(!e())return;R.value=r}catch(t){if(!e())return;throw t}finally{e()&&(z.value=!1)}}),Ce=T(),K=b(async()=>{let e=Ce();try{await Promise.all([G(),o.loadProviders()])}catch(t){if(!e())return;throw t}finally{e()&&(z.value=!1)}}),we=()=>w(K),Te=window.location.hostname,Ee=Number(window.location.port)||null,De=_(()=>se({protocol:window.location.protocol,hostname:Te,port:Ee})),q=n(null),J=n(null),Y=n(!1),X=n(Date.now()),Z=async()=>{try{let[e,t]=await Promise.all([x.processes(),f.get(`/api/config`).catch(()=>null)]);q.value=e[0]??null,Y.value=t===null,J.value=t?.masterKey??``,X.value=Date.now()}catch{}},Oe=e=>{if(!e.startedAt)return`-`;let t=Math.max(0,Math.floor((X.value-e.startedAt)/1e3)),n=Math.floor(t/86400);t-=n*86400;let r=Math.floor(t/3600);t-=r*3600;let i=Math.floor(t/60);return t-=i*60,[n?`${n}天`:``,r?`${r}时`:``,i?`${i}分`:``,`${t}秒`].filter(Boolean).join(` `)},ke=_(()=>{let e=[{key:`API 地址`,value:De.value},{key:`API 密钥`,value:Y.value?`获取失败,请刷新重试`:J.value||`未设置(/v1 免鉴权)`},{key:`供应商数量`,value:String(o.providerCount)},{key:`模型总数`,value:String(o.totalModelCount)}],t=q.value;return t&&e.push({key:`运行状态`,value:`运行中`},{key:`PID`,value:String(t.pid??`-`)},{key:`运行时长`,value:Oe(t)},{key:`内存占用`,value:ae(t.memory)}),e}),Q,$=async e=>{await ie(e)?c(`已复制到剪贴板`,`success`):c(`复制失败`,`error`)};return d(()=>{K(),Z(),Q=window.setInterval(Z,1e4)}),e(()=>window.clearInterval(Q)),(e,n)=>{let a=p(`el-button`),o=p(`el-alert`),c=p(`el-table-column`),d=p(`el-table`),f=p(`el-card`),_=p(`el-option`),b=p(`el-select`),x=p(`el-date-picker`),S=p(`el-tag`),C=p(`el-empty`),w=ne(`loading`);return y(),u(`div`,le,[i(m)?(y(),g(o,{key:0,title:i(m),type:`error`,"show-icon":``,closable:!1},{default:h(()=>[s(a,{size:`small`,onClick:we},{default:h(()=>[...n[3]||=[r(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):l(``,!0),s(f,{shadow:`never`},{header:h(()=>[...n[4]||=[t(`span`,{class:`fw-600`},`服务信息`,-1)]]),default:h(()=>[te((y(),g(d,{border:``,data:ke.value,"show-header":!1,class:`full-width`},{default:h(()=>[s(c,{prop:`key`,width:`160`}),s(c,null,{default:h(({row:e})=>[e.key===`API 地址`||e.key===`API 密钥`?(y(),u(`span`,{key:0,class:`copy-text`,onClick:t=>$(e.value)},v(e.value),9,ue)):(y(),u(`span`,de,v(e.value),1))]),_:1})]),_:1},8,[`data`])),[[w,z.value]])]),_:1}),R.value?(y(),g(f,{key:1,shadow:`never`},{header:h(()=>[t(`div`,fe,[n[6]||=t(`span`,{class:`fw-600`},`用量统计`,-1),t(`div`,pe,[s(b,{modelValue:B.value,"onUpdate:modelValue":n[0]||=e=>B.value=e,placeholder:`全部模型`,clearable:``,filterable:``,style:{width:`220px`},onChange:i(G)},{default:h(()=>[(y(!0),u(ee,null,re(xe.value,e=>(y(),g(_,{key:e,label:e,value:e},null,8,[`label`,`value`]))),128))]),_:1},8,[`modelValue`,`onChange`]),s(b,{modelValue:V.value,"onUpdate:modelValue":n[1]||=e=>V.value=e,placeholder:`时间范围`,clearable:``,style:{width:`130px`},onChange:ye},{default:h(()=>[s(_,{label:`今天`,value:`today`}),s(_,{label:`昨天`,value:`yesterday`}),s(_,{label:`前天`,value:`beforeYesterday`}),s(_,{label:`近三天`,value:`last3days`}),s(_,{label:`本周`,value:`week`}),s(_,{label:`本月`,value:`month`})]),_:1},8,[`modelValue`]),s(x,{modelValue:W.value,"onUpdate:modelValue":n[2]||=e=>W.value=e,type:`daterange`,"range-separator":`至`,"start-placeholder":`开始日期`,"end-placeholder":`结束日期`,"value-format":`YYYY-MM-DD`,style:{width:`260px`},onChange:be},null,8,[`modelValue`]),s(a,{loading:z.value,onClick:i(G)},{default:h(()=>[...n[5]||=[r(`刷新`,-1)]]),_:1},8,[`loading`,`onClick`])]),s(S,{type:`info`},{default:h(()=>[r(v(R.value.total.calls)+` 次调用`,1)]),_:1})])]),default:h(()=>[t(`div`,me,[t(`div`,he,[t(`div`,ge,v(i(E)(R.value.total.total_tokens)),1),n[7]||=t(`div`,{class:`usage-label`},`总 Token`,-1)]),t(`div`,_e,[t(`div`,D,v(i(E)(R.value.total.input_tokens)),1),n[8]||=t(`div`,{class:`usage-label`},`输入 Token`,-1)]),t(`div`,O,[t(`div`,k,v(i(E)(R.value.total.output_tokens)),1),n[9]||=t(`div`,{class:`usage-label`},`输出 Token`,-1)]),t(`div`,A,[t(`div`,j,v(i(E)(R.value.total.cache_read_tokens)),1),n[10]||=t(`div`,{class:`usage-label`},`缓存读取`,-1)]),t(`div`,M,[t(`div`,N,v(R.value.total.cache_rate==null?`-`:R.value.total.cache_rate+`%`),1),n[11]||=t(`div`,{class:`usage-label`},`缓存命中率`,-1)]),R.value.logStorageMB==null?l(``,!0):(y(),u(`div`,P,[t(`div`,F,v(R.value.logStorageMB),1),n[12]||=t(`div`,{class:`usage-label`},`数据目录占用 (MB)`,-1)]))]),R.value.models?.length?(y(),u(`div`,I,[n[13]||=t(`div`,{class:`section-title`},`模型用量`,-1),s(d,{data:R.value.models,border:``,stripe:``,class:`full-width`},{default:h(()=>[s(c,{prop:`model`,label:`模型`,"min-width":`200`},{default:h(({row:e})=>[t(`span`,{class:`copy-text`,onClick:t=>$(e.model)},v(e.model),9,L)]),_:1}),s(c,{label:`调用次数`,width:`90`,align:`center`},{default:h(({row:e})=>[r(v(e.calls),1)]),_:1}),s(c,{label:`平均耗时`,width:`110`,align:`center`},{default:h(({row:e})=>[r(v(i(oe)(e.avg_duration)),1)]),_:1}),s(c,{label:`总 Token`,width:`140`,align:`center`},{default:h(({row:e})=>[r(v(i(E)(e.total_tokens)),1)]),_:1}),s(c,{label:`输入 Token`,width:`140`,align:`center`},{default:h(({row:e})=>[r(v(i(E)(e.input_tokens)),1)]),_:1}),s(c,{label:`输出 Token`,width:`140`,align:`center`},{default:h(({row:e})=>[r(v(i(E)(e.output_tokens)),1)]),_:1}),s(c,{label:`缓存读取`,width:`130`,align:`center`},{default:h(({row:e})=>[r(v(i(E)(e.cache_read_tokens)),1)]),_:1}),s(c,{label:`缓存命中率`,width:`110`,align:`center`},{default:h(({row:e})=>[r(v(e.cache_rate==null?`-`:e.cache_rate+`%`),1)]),_:1})]),_:1},8,[`data`])])):(y(),g(C,{key:1,description:`暂无调用记录`,"image-size":60}))]),_:1})):l(``,!0)])}}}),[[`__scopeId`,`data-v-e5f81c6f`]]);export{R as default};
@@ -1 +1 @@
1
- import{A as e,Et as t,F as n,Ft as r,Gt as i,H as a,I as o,L as s,M as c,N as l,Q as u,T as d,at as f,d as p,gt as m,ht as h,i as g,j as _,k as v,l as y,n as b,o as x,ot as S,pn as C,tt as w,u as T,ut as E,yt as D}from"./router-DjNMXIJT.js";import{m as O}from"./message-D5w4qfHY.js";import{t as k}from"./index-DRdS47Vm.js";import{n as A,t as j}from"./useManagerApi-COiXXii9.js";import{t as M}from"./useToast-DKg4wBxA.js";import{n as N,t as P}from"./version-quqH5oMc.js";a(),i(),D();var F={class:`check-body`},I={class:`version-row`},L={class:`version-row`},R={class:`muted-sm`},z=p(s({__name:`UpdateCheckDialog`,props:{modelValue:{type:Boolean,default:!1},modelModifiers:{}},emits:[`update:modelValue`],setup(i){let{toast:a}=M(),s=E(i,`modelValue`),u=t(!1),p=t(!1),g=t(null),v=t(null),y=t(null),b=t(null),x=null,T=()=>x?.abort(),D=e=>new Date(e).toLocaleString(),O=e=>`https://www.npmjs.com/package/ezllm/v/${e}`,z=async()=>{v.value=null,y.value=null,b.value=null,u.value=!0;try{let e=await j.update();if(g.value=e,!e.supported)return;v.value=await j.checkUpdate()}catch(e){y.value=e.message||`检查更新失败`}finally{u.value=!1}},B=async()=>{if(!v.value)return;let e=v.value.latestVersion;try{await k.confirm(`确定更新到 ${e}?服务会自动重启并在启动时安装新版本:重启期间管理台短暂不可访问(预计 1~3 分钟),更新完成后自动恢复。`,`更新确认`,{confirmButtonText:`立即更新`,cancelButtonText:`取消`,type:`warning`})}catch{return}p.value=!0;let t=new AbortController;x=t;try{let e=await j.update(),n=await j.applyUpdate();if(!n.updated){b.value=`已是最新版本 ${n.toVersion}`,v.value={hasUpdate:!1,currentVersion:n.toVersion,latestVersion:n.toVersion,checkedAt:Date.now()};return}b.value=`正在重启服务并安装 ${n.toVersion}(预计 1~3 分钟):重启期间管理台短暂不可访问,完成后这里会自动显示结果。`;let r=await A({targetVersion:n.toVersion,sinceAppliedAt:e.lastApply?.appliedAt??null,signal:t.signal});if(t.signal.aborted)return;r&&!r.error?(a(`已更新到 ${r.toVersion}`,`success`),b.value=`已更新 ${r.fromVersion} → ${r.toVersion},服务已在新版本上运行。`,v.value={hasUpdate:!1,currentVersion:r.toVersion,latestVersion:r.toVersion,checkedAt:Date.now()}):r?.error?(a(`更新失败:${r.error}`,`error`),b.value=N(r.error)):b.value=`更新仍在进行(安装耗时较长):稍后刷新页面查看最新状态。`}catch(e){a(e.message||`更新失败`,`error`)}finally{p.value=!1,x===t&&(x=null)}};return(t,i)=>{let a=f(`el-alert`),x=f(`el-tag`),E=f(`el-link`),k=f(`el-button`),A=f(`el-dialog`),j=S(`loading`);return w(),_(A,{modelValue:s.value,"onUpdate:modelValue":i[2]||=e=>s.value=e,title:`检查更新`,width:`420px`,"close-on-click-modal":!p.value,onOpen:z,onClose:T},{footer:h(()=>[v.value?.hasUpdate?(w(),_(k,{key:0,type:`primary`,loading:p.value,onClick:B},{default:h(()=>[n(C(p.value?`更新中...`:`立即更新`),1)]),_:1},8,[`loading`])):c(``,!0),o(k,{onClick:i[1]||=e=>s.value=!1},{default:h(()=>[...i[11]||=[n(`关闭`,-1)]]),_:1})]),default:h(()=>[m((w(),l(`div`,F,[g.value&&!g.value.supported?(w(),_(a,{key:0,title:`当前为源码运行模式,更新功能不可用`,type:`info`,"show-icon":``,closable:!1},{default:h(()=>[...i[3]||=[e(`div`,{class:`alert-hint`},[n(` 仅 npm 全局安装的包支持自动更新。本地开发用仓库里的 `),e(`code`,null,`pnpm upgrade:local`),n(` 更新运行中的服务。 `)],-1)]]),_:1})):v.value?(w(),l(d,{key:1},[e(`div`,I,[i[5]||=e(`span`,{class:`version-label`},`当前版本`,-1),o(x,{size:`small`},{default:h(()=>[n(C(v.value.currentVersion),1)]),_:1}),r(P)(v.value.currentVersion)?(w(),_(x,{key:0,type:`warning`,size:`small`,"disable-transitions":``},{default:h(()=>[...i[4]||=[n(`本地构建`,-1)]]),_:1})):c(``,!0)]),e(`div`,L,[i[6]||=e(`span`,{class:`version-label`},`最新版本`,-1),o(x,{type:v.value.hasUpdate?`warning`:`success`,size:`small`,"disable-transitions":``},{default:h(()=>[n(C(v.value.latestVersion),1)]),_:1},8,[`type`])]),o(a,{title:v.value.hasUpdate?`发现新版本 ${v.value.latestVersion}(当前 ${v.value.currentVersion})`:`已是最新版本 ${v.value.currentVersion}`,type:v.value.hasUpdate?`warning`:`success`,"show-icon":``,closable:!1,class:`mt-12`},null,8,[`title`,`type`]),e(`div`,R,[n(` 检查时间:`+C(D(v.value.checkedAt))+` `,1),v.value.hasUpdate?(w(),l(d,{key:0},[i[8]||=n(` · `,-1),o(E,{type:`primary`,href:O(v.value.latestVersion),target:`_blank`},{default:h(()=>[...i[7]||=[n(`在 npm 查看该版本`,-1)]]),_:1},8,[`href`])],64)):c(``,!0)])],64)):!u.value&&y.value?(w(),_(a,{key:2,title:y.value,type:`error`,"show-icon":``,closable:!1},{default:h(()=>[i[10]||=e(`div`,{class:`alert-hint`},` 检查需访问 npm 源,可能是网络不可达或版本号解析失败;稍后重试即可。 `,-1),o(k,{size:`small`,class:`mt-8`,onClick:z},{default:h(()=>[...i[9]||=[n(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):c(``,!0)])),[[j,u.value]]),b.value?(w(),_(a,{key:0,title:b.value,type:`info`,"show-icon":``,class:`mt-12`,onClose:i[0]||=e=>b.value=null},null,8,[`title`])):c(``,!0)]),_:1},8,[`modelValue`,`close-on-click-modal`])}}}),[[`__scopeId`,`data-v-ea1efda8`]]);a(),i(),D();var B={class:`layout`},V={class:`header-nav`},H={class:`header-actions`},U={class:`main`},W={class:`page`},G=s({__name:`LayoutView`,setup(i){let a=y(),s=T(),d=t(!1);u(()=>{b()});let p=v(()=>g.value?.authRequired!==!1),m=v(()=>{if(!p.value)return`免登录`;let e=localStorage.getItem(`webui_token`)||``;try{return atob(e).split(`:`)[0]||`用户`}catch{return`用户`}}),S=v(()=>{let e=a.path;return e===`/`?`/`:e.startsWith(`/providers`)?`/providers`:e.startsWith(`/service-status`)?`/service-status`:e.startsWith(`/settings`)?`/settings`:e.startsWith(`/logs`)?`/logs`:`/`}),E=()=>{x(),s.push(`/login`)},D=e=>{e===`logout`&&E(),e===`check-update`&&(d.value=!0)};return(t,i)=>{let a=f(`el-menu-item`),s=f(`el-menu`),u=f(`el-icon`),g=f(`el-button`),v=f(`el-dropdown-item`),y=f(`el-dropdown-menu`),b=f(`el-dropdown`),x=f(`el-header`),T=f(`router-view`);return w(),l(`div`,B,[o(x,{class:`header`},{default:h(()=>[i[8]||=e(`h1`,null,[e(`span`,null,`E`),n(`ZLLM`)],-1),e(`nav`,V,[o(s,{mode:`horizontal`,"default-active":S.value,ellipsis:!1,router:``,class:`header-menu`},{default:h(()=>[o(a,{index:`/`},{default:h(()=>[...i[1]||=[n(`首页`,-1)]]),_:1}),o(a,{index:`/providers`},{default:h(()=>[...i[2]||=[n(`供应商管理`,-1)]]),_:1}),o(a,{index:`/service-status`},{default:h(()=>[...i[3]||=[n(`服务状态`,-1)]]),_:1}),o(a,{index:`/logs`},{default:h(()=>[...i[4]||=[n(`日志`,-1)]]),_:1}),o(a,{index:`/settings`},{default:h(()=>[...i[5]||=[n(`设置`,-1)]]),_:1})]),_:1},8,[`default-active`])]),e(`div`,H,[o(b,{trigger:`click`,onCommand:D,class:`ml-12`},{dropdown:h(()=>[o(y,null,{default:h(()=>[o(v,{command:`check-update`},{default:h(()=>[...i[6]||=[n(`检查更新`,-1)]]),_:1}),p.value?(w(),_(v,{key:0,command:`logout`,divided:``},{default:h(()=>[...i[7]||=[n(`退出`,-1)]]),_:1})):c(``,!0)]),_:1})]),default:h(()=>[o(g,null,{default:h(()=>[n(C(m.value)+` `,1),o(u,{class:`el-icon--right`},{default:h(()=>[o(r(O))]),_:1})]),_:1})]),_:1})])]),_:1}),e(`main`,U,[e(`div`,W,[o(T)])]),o(z,{modelValue:d.value,"onUpdate:modelValue":i[0]||=e=>d.value=e},null,8,[`modelValue`])])}}});export{G as default};
1
+ import{A as e,Et as t,F as n,Ft as r,Gt as i,H as a,I as o,L as s,M as c,N as l,Q as u,T as d,at as f,d as p,gt as m,ht as h,i as g,j as _,k as v,l as y,n as b,o as x,ot as S,pn as C,tt as w,u as T,ut as E,yt as D}from"./router-BqkHX02I.js";import{m as O}from"./message-CEBSyhlN.js";import{t as k}from"./index-C6wv4UB9.js";import{n as A,t as j}from"./useManagerApi-fVrRDZkc.js";import{t as M}from"./useToast-DJwG-33L.js";import{n as N,t as P}from"./version-quqH5oMc.js";a(),i(),D();var F={class:`check-body`},I={class:`version-row`},L={class:`version-row`},R={class:`muted-sm`},z=p(s({__name:`UpdateCheckDialog`,props:{modelValue:{type:Boolean,default:!1},modelModifiers:{}},emits:[`update:modelValue`],setup(i){let{toast:a}=M(),s=E(i,`modelValue`),u=t(!1),p=t(!1),g=t(null),v=t(null),y=t(null),b=t(null),x=null,T=()=>x?.abort(),D=e=>new Date(e).toLocaleString(),O=e=>`https://www.npmjs.com/package/ezllm/v/${e}`,z=async()=>{v.value=null,y.value=null,b.value=null,u.value=!0;try{let e=await j.update();if(g.value=e,!e.supported)return;v.value=await j.checkUpdate()}catch(e){y.value=e.message||`检查更新失败`}finally{u.value=!1}},B=async()=>{if(!v.value)return;let e=v.value.latestVersion;try{await k.confirm(`确定更新到 ${e}?服务会自动重启并在启动时安装新版本:重启期间管理台短暂不可访问(预计 1~3 分钟),更新完成后自动恢复。`,`更新确认`,{confirmButtonText:`立即更新`,cancelButtonText:`取消`,type:`warning`})}catch{return}p.value=!0;let t=new AbortController;x=t;try{let e=await j.update(),n=await j.applyUpdate();if(!n.updated){b.value=`已是最新版本 ${n.toVersion}`,v.value={hasUpdate:!1,currentVersion:n.toVersion,latestVersion:n.toVersion,checkedAt:Date.now()};return}b.value=`正在重启服务并安装 ${n.toVersion}(预计 1~3 分钟):重启期间管理台短暂不可访问,完成后这里会自动显示结果。`;let r=await A({targetVersion:n.toVersion,sinceAppliedAt:e.lastApply?.appliedAt??null,signal:t.signal});if(t.signal.aborted)return;r&&!r.error?(a(`已更新到 ${r.toVersion}`,`success`),b.value=`已更新 ${r.fromVersion} → ${r.toVersion},服务已在新版本上运行。`,v.value={hasUpdate:!1,currentVersion:r.toVersion,latestVersion:r.toVersion,checkedAt:Date.now()}):r?.error?(a(`更新失败:${r.error}`,`error`),b.value=N(r.error)):b.value=`更新仍在进行(安装耗时较长):稍后刷新页面查看最新状态。`}catch(e){a(e.message||`更新失败`,`error`)}finally{p.value=!1,x===t&&(x=null)}};return(t,i)=>{let a=f(`el-alert`),x=f(`el-tag`),E=f(`el-link`),k=f(`el-button`),A=f(`el-dialog`),j=S(`loading`);return w(),_(A,{modelValue:s.value,"onUpdate:modelValue":i[2]||=e=>s.value=e,title:`检查更新`,width:`420px`,"close-on-click-modal":!p.value,onOpen:z,onClose:T},{footer:h(()=>[v.value?.hasUpdate?(w(),_(k,{key:0,type:`primary`,loading:p.value,onClick:B},{default:h(()=>[n(C(p.value?`更新中...`:`立即更新`),1)]),_:1},8,[`loading`])):c(``,!0),o(k,{onClick:i[1]||=e=>s.value=!1},{default:h(()=>[...i[11]||=[n(`关闭`,-1)]]),_:1})]),default:h(()=>[m((w(),l(`div`,F,[g.value&&!g.value.supported?(w(),_(a,{key:0,title:`当前为源码运行模式,更新功能不可用`,type:`info`,"show-icon":``,closable:!1},{default:h(()=>[...i[3]||=[e(`div`,{class:`alert-hint`},[n(` 仅 npm 全局安装的包支持自动更新。本地开发用仓库里的 `),e(`code`,null,`pnpm upgrade:local`),n(` 更新运行中的服务。 `)],-1)]]),_:1})):v.value?(w(),l(d,{key:1},[e(`div`,I,[i[5]||=e(`span`,{class:`version-label`},`当前版本`,-1),o(x,{size:`small`},{default:h(()=>[n(C(v.value.currentVersion),1)]),_:1}),r(P)(v.value.currentVersion)?(w(),_(x,{key:0,type:`warning`,size:`small`,"disable-transitions":``},{default:h(()=>[...i[4]||=[n(`本地构建`,-1)]]),_:1})):c(``,!0)]),e(`div`,L,[i[6]||=e(`span`,{class:`version-label`},`最新版本`,-1),o(x,{type:v.value.hasUpdate?`warning`:`success`,size:`small`,"disable-transitions":``},{default:h(()=>[n(C(v.value.latestVersion),1)]),_:1},8,[`type`])]),o(a,{title:v.value.hasUpdate?`发现新版本 ${v.value.latestVersion}(当前 ${v.value.currentVersion})`:`已是最新版本 ${v.value.currentVersion}`,type:v.value.hasUpdate?`warning`:`success`,"show-icon":``,closable:!1,class:`mt-12`},null,8,[`title`,`type`]),e(`div`,R,[n(` 检查时间:`+C(D(v.value.checkedAt))+` `,1),v.value.hasUpdate?(w(),l(d,{key:0},[i[8]||=n(` · `,-1),o(E,{type:`primary`,href:O(v.value.latestVersion),target:`_blank`},{default:h(()=>[...i[7]||=[n(`在 npm 查看该版本`,-1)]]),_:1},8,[`href`])],64)):c(``,!0)])],64)):!u.value&&y.value?(w(),_(a,{key:2,title:y.value,type:`error`,"show-icon":``,closable:!1},{default:h(()=>[i[10]||=e(`div`,{class:`alert-hint`},` 检查需访问 npm 源,可能是网络不可达或版本号解析失败;稍后重试即可。 `,-1),o(k,{size:`small`,class:`mt-8`,onClick:z},{default:h(()=>[...i[9]||=[n(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):c(``,!0)])),[[j,u.value]]),b.value?(w(),_(a,{key:0,title:b.value,type:`info`,"show-icon":``,class:`mt-12`,onClose:i[0]||=e=>b.value=null},null,8,[`title`])):c(``,!0)]),_:1},8,[`modelValue`,`close-on-click-modal`])}}}),[[`__scopeId`,`data-v-ea1efda8`]]);a(),i(),D();var B={class:`layout`},V={class:`header-nav`},H={class:`header-actions`},U={class:`main`},W={class:`page`},G=s({__name:`LayoutView`,setup(i){let a=y(),s=T(),d=t(!1);u(()=>{b()});let p=v(()=>g.value?.authRequired!==!1),m=v(()=>{if(!p.value)return`免登录`;let e=localStorage.getItem(`webui_token`)||``;try{return atob(e).split(`:`)[0]||`用户`}catch{return`用户`}}),S=v(()=>{let e=a.path;return e===`/`?`/`:e.startsWith(`/providers`)?`/providers`:e.startsWith(`/service-status`)?`/service-status`:e.startsWith(`/settings`)?`/settings`:e.startsWith(`/logs`)?`/logs`:`/`}),E=()=>{x(),s.push(`/login`)},D=e=>{e===`logout`&&E(),e===`check-update`&&(d.value=!0)};return(t,i)=>{let a=f(`el-menu-item`),s=f(`el-menu`),u=f(`el-icon`),g=f(`el-button`),v=f(`el-dropdown-item`),y=f(`el-dropdown-menu`),b=f(`el-dropdown`),x=f(`el-header`),T=f(`router-view`);return w(),l(`div`,B,[o(x,{class:`header`},{default:h(()=>[i[8]||=e(`h1`,null,[e(`span`,null,`E`),n(`ZLLM`)],-1),e(`nav`,V,[o(s,{mode:`horizontal`,"default-active":S.value,ellipsis:!1,router:``,class:`header-menu`},{default:h(()=>[o(a,{index:`/`},{default:h(()=>[...i[1]||=[n(`首页`,-1)]]),_:1}),o(a,{index:`/providers`},{default:h(()=>[...i[2]||=[n(`供应商管理`,-1)]]),_:1}),o(a,{index:`/service-status`},{default:h(()=>[...i[3]||=[n(`服务状态`,-1)]]),_:1}),o(a,{index:`/logs`},{default:h(()=>[...i[4]||=[n(`日志`,-1)]]),_:1}),o(a,{index:`/settings`},{default:h(()=>[...i[5]||=[n(`设置`,-1)]]),_:1})]),_:1},8,[`default-active`])]),e(`div`,H,[o(b,{trigger:`click`,onCommand:D,class:`ml-12`},{dropdown:h(()=>[o(y,null,{default:h(()=>[o(v,{command:`check-update`},{default:h(()=>[...i[6]||=[n(`检查更新`,-1)]]),_:1}),p.value?(w(),_(v,{key:0,command:`logout`,divided:``},{default:h(()=>[...i[7]||=[n(`退出`,-1)]]),_:1})):c(``,!0)]),_:1})]),default:h(()=>[o(g,null,{default:h(()=>[n(C(m.value)+` `,1),o(u,{class:`el-icon--right`},{default:h(()=>[o(r(O))]),_:1})]),_:1})]),_:1})])]),_:1}),e(`main`,U,[e(`div`,W,[o(T)])]),o(z,{modelValue:d.value,"onUpdate:modelValue":i[0]||=e=>d.value=e},null,8,[`modelValue`])])}}});export{G as default};
@@ -1 +1 @@
1
- import{A as e,C as t,Et as n,F as r,H as i,I as a,L as o,M as s,N as c,S as l,a as u,at as d,c as f,d as p,h as m,ht as h,j as g,tt as _,u as v,yt as y}from"./router-DjNMXIJT.js";i(),m(),y();var b={class:`login-wrap`},x=p(o({__name:`LoginView`,setup(i){let o=v(),p=n(``),m=n(``),y=n(``),x=n(!1);async function S(){if(!p.value){y.value=`请输入用户名`;return}if(!m.value){y.value=`请输入密码`;return}x.value=!0,y.value=``;try{let e=await u.post(`/api/auth/login`,{username:p.value.trim(),password:m.value});f(e.token),o.push(`/`)}catch(e){y.value=e.message||`登录失败`}finally{x.value=!1}}return(n,i)=>{let o=d(`el-input`),u=d(`el-form-item`),f=d(`el-alert`),v=d(`el-button`),C=d(`el-form`),w=d(`el-card`);return _(),c(`div`,b,[a(w,{shadow:`hover`,class:`login-card`},{default:h(()=>[i[3]||=e(`h2`,{class:`login-title`},`EZLLM`,-1),a(C,{"label-position":`top`,onSubmit:t(S,[`prevent`])},{default:h(()=>[a(u,{label:`用户名`},{default:h(()=>[a(o,{modelValue:p.value,"onUpdate:modelValue":i[0]||=e=>p.value=e,placeholder:`请输入用户名`,onKeyup:l(S,[`enter`])},null,8,[`modelValue`])]),_:1}),a(u,{label:`密码`},{default:h(()=>[a(o,{modelValue:m.value,"onUpdate:modelValue":i[1]||=e=>m.value=e,type:`password`,placeholder:`请输入密码`,"show-password":``,onKeyup:l(S,[`enter`])},null,8,[`modelValue`])]),_:1}),y.value?(_(),g(f,{key:0,title:y.value,type:`error`,"show-icon":``,class:`mb-16`},null,8,[`title`])):s(``,!0),a(v,{type:`primary`,class:`full-width`,loading:x.value,onClick:S},{default:h(()=>[...i[2]||=[r(` 登录 `,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})])}}}),[[`__scopeId`,`data-v-c8c59fbd`]]);export{x as default};
1
+ import{A as e,C as t,Et as n,F as r,H as i,I as a,L as o,M as s,N as c,S as l,a as u,at as d,c as f,d as p,h as m,ht as h,j as g,tt as _,u as v,yt as y}from"./router-BqkHX02I.js";i(),m(),y();var b={class:`login-wrap`},x=p(o({__name:`LoginView`,setup(i){let o=v(),p=n(``),m=n(``),y=n(``),x=n(!1);async function S(){if(!p.value){y.value=`请输入用户名`;return}if(!m.value){y.value=`请输入密码`;return}x.value=!0,y.value=``;try{let e=await u.post(`/api/auth/login`,{username:p.value.trim(),password:m.value});f(e.token),o.push(`/`)}catch(e){y.value=e.message||`登录失败`}finally{x.value=!1}}return(n,i)=>{let o=d(`el-input`),u=d(`el-form-item`),f=d(`el-alert`),v=d(`el-button`),C=d(`el-form`),w=d(`el-card`);return _(),c(`div`,b,[a(w,{shadow:`hover`,class:`login-card`},{default:h(()=>[i[3]||=e(`h2`,{class:`login-title`},`EZLLM`,-1),a(C,{"label-position":`top`,onSubmit:t(S,[`prevent`])},{default:h(()=>[a(u,{label:`用户名`},{default:h(()=>[a(o,{modelValue:p.value,"onUpdate:modelValue":i[0]||=e=>p.value=e,placeholder:`请输入用户名`,onKeyup:l(S,[`enter`])},null,8,[`modelValue`])]),_:1}),a(u,{label:`密码`},{default:h(()=>[a(o,{modelValue:m.value,"onUpdate:modelValue":i[1]||=e=>m.value=e,type:`password`,placeholder:`请输入密码`,"show-password":``,onKeyup:l(S,[`enter`])},null,8,[`modelValue`])]),_:1}),y.value?(_(),g(f,{key:0,title:y.value,type:`error`,"show-icon":``,class:`mb-16`},null,8,[`title`])):s(``,!0),a(v,{type:`primary`,class:`full-width`,loading:x.value,onClick:S},{default:h(()=>[...i[2]||=[r(` 登录 `,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})])}}}),[[`__scopeId`,`data-v-c8c59fbd`]]);export{x as default};
@@ -1,4 +1,4 @@
1
- import{A as e,Et as t,F as n,Ft as r,Gt as i,H as a,I as o,L as s,M as c,N as l,Q as u,S as d,T as f,a as p,at as m,d as h,gt as ee,h as g,ht as _,j as v,k as y,ot as b,pn as x,rt as S,tt as C,yt as w}from"./router-DjNMXIJT.js";import{t as T}from"./index-DRdS47Vm.js";import{t as E}from"./providers-BM4fqFJi.js";import{t as D}from"./useToast-DKg4wBxA.js";import{t as O}from"./useLoadError-qYTOtUW0.js";import{t as k}from"./latestRequest-Dk21IcrH.js";import{t as A}from"./clipboard-CorU9X8e.js";import{c as j,l as M,n as N,o as P,r as te,s as F}from"./format-DrbP4_cY.js";a(),w(),i();var I={key:1,class:`detail-content`},L={key:0,class:`ip-link`},R={class:`ip-pop`},z={class:`ip-pop-row`},B={key:0,class:`ip-pop-row`},V={key:1,class:`ip-pop-error`},H={key:2},U=[`href`],W={key:1},G={key:1},ne={class:`error-text`},re={class:`pane-bar`},ie={class:`json-block`},K={class:`pane-bar`},q={class:`json-block`},J={class:`pane-bar`},ae={class:`json-block`},Y={class:`pane-bar`},oe={class:`json-block`},X={class:`pane-bar`},Z={class:`json-block`},se={class:`pane-bar`},Q={class:`json-block`},ce={class:`pane-bar`},le={class:`json-block`},ue={class:`pane-bar`},de={class:`json-block`},fe=h(s({__name:`LogDetailDialog`,setup(i,{expose:a}){let{toast:s}=D(),{loadError:u,run:d}=O(),h=t(!1),g=t(null),y=t(`info`),S=t(!1),w=t(null),T=k(),E=e=>{w.value=e,h.value=!0,g.value=null,y.value=`info`;let t=T();d(async()=>{try{let n=e.id?`?id=${e.id}`:`?_file=${encodeURIComponent(e._file)}`,r=await p.get(`/api/logs/detail`+n);if(!t())return;r&&r.detail?g.value={...r.detail,...r.meta}:g.value=r}catch(e){if(!t())return;throw e}})},fe=()=>{w.value&&E(w.value)};a({open:E});let $=async e=>{await A(e)?s(`已复制到剪贴板`,`success`):s(`复制失败`,`error`)},pe=async e=>{if(!(e._ipInfo||e._ipInfoError)){S.value=!0;try{let t=await p.get(`/api/logs/ipinfo?ip=${encodeURIComponent(e.ip)}`);t.error?e._ipInfoError=t.error:e._ipInfo=t}catch{e._ipInfoError=`查询失败`}finally{S.value=!1}}},me=e=>[e.country,e.region,e.city].filter(Boolean).join(` · `)||`-`;return(t,i)=>{let a=m(`el-button`),s=m(`el-alert`),d=m(`el-descriptions-item`),p=m(`el-tag`),w=m(`el-divider`),T=m(`el-popover`),E=m(`el-descriptions`),D=m(`el-tab-pane`),O=m(`el-tabs`),k=m(`el-dialog`),A=b(`loading`);return C(),v(k,{modelValue:h.value,"onUpdate:modelValue":i[11]||=e=>h.value=e,title:`日志详情`,width:`80%`,top:`5vh`,"destroy-on-close":``},{default:_(()=>[r(u)?(C(),v(s,{key:0,title:r(u),type:`error`,"show-icon":``,closable:!1,class:`mb-16`},{default:_(()=>[o(a,{size:`small`,onClick:fe},{default:_(()=>[...i[12]||=[n(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):c(``,!0),g.value?(C(),l(`div`,I,[o(O,{modelValue:y.value,"onUpdate:modelValue":i[10]||=e=>y.value=e},{default:_(()=>[o(D,{label:`基本信息`,name:`info`},{default:_(()=>[o(E,{column:2,border:``},{default:_(()=>[o(d,{label:`时间`},{default:_(()=>[n(x(r(j)(g.value.time)),1)]),_:1}),o(d,{label:`模型`},{default:_(()=>[n(x(g.value.model),1)]),_:1}),o(d,{label:`方法`},{default:_(()=>[n(x(g.value.method),1)]),_:1}),o(d,{label:`路径`},{default:_(()=>[n(x(g.value.path),1)]),_:1}),o(d,{label:`状态码`},{default:_(()=>[n(x(g.value.status),1)]),_:1}),o(d,{label:`流式`},{default:_(()=>[o(p,{type:g.value.stream?`warning`:`info`},{default:_(()=>[n(x(g.value.stream?`是`:`否`),1)]),_:1},8,[`type`])]),_:1}),o(d,{label:`IP`},{default:_(()=>[g.value.ip?(C(),l(`span`,L,[o(T,{placement:`right`,width:`240`,trigger:`click`,onShow:i[0]||=e=>pe(g.value)},{reference:_(()=>[e(`span`,null,x(g.value.ip),1)]),default:_(()=>[ee((C(),l(`div`,R,[g.value._ipInfo?(C(),l(f,{key:0},[e(`div`,z,[i[13]||=e(`span`,{class:`ip-pop-label`},`归属地`,-1),e(`span`,null,x(me(g.value._ipInfo)),1)]),g.value._ipInfo.isp?(C(),l(`div`,B,[i[14]||=e(`span`,{class:`ip-pop-label`},`运营商`,-1),e(`span`,null,x(g.value._ipInfo.isp),1)])):c(``,!0)],64)):g.value._ipInfoError?(C(),l(`div`,V,x(g.value._ipInfoError),1)):(C(),l(`span`,H,`查询中...`)),o(w,{style:{margin:`8px 0`}}),e(`a`,{class:`ip-pop-link`,href:`https://m.ip138.com/iplookup.php?ip=${encodeURIComponent(g.value.ip)}`,target:`_blank`},`在 ip138 查看`,8,U)])),[[A,S.value]])]),_:1})])):(C(),l(`span`,W,`-`))]),_:1}),o(d,{label:`Session`},{default:_(()=>[n(x(g.value.session_id||`-`),1)]),_:1}),o(d,{label:`Request ID`},{default:_(()=>[g.value.request_id?(C(),l(`span`,{key:0,class:`request-id`,title:`点击复制(可用于关联控制台日志与检索)`,onClick:i[1]||=e=>$(g.value.request_id)},x(g.value.request_id),1)):(C(),l(`span`,G,`-`))]),_:1}),o(d,{label:`输入 Token(含缓存读取)`},{default:_(()=>[n(x(r(P)(g.value.input_tokens??0)),1)]),_:1}),o(d,{label:`输出 Token`},{default:_(()=>[n(x(r(P)(g.value.output_tokens??0)),1)]),_:1}),o(d,{label:`缓存读取`},{default:_(()=>[n(x(r(P)(g.value.cache_read_tokens??0)),1)]),_:1}),o(d,{label:`请求耗时`},{default:_(()=>[n(x(r(te)(g.value.duration)),1)]),_:1}),o(d,{label:`缓存命中率`},{default:_(()=>[n(x(r(N)(g.value)),1)]),_:1}),g.value.error?(C(),v(d,{key:0,label:`错误`,span:2},{default:_(()=>[e(`span`,ne,x(g.value.error),1)]),_:1})):c(``,!0)]),_:1}),g.value.response?.truncated?(C(),v(s,{key:0,class:`mt-16`,type:`warning`,closable:!1,title:`内容超过单请求捕获上限,已截断`})):c(``,!0)]),_:1}),g.value.response?._capture?(C(),l(f,{key:0},[o(D,{label:`客户端请求`,name:`inbound`},{default:_(()=>[e(`div`,re,[o(a,{size:`small`,class:`copy-btn`,onClick:i[2]||=e=>$(r(F)(g.value.response.inbound_request))},{default:_(()=>[...i[15]||=[n(` 复制 `,-1)]]),_:1})]),e(`pre`,ie,x(r(F)(g.value.response.inbound_request)),1)]),_:1}),o(D,{label:`转发上游的请求`,name:`upstreamReq`},{default:_(()=>[e(`div`,K,[o(a,{size:`small`,class:`copy-btn`,onClick:i[3]||=e=>$(r(F)(g.value.response.upstream_request))},{default:_(()=>[...i[16]||=[n(` 复制 `,-1)]]),_:1})]),e(`pre`,q,x(r(F)(g.value.response.upstream_request)),1)]),_:1}),g.value.response.upstream_response_text?(C(),v(D,{key:0,label:`上游响应(原始)`,name:`upstreamRes`},{default:_(()=>[e(`div`,J,[o(a,{size:`small`,class:`copy-btn`,onClick:i[4]||=e=>$(g.value.response.upstream_response_text)},{default:_(()=>[...i[17]||=[n(` 复制 `,-1)]]),_:1})]),e(`pre`,ae,x(r(M)(g.value.response.upstream_response_text)),1)]),_:1})):g.value.response.upstream_stream_frames?.length?(C(),v(D,{key:1,label:`上游响应(原始流,${g.value.response.upstream_stream_frames.length} 帧)`,name:`upstreamStream`},{default:_(()=>[e(`div`,Y,[o(a,{size:`small`,class:`copy-btn`,onClick:i[5]||=e=>$(g.value.response.upstream_stream_frames.join(`
1
+ import{A as e,Et as t,F as n,Ft as r,Gt as i,H as a,I as o,L as s,M as c,N as l,Q as u,S as d,T as f,a as p,at as m,d as h,gt as ee,h as g,ht as _,j as v,k as y,ot as b,pn as x,rt as S,tt as C,yt as w}from"./router-BqkHX02I.js";import{t as T}from"./index-C6wv4UB9.js";import{t as E}from"./providers-DkujdEZ8.js";import{t as D}from"./useToast-DJwG-33L.js";import{t as O}from"./useLoadError-Yu7YH4ZH.js";import{t as k}from"./latestRequest-Dk21IcrH.js";import{t as A}from"./clipboard-CorU9X8e.js";import{c as j,l as M,n as N,o as P,r as te,s as F}from"./format-DrbP4_cY.js";a(),w(),i();var I={key:1,class:`detail-content`},L={key:0,class:`ip-link`},R={class:`ip-pop`},z={class:`ip-pop-row`},B={key:0,class:`ip-pop-row`},V={key:1,class:`ip-pop-error`},H={key:2},U=[`href`],W={key:1},G={key:1},ne={class:`error-text`},re={class:`pane-bar`},ie={class:`json-block`},K={class:`pane-bar`},q={class:`json-block`},J={class:`pane-bar`},ae={class:`json-block`},Y={class:`pane-bar`},oe={class:`json-block`},X={class:`pane-bar`},Z={class:`json-block`},se={class:`pane-bar`},Q={class:`json-block`},ce={class:`pane-bar`},le={class:`json-block`},ue={class:`pane-bar`},de={class:`json-block`},fe=h(s({__name:`LogDetailDialog`,setup(i,{expose:a}){let{toast:s}=D(),{loadError:u,run:d}=O(),h=t(!1),g=t(null),y=t(`info`),S=t(!1),w=t(null),T=k(),E=e=>{w.value=e,h.value=!0,g.value=null,y.value=`info`;let t=T();d(async()=>{try{let n=e.id?`?id=${e.id}`:`?_file=${encodeURIComponent(e._file)}`,r=await p.get(`/api/logs/detail`+n);if(!t())return;r&&r.detail?g.value={...r.detail,...r.meta}:g.value=r}catch(e){if(!t())return;throw e}})},fe=()=>{w.value&&E(w.value)};a({open:E});let $=async e=>{await A(e)?s(`已复制到剪贴板`,`success`):s(`复制失败`,`error`)},pe=async e=>{if(!(e._ipInfo||e._ipInfoError)){S.value=!0;try{let t=await p.get(`/api/logs/ipinfo?ip=${encodeURIComponent(e.ip)}`);t.error?e._ipInfoError=t.error:e._ipInfo=t}catch{e._ipInfoError=`查询失败`}finally{S.value=!1}}},me=e=>[e.country,e.region,e.city].filter(Boolean).join(` · `)||`-`;return(t,i)=>{let a=m(`el-button`),s=m(`el-alert`),d=m(`el-descriptions-item`),p=m(`el-tag`),w=m(`el-divider`),T=m(`el-popover`),E=m(`el-descriptions`),D=m(`el-tab-pane`),O=m(`el-tabs`),k=m(`el-dialog`),A=b(`loading`);return C(),v(k,{modelValue:h.value,"onUpdate:modelValue":i[11]||=e=>h.value=e,title:`日志详情`,width:`80%`,top:`5vh`,"destroy-on-close":``},{default:_(()=>[r(u)?(C(),v(s,{key:0,title:r(u),type:`error`,"show-icon":``,closable:!1,class:`mb-16`},{default:_(()=>[o(a,{size:`small`,onClick:fe},{default:_(()=>[...i[12]||=[n(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):c(``,!0),g.value?(C(),l(`div`,I,[o(O,{modelValue:y.value,"onUpdate:modelValue":i[10]||=e=>y.value=e},{default:_(()=>[o(D,{label:`基本信息`,name:`info`},{default:_(()=>[o(E,{column:2,border:``},{default:_(()=>[o(d,{label:`时间`},{default:_(()=>[n(x(r(j)(g.value.time)),1)]),_:1}),o(d,{label:`模型`},{default:_(()=>[n(x(g.value.model),1)]),_:1}),o(d,{label:`方法`},{default:_(()=>[n(x(g.value.method),1)]),_:1}),o(d,{label:`路径`},{default:_(()=>[n(x(g.value.path),1)]),_:1}),o(d,{label:`状态码`},{default:_(()=>[n(x(g.value.status),1)]),_:1}),o(d,{label:`流式`},{default:_(()=>[o(p,{type:g.value.stream?`warning`:`info`},{default:_(()=>[n(x(g.value.stream?`是`:`否`),1)]),_:1},8,[`type`])]),_:1}),o(d,{label:`IP`},{default:_(()=>[g.value.ip?(C(),l(`span`,L,[o(T,{placement:`right`,width:`240`,trigger:`click`,onShow:i[0]||=e=>pe(g.value)},{reference:_(()=>[e(`span`,null,x(g.value.ip),1)]),default:_(()=>[ee((C(),l(`div`,R,[g.value._ipInfo?(C(),l(f,{key:0},[e(`div`,z,[i[13]||=e(`span`,{class:`ip-pop-label`},`归属地`,-1),e(`span`,null,x(me(g.value._ipInfo)),1)]),g.value._ipInfo.isp?(C(),l(`div`,B,[i[14]||=e(`span`,{class:`ip-pop-label`},`运营商`,-1),e(`span`,null,x(g.value._ipInfo.isp),1)])):c(``,!0)],64)):g.value._ipInfoError?(C(),l(`div`,V,x(g.value._ipInfoError),1)):(C(),l(`span`,H,`查询中...`)),o(w,{style:{margin:`8px 0`}}),e(`a`,{class:`ip-pop-link`,href:`https://m.ip138.com/iplookup.php?ip=${encodeURIComponent(g.value.ip)}`,target:`_blank`},`在 ip138 查看`,8,U)])),[[A,S.value]])]),_:1})])):(C(),l(`span`,W,`-`))]),_:1}),o(d,{label:`Session`},{default:_(()=>[n(x(g.value.session_id||`-`),1)]),_:1}),o(d,{label:`Request ID`},{default:_(()=>[g.value.request_id?(C(),l(`span`,{key:0,class:`request-id`,title:`点击复制(可用于关联控制台日志与检索)`,onClick:i[1]||=e=>$(g.value.request_id)},x(g.value.request_id),1)):(C(),l(`span`,G,`-`))]),_:1}),o(d,{label:`输入 Token(含缓存读取)`},{default:_(()=>[n(x(r(P)(g.value.input_tokens??0)),1)]),_:1}),o(d,{label:`输出 Token`},{default:_(()=>[n(x(r(P)(g.value.output_tokens??0)),1)]),_:1}),o(d,{label:`缓存读取`},{default:_(()=>[n(x(r(P)(g.value.cache_read_tokens??0)),1)]),_:1}),o(d,{label:`请求耗时`},{default:_(()=>[n(x(r(te)(g.value.duration)),1)]),_:1}),o(d,{label:`缓存命中率`},{default:_(()=>[n(x(r(N)(g.value)),1)]),_:1}),g.value.error?(C(),v(d,{key:0,label:`错误`,span:2},{default:_(()=>[e(`span`,ne,x(g.value.error),1)]),_:1})):c(``,!0)]),_:1}),g.value.response?.truncated?(C(),v(s,{key:0,class:`mt-16`,type:`warning`,closable:!1,title:`内容超过单请求捕获上限,已截断`})):c(``,!0)]),_:1}),g.value.response?._capture?(C(),l(f,{key:0},[o(D,{label:`客户端请求`,name:`inbound`},{default:_(()=>[e(`div`,re,[o(a,{size:`small`,class:`copy-btn`,onClick:i[2]||=e=>$(r(F)(g.value.response.inbound_request))},{default:_(()=>[...i[15]||=[n(` 复制 `,-1)]]),_:1})]),e(`pre`,ie,x(r(F)(g.value.response.inbound_request)),1)]),_:1}),o(D,{label:`转发上游的请求`,name:`upstreamReq`},{default:_(()=>[e(`div`,K,[o(a,{size:`small`,class:`copy-btn`,onClick:i[3]||=e=>$(r(F)(g.value.response.upstream_request))},{default:_(()=>[...i[16]||=[n(` 复制 `,-1)]]),_:1})]),e(`pre`,q,x(r(F)(g.value.response.upstream_request)),1)]),_:1}),g.value.response.upstream_response_text?(C(),v(D,{key:0,label:`上游响应(原始)`,name:`upstreamRes`},{default:_(()=>[e(`div`,J,[o(a,{size:`small`,class:`copy-btn`,onClick:i[4]||=e=>$(g.value.response.upstream_response_text)},{default:_(()=>[...i[17]||=[n(` 复制 `,-1)]]),_:1})]),e(`pre`,ae,x(r(M)(g.value.response.upstream_response_text)),1)]),_:1})):g.value.response.upstream_stream_frames?.length?(C(),v(D,{key:1,label:`上游响应(原始流,${g.value.response.upstream_stream_frames.length} 帧)`,name:`upstreamStream`},{default:_(()=>[e(`div`,Y,[o(a,{size:`small`,class:`copy-btn`,onClick:i[5]||=e=>$(g.value.response.upstream_stream_frames.join(`
2
2
 
3
3
  `))},{default:_(()=>[...i[18]||=[n(` 复制 `,-1)]]),_:1})]),e(`pre`,oe,x(g.value.response.upstream_stream_frames.join(`
4
4