ezllm 0.0.37 → 0.0.39

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 (45) hide show
  1. package/README.md +63 -22
  2. package/dist/cli.js +26 -28
  3. package/dist/daemon.js +10 -15
  4. package/dist/plugin-host.js +5 -0
  5. package/dist/server.js +79 -77
  6. package/dist/web-ui/assets/DashboardView-STI4Bkxw.js +1 -0
  7. package/dist/web-ui/assets/LayoutView-CQJN31vh.css +1 -0
  8. package/dist/web-ui/assets/LayoutView-DHXc1jcz.js +1 -0
  9. package/dist/web-ui/assets/{LoginView-Bzngu7qF.js → LoginView-CwH6eISd.js} +1 -1
  10. package/dist/web-ui/assets/LogsView-CGgzVbGZ.js +5 -0
  11. package/dist/web-ui/assets/{ManagedServicesView-5QT_g-5a.js → ManagedServicesView-BvzeGhcb.js} +2 -2
  12. package/dist/web-ui/assets/PluginsView-CNDVeVj8.js +2 -0
  13. package/dist/web-ui/assets/PluginsView-zyTILSF1.css +1 -0
  14. package/dist/web-ui/assets/ProvidersView-DTIx2VSi.js +14 -0
  15. package/dist/web-ui/assets/{ServiceStatusView--D1wfNAZ.js → ServiceStatusView-BvuFCgT-.js} +2 -2
  16. package/dist/web-ui/assets/SettingsView-CB_w0Y2E.css +1 -0
  17. package/dist/web-ui/assets/SettingsView-W9bjQ9ly.js +1 -0
  18. package/dist/web-ui/assets/{SetupView-DcThD5bd.js → SetupView-D5KYolFQ.js} +1 -1
  19. package/dist/web-ui/assets/{config-B8sfBKCc.js → config-CX31TfJ4.js} +1 -1
  20. package/dist/web-ui/assets/format-7jzqzMS_.js +1 -0
  21. package/dist/web-ui/assets/{index-CZeCUjra.js → index-D6ydfwRk.js} +1 -1
  22. package/dist/web-ui/assets/{message-sILMvZIb.js → message-B022-tc2.js} +1 -1
  23. package/dist/web-ui/assets/providers-C6QKdfzy.js +1 -0
  24. package/dist/web-ui/assets/{router-BUuAm5No.js → router-Daxct41S.js} +2 -2
  25. package/dist/web-ui/assets/src-DLq1gsLr.js +1 -0
  26. package/dist/web-ui/assets/updateNote-CttDt8SZ.js +1 -0
  27. package/dist/web-ui/assets/useCopyToast-0dz9T4-H.js +1 -0
  28. package/dist/web-ui/assets/useManagerApi-DdgACgsq.js +1 -0
  29. package/dist/web-ui/assets/{useQuickRangeFilter-Bg2q_vMO.js → useQuickRangeFilter-D8oTOMUp.js} +1 -1
  30. package/dist/web-ui/assets/useToast-nQZ8k6t7.js +1 -0
  31. package/dist/web-ui/index.html +3 -3
  32. package/package.json +4 -1
  33. package/dist/cpa.js +0 -15
  34. package/dist/web-ui/assets/DashboardView-BzKKC4LZ.js +0 -1
  35. package/dist/web-ui/assets/LayoutView-BLjO8ed9.css +0 -1
  36. package/dist/web-ui/assets/LayoutView-BzBSYjz8.js +0 -1
  37. package/dist/web-ui/assets/LogsView-C2cZgB5T.js +0 -5
  38. package/dist/web-ui/assets/ProvidersView-BPOzev6x.js +0 -14
  39. package/dist/web-ui/assets/SettingsView-DygfiGG8.css +0 -1
  40. package/dist/web-ui/assets/SettingsView-XXHJh2h8.js +0 -1
  41. package/dist/web-ui/assets/format-CO5kjbrg.js +0 -1
  42. package/dist/web-ui/assets/providers-dvoMyAle.js +0 -1
  43. package/dist/web-ui/assets/useManagerApi-XPGaswMw.js +0 -1
  44. package/dist/web-ui/assets/useToast-BscapoHF.js +0 -1
  45. package/dist/web-ui/assets/version-DegxwpkG.js +0 -1
@@ -1 +0,0 @@
1
- .switch-divider[data-v-fb084a91],.cpa-divider[data-v-72b7f80b]{margin:16px 0}.mono[data-v-72b7f80b]{font-family:var(--el-font-family-mono,ui-monospace, monospace)}.ml-8[data-v-72b7f80b]{margin-left:8px}.mb-16[data-v-af77e8e0]{margin-bottom:16px}.mt-8[data-v-af77e8e0]{margin-top:8px}.version-row[data-v-af77e8e0]{align-items:center;gap:8px;margin-bottom:8px;display:flex}.version-label[data-v-af77e8e0]{color:var(--el-text-color-secondary);font-size:13px}.version-actions[data-v-af77e8e0]{gap:8px;margin-top:12px;display:flex}.settings-tabs[data-v-f66f6c37] .el-tabs__content{overflow:visible}.save-bar[data-v-f66f6c37]{justify-content:center;padding:8px 0;display:flex;position:sticky;bottom:0}.login-row[data-v-f66f6c37]{flex-wrap:wrap;gap:8px;display:flex}.monitor-row[data-v-f66f6c37]{justify-content:space-between;align-items:center;gap:24px;display:flex}.monitor-title[data-v-f66f6c37]{margin-bottom:4px;font-weight:600}
@@ -1 +0,0 @@
1
- import{A as e,F as t,I as n,M as r,O as i,P as a,Pt as o,Q as s,Tt as c,V as l,Wt as u,Z as d,a as f,at as p,et as m,fn as h,ht as g,it as _,j as v,k as y,mt as b,n as x,nt as S,vt as C,w}from"./router-BUuAm5No.js";import{n as T,t as E}from"./index-CZeCUjra.js";import{n as D,t as O}from"./useManagerApi-XPGaswMw.js";import{t as k}from"./config-B8sfBKCc.js";import{C as A,h as j,w as M}from"./format-CO5kjbrg.js";import{t as N}from"./useToast-BscapoHF.js";import{n as P,t as F}from"./version-DegxwpkG.js";l(),u(),C();var I={class:`flex-between`},L={class:`muted-sm mb-8`},R={class:`switch-row`},z={class:`switch-info`},B={class:`switch-title`},V={class:`switch-row`},H={class:`switch-info`},U={class:`switch-title`},W={class:`switch-row`},G={class:`switch-info`},K={class:`switch-title`},q=E(n({__name:`HarnessSwitches`,setup(n){let{toast:i}=N(),o=c(!0),s=c(!1),l=c(null),u=c(null),f=c(null),x=c(null),S=c(`success`),C=async e=>{e||(l.value?s.value=!0:o.value=!0);try{l.value=await O.harness(),u.value=null}catch(t){e||(u.value=t.message)}finally{e||(o.value=!1,s.value=!1)}};d(()=>C(!1));let E={claudeCode:{label:`Claude Code`,file:`~/.claude/settings.json`,toggle:O.setClaudeCode},codex:{label:`Codex CLI`,file:`~/.codex/config.toml`,toggle:O.setCodex},dsh:{label:`DeepSeek Harness`,file:`~/.dsh/settings.yaml`,toggle:O.setDsh}},D=async(e,t)=>{if(!l.value||f.value)return;let n=E[e],r=t?`确定开启 ${n.label} 代理?将改写 ${n.file}(开启前自动备份,关闭时从备份还原)。`:`确定关闭 ${n.label} 代理?将从备份还原 ${n.file}(无备份时移除代理相关配置)。`;try{await T.confirm(r,t?`开启确认`:`关闭确认`,{confirmButtonText:t?`开启`:`关闭`,cancelButtonText:`取消`,type:`warning`})}catch{return}f.value=e;try{let e=await n.toggle(t);x.value=e.note??null,S.value=t?`success`:`info`,i(t?`已开启代理`:`已关闭代理`,`success`)}catch(e){i(e.message||`操作失败`,`error`)}finally{f.value=null,await C(!0)}};return(n,i)=>{let c=_(`el-button`),d=_(`el-alert`),T=_(`el-tag`),E=_(`el-switch`),O=_(`el-divider`),k=_(`el-empty`),A=_(`el-card`),j=p(`loading`);return g((m(),e(A,{shadow:`never`},{header:b(()=>[y(`div`,I,[i[7]||=y(`span`,{class:`fw-600`},`代理开关`,-1),t(c,{loading:s.value,onClick:i[0]||=e=>C(!1)},{default:b(()=>[...i[6]||=[a(`刷新`,-1)]]),_:1},8,[`loading`])])]),default:b(()=>[l.value?(m(),r(w,{key:0},[y(`div`,L,` 将本机 CLI 工具的流量一键指向本机网关(`+h(l.value.gatewayBase)+`)。 开启前自动备份原配置为 *.ellm-bak(保留最早的原始备份),关闭时从备份还原。 `,1),x.value?(m(),e(d,{key:0,title:x.value,type:S.value,"show-icon":``,class:`mb-8`,onClose:i[1]||=e=>x.value=null},null,8,[`title`,`type`])):v(``,!0),y(`div`,R,[y(`div`,z,[y(`div`,B,[i[8]||=a(` Claude Code `,-1),t(T,{type:l.value.claudeCode?`success`:`info`,size:`small`,"disable-transitions":``},{default:b(()=>[a(h(l.value.claudeCode?`已开启`:`未开启`),1)]),_:1},8,[`type`])]),i[9]||=y(`div`,{class:`switch-desc`},[a(` 写入 `),y(`code`,null,`~/.claude/settings.json`),a(` 的 env.ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN, 鉴权取网关配置的 masterKey(未设置将开启失败);其余字段原样保留,重启 Claude Code 会话后生效。 `)],-1)]),t(E,{"model-value":l.value.claudeCode,loading:f.value===`claudeCode`,onChange:i[2]||=e=>D(`claudeCode`,!!e)},null,8,[`model-value`,`loading`])]),t(O,{class:`switch-divider`}),y(`div`,V,[y(`div`,H,[y(`div`,U,[i[10]||=a(` Codex CLI `,-1),t(T,{type:l.value.codex?`success`:`info`,size:`small`,"disable-transitions":``},{default:b(()=>[a(h(l.value.codex?`已开启`:`未开启`),1)]),_:1},8,[`type`])]),i[11]||=y(`div`,{class:`switch-desc`},[a(` 写入 `),y(`code`,null,`~/.codex/config.toml`),a(` 的 model_provider 与 [model_providers.ellm-proxy] 段 (保留用户注释与排版);开启后需按提示设置环境变量 ELLM_PROXY_API_KEY 供 Codex 鉴权。 `)],-1)]),t(E,{"model-value":l.value.codex,loading:f.value===`codex`,onChange:i[3]||=e=>D(`codex`,!!e)},null,8,[`model-value`,`loading`])]),t(O,{class:`switch-divider`}),y(`div`,W,[y(`div`,G,[y(`div`,K,[i[12]||=a(` DeepSeek Harness `,-1),t(T,{type:l.value.dsh?`success`:`info`,size:`small`,"disable-transitions":``},{default:b(()=>[a(h(l.value.dsh?`已开启`:`未开启`),1)]),_:1},8,[`type`])]),i[13]||=y(`div`,{class:`switch-desc`},[a(` 写入 `),y(`code`,null,`~/.dsh/settings.yaml`),a(` 的 llm-deepseek.baseURL 与 `),y(`code`,null,`~/.dsh/.credentials.yaml`),a(` 的 DEEPSEEK_API_KEY(取网关 masterKey,未设置则写占位值);模型请在 dsh 内选择与网关部署名一致的模型。 dsh 为 developer preview,配置格式可能随版本变化。 `)],-1)]),t(E,{"model-value":l.value.dsh,loading:f.value===`dsh`,onChange:i[4]||=e=>D(`dsh`,!!e)},null,8,[`model-value`,`loading`])])],64)):(m(),e(k,{key:1,description:u.value||`暂无开关状态`},{default:b(()=>[t(c,{type:`primary`,onClick:i[5]||=e=>C(!1)},{default:b(()=>[...i[14]||=[a(`重试`,-1)]]),_:1})]),_:1},8,[`description`]))]),_:1})),[[j,o.value]])}}}),[[`__scopeId`,`data-v-fb084a91`]]);l(),u(),C();var J={class:`flex-between`},Y={class:`muted-sm mb-8`},ee={class:`switch-row`},X={class:`switch-info`},Z={class:`switch-title`},Q={class:`switch-desc`},te={class:`mono`},ne={class:`switch-row`},re=E(n({__name:`CpaSettings`,setup(n){let{toast:o}=N(),{copyWithToast:l}=A(),u=c(!0),f=c(!1),x=c(!1),S=c(null),C=c(null),E=c(null),D=c(`success`),k=c(`auto`),M=c(!1),P=i(()=>S.value?S.value.running?`运行中`:S.value.enabled?`已开启(未运行)`:`已停止`:``),F=i(()=>j(S.value?.startedAt??null)),I=i(()=>{let e=S.value?.credential;if(e){let t=typeof e.nickname==`string`?e.nickname:``,n=typeof e.enterpriseName==`string`?e.enterpriseName:``,r=e.tokenExpired===!0?`token 已过期(将自动刷新)`:`token 有效`;return`${[t,n].filter(e=>e!==``).join(` / `)||`已读取`}(${r})`}return S.value?.credentialError?S.value.credentialError:S.value?.running?`未上报`:`—`}),L=e=>Number.isInteger(e)?String(e):e.toFixed(2),R=i(()=>{let e=S.value?.usage;if(!e)return`—`;let t=typeof e.unit==`string`&&e.unit!==``?e.unit:`credits`;return`${`剩余 ${L(e.remain)} / ${L(e.total)} ${t}`}${typeof e.subscriptionName==`string`&&e.subscriptionName!==``?`(${e.subscriptionName})`:``}`}),z=async e=>{e||(S.value?f.value=!0:u.value=!0);try{S.value=await O.cpa(),C.value=null,M.value||(k.value=S.value.credSource??`auto`)}catch(t){e||(C.value=t.message)}finally{e||(u.value=!1,f.value=!1)}},B=()=>{M.value=!0},V=async()=>{S.value&&await l(S.value.baseUrl)},H=null;d(()=>{z(!1),H=window.setInterval(()=>{document.hidden||x.value||S.value===null||z(!0)},5e3)}),s(()=>{H!==null&&window.clearInterval(H)});let U=async e=>{if(!S.value||x.value)return;let t=!!e,n=t?`确定开启 CPA 代理?将在 127.0.0.1:${S.value.port} 启动常驻代理进程;若配置中缺少指向它的供应商,会自动补一条(从代理拉取模型清单)。`:`确定关闭 CPA 代理?将停止代理进程(供应商配置保留,模型请求将不可用)。`;try{await T.confirm(n,t?`开启确认`:`关闭确认`,{confirmButtonText:t?`开启`:`关闭`,cancelButtonText:`取消`,type:`warning`})}catch{return}x.value=!0;try{let e=await O.setCpaEnabled(t,t?k.value:void 0);E.value=e.note??null,D.value=t?`success`:`info`,o(t?`已开启 CPA 代理`:`已关闭 CPA 代理`,`success`)}catch(e){o(e.message||`操作失败`,`error`)}finally{x.value=!1,await z(!0)}};return(n,i)=>{let o=_(`el-button`),s=_(`el-alert`),c=_(`el-tag`),l=_(`el-switch`),d=_(`el-divider`),T=_(`el-descriptions-item`),O=_(`el-descriptions`),A=_(`el-option`),j=_(`el-select`),M=_(`el-empty`),N=_(`el-card`),L=p(`loading`);return g((m(),e(N,{shadow:`never`},{header:b(()=>[y(`div`,J,[i[5]||=y(`span`,{class:`fw-600`},`CPA 代理(CodeBuddy / WorkBuddy 订阅)`,-1),t(o,{loading:f.value,onClick:i[0]||=e=>z(!1)},{default:b(()=>[...i[4]||=[a(`刷新`,-1)]]),_:1},8,[`loading`])])]),default:b(()=>[S.value?(m(),r(w,{key:0},[y(`div`,Y,[i[6]||=a(` 通过本机 CPA 代理复用已登录的 CodeBuddy / WorkBuddy 订阅,在 `,-1),y(`code`,null,h(S.value.baseUrl),1),i[7]||=a(` 暴露 OpenAI 兼容接口,由 ezllm 主进程守护; 开启后自动补供应商配置(缺则添加),网关即可路由 cb/* 模型。 `,-1)]),E.value?(m(),e(s,{key:0,title:E.value,type:D.value,"show-icon":``,class:`mb-8`,onClose:i[1]||=e=>E.value=null},null,8,[`title`,`type`])):v(``,!0),y(`div`,ee,[y(`div`,X,[y(`div`,Z,[i[8]||=a(` 一键开启 `,-1),t(c,{type:S.value.running?`success`:`info`,size:`small`,"disable-transitions":``},{default:b(()=>[a(h(P.value),1)]),_:1},8,[`type`])]),y(`div`,Q,[i[9]||=a(` 开启后在 `,-1),y(`code`,null,`127.0.0.1:`+h(S.value.port),1),i[10]||=a(` 启动常驻代理(随 ezllm 启动自动恢复); 关闭只停进程、不删供应商配置。需先在本机完成 CodeBuddy 桌面端或 JetBrains IDE 登录。 `,-1)])]),t(l,{"model-value":S.value.enabled,loading:x.value,onChange:U},null,8,[`model-value`,`loading`])]),t(d,{class:`cpa-divider`}),t(O,{column:1,size:`small`,border:``},{default:b(()=>[t(T,{label:`运行状态`},{default:b(()=>[S.value.running?(m(),r(w,{key:0},[a(`运行中(pid `+h(S.value.pid)+`,`+h(F.value)+` 启动)`,1)],64)):(m(),r(w,{key:1},[a(`未运行`)],64))]),_:1}),t(T,{label:`接入地址`},{default:b(()=>[y(`span`,te,h(S.value.baseUrl),1),t(o,{link:``,size:`small`,class:`ml-8`,onClick:V},{default:b(()=>[...i[11]||=[a(`复制`,-1)]]),_:1})]),_:1}),t(T,{label:`上游地址`},{default:b(()=>[a(h(S.value.upstream||`—`),1)]),_:1}),t(T,{label:`登录凭据`},{default:b(()=>[a(h(I.value),1)]),_:1}),t(T,{label:`账号额度`},{default:b(()=>[a(h(R.value),1)]),_:1}),t(T,{label:`供应商配置`},{default:b(()=>[S.value.provider.configured?(m(),r(w,{key:0},[a(` 已配置(`+h(S.value.provider.name)+`,`+h(S.value.provider.modelCount)+` 个模型) `,1)],64)):S.value.provider.configError?(m(),r(w,{key:1},[a(h(S.value.provider.configError),1)],64)):(m(),r(w,{key:2},[a(`未配置(开启时自动添加)`)],64))]),_:1}),t(T,{label:`运行日志`},{default:b(()=>[a(h(S.value.logFiles.out),1)]),_:1})]),_:1}),t(d,{class:`cpa-divider`}),y(`div`,ne,[i[12]||=y(`div`,{class:`switch-info`},[y(`div`,{class:`switch-title`},`凭据来源`),y(`div`,{class:`switch-desc`},` 默认「自动」:桌面端登录文件优先、回落 JetBrains IDE 登录态;以 IDE 登录为准时可切「JetBrains 优先」。 运行中不可改(先关闭代理再调整,下次开启生效)。 `)],-1),t(j,{modelValue:k.value,"onUpdate:modelValue":i[2]||=e=>k.value=e,style:{width:`190px`},disabled:x.value||S.value.running,onChange:B},{default:b(()=>[t(A,{label:`自动`,value:`auto`}),t(A,{label:`JetBrains 优先`,value:`jetbrains`})]),_:1},8,[`modelValue`,`disabled`])])],64)):(m(),e(M,{key:1,description:C.value||`暂无 CPA 状态`},{default:b(()=>[t(o,{type:`primary`,onClick:i[3]||=e=>z(!1)},{default:b(()=>[...i[13]||=[a(`重试`,-1)]]),_:1})]),_:1},8,[`description`]))]),_:1})),[[L,u.value]])}}}),[[`__scopeId`,`data-v-72b7f80b`]]);l(),u(),C();var ie={class:`service-settings`},ae={class:`flex-between`},oe={key:0,class:`switch-row`},se={class:`switch-info`},ce={class:`switch-title`},le={key:0,class:`switch-row`},ue={class:`switch-info`},de={class:`switch-title`},fe={class:`switch-desc`},pe={key:0,class:`version-block`},me={class:`version-row`},he={class:`muted-sm`},ge={key:0,class:`muted-sm`},_e={key:1,class:`muted-sm`},ve={class:`muted-sm`},$={class:`version-actions`},ye=E(n({__name:`ServiceUpdateSettings`,setup(n){let{toast:o}=N(),l=c(!0),u=c(!1),f=c(null),x=c(null),S=c(null),C=c(null),E=c(null),k=c(!1),A=c(!1),j=c(null),M=null;s(()=>M?.abort());let I=async e=>{if(!e){if(u.value)return;u.value=!0}try{let[e,t]=await Promise.allSettled([O.autostart(),O.update()]);f.value=e.status===`fulfilled`?e.value:null,x.value=e.status===`rejected`?e.reason?.message:null,S.value=t.status===`fulfilled`?t.value:null,C.value=t.status===`rejected`?t.reason?.message:null}finally{l.value=!1,u.value=!1}};d(()=>I(!1));let L=e=>e?new Date(e).toLocaleString():``,R=i(()=>S.value?.lastCheck?.currentVersion??null),z=i(()=>F(R.value)),B=i(()=>S.value?.lastCheck?.latestVersion??null),V=i(()=>S.value?.lastCheck?.hasUpdate===!0),H=i(()=>{let e=S.value?.lastCheck?.checkedAt;return e?new Date(e).toLocaleString():null}),U=i(()=>{let e=S.value?.lastApply;if(!e)return null;let t=L(e.appliedAt);return`${e.fromVersion} → ${e.toVersion}${t?`(${t})`:``}`}),W=async e=>{if(!f.value||E.value)return;let t=e?`确定开启开机自启?将注册登录触发的自启项(登录时自动恢复服务)。`:`确定关闭开机自启?将移除自启项,且此后首次启动不再自动注册。`;try{await T.confirm(t,e?`开启确认`:`关闭确认`,{confirmButtonText:e?`开启`:`关闭`,cancelButtonText:`取消`,type:`warning`})}catch{return}E.value=`autostart`;try{let t=await O.setAutostart(e);t.note?o(t.note,`success`):o(e?`开机自启已开启`:`开机自启已关闭`,`success`)}catch(e){o(e.message||`操作失败`,`error`)}finally{E.value=null,await I(!0)}},G=async e=>{if(!S.value||E.value)return;let t=e?`确定开启自动更新?将每小时检查 npm 源,发现新版本自动安装并重启服务。`:`确定关闭自动更新?不再自动检查(手动检查更新不受影响)。`;try{await T.confirm(t,e?`开启确认`:`关闭确认`,{confirmButtonText:e?`开启`:`关闭`,cancelButtonText:`取消`,type:`warning`})}catch{return}E.value=`updateEnabled`;try{let t=await O.setUpdateEnabled(e);t.note&&o(t.note,`success`)}catch(e){o(e.message||`操作失败`,`error`)}finally{E.value=null,await I(!0)}},K=async()=>{k.value=!0;try{let e=await O.checkUpdate();j.value=e.hasUpdate?`发现新版本 ${e.latestVersion}(当前 ${e.currentVersion}):点击「立即更新」即可安装(服务自动重启,期间管理台短暂不可访问)`:`已是最新版本 ${e.currentVersion}(检查于 ${L(e.checkedAt)})`,await I(!0)}catch(e){o(e.message||`检查更新失败`,`error`)}finally{k.value=!1}},q=async()=>{try{await T.confirm(`确定更新到 ${B.value}?服务会自动重启并在启动时安装新版本:重启期间管理台短暂不可访问(预计 1~3 分钟),更新完成后自动恢复。`,`更新确认`,{confirmButtonText:`立即更新`,cancelButtonText:`取消`,type:`warning`})}catch{return}A.value=!0;let e=new AbortController;M=e;try{let t=await O.update(),n=await O.applyUpdate();if(!n.updated){j.value=`已是最新版本 ${n.toVersion}`;return}j.value=`正在重启服务并安装 ${n.toVersion}(预计 1~3 分钟):重启期间管理台短暂不可访问,完成后这里会自动显示结果。`;let r=await D({targetVersion:n.toVersion,sinceAppliedAt:t.lastApply?.appliedAt??null,signal:e.signal});if(e.signal.aborted)return;r&&!r.error?(o(`已更新到 ${r.toVersion}`,`success`),j.value=`已更新 ${r.fromVersion} → ${r.toVersion},服务已在新版本上运行。`):r?.error?(o(`更新失败:${r.error}`,`error`),j.value=P(r.error)):j.value=`更新仍在进行(安装耗时较长):稍后刷新页面查看最新状态。`}catch(e){o(e.message||`更新失败`,`error`)}finally{A.value=!1,M===e&&(M=null),e.signal.aborted||await I(!0)}};return(n,i)=>{let o=_(`el-button`),s=_(`el-tag`),c=_(`el-switch`),d=_(`el-empty`),T=_(`el-card`),D=_(`el-link`),O=_(`el-alert`),M=p(`loading`);return g((m(),r(`div`,ie,[t(T,{shadow:`never`,class:`mb-16`},{header:b(()=>[y(`div`,ae,[i[7]||=y(`span`,{class:`fw-600`},`开机自启`,-1),t(o,{loading:u.value,onClick:i[0]||=e=>I(!1)},{default:b(()=>[...i[6]||=[a(`刷新`,-1)]]),_:1},8,[`loading`])])]),default:b(()=>[f.value?(m(),r(`div`,oe,[y(`div`,se,[y(`div`,ce,[i[8]||=a(` 登录时自动启动服务 `,-1),t(s,{type:f.value.enabled?`success`:`info`,size:`small`,"disable-transitions":``},{default:b(()=>[a(h(f.value.enabled?`已开启`:`未开启`),1)]),_:1},8,[`type`])]),i[9]||=y(`div`,{class:`switch-desc`},[a(` 登录操作系统时自动启动服务(无需管理员权限)。 关闭后首次启动 `),y(`code`,null,`ezllm`),a(` 也不会再自动注册,重新开启即可解除。 `)],-1)]),t(c,{"model-value":f.value.enabled,loading:E.value===`autostart`,onChange:i[1]||=e=>W(!!e)},null,8,[`model-value`,`loading`])])):(m(),e(d,{key:1,description:x.value||`暂无自启状态`},{default:b(()=>[t(o,{type:`primary`,onClick:i[2]||=e=>I(!1)},{default:b(()=>[...i[10]||=[a(`重试`,-1)]]),_:1})]),_:1},8,[`description`]))]),_:1}),t(T,{shadow:`never`,class:`mb-16`},{header:b(()=>[...i[11]||=[y(`div`,{class:`flex-between`},[y(`span`,{class:`fw-600`},`自动更新`)],-1)]]),default:b(()=>[S.value?(m(),r(`div`,le,[y(`div`,ue,[y(`div`,de,[i[12]||=a(` 自动检查并安装新版本 `,-1),t(s,{type:S.value.enabled?`success`:`info`,size:`small`,"disable-transitions":``},{default:b(()=>[a(h(S.value.enabled?`已开启`:`未开启`),1)]),_:1},8,[`type`])]),y(`div`,fe,[S.value.supported?(m(),r(w,{key:1},[a(` 每小时检查 npm 源,发现新版本自动重启服务并在启动时安装(更新期间服务短暂中断); 关闭后仍可在下方手动检查与更新。 `)],64)):(m(),r(w,{key:0},[a(` 当前为源码运行模式,更新功能不可用(仅 npm 全局安装的包支持自动更新)。 `)],64))])]),S.value.supported?(m(),e(c,{key:0,"model-value":S.value.enabled,loading:E.value===`updateEnabled`,onChange:i[3]||=e=>G(!!e)},null,8,[`model-value`,`loading`])):v(``,!0)])):(m(),e(d,{key:1,description:C.value||`暂无更新状态`},{default:b(()=>[t(o,{type:`primary`,onClick:i[4]||=e=>I(!1)},{default:b(()=>[...i[13]||=[a(`重试`,-1)]]),_:1})]),_:1},8,[`description`]))]),_:1}),t(T,{shadow:`never`},{header:b(()=>[...i[14]||=[y(`div`,{class:`flex-between`},[y(`span`,{class:`fw-600`},`版本信息`)],-1)]]),default:b(()=>[S.value?(m(),r(`div`,pe,[y(`div`,me,[i[18]||=y(`span`,{class:`version-label`},`当前版本`,-1),t(s,{size:`small`},{default:b(()=>[a(h(R.value||`未知`),1)]),_:1}),z.value?(m(),e(s,{key:0,type:`warning`,size:`small`,"disable-transitions":``},{default:b(()=>[...i[15]||=[a(`本地构建`,-1)]]),_:1})):v(``,!0),B.value?(m(),r(w,{key:1},[i[17]||=y(`span`,{class:`version-label`},`最新版本`,-1),t(s,{type:V.value?`warning`:`success`,size:`small`,"disable-transitions":``},{default:b(()=>[a(h(B.value),1)]),_:1},8,[`type`]),V.value?(m(),e(s,{key:0,type:`warning`,size:`small`,"disable-transitions":``},{default:b(()=>[...i[16]||=[a(`发现新版本`,-1)]]),_:1})):v(``,!0)],64)):v(``,!0)]),y(`div`,he,[a(` 上次检查:`+h(H.value||`尚未检查`)+` `,1),S.value.updating?(m(),r(w,{key:0},[a(` · 更新执行中...`)],64)):v(``,!0)]),S.value.supported?v(``,!0):(m(),r(`div`,ge,`源码运行模式:手动检查与更新不可用。`)),U.value?(m(),r(`div`,_e,`上次更新:`+h(U.value),1)):v(``,!0),y(`div`,ve,[i[20]||=a(` npm 包主页:`,-1),t(D,{type:`primary`,href:`https://www.npmjs.com/package/ezllm`,target:`_blank`},{default:b(()=>[...i[19]||=[a(`npmjs.com/package/ezllm`,-1)]]),_:1})]),y(`div`,$,[t(o,{loading:k.value,disabled:!S.value.supported||S.value.updating,onClick:K},{default:b(()=>[a(h(k.value?`检查中...`:`检查更新`),1)]),_:1},8,[`loading`,`disabled`]),V.value&&S.value.supported?(m(),e(o,{key:0,type:`primary`,loading:A.value,disabled:S.value.updating,onClick:q},{default:b(()=>[a(h(A.value?`更新中...`:`立即更新到 ${B.value}`),1)]),_:1},8,[`loading`,`disabled`])):v(``,!0)]),j.value?(m(),e(O,{key:2,title:j.value,type:`info`,"show-icon":``,class:`mt-8`,onClose:i[5]||=e=>j.value=null},null,8,[`title`])):v(``,!0)])):(m(),e(d,{key:1,description:`更新状态不可用`}))]),_:1})])),[[M,l.value]])}}}),[[`__scopeId`,`data-v-af77e8e0`]]);l(),C(),u();var be={class:`flex-col-gap`},xe={class:`flex-between`},Se={class:`muted-sm`},Ce={class:`login-row`},we={class:`muted-sm`},Te={class:`flex-between`},Ee={class:`monitor-row`},De={class:`save-bar`},Oe=E(n({__name:`SettingsView`,setup(n){let{toast:s}=N(),{copyWithToast:l}=A(),u=k(),C=c(`basic`),T=c(!0),E=c(!1),D=c(``),O=async()=>{D.value&&await l(D.value)},j=c(29770),P=c(null),F=c(!0),I=c(``),L=c(``),R=i(()=>D.value.trim()!==``||I.value!==``&&L.value!==``),z=c(!1),B=c(60),V=[{label:`10分钟`,value:10},{label:`30分钟`,value:30},{label:`1小时`,value:60},{label:`2小时`,value:120},{label:`5小时`,value:300},{label:`8小时`,value:480},{label:`12小时`,value:720},{label:`24小时`,value:1440}],{loadError:H,run:U}=M(`设置加载失败,请重试`),W=()=>U(async()=>{try{await u.load(),D.value=u.config.masterKey||``,j.value=u.config.port??29770;let e=u.config[`web-ui`]?.[`login-account`];I.value=e?.username||``,L.value=e?.password||``,F.value=u.config.allowLan!==!1;let t=u.config.latencyTest;z.value=t?.enabled===!0,B.value=t?.intervalMinutes??60;try{let e=await f.get(`/api/status`);P.value=e.listenPort}catch{P.value=null}}finally{T.value=!1}}),G=async()=>{if(I.value.trim()!==``||L.value!==``){if(I.value.trim()===``||L.value===``){s(`用户名和密码需同时填写`,`error`);return}if(L.value.length<6){s(`密码至少 6 位`,`error`);return}}E.value=!0;let e=z.value&&u.config.latencyTest?.enabled!==!0;try{let t=await u.update(e=>{e.masterKey=D.value.trim(),e.port=j.value,e.allowLan=F.value,e.latencyTest={enabled:z.value,intervalMinutes:B.value},e[`web-ui`]??={},e[`web-ui`][`login-account`]??={},e[`web-ui`][`login-account`].username=I.value.trim(),e[`web-ui`][`login-account`].password=L.value});await x(!0),s(`设置已保存并生效`,`success`),e&&s(`已开启自动测试:服务将定期直连所有上游发送探测请求,有少量 token 消耗`,`warning`),t.restartHint&&s(t.restartHint,`warning`)}catch(e){s(e.message,`error`)}finally{E.value=!1}};return d(W),(n,i)=>{let s=_(`el-button`),c=_(`el-alert`),l=_(`el-tag`),u=_(`el-input`),d=_(`el-form-item`),f=_(`el-input-number`),x=_(`el-form`),k=_(`el-card`),A=_(`el-switch`),M=_(`el-divider`),N=_(`el-option`),U=_(`el-select`),K=_(`el-tab-pane`),J=_(`el-tabs`),Y=p(`loading`);return m(),e(J,{modelValue:C.value,"onUpdate:modelValue":i[7]||=e=>C.value=e,class:`settings-tabs`},{default:b(()=>[t(K,{label:`基本设置`,name:`basic`},{default:b(()=>[g((m(),r(`div`,be,[o(H)?(m(),e(c,{key:0,title:o(H),type:`error`,"show-icon":``,closable:!1},{default:b(()=>[t(s,{size:`small`,onClick:W},{default:b(()=>[...i[8]||=[a(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):v(``,!0),t(k,{shadow:`never`},{header:b(()=>[y(`div`,xe,[i[10]||=y(`span`,{class:`fw-600`},`网关访问`,-1),t(l,{type:`success`},{default:b(()=>[...i[9]||=[a(`运行中`,-1)]]),_:1})])]),default:b(()=>[t(x,{"label-position":`top`},{default:b(()=>[t(d,{label:`API 密钥 (masterKey)`},{default:b(()=>[t(u,{modelValue:D.value,"onUpdate:modelValue":i[0]||=e=>D.value=e,placeholder:`sk-1234`,clearable:``},{append:b(()=>[t(s,{onClick:O},{default:b(()=>[...i[11]||=[a(`复制`,-1)]]),_:1})]),_:1},8,[`modelValue`]),i[12]||=y(`div`,{class:`muted-sm`},`客户端请求 /v1/* 时的认证密钥(Bearer <masterKey>;留空 = 本地放行)`,-1)]),_:1}),t(d,{label:`监听端口 (port)`},{default:b(()=>[t(f,{modelValue:j.value,"onUpdate:modelValue":i[1]||=e=>j.value=e,min:1,max:65535,step:1,style:{width:`220px`}},null,8,[`modelValue`]),y(`div`,Se,[i[14]||=a(` 网关与管理台 API 的监听端口,保存后重启生效 `,-1),P.value===null?v(``,!0):(m(),r(w,{key:0},[i[13]||=a(` ;服务上报的端口 `,-1),y(`b`,null,h(P.value),1)],64))])]),_:1})]),_:1})]),_:1}),t(k,{shadow:`never`},{header:b(()=>[...i[15]||=[y(`span`,{class:`fw-600`},`安全与登录`,-1)]]),default:b(()=>[t(x,{"label-position":`top`},{default:b(()=>[t(d,{label:`管理台登录账号`},{default:b(()=>[y(`div`,Ce,[t(u,{modelValue:I.value,"onUpdate:modelValue":i[2]||=e=>I.value=e,placeholder:`用户名`,style:{width:`200px`}},null,8,[`modelValue`]),t(u,{modelValue:L.value,"onUpdate:modelValue":i[3]||=e=>L.value=e,type:`password`,placeholder:`密码(至少 6 位)`,"show-password":``,style:{width:`200px`}},null,8,[`modelValue`])]),i[16]||=y(`div`,{class:`muted-sm`},` 管理台登录凭证;留空 = 免登录直接访问(仅本机使用时推荐)。 修改后立即生效,需用新账号重新登录 `,-1)]),_:1}),t(d,{label:`允许局域网访问 (allowLan)`},{default:b(()=>[t(A,{modelValue:F.value,"onUpdate:modelValue":i[4]||=e=>F.value=e},null,8,[`modelValue`]),y(`div`,we,[i[17]||=a(` 开启 = 绑定全部网卡,局域网内设备可访问;关闭 = 仅本机 127.0.0.1。保存后需重启生效 `,-1),R.value?v(``,!0):(m(),r(w,{key:0},[a(` ;未设置任何凭据(密钥与登录账号皆无)时始终仅绑本机 `)],64))])]),_:1})]),_:1})]),_:1}),t(k,{shadow:`never`},{header:b(()=>[y(`div`,Te,[i[18]||=y(`span`,{class:`fw-600`},`模型服务监控`,-1),t(l,{type:z.value?`success`:`info`,size:`small`,"disable-transitions":``},{default:b(()=>[a(h(z.value?`已开启`:`未开启`),1)]),_:1},8,[`type`])])]),default:b(()=>[y(`div`,Ee,[i[19]||=y(`div`,{class:`monitor-info`},[y(`div`,{class:`monitor-title`},`自动测试模型服务状态`),y(`div`,{class:`muted-sm`},` 按设定间隔自动直连所有上游模型发送极小探测请求(max_tokens: 1,有少量 token 消耗); 测试结果展示在「服务状态」页面 `)],-1),t(A,{modelValue:z.value,"onUpdate:modelValue":i[5]||=e=>z.value=e},null,8,[`modelValue`])]),z.value?(m(),r(w,{key:0},[t(M,{style:{margin:`12px 0`}}),t(x,{"label-position":`top`},{default:b(()=>[t(d,{label:`测试间隔`,style:{"margin-bottom":`0`}},{default:b(()=>[t(U,{modelValue:B.value,"onUpdate:modelValue":i[6]||=e=>B.value=e,style:{width:`220px`}},{default:b(()=>[(m(),r(w,null,S(V,e=>t(N,{key:e.value,label:e.label,value:e.value},null,8,[`label`,`value`])),64))]),_:1},8,[`modelValue`]),i[20]||=y(`div`,{class:`muted-sm`},`自动测试的执行间隔,保存后即时生效(无需重启)`,-1)]),_:1})]),_:1})],64)):v(``,!0)]),_:1}),y(`div`,De,[t(s,{type:`primary`,size:`large`,loading:E.value,disabled:E.value||o(H)!==null,onClick:G},{default:b(()=>[a(h(E.value?`保存中...`:`保存设置`),1)]),_:1},8,[`loading`,`disabled`])])])),[[Y,T.value]])]),_:1}),t(K,{label:`代理开关`,name:`harness`,lazy:``},{default:b(()=>[t(q)]),_:1}),t(K,{label:`CPA`,name:`cpa`,lazy:``},{default:b(()=>[t(re)]),_:1}),t(K,{label:`服务与更新`,name:`service`,lazy:``},{default:b(()=>[t(ye)]),_:1})]),_:1},8,[`modelValue`])}}}),[[`__scopeId`,`data-v-f66f6c37`]]);export{Oe as default};
@@ -1 +0,0 @@
1
- import{Tt as e,vt as t}from"./router-BUuAm5No.js";import{t as n}from"./clipboard-JYWVdyjq.js";import{t as r}from"./useToast-BscapoHF.js";t();function i(t=`加载失败,请稍后重试`){let n=e(null);async function r(e){n.value=null;try{return await e(),!0}catch(e){return n.value=e?.message||t,!1}}return{loadError:n,run:r}}function a(){let{toast:e}=r();async function t(t,r=`已复制到剪贴板`){let i=await n(t);return i?e(r,`success`):e(`复制失败`,`error`),i}return{copyWithToast:t}}var o=e=>String(e).padStart(2,`0`),s=e=>{let t=e=>`${e.getFullYear()}-${o(e.getMonth()+1)}-${o(e.getDate())}`,n=new Date,r=new Date(n),i=n;if(e!==`today`){if(e===`yesterday`)r.setDate(n.getDate()-1),i=r;else if(e===`beforeYesterday`)r.setDate(n.getDate()-2),i=r;else if(e===`last3days`)r.setDate(n.getDate()-2);else if(e===`week`)r.setDate(n.getDate()-(n.getDay()+6)%7);else if(e===`month`)r.setDate(1);else return null}return[t(r),t(i)]},c=e=>{if(e.name)return e.name;if(e.alias)return e.alias;let t=e.apiKey||``;if(!t)return`-`;if(t.length<=8)return t;let n=Math.min(6,Math.floor(t.length/4));return`${t.substring(0,n)}...${t.substring(t.length-n)}`},l=e=>{if(!e)return null;let t=new Date(e);return Number.isNaN(t.getTime())?null:t},u=e=>{let t=l(e);return t===null?`-`:`${t.getFullYear()}-${o(t.getMonth()+1)}-${o(t.getDate())} ${o(t.getHours())}:${o(t.getMinutes())}:${o(t.getSeconds())}`},d=e=>{let t=l(e);return t===null?`-`:`${t.getFullYear()}-${o(t.getMonth()+1)}-${o(t.getDate())}`},f=e=>{let t=l(e);return t===null?`-`:`${o(t.getHours())}:${o(t.getMinutes())}:${o(t.getSeconds())}`},p=e=>e?typeof e==`string`?e:JSON.stringify(e,null,2):`-`,m=e=>{if(!e)return`-`;try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}},h=e=>{let t=e??0;return(Number.isFinite(t)?t:0).toLocaleString()},g=({protocol:e,hostname:t,port:n})=>`${e===`https:`?`https:`:`http:`}//${t}${n?`:${n}`:``}`,_=e=>e==null||e<=0?`-`:e>=1e3?(e/1e3).toFixed(2)+`s`:Math.round(e)+`ms`,v=e=>e==null?`-`:e<1e3?`${String(e)}ms`:`${(e/1e3).toFixed(1)}s`,y=e=>typeof e!=`number`||!Number.isFinite(e)||e<=0?`-`:`${e.toFixed(1)} tok/s`,b=`首 token 延迟:请求进入网关 → 上游首个响应数据块到达(仅流式请求有观测点)`,x=`生成速度:输出 token ÷ 生成窗口。流式 = 首 token 后到流结束;非流式无更细观测点,按总耗时近似`,S=e=>{let t=e.input_tokens||0,n=e.cache_read_tokens||0;return t<=0?`-`:(n/t*100).toFixed(1)+`%`},C=(e,t=262144)=>{let n=e??``;return n.length<=t?n:`${n.slice(0,t)}\n\n…(已省略 ${(n.length-t).toLocaleString()} 字符以保持流畅,完整内容请点上方「复制」按钮)`},w=e=>{let t=(e||``).trim();return t?t.length>8?`${t.slice(0,8)}…`:t:`-`},T=e=>e.client||e.user_agent||`-`,E=e=>e.user_agent?`${e.user_agent}(点击复制)`:`${e.client??`-`}(点击复制)`,D=e=>{let t=(e||``).split(`,`).map(e=>e.trim()).filter(Boolean).sort();return t.length===0?`-`:t.length<=2?t.join(`, `):`${t.slice(0,2).join(`, `)} +${t.length-2}`},O=e=>D(e),k=e=>D(e);export{a as C,C as S,O as _,T as a,s as b,f as c,v as d,h as f,m as g,u as h,S as i,d as l,p as m,b as n,E as o,y as p,g as r,k as s,x as t,_ as u,o as v,i as w,w as x,c as y};
@@ -1 +0,0 @@
1
- import{O as e,Tt as t,V as n,vt as r}from"./router-BUuAm5No.js";import{r as i}from"./index-CZeCUjra.js";import{t as a}from"./config-B8sfBKCc.js";function o(e,...t){console.warn(e,...t)}r(),n();var s=i(`providers`,()=>{let n=a(),r=e(()=>n.providers),i=e(()=>n.loading),s=e(()=>n.providerCount),c=e(()=>n.totalModelCount),l=t([]);function u(){try{let e=localStorage.getItem(`provider_order`);if(!e)return;let t=JSON.parse(e);if(!Array.isArray(t))return;l.value=t}catch{}}function d(){try{localStorage.setItem(`provider_order`,JSON.stringify(l.value))}catch(e){o(`[ezllm] 供应商卡片顺序持久化失败(本次会话内仍生效,刷新后回退):${e instanceof Error?e.message:String(e)}`)}}let f=e(()=>{let e=l.value.map(e=>r.value.find(t=>t.name===e)).filter(e=>!!e);for(let t of r.value)e.find(e=>e.name===t.name)||e.push(t);let t=e.map(e=>e.name);(t.length!==l.value.length||t.some((e,t)=>l.value[t]!==e))&&(l.value=t,d());let n=e=>e.enabled!==!1;return[...e.filter(n),...e.filter(e=>!n(e))]});function p(e){l.value=e.map(e=>e.name),d()}function m(e){l.value=[e,...l.value.filter(t=>t!==e)],d()}async function h(){u(),await n.load()}async function g(e){let t=n.config.providers;n.config.providers=e;try{return await n.save()}catch(e){throw n.config.providers=t,e}}async function _(e){return await g(r.value.filter(t=>t.name!==e.name))}async function v(e){return await g(r.value.map(t=>({...t,models:(t.models??[]).filter(t=>t.modelName!==e)})))}return{suppliers:r,loading:i,orderedProviders:f,providerCount:s,totalModelCount:c,loadProviders:h,saveProviders:g,deleteProvider:_,deleteModel:v,reorderProviders:p,moveProviderToTop:m}});export{s as t};
@@ -1 +0,0 @@
1
- import{s as e}from"./router-BUuAm5No.js";var t=`/api/service`,n=`/api/harness`,r=`${t}/managed`,i=`${t}/cpa`,a={processes:()=>e.get(`${t}/processes`).then(e=>e.processes),autostart:()=>e.get(`${t}/autostart`),setAutostart:n=>e.post(`${t}/autostart`,{enabled:n}),update:()=>e.get(`${t}/update`),setUpdateEnabled:n=>e.post(`${t}/update/enabled`,{enabled:n}),checkUpdate:()=>e.post(`${t}/update/check`),applyUpdate:()=>e.post(`${t}/update/apply`),managedServices:()=>e.get(r),createManagedService:t=>e.post(r,t),updateManagedService:(t,n)=>e.put(`${r}/${encodeURIComponent(t)}`,n),removeManagedService:t=>e.del(`${r}/${encodeURIComponent(t)}`),startManagedService:t=>e.post(`${r}/${encodeURIComponent(t)}/start`),stopManagedService:t=>e.post(`${r}/${encodeURIComponent(t)}/stop`),restartManagedService:t=>e.post(`${r}/${encodeURIComponent(t)}/restart`),cpa:()=>e.get(i),setCpaEnabled:(t,n)=>e.post(`${i}/enabled`,n===void 0?{enabled:t}:{enabled:t,credSource:n}),harness:()=>e.get(n),setClaudeCode:t=>e.post(`${n}/claude-code`,{enabled:t}),setCodex:t=>e.post(`${n}/codex`,{enabled:t}),setDsh:t=>e.post(`${n}/dsh`,{enabled:t})};async function o(e){let{targetVersion:t,sinceAppliedAt:n,timeoutMs:r=6e5,pollMs:i=3e3,signal:o}=e,s=Date.now()+r;for(;;){if(o?.aborted||Date.now()>=s||(await new Promise(e=>setTimeout(e,i)),o?.aborted))return null;let e=null;try{e=await a.update()}catch{continue}let r=e.lastApply;if(r&&r.toVersion===t&&(r.appliedAt??0)>(n??0))return r;if(Date.now()>=s)return null}}export{o as n,a as t};
@@ -1 +0,0 @@
1
- import{t as e}from"./message-sILMvZIb.js";function t(){function t(t,n=`success`){e({message:t,type:n,duration:n===`warning`?8e3:3e3})}return{toast:t}}export{t};
@@ -1 +0,0 @@
1
- function e(e){return`更新失败:${e}(更新未生效,服务仍在旧版本运行)`}function t(e){return typeof e==`string`&&/-\d{4}\.\d{1,2}\.\d{1,2}-\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(e)}export{e as n,t};