ellm-proxy 0.0.3 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -60
- package/bin/commands/autostart.js +37 -213
- package/bin/commands/config.js +8 -8
- package/bin/commands/pm2.js +28 -28
- package/bin/commands/service.js +107 -107
- package/bin/ellm-proxy.js +61 -61
- package/dist/server.js +33 -2
- package/dist/web-ui/assets/DashboardView-BHG4xIdN.css +1 -0
- package/dist/web-ui/assets/{DashboardView-B71Dy3px.js → DashboardView-C3Hw-HpG.js} +1 -1
- package/dist/web-ui/assets/LayoutView-BUA8-IzK.css +1 -0
- package/dist/web-ui/assets/LayoutView-C9phf7in.js +1 -0
- package/dist/web-ui/assets/LoginView-DrhfslIY.css +1 -0
- package/dist/web-ui/assets/LoginView-es6pSPgt.js +1 -0
- package/dist/web-ui/assets/{LogsView-B2ctrpv4.js → LogsView-BlXITEJH.js} +1 -1
- package/dist/web-ui/assets/ProcessView-Bgf7-MLQ.js +1 -0
- package/dist/web-ui/assets/ProcessView-DK0TvDpR.css +1 -0
- package/dist/web-ui/assets/{ProvidersView-mb0KU16n.js → ProvidersView-f8aZkMdw.js} +1 -1
- package/dist/web-ui/assets/SettingsView-BkwyoOUb.css +1 -0
- package/dist/web-ui/assets/SettingsView-GvAA2vLp.js +1 -0
- package/dist/web-ui/assets/config-Cs3_z63C.js +1 -0
- package/dist/web-ui/assets/{index-dPeMJjrz.js → index-GohB4JDa.js} +2 -2
- package/dist/web-ui/assets/{providers-Dmz2p4Wd.js → providers-Djtdy2tv.js} +1 -1
- package/dist/web-ui/assets/{useApi-C1Ngt_o5.js → useApi-D_iJ2Mca.js} +1 -1
- package/dist/web-ui/assets/useManagerApi-C1_COxKg.js +1 -0
- package/dist/web-ui/assets/{useToast-B3quFZyv.js → useToast-C0UMTbkx.js} +1 -1
- package/dist/web-ui/config.js +10 -10
- package/dist/web-ui/index.html +13 -13
- package/docs/DESIGN.md +99 -99
- package/manager/api/harness/backup.js +20 -20
- package/manager/api/harness/claude-code.js +54 -54
- package/manager/api/harness/codex.js +123 -123
- package/manager/api/harness/dsh.js +175 -175
- package/manager/api/harness/index.js +118 -118
- package/manager/api/service-settings.js +98 -0
- package/manager/api/service.js +3 -13
- package/manager/index.js +182 -170
- package/package.json +4 -1
- package/utils/autostart.js +219 -0
- package/utils/interpreter.js +14 -14
- package/utils/paths.js +45 -44
- package/utils/pm2.js +130 -130
- package/utils/service.js +205 -189
- package/utils/updater.js +210 -0
- package/dist/web-ui/assets/DashboardView-qrQrkgLA.css +0 -1
- package/dist/web-ui/assets/LayoutView-C03GVB3X.js +0 -1
- package/dist/web-ui/assets/LoginView-Dg0mB6ok.css +0 -1
- package/dist/web-ui/assets/LoginView-XTF5dHvd.js +0 -1
- package/dist/web-ui/assets/ProcessView-DmbVABQV.js +0 -1
- package/dist/web-ui/assets/ProcessView-DzMqBJmd.css +0 -1
- package/dist/web-ui/assets/SettingsView-BDkymsrS.css +0 -1
- package/dist/web-ui/assets/SettingsView-BgLQVC2H.js +0 -1
- package/dist/web-ui/assets/config-BRaXq0LL.js +0 -1
- package/dist/web-ui/assets/useManagerApi-BDWJz5o6.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as Y}from"./useToast-C0UMTbkx.js";import{A as ee}from"./useApi-D_iJ2Mca.js";import{u as le}from"./config-Cs3_z63C.js";import{d as F,p as X,b as q,o as m,c as B,w as n,e as $,f as s,t as h,i as D,h as u,g as a,F as H,r as d,E as W,a as c,j as G,_ as Q,l as j}from"./index-GohB4JDa.js";import{m as K}from"./useManagerApi-C1_COxKg.js";const te={class:"flex-between"},se={class:"muted-sm mb-8"},ae={class:"switch-row"},oe={class:"switch-info"},ne={class:"switch-title"},ue={class:"switch-row"},ie={class:"switch-info"},de={class:"switch-title"},re={class:"switch-row"},ce={class:"switch-info"},ve={class:"switch-title"},pe=F({__name:"HarnessSwitches",setup(J){const{toast:g}=Y(),b=d(!0),E=d(!1),v=d(null),V=d(null),i=d(null),x=d(null),_=d("success"),C=async f=>{f||(v.value?E.value=!0:b.value=!0);try{v.value=await K.harness(),V.value=null}catch(o){f||(V.value=o.message)}finally{f||(b.value=!1,E.value=!1)}};X(()=>C(!1));const S={claudeCode:{label:"Claude Code",file:"~/.claude/settings.json",toggle:K.setClaudeCode},codex:{label:"Codex CLI",file:"~/.codex/config.toml",toggle:K.setCodex},dsh:{label:"DeepSeek Harness",file:"~/.dsh/settings.yaml",toggle:K.setDsh}},k=async(f,o)=>{if(!v.value||i.value)return;const y=S[f],p=o?`确定开启 ${y.label} 代理?将改写 ${y.file}(开启前自动备份,关闭时从备份还原)。`:`确定关闭 ${y.label} 代理?将从备份还原 ${y.file}(无备份时移除代理相关配置)。`;try{await W.confirm(p,o?"开启确认":"关闭确认",{confirmButtonText:o?"开启":"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}i.value=f;try{const l=await y.toggle(o);x.value=l.note??null,_.value=o?"success":"info",g(o?"已开启代理":"已关闭代理","success")}catch(l){g(l.message||"操作失败","error")}finally{i.value=null,await C(!0)}};return(f,o)=>{const y=c("el-button"),p=c("el-alert"),l=c("el-tag"),I=c("el-switch"),z=c("el-divider"),P=c("el-empty"),R=c("el-card"),N=G("loading");return q((m(),B(R,{shadow:"never"},{header:n(()=>[s("div",te,[o[7]||(o[7]=s("span",{class:"fw-600"},"代理开关",-1)),a(y,{loading:E.value,onClick:o[0]||(o[0]=T=>C(!1))},{default:n(()=>[...o[6]||(o[6]=[u("刷新",-1)])]),_:1},8,["loading"])])]),default:n(()=>[v.value?(m(),$(H,{key:0},[s("div",se," 将本机 CLI 工具的流量一键指向 ellm-proxy 网关("+h(v.value.gatewayBase)+")。 开启前自动备份原配置为 *.ellm-bak(保留最早的原始备份),关闭时从备份还原。 ",1),x.value?(m(),B(p,{key:0,title:x.value,type:_.value,"show-icon":"",class:"mb-8",onClose:o[1]||(o[1]=T=>x.value=null)},null,8,["title","type"])):D("",!0),s("div",ae,[s("div",oe,[s("div",ne,[o[8]||(o[8]=u(" Claude Code ",-1)),a(l,{type:v.value.claudeCode?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[u(h(v.value.claudeCode?"已开启":"未开启"),1)]),_:1},8,["type"])]),o[9]||(o[9]=s("div",{class:"switch-desc"},[u(" 写入 "),s("code",null,"~/.claude/settings.json"),u(" 的 env.ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN, 鉴权取网关配置的 masterKey(未设置将开启失败);其余字段原样保留,重启 Claude Code 会话后生效。 ")],-1))]),a(I,{"model-value":v.value.claudeCode,loading:i.value==="claudeCode",onChange:o[2]||(o[2]=T=>k("claudeCode",!!T))},null,8,["model-value","loading"])]),a(z,{class:"switch-divider"}),s("div",ue,[s("div",ie,[s("div",de,[o[10]||(o[10]=u(" Codex CLI ",-1)),a(l,{type:v.value.codex?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[u(h(v.value.codex?"已开启":"未开启"),1)]),_:1},8,["type"])]),o[11]||(o[11]=s("div",{class:"switch-desc"},[u(" 写入 "),s("code",null,"~/.codex/config.toml"),u(" 的 model_provider 与 [model_providers.ellm-proxy] 段 (保留用户注释与排版);开启后需按提示设置环境变量 ELLM_PROXY_API_KEY 供 Codex 鉴权。 ")],-1))]),a(I,{"model-value":v.value.codex,loading:i.value==="codex",onChange:o[3]||(o[3]=T=>k("codex",!!T))},null,8,["model-value","loading"])]),a(z,{class:"switch-divider"}),s("div",re,[s("div",ce,[s("div",ve,[o[12]||(o[12]=u(" DeepSeek Harness ",-1)),a(l,{type:v.value.dsh?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[u(h(v.value.dsh?"已开启":"未开启"),1)]),_:1},8,["type"])]),o[13]||(o[13]=s("div",{class:"switch-desc"},[u(" 写入 "),s("code",null,"~/.dsh/settings.yaml"),u(" 的 llm-deepseek.baseURL 与 "),s("code",null,"~/.dsh/.credentials.yaml"),u(" 的 DEEPSEEK_API_KEY(取网关 masterKey,未设置则写占位值);模型请在 dsh 内选择与网关部署名一致的模型。 dsh 为 developer preview,配置格式可能随版本变化。 ")],-1))]),a(I,{"model-value":v.value.dsh,loading:i.value==="dsh",onChange:o[4]||(o[4]=T=>k("dsh",!!T))},null,8,["model-value","loading"])])],64)):(m(),B(P,{key:1,description:V.value||"暂无开关状态"},{default:n(()=>[a(y,{type:"primary",onClick:o[5]||(o[5]=T=>C(!1))},{default:n(()=>[...o[14]||(o[14]=[u("重试",-1)])]),_:1})]),_:1},8,["description"]))]),_:1})),[[N,b.value]])}}}),me=Q(pe,[["__scopeId","data-v-65e65156"]]),fe={class:"service-settings"},_e={class:"flex-between"},ge={key:0,class:"switch-row"},ye={class:"switch-info"},we={class:"switch-title"},be={key:0,class:"switch-row"},Ce={class:"switch-info"},ke={class:"switch-title"},he={class:"switch-desc"},xe={key:0,class:"version-block"},$e={class:"version-row"},Ve={class:"muted-sm"},Te={key:0,class:"muted-sm"},Ae={class:"version-actions"},Ee=F({__name:"ServiceUpdateSettings",setup(J){const{toast:g}=Y(),b=d(!0),E=d(!1),v=d(null),V=d(null),i=d(null),x=d(null),_=d(null),C=d(!1),S=d(!1),k=d(null),f=async t=>{var e,r;if(!t){if(E.value)return;E.value=!0}try{const[A,U]=await Promise.allSettled([K.autostart(),K.update()]);v.value=A.status==="fulfilled"?A.value:null,V.value=A.status==="rejected"?(e=A.reason)==null?void 0:e.message:null,i.value=U.status==="fulfilled"?U.value:null,x.value=U.status==="rejected"?(r=U.reason)==null?void 0:r.message:null}finally{b.value=!1,E.value=!1}};X(()=>f(!1));const o=j(()=>{var t,e;return((e=(t=i.value)==null?void 0:t.lastCheck)==null?void 0:e.currentVersion)??null}),y=j(()=>{var t,e;return((e=(t=i.value)==null?void 0:t.lastCheck)==null?void 0:e.latestVersion)??null}),p=j(()=>{var t,e;return((e=(t=i.value)==null?void 0:t.lastCheck)==null?void 0:e.hasUpdate)===!0}),l=j(()=>{var e,r;const t=(r=(e=i.value)==null?void 0:e.lastCheck)==null?void 0:r.checkedAt;return t?new Date(t).toLocaleString():null}),I=j(()=>{var r;const t=(r=i.value)==null?void 0:r.lastApply;if(!t)return null;const e=t.appliedAt?new Date(t.appliedAt).toLocaleString():"";return`${t.fromVersion} → ${t.toVersion}${e?`(${e})`:""}`}),z=t=>t?new Date(t).toLocaleString():"",P=async t=>{if(!v.value||_.value)return;const e=t?"确定开启开机自启?将注册登录触发的自启项(登录时自动恢复服务)。":"确定关闭开机自启?将移除自启项,且此后首次启动不再自动注册。";try{await W.confirm(e,t?"开启确认":"关闭确认",{confirmButtonText:t?"开启":"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}_.value="autostart";try{const r=await K.setAutostart(t);r.note?g(r.note,"success"):g(t?"开机自启已开启":"开机自启已关闭","success")}catch(r){g(r.message||"操作失败","error")}finally{_.value=null,await f(!0)}},R=async t=>{if(!i.value||_.value)return;const e=t?"确定开启自动更新?将每小时检查私有源,发现新版本自动安装并重启服务。":"确定关闭自动更新?不再自动检查(手动检查更新不受影响)。";try{await W.confirm(e,t?"开启确认":"关闭确认",{confirmButtonText:t?"开启":"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}_.value="updateEnabled";try{const r=await K.setUpdateEnabled(t);r.note&&g(r.note,"success")}catch(r){g(r.message||"操作失败","error")}finally{_.value=null,await f(!0)}},N=async()=>{C.value=!0;try{const t=await K.checkUpdate();k.value=t.hasUpdate?`发现新版本 ${t.latestVersion}(当前 ${t.currentVersion}),可点击「立即更新」安装`:`已是最新版本 ${t.currentVersion}(检查于 ${z(t.checkedAt)})`,await f(!0)}catch(t){g(t.message||"检查更新失败","error")}finally{C.value=!1}},T=async()=>{try{await W.confirm(`确定更新到 ${y.value}?更新期间主服务将短暂中断(停服安装后自动重启),管理服务随后自重启。`,"更新确认",{confirmButtonText:"立即更新",cancelButtonText:"取消",type:"warning"})}catch{return}S.value=!0;try{const t=await K.applyUpdate();k.value=t.note??`已更新 ${t.fromVersion} → ${t.toVersion}`,g("更新完成","success"),await f(!0)}catch(t){g(t.message||"更新失败","error"),await f(!0)}finally{S.value=!1}};return(t,e)=>{const r=c("el-button"),A=c("el-tag"),U=c("el-switch"),M=c("el-empty"),O=c("el-card"),w=c("el-alert"),Z=G("loading");return q((m(),$("div",fe,[a(O,{shadow:"never",class:"mb-16"},{header:n(()=>[s("div",_e,[e[7]||(e[7]=s("span",{class:"fw-600"},"开机自启",-1)),a(r,{loading:E.value,onClick:e[0]||(e[0]=L=>f(!1))},{default:n(()=>[...e[6]||(e[6]=[u("刷新",-1)])]),_:1},8,["loading"])])]),default:n(()=>[v.value?(m(),$("div",ge,[s("div",ye,[s("div",we,[e[8]||(e[8]=u(" 登录时自动启动服务 ",-1)),a(A,{type:v.value.enabled?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[u(h(v.value.enabled?"已开启":"未开启"),1)]),_:1},8,["type"])]),e[9]||(e[9]=s("div",{class:"switch-desc"},[u(" 登录操作系统时自动执行 "),s("code",null,"pm2 resurrect"),u(" 恢复服务(无需管理员权限)。 关闭后首次启动 "),s("code",null,"ellm-proxy"),u(" 也不会再自动注册,重新开启即可解除。 ")],-1))]),a(U,{"model-value":v.value.enabled,loading:_.value==="autostart",onChange:e[1]||(e[1]=L=>P(!!L))},null,8,["model-value","loading"])])):(m(),B(M,{key:1,description:V.value||"暂无自启状态"},{default:n(()=>[a(r,{type:"primary",onClick:e[2]||(e[2]=L=>f(!1))},{default:n(()=>[...e[10]||(e[10]=[u("重试",-1)])]),_:1})]),_:1},8,["description"]))]),_:1}),a(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(()=>[i.value?(m(),$("div",be,[s("div",Ce,[s("div",ke,[e[12]||(e[12]=u(" 自动检查并安装新版本 ",-1)),a(A,{type:i.value.enabled?"success":"info",size:"small","disable-transitions":""},{default:n(()=>[u(h(i.value.enabled?"已开启":"未开启"),1)]),_:1},8,["type"])]),s("div",he,[i.value.supported?(m(),$(H,{key:1},[u(" 每小时检查私有 npm 源,发现新版本自动安装并重启服务(更新期间服务短暂中断); 关闭后仍可在下方手动检查与更新。 ")],64)):(m(),$(H,{key:0},[u(" 当前为源码运行模式,更新功能不可用(仅 npm 全局安装的包支持自动更新)。 ")],64))])]),i.value.supported?(m(),B(U,{key:0,"model-value":i.value.enabled,loading:_.value==="updateEnabled",onChange:e[3]||(e[3]=L=>R(!!L))},null,8,["model-value","loading"])):D("",!0)])):(m(),B(M,{key:1,description:x.value||"暂无更新状态"},{default:n(()=>[a(r,{type:"primary",onClick:e[4]||(e[4]=L=>f(!1))},{default:n(()=>[...e[13]||(e[13]=[u("重试",-1)])]),_:1})]),_:1},8,["description"]))]),_:1}),a(O,{shadow:"never"},{header:n(()=>[...e[14]||(e[14]=[s("div",{class:"flex-between"},[s("span",{class:"fw-600"},"版本信息")],-1)])]),default:n(()=>[i.value?(m(),$("div",xe,[s("div",$e,[e[17]||(e[17]=s("span",{class:"version-label"},"当前版本",-1)),a(A,{size:"small"},{default:n(()=>[u(h(o.value||"未知"),1)]),_:1}),y.value?(m(),$(H,{key:0},[e[16]||(e[16]=s("span",{class:"version-label"},"最新版本",-1)),a(A,{type:p.value?"warning":"success",size:"small","disable-transitions":""},{default:n(()=>[u(h(y.value),1)]),_:1},8,["type"]),p.value?(m(),B(A,{key:0,type:"warning",size:"small","disable-transitions":""},{default:n(()=>[...e[15]||(e[15]=[u("发现新版本",-1)])]),_:1})):D("",!0)],64)):D("",!0)]),s("div",Ve,[u(" 上次检查:"+h(l.value||"尚未检查")+" ",1),i.value.updating?(m(),$(H,{key:0},[u(" · 更新执行中...")],64)):D("",!0)]),I.value?(m(),$("div",Te,"上次更新:"+h(I.value),1)):D("",!0),s("div",Ae,[a(r,{loading:C.value,disabled:i.value.updating,onClick:N},{default:n(()=>[u(h(C.value?"检查中...":"检查更新"),1)]),_:1},8,["loading","disabled"]),p.value?(m(),B(r,{key:0,type:"primary",loading:S.value,disabled:i.value.updating,onClick:T},{default:n(()=>[u(h(S.value?"更新中...":`立即更新到 ${y.value}`),1)]),_:1},8,["loading","disabled"])):D("",!0)]),k.value?(m(),B(w,{key:1,title:k.value,type:"info","show-icon":"",class:"mt-8",onClose:e[5]||(e[5]=L=>k.value=null)},null,8,["title"])):D("",!0)])):(m(),B(M,{key:1,description:"更新状态不可用"}))]),_:1})])),[[Z,b.value]])}}}),Se=Q(Ee,[["__scopeId","data-v-03f482d3"]]),Ue={class:"flex-col-gap"},Be={class:"flex-between"},Ie={class:"muted-sm"},Pe={key:0},De={class:"flex-end"},Re=F({__name:"SettingsView",setup(J){const{toast:g}=Y(),b=le(),E=d("basic"),v=d(!0),V=d(!1),i=d(""),x=d(29770),_=d(null),C=d("passthrough"),S=d(!1),k=d(""),f=d(""),o=async()=>{var p;try{await b.load(),i.value=b.config.masterKey||"",x.value=b.config.port??29770,C.value=b.config.compression==="plain"?"plain":"passthrough",S.value=b.config.logContent===!0;const l=(p=b.config["web-ui"])==null?void 0:p["login-account"];k.value=(l==null?void 0:l.username)||"",f.value=(l==null?void 0:l.password)||"";try{_.value=await ee.get("/api/status").then(I=>I.listenPort)}catch{_.value=null}}catch(l){g(l.message,"error")}finally{v.value=!1}},y=async()=>{V.value=!0;try{await b.update(p=>{var l;p.masterKey=i.value.trim(),p.port=x.value,p.compression=C.value,p.logContent=S.value,p["web-ui"]??(p["web-ui"]={}),(l=p["web-ui"])["login-account"]??(l["login-account"]={}),p["web-ui"]["login-account"].username=k.value.trim(),p["web-ui"]["login-account"].password=f.value}),g("设置已保存并生效","success")}catch(p){g(p.message,"error")}finally{V.value=!1}};return X(o),(p,l)=>{const I=c("el-tag"),z=c("el-input"),P=c("el-form-item"),R=c("el-input-number"),N=c("el-option"),T=c("el-select"),t=c("el-switch"),e=c("el-form"),r=c("el-card"),A=c("el-button"),U=c("el-tab-pane"),M=c("el-tabs"),O=G("loading");return m(),B(M,{modelValue:E.value,"onUpdate:modelValue":l[6]||(l[6]=w=>E.value=w),class:"settings-tabs"},{default:n(()=>[a(U,{label:"基本设置",name:"basic"},{default:n(()=>[q((m(),$("div",Ue,[a(r,{shadow:"never"},{header:n(()=>[s("div",Be,[l[8]||(l[8]=s("span",{class:"fw-600"},"基本设置",-1)),a(I,{type:"success"},{default:n(()=>[...l[7]||(l[7]=[u("运行中",-1)])]),_:1})])]),default:n(()=>[a(e,{"label-position":"top"},{default:n(()=>[a(P,{label:"Master Key"},{default:n(()=>[a(z,{modelValue:i.value,"onUpdate:modelValue":l[0]||(l[0]=w=>i.value=w),type:"password",placeholder:"sk-1234","show-password":""},null,8,["modelValue"]),l[9]||(l[9]=s("div",{class:"muted-sm"}," 网关 API 认证密钥(/v1/* 请求需携带 Bearer <masterKey>;留空 = 本地放行) ",-1))]),_:1}),a(P,{label:"监听端口 (port)"},{default:n(()=>[a(R,{modelValue:x.value,"onUpdate:modelValue":l[1]||(l[1]=w=>x.value=w),min:1,max:65535,step:1,class:"full-width"},null,8,["modelValue"]),s("div",Ie,[l[11]||(l[11]=u(" 网关与管理台 API 的监听端口,保存后下次重启生效 ",-1)),_.value!==null?(m(),$(H,{key:0},[l[10]||(l[10]=u(" ;当前实际监听 ",-1)),s("b",null,h(_.value),1),_.value!==x.value?(m(),$("span",Pe,"(由 PORT 环境变量覆盖,此配置值不生效)")):D("",!0)],64)):D("",!0)])]),_:1}),a(P,{label:"压缩语义 (compression)"},{default:n(()=>[a(T,{modelValue:C.value,"onUpdate:modelValue":l[2]||(l[2]=w=>C.value=w),class:"full-width"},{default:n(()=>[a(N,{label:"透传 (passthrough)",value:"passthrough"}),a(N,{label:"明文 (plain)",value:"plain"})]),_:1},8,["modelValue"]),l[12]||(l[12]=s("div",{class:"muted-sm"},"上游响应压缩处理方式,保存后热更新生效",-1))]),_:1}),a(P,{label:"日志内容捕获 (logContent)"},{default:n(()=>[a(t,{modelValue:S.value,"onUpdate:modelValue":l[3]||(l[3]=w=>S.value=w)},null,8,["modelValue"]),l[13]||(l[13]=s("div",{class:"muted-sm"}," 记录完整请求/响应(含上游原始与转换后内容)进日志详情,用于排查定位问题;开启后有少量内存与存储开销,保存后热更新生效 ",-1))]),_:1}),a(P,{label:"管理台用户名 (web-ui.login-account.username)"},{default:n(()=>[a(z,{modelValue:k.value,"onUpdate:modelValue":l[4]||(l[4]=w=>k.value=w),placeholder:"admin"},null,8,["modelValue"]),l[14]||(l[14]=s("div",{class:"muted-sm"}," 管理台登录用户名,留空回退 WEBUI_USERNAME 环境变量或默认 admin ",-1))]),_:1}),a(P,{label:"管理台密码 (web-ui.login-account.password)"},{default:n(()=>[a(z,{modelValue:f.value,"onUpdate:modelValue":l[5]||(l[5]=w=>f.value=w),type:"password",placeholder:"admin","show-password":""},null,8,["modelValue"]),l[15]||(l[15]=s("div",{class:"muted-sm"}," 管理台登录密码,留空回退 WEBUI_PASSWORD 环境变量或默认 admin;修改保存后立即生效,需用新账号重新登录 ",-1))]),_:1})]),_:1})]),_:1}),s("div",De,[a(A,{type:"primary",loading:V.value,disabled:V.value,onClick:y},{default:n(()=>[u(h(V.value?"保存中...":"保存设置"),1)]),_:1},8,["loading","disabled"])])])),[[O,v.value]])]),_:1}),a(U,{label:"代理开关",name:"harness",lazy:""},{default:n(()=>[a(me)]),_:1}),a(U,{label:"服务与更新",name:"service",lazy:""},{default:n(()=>[a(Se)]),_:1})]),_:1},8,["modelValue"])}}});export{Re as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{W as p,r as u,l as r}from"./index-GohB4JDa.js";import{A as i}from"./useApi-D_iJ2Mca.js";const m=p("config",()=>{const e=u({}),o=u(!1),t=r(()=>Array.isArray(e.value.providers)?e.value.providers:[]),l=r(()=>t.value.length),c=r(()=>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 n(){return await i.put("/api/config",e.value)}async function v(a){return a(e.value),await n()}return{config:e,loading:o,providers:t,providerCount:l,totalModelCount:c,load:f,save:n,update:v}});export{m as u};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/LayoutView-C9phf7in.js","assets/useManagerApi-C1_COxKg.js","assets/useToast-C0UMTbkx.js","assets/LayoutView-BUA8-IzK.css","assets/DashboardView-C3Hw-HpG.js","assets/useApi-D_iJ2Mca.js","assets/providers-Djtdy2tv.js","assets/config-Cs3_z63C.js","assets/DashboardView-BHG4xIdN.css","assets/ProvidersView-f8aZkMdw.js","assets/ProvidersView-Dpr8MI-p.css","assets/SettingsView-GvAA2vLp.js","assets/SettingsView-BkwyoOUb.css","assets/LogsView-BlXITEJH.js","assets/LogsView-Dl50XQhG.css","assets/ProcessView-Bgf7-MLQ.js","assets/ProcessView-DK0TvDpR.css","assets/LoginView-es6pSPgt.js","assets/LoginView-DrhfslIY.css"])))=>i.map(i=>d[i]);
|
|
2
2
|
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))a(l);new MutationObserver(l=>{for(const o of l)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&a(r)}).observe(document,{childList:!0,subtree:!0});function n(l){const o={};return l.integrity&&(o.integrity=l.integrity),l.referrerPolicy&&(o.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?o.credentials="include":l.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(l){if(l.ep)return;l.ep=!0;const o=n(l);fetch(l.href,o)}})();/**
|
|
3
3
|
* @vue/shared v3.5.42
|
|
4
4
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
@@ -93,4 +93,4 @@ usage: app.provide(ZINDEX_INJECTION_KEY, { current: 0 })`),{initialZIndex:a,curr
|
|
|
93
93
|
* vue-router v4.6.4
|
|
94
94
|
* (c) 2025 Eduardo San Martin Morote
|
|
95
95
|
* @license MIT
|
|
96
|
-
*/let ofe=()=>location.protocol+"//"+location.host;function r9(e,t){const{pathname:n,search:a,hash:l}=t,o=e.indexOf("#");if(o>-1){let r=l.includes(e.slice(o))?e.slice(o).length:1,s=l.slice(r);return s[0]!=="/"&&(s="/"+s),r3(s,"")}return r3(n,e)+a+l}function rfe(e,t,n,a){let l=[],o=[],r=null;const s=({state:p})=>{const m=r9(e,location),v=n.value,h=t.value;let g=0;if(p){if(n.value=m,t.value=p,r&&r===v){r=null;return}g=h?p.position-h.position:0}else a(m);l.forEach(y=>{y(n.value,v,{delta:g,type:j2.pop,direction:g?g>0?vp.forward:vp.back:vp.unknown})})};function u(){r=n.value}function c(p){l.push(p);const m=()=>{const v=l.indexOf(p);v>-1&&l.splice(v,1)};return o.push(m),m}function d(){if(document.visibilityState==="hidden"){const{history:p}=window;if(!p.state)return;p.replaceState(cn({},p.state,{scroll:v0()}),"")}}function f(){for(const p of o)p();o=[],window.removeEventListener("popstate",s),window.removeEventListener("pagehide",d),document.removeEventListener("visibilitychange",d)}return window.addEventListener("popstate",s),window.addEventListener("pagehide",d),document.addEventListener("visibilitychange",d),{pauseListeners:u,listen:c,destroy:f}}function d3(e,t,n,a=!1,l=!1){return{back:e,current:t,forward:n,replaced:a,position:window.history.length,scroll:l?v0():null}}function sfe(e){const{history:t,location:n}=window,a={value:r9(e,n)},l={value:t.state};l.value||o(a.value,{back:null,current:a.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function o(u,c,d){const f=e.indexOf("#"),p=f>-1?(n.host&&document.querySelector("base")?e:e.slice(f))+u:ofe()+e+u;try{t[d?"replaceState":"pushState"](c,"",p),l.value=c}catch(m){console.error(m),n[d?"replace":"assign"](p)}}function r(u,c){o(u,cn({},t.state,d3(l.value.back,u,l.value.forward,!0),c,{position:l.value.position}),!0),a.value=u}function s(u,c){const d=cn({},l.value,t.state,{forward:u,scroll:v0()});o(d.current,d,!0),o(u,cn({},d3(a.value,u,null),{position:d.position+1},c),!1),a.value=u}return{location:a,state:l,push:s,replace:r}}function ife(e){e=jde(e);const t=sfe(e),n=rfe(e,t.state,t.location,t.replace);function a(o,r=!0){r||n.pauseListeners(),history.go(o)}const l=cn({location:"",base:e,go:a,createHref:Ude.bind(null,e)},t,n);return Object.defineProperty(l,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(l,"state",{enumerable:!0,get:()=>t.state.value}),l}function ufe(e){return e=location.host?e||location.pathname+location.search:"",e.includes("#")||(e+="#"),ife(e)}let Tr=(function(e){return e[e.Static=0]="Static",e[e.Param=1]="Param",e[e.Group=2]="Group",e})({});var Wn=(function(e){return e[e.Static=0]="Static",e[e.Param=1]="Param",e[e.ParamRegExp=2]="ParamRegExp",e[e.ParamRegExpEnd=3]="ParamRegExpEnd",e[e.EscapeNext=4]="EscapeNext",e})(Wn||{});const cfe={type:Tr.Static,value:""},dfe=/[a-zA-Z0-9_]/;function ffe(e){if(!e)return[[]];if(e==="/")return[[cfe]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(m){throw new Error(`ERR (${n})/"${c}": ${m}`)}let n=Wn.Static,a=n;const l=[];let o;function r(){o&&l.push(o),o=[]}let s=0,u,c="",d="";function f(){c&&(n===Wn.Static?o.push({type:Tr.Static,value:c}):n===Wn.Param||n===Wn.ParamRegExp||n===Wn.ParamRegExpEnd?(o.length>1&&(u==="*"||u==="+")&&t(`A repeatable param (${c}) must be alone in its segment. eg: '/:ids+.`),o.push({type:Tr.Param,value:c,regexp:d,repeatable:u==="*"||u==="+",optional:u==="*"||u==="?"})):t("Invalid state to consume buffer"),c="")}function p(){c+=u}for(;s<e.length;){if(u=e[s++],u==="\\"&&n!==Wn.ParamRegExp){a=n,n=Wn.EscapeNext;continue}switch(n){case Wn.Static:u==="/"?(c&&f(),r()):u===":"?(f(),n=Wn.Param):p();break;case Wn.EscapeNext:p(),n=a;break;case Wn.Param:u==="("?n=Wn.ParamRegExp:dfe.test(u)?p():(f(),n=Wn.Static,u!=="*"&&u!=="?"&&u!=="+"&&s--);break;case Wn.ParamRegExp:u===")"?d[d.length-1]=="\\"?d=d.slice(0,-1)+u:n=Wn.ParamRegExpEnd:d+=u;break;case Wn.ParamRegExpEnd:f(),n=Wn.Static,u!=="*"&&u!=="?"&&u!=="+"&&s--,d="";break;default:t("Unknown state");break}}return n===Wn.ParamRegExp&&t(`Unfinished custom RegExp for param "${c}"`),f(),r(),l}const f3="[^/]+?",pfe={sensitive:!1,strict:!1,start:!0,end:!0};var ga=(function(e){return e[e._multiplier=10]="_multiplier",e[e.Root=90]="Root",e[e.Segment=40]="Segment",e[e.SubSegment=30]="SubSegment",e[e.Static=40]="Static",e[e.Dynamic=20]="Dynamic",e[e.BonusCustomRegExp=10]="BonusCustomRegExp",e[e.BonusWildcard=-50]="BonusWildcard",e[e.BonusRepeatable=-20]="BonusRepeatable",e[e.BonusOptional=-8]="BonusOptional",e[e.BonusStrict=.7000000000000001]="BonusStrict",e[e.BonusCaseSensitive=.25]="BonusCaseSensitive",e})(ga||{});const hfe=/[.+*?^${}()[\]/\\]/g;function vfe(e,t){const n=cn({},pfe,t),a=[];let l=n.start?"^":"";const o=[];for(const c of e){const d=c.length?[]:[ga.Root];n.strict&&!c.length&&(l+="/");for(let f=0;f<c.length;f++){const p=c[f];let m=ga.Segment+(n.sensitive?ga.BonusCaseSensitive:0);if(p.type===Tr.Static)f||(l+="/"),l+=p.value.replace(hfe,"\\$&"),m+=ga.Static;else if(p.type===Tr.Param){const{value:v,repeatable:h,optional:g,regexp:y}=p;o.push({name:v,repeatable:h,optional:g});const b=y||f3;if(b!==f3){m+=ga.BonusCustomRegExp;try{`${b}`}catch(_){throw new Error(`Invalid custom RegExp for param "${v}" (${b}): `+_.message)}}let w=h?`((?:${b})(?:/(?:${b}))*)`:`(${b})`;f||(w=g&&c.length<2?`(?:/${w})`:"/"+w),g&&(w+="?"),l+=w,m+=ga.Dynamic,g&&(m+=ga.BonusOptional),h&&(m+=ga.BonusRepeatable),b===".*"&&(m+=ga.BonusWildcard)}d.push(m)}a.push(d)}if(n.strict&&n.end){const c=a.length-1;a[c][a[c].length-1]+=ga.BonusStrict}n.strict||(l+="/?"),n.end?l+="$":n.strict&&!l.endsWith("/")&&(l+="(?:/|$)");const r=new RegExp(l,n.sensitive?"":"i");function s(c){const d=c.match(r),f={};if(!d)return null;for(let p=1;p<d.length;p++){const m=d[p]||"",v=o[p-1];f[v.name]=m&&v.repeatable?m.split("/"):m}return f}function u(c){let d="",f=!1;for(const p of e){(!f||!d.endsWith("/"))&&(d+="/"),f=!1;for(const m of p)if(m.type===Tr.Static)d+=m.value;else if(m.type===Tr.Param){const{value:v,repeatable:h,optional:g}=m,y=v in c?c[v]:"";if(Cl(y)&&!h)throw new Error(`Provided param "${v}" is an array but it is not repeatable (* or + modifiers)`);const b=Cl(y)?y.join("/"):y;if(!b)if(g)p.length<2&&(d.endsWith("/")?d=d.slice(0,-1):f=!0);else throw new Error(`Missing required param "${v}"`);d+=b}}return d||"/"}return{re:r,score:a,keys:o,parse:s,stringify:u}}function mfe(e,t){let n=0;for(;n<e.length&&n<t.length;){const a=t[n]-e[n];if(a)return a;n++}return e.length<t.length?e.length===1&&e[0]===ga.Static+ga.Segment?-1:1:e.length>t.length?t.length===1&&t[0]===ga.Static+ga.Segment?1:-1:0}function s9(e,t){let n=0;const a=e.score,l=t.score;for(;n<a.length&&n<l.length;){const o=mfe(a[n],l[n]);if(o)return o;n++}if(Math.abs(l.length-a.length)===1){if(p3(a))return 1;if(p3(l))return-1}return l.length-a.length}function p3(e){const t=e[e.length-1];return e.length>0&&t[t.length-1]<0}const gfe={strict:!1,end:!0,sensitive:!1};function yfe(e,t,n){const a=vfe(ffe(e.path),n),l=cn(a,{record:e,parent:t,children:[],alias:[]});return t&&!l.record.aliasOf==!t.record.aliasOf&&t.children.push(l),l}function bfe(e,t){const n=[],a=new Map;t=o3(gfe,t);function l(f){return a.get(f)}function o(f,p,m){const v=!m,h=v3(f);h.aliasOf=m&&m.record;const g=o3(t,f),y=[h];if("alias"in f){const _=typeof f.alias=="string"?[f.alias]:f.alias;for(const C of _)y.push(v3(cn({},h,{components:m?m.record.components:h.components,path:C,aliasOf:m?m.record:h})))}let b,w;for(const _ of y){const{path:C}=_;if(p&&C[0]!=="/"){const k=p.record.path,T=k[k.length-1]==="/"?"":"/";_.path=p.record.path+(C&&T+C)}if(b=yfe(_,p,g),m?m.alias.push(b):(w=w||b,w!==b&&w.alias.push(b),v&&f.name&&!m3(b)&&r(f.name)),i9(b)&&u(b),h.children){const k=h.children;for(let T=0;T<k.length;T++)o(k[T],b,m&&m.children[T])}m=m||b}return w?()=>{r(w)}:Zi}function r(f){if(l9(f)){const p=a.get(f);p&&(a.delete(f),n.splice(n.indexOf(p),1),p.children.forEach(r),p.alias.forEach(r))}else{const p=n.indexOf(f);p>-1&&(n.splice(p,1),f.record.name&&a.delete(f.record.name),f.children.forEach(r),f.alias.forEach(r))}}function s(){return n}function u(f){const p=Cfe(f,n);n.splice(p,0,f),f.record.name&&!m3(f)&&a.set(f.record.name,f)}function c(f,p){let m,v={},h,g;if("name"in f&&f.name){if(m=a.get(f.name),!m)throw ai(Rn.MATCHER_NOT_FOUND,{location:f});g=m.record.name,v=cn(h3(p.params,m.keys.filter(w=>!w.optional).concat(m.parent?m.parent.keys.filter(w=>w.optional):[]).map(w=>w.name)),f.params&&h3(f.params,m.keys.map(w=>w.name))),h=m.stringify(v)}else if(f.path!=null)h=f.path,m=n.find(w=>w.re.test(h)),m&&(v=m.parse(h),g=m.record.name);else{if(m=p.name?a.get(p.name):n.find(w=>w.re.test(p.path)),!m)throw ai(Rn.MATCHER_NOT_FOUND,{location:f,currentLocation:p});g=m.record.name,v=cn({},p.params,f.params),h=m.stringify(v)}const y=[];let b=m;for(;b;)y.unshift(b.record),b=b.parent;return{name:g,path:h,params:v,matched:y,meta:_fe(y)}}e.forEach(f=>o(f));function d(){n.length=0,a.clear()}return{addRoute:o,resolve:c,removeRoute:r,clearRoutes:d,getRoutes:s,getRecordMatcher:l}}function h3(e,t){const n={};for(const a of t)a in e&&(n[a]=e[a]);return n}function v3(e){const t={path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:e.aliasOf,beforeEnter:e.beforeEnter,props:wfe(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}};return Object.defineProperty(t,"mods",{value:{}}),t}function wfe(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const a in e.components)t[a]=typeof n=="object"?n[a]:n;return t}function m3(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function _fe(e){return e.reduce((t,n)=>cn(t,n.meta),{})}function Cfe(e,t){let n=0,a=t.length;for(;n!==a;){const o=n+a>>1;s9(e,t[o])<0?a=o:n=o+1}const l=Sfe(e);return l&&(a=t.lastIndexOf(l,a-1)),a}function Sfe(e){let t=e;for(;t=t.parent;)if(i9(t)&&s9(e,t)===0)return t}function i9({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function g3(e){const t=Ve(m0),n=Ve(u1),a=x(()=>{const u=i(e.to);return t.resolve(u)}),l=x(()=>{const{matched:u}=a.value,{length:c}=u,d=u[c-1],f=n.matched;if(!d||!f.length)return-1;const p=f.findIndex(ni.bind(null,d));if(p>-1)return p;const m=y3(u[c-2]);return c>1&&y3(d)===m&&f[f.length-1].path!==m?f.findIndex(ni.bind(null,u[c-2])):p}),o=x(()=>l.value>-1&&Mfe(n.params,a.value.params)),r=x(()=>l.value>-1&&l.value===n.matched.length-1&&a9(n.params,a.value.params));function s(u={}){if(Tfe(u)){const c=t[i(e.replace)?"replace":"push"](i(e.to)).catch(Zi);return e.viewTransition&&typeof document<"u"&&"startViewTransition"in document&&document.startViewTransition(()=>c),c}return Promise.resolve()}return{route:a,href:x(()=>a.value.href),isActive:o,isExactActive:r,navigate:s}}function xfe(e){return e.length===1?e[0]:e}const kfe=K({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"},viewTransition:Boolean},useLink:g3,setup(e,{slots:t}){const n=Dt(g3(e)),{options:a}=Ve(m0),l=x(()=>({[b3(e.activeClass,a.linkActiveClass,"router-link-active")]:n.isActive,[b3(e.exactActiveClass,a.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const o=t.default&&xfe(t.default(n));return e.custom?o:Ze("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:l.value},o)}}}),Efe=kfe;function Tfe(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Mfe(e,t){for(const n in t){const a=t[n],l=e[n];if(typeof a=="string"){if(a!==l)return!1}else if(!Cl(l)||l.length!==a.length||a.some((o,r)=>o.valueOf()!==l[r].valueOf()))return!1}return!0}function y3(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const b3=(e,t,n)=>e??t??n,Ofe=K({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const a=Ve(U2),l=x(()=>e.route||a.value),o=Ve(c3,0),r=x(()=>{let c=i(o);const{matched:d}=l.value;let f;for(;(f=d[c])&&!f.components;)c++;return c}),s=x(()=>l.value.matched[r.value]);St(c3,x(()=>r.value+1)),St(afe,s),St(U2,l);const u=B();return fe(()=>[u.value,s.value,e.name],([c,d,f],[p,m,v])=>{d&&(d.instances[f]=c,m&&m!==d&&c&&c===p&&(d.leaveGuards.size||(d.leaveGuards=m.leaveGuards),d.updateGuards.size||(d.updateGuards=m.updateGuards))),c&&d&&(!m||!ni(d,m)||!p)&&(d.enterCallbacks[f]||[]).forEach(h=>h(c))},{flush:"post"}),()=>{const c=l.value,d=e.name,f=s.value,p=f&&f.components[d];if(!p)return w3(n.default,{Component:p,route:c});const m=f.props[d],v=m?m===!0?c.params:typeof m=="function"?m(c):m:null,g=Ze(p,cn({},v,t,{onVnodeUnmounted:y=>{y.component.isUnmounted&&(f.instances[d]=null)},ref:u}));return w3(n.default,{Component:g,route:c})||g}}});function w3(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const Nfe=Ofe;function Afe(e){const t=bfe(e.routes,e),n=e.parseQuery||tfe,a=e.stringifyQuery||u3,l=e.history,o=Mi(),r=Mi(),s=Mi(),u=Et(Bo);let c=Bo;Ss&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const d=pp.bind(null,Q=>""+Q),f=pp.bind(null,Bde),p=pp.bind(null,Au);function m(Q,ce){let de,Y;return l9(Q)?(de=t.getRecordMatcher(Q),Y=ce):Y=Q,t.addRoute(Y,de)}function v(Q){const ce=t.getRecordMatcher(Q);ce&&t.removeRoute(ce)}function h(){return t.getRoutes().map(Q=>Q.record)}function g(Q){return!!t.getRecordMatcher(Q)}function y(Q,ce){if(ce=cn({},ce||u.value),typeof Q=="string"){const G=hp(n,Q,ce.path),se=t.resolve({path:G.path},ce),he=l.createHref(G.fullPath);return cn(G,se,{params:p(se.params),hash:Au(G.hash),redirectedFrom:void 0,href:he})}let de;if(Q.path!=null)de=cn({},Q,{path:hp(n,Q.path,ce.path).path});else{const G=cn({},Q.params);for(const se in G)G[se]==null&&delete G[se];de=cn({},Q,{params:f(G)}),ce.params=f(ce.params)}const Y=t.resolve(de,ce),Z=Q.hash||"";Y.params=d(p(Y.params));const ie=Hde(a,cn({},Q,{hash:Ide(Z),path:Y.path})),j=l.createHref(ie);return cn({fullPath:ie,hash:Z,query:a===u3?nfe(Q.query):Q.query||{}},Y,{redirectedFrom:void 0,href:j})}function b(Q){return typeof Q=="string"?hp(n,Q,u.value.path):cn({},Q)}function w(Q,ce){if(c!==Q)return ai(Rn.NAVIGATION_CANCELLED,{from:ce,to:Q})}function _(Q){return T(Q)}function C(Q){return _(cn(b(Q),{replace:!0}))}function k(Q,ce){const de=Q.matched[Q.matched.length-1];if(de&&de.redirect){const{redirect:Y}=de;let Z=typeof Y=="function"?Y(Q,ce):Y;return typeof Z=="string"&&(Z=Z.includes("?")||Z.includes("#")?Z=b(Z):{path:Z},Z.params={}),cn({query:Q.query,hash:Q.hash,params:Z.path!=null?{}:Q.params},Z)}}function T(Q,ce){const de=c=y(Q),Y=u.value,Z=Q.state,ie=Q.force,j=Q.replace===!0,G=k(de,Y);if(G)return T(cn(b(G),{state:typeof G=="object"?cn({},Z,G.state):Z,force:ie,replace:j}),ce||de);const se=de;se.redirectedFrom=ce;let he;return!ie&&Fde(a,Y,de)&&(he=ai(Rn.NAVIGATION_DUPLICATED,{to:se,from:Y}),q(Y,Y,!0,!1)),(he?Promise.resolve(he):N(se,Y)).catch(ve=>oo(ve)?oo(ve,Rn.NAVIGATION_GUARD_REDIRECT)?ve:D(ve):L(ve,se,Y)).then(ve=>{if(ve){if(oo(ve,Rn.NAVIGATION_GUARD_REDIRECT))return T(cn({replace:j},b(ve.to),{state:typeof ve.to=="object"?cn({},Z,ve.to.state):Z,force:ie}),ce||se)}else ve=$(se,Y,!0,j,Z);return R(se,Y,ve),ve})}function E(Q,ce){const de=w(Q,ce);return de?Promise.reject(de):Promise.resolve()}function M(Q){const ce=ee.values().next().value;return ce&&typeof ce.runWithContext=="function"?ce.runWithContext(Q):Q()}function N(Q,ce){let de;const[Y,Z,ie]=lfe(Q,ce);de=mp(Y.reverse(),"beforeRouteLeave",Q,ce);for(const G of Y)G.leaveGuards.forEach(se=>{de.push(Yo(se,Q,ce))});const j=E.bind(null,Q,ce);return de.push(j),le(de).then(()=>{de=[];for(const G of o.list())de.push(Yo(G,Q,ce));return de.push(j),le(de)}).then(()=>{de=mp(Z,"beforeRouteUpdate",Q,ce);for(const G of Z)G.updateGuards.forEach(se=>{de.push(Yo(se,Q,ce))});return de.push(j),le(de)}).then(()=>{de=[];for(const G of ie)if(G.beforeEnter)if(Cl(G.beforeEnter))for(const se of G.beforeEnter)de.push(Yo(se,Q,ce));else de.push(Yo(G.beforeEnter,Q,ce));return de.push(j),le(de)}).then(()=>(Q.matched.forEach(G=>G.enterCallbacks={}),de=mp(ie,"beforeRouteEnter",Q,ce,M),de.push(j),le(de))).then(()=>{de=[];for(const G of r.list())de.push(Yo(G,Q,ce));return de.push(j),le(de)}).catch(G=>oo(G,Rn.NAVIGATION_CANCELLED)?G:Promise.reject(G))}function R(Q,ce,de){s.list().forEach(Y=>M(()=>Y(Q,ce,de)))}function $(Q,ce,de,Y,Z){const ie=w(Q,ce);if(ie)return ie;const j=ce===Bo,G=Ss?history.state:{};de&&(Y||j?l.replace(Q.fullPath,cn({scroll:j&&G&&G.scroll},Z)):l.push(Q.fullPath,Z)),u.value=Q,q(Q,ce,de,j),D()}let I;function W(){I||(I=l.listen((Q,ce,de)=>{if(!ne.listening)return;const Y=y(Q),Z=k(Y,ne.currentRoute.value);if(Z){T(cn(Z,{replace:!0,force:!0}),Y).catch(Zi);return}c=Y;const ie=u.value;Ss&&Xde(i3(ie.fullPath,de.delta),v0()),N(Y,ie).catch(j=>oo(j,Rn.NAVIGATION_ABORTED|Rn.NAVIGATION_CANCELLED)?j:oo(j,Rn.NAVIGATION_GUARD_REDIRECT)?(T(cn(b(j.to),{force:!0}),Y).then(G=>{oo(G,Rn.NAVIGATION_ABORTED|Rn.NAVIGATION_DUPLICATED)&&!de.delta&&de.type===j2.pop&&l.go(-1,!1)}).catch(Zi),Promise.reject()):(de.delta&&l.go(-de.delta,!1),L(j,Y,ie))).then(j=>{j=j||$(Y,ie,!1),j&&(de.delta&&!oo(j,Rn.NAVIGATION_CANCELLED)?l.go(-de.delta,!1):de.type===j2.pop&&oo(j,Rn.NAVIGATION_ABORTED|Rn.NAVIGATION_DUPLICATED)&&l.go(-1,!1)),R(Y,ie,j)}).catch(Zi)}))}let H=Mi(),z=Mi(),V;function L(Q,ce,de){D(Q);const Y=z.list();return Y.length?Y.forEach(Z=>Z(Q,ce,de)):console.error(Q),Promise.reject(Q)}function F(){return V&&u.value!==Bo?Promise.resolve():new Promise((Q,ce)=>{H.add([Q,ce])})}function D(Q){return V||(V=!Q,W(),H.list().forEach(([ce,de])=>Q?de(Q):ce()),H.reset()),Q}function q(Q,ce,de,Y){const{scrollBehavior:Z}=e;if(!Ss||!Z)return Promise.resolve();const ie=!de&&Zde(i3(Q.fullPath,0))||(Y||!de)&&history.state&&history.state.scroll||null;return Re().then(()=>Z(Q,ce,ie)).then(j=>j&&Gde(j)).catch(j=>L(j,Q,ce))}const U=Q=>l.go(Q);let te;const ee=new Set,ne={currentRoute:u,listening:!0,addRoute:m,removeRoute:v,clearRoutes:t.clearRoutes,hasRoute:g,getRoutes:h,resolve:y,options:e,push:_,replace:C,go:U,back:()=>U(-1),forward:()=>U(1),beforeEach:o.add,beforeResolve:r.add,afterEach:s.add,onError:z.add,isReady:F,install(Q){Q.component("RouterLink",Efe),Q.component("RouterView",Nfe),Q.config.globalProperties.$router=ne,Object.defineProperty(Q.config.globalProperties,"$route",{enumerable:!0,get:()=>i(u)}),Ss&&!te&&u.value===Bo&&(te=!0,_(l.location).catch(Y=>{}));const ce={};for(const Y in Bo)Object.defineProperty(ce,Y,{get:()=>u.value[Y],enumerable:!0});Q.provide(m0,ne),Q.provide(u1,Bu(ce)),Q.provide(U2,u);const de=Q.unmount;ee.add(Q),Q.unmount=function(){ee.delete(Q),ee.size<1&&(c=Bo,I&&I(),I=null,u.value=Bo,te=!1,V=!1),de()}}};function le(Q){return Q.reduce((ce,de)=>ce.then(()=>M(de)),Promise.resolve())}return ne}function Vfe(){return Ve(m0)}function Lfe(e){return Ve(u1)}const u9=Afe({history:ufe(),routes:[{path:"/",component:()=>gr(()=>import("./LayoutView-C03GVB3X.js"),[]),children:[{path:"",name:"Dashboard",component:()=>gr(()=>import("./DashboardView-B71Dy3px.js"),__vite__mapDeps([0,1,2,3,4,5]))},{path:"providers",name:"Providers",component:()=>gr(()=>import("./ProvidersView-mb0KU16n.js"),__vite__mapDeps([6,4,2,3,1,7]))},{path:"settings",name:"Settings",component:()=>gr(()=>import("./SettingsView-BgLQVC2H.js"),__vite__mapDeps([8,4,1,3,9,10]))},{path:"logs",name:"Logs",component:()=>gr(()=>import("./LogsView-B2ctrpv4.js"),__vite__mapDeps([11,1,2,3,4,12]))},{path:"process",name:"Process",component:()=>gr(()=>import("./ProcessView-DmbVABQV.js"),__vite__mapDeps([13,9,4,14]))}]},{path:"/login",name:"Login",component:()=>gr(()=>import("./LoginView-XTF5dHvd.js"),__vite__mapDeps([15,1,16]))},{path:"/:pathMatch(.*)*",redirect:"/"}]});u9.beforeEach(e=>{const t=localStorage.getItem("webui_token");if(e.name!=="Login"&&!t)return{name:"Login"}});const yc=uu(Cde);yc.use(uk());yc.use(gde,{locale:yde});yc.use(u9);for(const[e,t]of Object.entries(lK))yc.component(e,t);yc.mount("#app");export{C8 as $,Qr as A,st as B,vo as C,ge as D,en as E,Fe as F,li as G,Qo as H,z9 as I,V9 as J,L9 as K,B9 as L,S3 as M,dt as N,Ra as O,T9 as P,Ife as Q,Rfe as R,$fe as S,to as T,Pfe as U,Dt as V,Qe as W,P as X,mD as Y,fe as Z,bde as _,J as a,rn as a0,tde as a1,eV as a2,kF as a3,m8 as a4,ov as a5,To as a6,Hce as a7,u9 as a8,O as b,A as c,K as d,bt as e,x as f,Vfe as g,Lfe as h,gt as i,ue as j,re as k,B as l,ch as m,ht as n,S as o,_t as p,sn as q,Kt as r,Ie as s,Se as t,i as u,Y2 as v,ae as w,Mn as x,$a as y,h9 as z};
|
|
96
|
+
*/let ofe=()=>location.protocol+"//"+location.host;function r9(e,t){const{pathname:n,search:a,hash:l}=t,o=e.indexOf("#");if(o>-1){let r=l.includes(e.slice(o))?e.slice(o).length:1,s=l.slice(r);return s[0]!=="/"&&(s="/"+s),r3(s,"")}return r3(n,e)+a+l}function rfe(e,t,n,a){let l=[],o=[],r=null;const s=({state:p})=>{const m=r9(e,location),v=n.value,h=t.value;let g=0;if(p){if(n.value=m,t.value=p,r&&r===v){r=null;return}g=h?p.position-h.position:0}else a(m);l.forEach(y=>{y(n.value,v,{delta:g,type:j2.pop,direction:g?g>0?vp.forward:vp.back:vp.unknown})})};function u(){r=n.value}function c(p){l.push(p);const m=()=>{const v=l.indexOf(p);v>-1&&l.splice(v,1)};return o.push(m),m}function d(){if(document.visibilityState==="hidden"){const{history:p}=window;if(!p.state)return;p.replaceState(cn({},p.state,{scroll:v0()}),"")}}function f(){for(const p of o)p();o=[],window.removeEventListener("popstate",s),window.removeEventListener("pagehide",d),document.removeEventListener("visibilitychange",d)}return window.addEventListener("popstate",s),window.addEventListener("pagehide",d),document.addEventListener("visibilitychange",d),{pauseListeners:u,listen:c,destroy:f}}function d3(e,t,n,a=!1,l=!1){return{back:e,current:t,forward:n,replaced:a,position:window.history.length,scroll:l?v0():null}}function sfe(e){const{history:t,location:n}=window,a={value:r9(e,n)},l={value:t.state};l.value||o(a.value,{back:null,current:a.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function o(u,c,d){const f=e.indexOf("#"),p=f>-1?(n.host&&document.querySelector("base")?e:e.slice(f))+u:ofe()+e+u;try{t[d?"replaceState":"pushState"](c,"",p),l.value=c}catch(m){console.error(m),n[d?"replace":"assign"](p)}}function r(u,c){o(u,cn({},t.state,d3(l.value.back,u,l.value.forward,!0),c,{position:l.value.position}),!0),a.value=u}function s(u,c){const d=cn({},l.value,t.state,{forward:u,scroll:v0()});o(d.current,d,!0),o(u,cn({},d3(a.value,u,null),{position:d.position+1},c),!1),a.value=u}return{location:a,state:l,push:s,replace:r}}function ife(e){e=jde(e);const t=sfe(e),n=rfe(e,t.state,t.location,t.replace);function a(o,r=!0){r||n.pauseListeners(),history.go(o)}const l=cn({location:"",base:e,go:a,createHref:Ude.bind(null,e)},t,n);return Object.defineProperty(l,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(l,"state",{enumerable:!0,get:()=>t.state.value}),l}function ufe(e){return e=location.host?e||location.pathname+location.search:"",e.includes("#")||(e+="#"),ife(e)}let Tr=(function(e){return e[e.Static=0]="Static",e[e.Param=1]="Param",e[e.Group=2]="Group",e})({});var Wn=(function(e){return e[e.Static=0]="Static",e[e.Param=1]="Param",e[e.ParamRegExp=2]="ParamRegExp",e[e.ParamRegExpEnd=3]="ParamRegExpEnd",e[e.EscapeNext=4]="EscapeNext",e})(Wn||{});const cfe={type:Tr.Static,value:""},dfe=/[a-zA-Z0-9_]/;function ffe(e){if(!e)return[[]];if(e==="/")return[[cfe]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(m){throw new Error(`ERR (${n})/"${c}": ${m}`)}let n=Wn.Static,a=n;const l=[];let o;function r(){o&&l.push(o),o=[]}let s=0,u,c="",d="";function f(){c&&(n===Wn.Static?o.push({type:Tr.Static,value:c}):n===Wn.Param||n===Wn.ParamRegExp||n===Wn.ParamRegExpEnd?(o.length>1&&(u==="*"||u==="+")&&t(`A repeatable param (${c}) must be alone in its segment. eg: '/:ids+.`),o.push({type:Tr.Param,value:c,regexp:d,repeatable:u==="*"||u==="+",optional:u==="*"||u==="?"})):t("Invalid state to consume buffer"),c="")}function p(){c+=u}for(;s<e.length;){if(u=e[s++],u==="\\"&&n!==Wn.ParamRegExp){a=n,n=Wn.EscapeNext;continue}switch(n){case Wn.Static:u==="/"?(c&&f(),r()):u===":"?(f(),n=Wn.Param):p();break;case Wn.EscapeNext:p(),n=a;break;case Wn.Param:u==="("?n=Wn.ParamRegExp:dfe.test(u)?p():(f(),n=Wn.Static,u!=="*"&&u!=="?"&&u!=="+"&&s--);break;case Wn.ParamRegExp:u===")"?d[d.length-1]=="\\"?d=d.slice(0,-1)+u:n=Wn.ParamRegExpEnd:d+=u;break;case Wn.ParamRegExpEnd:f(),n=Wn.Static,u!=="*"&&u!=="?"&&u!=="+"&&s--,d="";break;default:t("Unknown state");break}}return n===Wn.ParamRegExp&&t(`Unfinished custom RegExp for param "${c}"`),f(),r(),l}const f3="[^/]+?",pfe={sensitive:!1,strict:!1,start:!0,end:!0};var ga=(function(e){return e[e._multiplier=10]="_multiplier",e[e.Root=90]="Root",e[e.Segment=40]="Segment",e[e.SubSegment=30]="SubSegment",e[e.Static=40]="Static",e[e.Dynamic=20]="Dynamic",e[e.BonusCustomRegExp=10]="BonusCustomRegExp",e[e.BonusWildcard=-50]="BonusWildcard",e[e.BonusRepeatable=-20]="BonusRepeatable",e[e.BonusOptional=-8]="BonusOptional",e[e.BonusStrict=.7000000000000001]="BonusStrict",e[e.BonusCaseSensitive=.25]="BonusCaseSensitive",e})(ga||{});const hfe=/[.+*?^${}()[\]/\\]/g;function vfe(e,t){const n=cn({},pfe,t),a=[];let l=n.start?"^":"";const o=[];for(const c of e){const d=c.length?[]:[ga.Root];n.strict&&!c.length&&(l+="/");for(let f=0;f<c.length;f++){const p=c[f];let m=ga.Segment+(n.sensitive?ga.BonusCaseSensitive:0);if(p.type===Tr.Static)f||(l+="/"),l+=p.value.replace(hfe,"\\$&"),m+=ga.Static;else if(p.type===Tr.Param){const{value:v,repeatable:h,optional:g,regexp:y}=p;o.push({name:v,repeatable:h,optional:g});const b=y||f3;if(b!==f3){m+=ga.BonusCustomRegExp;try{`${b}`}catch(_){throw new Error(`Invalid custom RegExp for param "${v}" (${b}): `+_.message)}}let w=h?`((?:${b})(?:/(?:${b}))*)`:`(${b})`;f||(w=g&&c.length<2?`(?:/${w})`:"/"+w),g&&(w+="?"),l+=w,m+=ga.Dynamic,g&&(m+=ga.BonusOptional),h&&(m+=ga.BonusRepeatable),b===".*"&&(m+=ga.BonusWildcard)}d.push(m)}a.push(d)}if(n.strict&&n.end){const c=a.length-1;a[c][a[c].length-1]+=ga.BonusStrict}n.strict||(l+="/?"),n.end?l+="$":n.strict&&!l.endsWith("/")&&(l+="(?:/|$)");const r=new RegExp(l,n.sensitive?"":"i");function s(c){const d=c.match(r),f={};if(!d)return null;for(let p=1;p<d.length;p++){const m=d[p]||"",v=o[p-1];f[v.name]=m&&v.repeatable?m.split("/"):m}return f}function u(c){let d="",f=!1;for(const p of e){(!f||!d.endsWith("/"))&&(d+="/"),f=!1;for(const m of p)if(m.type===Tr.Static)d+=m.value;else if(m.type===Tr.Param){const{value:v,repeatable:h,optional:g}=m,y=v in c?c[v]:"";if(Cl(y)&&!h)throw new Error(`Provided param "${v}" is an array but it is not repeatable (* or + modifiers)`);const b=Cl(y)?y.join("/"):y;if(!b)if(g)p.length<2&&(d.endsWith("/")?d=d.slice(0,-1):f=!0);else throw new Error(`Missing required param "${v}"`);d+=b}}return d||"/"}return{re:r,score:a,keys:o,parse:s,stringify:u}}function mfe(e,t){let n=0;for(;n<e.length&&n<t.length;){const a=t[n]-e[n];if(a)return a;n++}return e.length<t.length?e.length===1&&e[0]===ga.Static+ga.Segment?-1:1:e.length>t.length?t.length===1&&t[0]===ga.Static+ga.Segment?1:-1:0}function s9(e,t){let n=0;const a=e.score,l=t.score;for(;n<a.length&&n<l.length;){const o=mfe(a[n],l[n]);if(o)return o;n++}if(Math.abs(l.length-a.length)===1){if(p3(a))return 1;if(p3(l))return-1}return l.length-a.length}function p3(e){const t=e[e.length-1];return e.length>0&&t[t.length-1]<0}const gfe={strict:!1,end:!0,sensitive:!1};function yfe(e,t,n){const a=vfe(ffe(e.path),n),l=cn(a,{record:e,parent:t,children:[],alias:[]});return t&&!l.record.aliasOf==!t.record.aliasOf&&t.children.push(l),l}function bfe(e,t){const n=[],a=new Map;t=o3(gfe,t);function l(f){return a.get(f)}function o(f,p,m){const v=!m,h=v3(f);h.aliasOf=m&&m.record;const g=o3(t,f),y=[h];if("alias"in f){const _=typeof f.alias=="string"?[f.alias]:f.alias;for(const C of _)y.push(v3(cn({},h,{components:m?m.record.components:h.components,path:C,aliasOf:m?m.record:h})))}let b,w;for(const _ of y){const{path:C}=_;if(p&&C[0]!=="/"){const k=p.record.path,T=k[k.length-1]==="/"?"":"/";_.path=p.record.path+(C&&T+C)}if(b=yfe(_,p,g),m?m.alias.push(b):(w=w||b,w!==b&&w.alias.push(b),v&&f.name&&!m3(b)&&r(f.name)),i9(b)&&u(b),h.children){const k=h.children;for(let T=0;T<k.length;T++)o(k[T],b,m&&m.children[T])}m=m||b}return w?()=>{r(w)}:Zi}function r(f){if(l9(f)){const p=a.get(f);p&&(a.delete(f),n.splice(n.indexOf(p),1),p.children.forEach(r),p.alias.forEach(r))}else{const p=n.indexOf(f);p>-1&&(n.splice(p,1),f.record.name&&a.delete(f.record.name),f.children.forEach(r),f.alias.forEach(r))}}function s(){return n}function u(f){const p=Cfe(f,n);n.splice(p,0,f),f.record.name&&!m3(f)&&a.set(f.record.name,f)}function c(f,p){let m,v={},h,g;if("name"in f&&f.name){if(m=a.get(f.name),!m)throw ai(Rn.MATCHER_NOT_FOUND,{location:f});g=m.record.name,v=cn(h3(p.params,m.keys.filter(w=>!w.optional).concat(m.parent?m.parent.keys.filter(w=>w.optional):[]).map(w=>w.name)),f.params&&h3(f.params,m.keys.map(w=>w.name))),h=m.stringify(v)}else if(f.path!=null)h=f.path,m=n.find(w=>w.re.test(h)),m&&(v=m.parse(h),g=m.record.name);else{if(m=p.name?a.get(p.name):n.find(w=>w.re.test(p.path)),!m)throw ai(Rn.MATCHER_NOT_FOUND,{location:f,currentLocation:p});g=m.record.name,v=cn({},p.params,f.params),h=m.stringify(v)}const y=[];let b=m;for(;b;)y.unshift(b.record),b=b.parent;return{name:g,path:h,params:v,matched:y,meta:_fe(y)}}e.forEach(f=>o(f));function d(){n.length=0,a.clear()}return{addRoute:o,resolve:c,removeRoute:r,clearRoutes:d,getRoutes:s,getRecordMatcher:l}}function h3(e,t){const n={};for(const a of t)a in e&&(n[a]=e[a]);return n}function v3(e){const t={path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:e.aliasOf,beforeEnter:e.beforeEnter,props:wfe(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}};return Object.defineProperty(t,"mods",{value:{}}),t}function wfe(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const a in e.components)t[a]=typeof n=="object"?n[a]:n;return t}function m3(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function _fe(e){return e.reduce((t,n)=>cn(t,n.meta),{})}function Cfe(e,t){let n=0,a=t.length;for(;n!==a;){const o=n+a>>1;s9(e,t[o])<0?a=o:n=o+1}const l=Sfe(e);return l&&(a=t.lastIndexOf(l,a-1)),a}function Sfe(e){let t=e;for(;t=t.parent;)if(i9(t)&&s9(e,t)===0)return t}function i9({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function g3(e){const t=Ve(m0),n=Ve(u1),a=x(()=>{const u=i(e.to);return t.resolve(u)}),l=x(()=>{const{matched:u}=a.value,{length:c}=u,d=u[c-1],f=n.matched;if(!d||!f.length)return-1;const p=f.findIndex(ni.bind(null,d));if(p>-1)return p;const m=y3(u[c-2]);return c>1&&y3(d)===m&&f[f.length-1].path!==m?f.findIndex(ni.bind(null,u[c-2])):p}),o=x(()=>l.value>-1&&Mfe(n.params,a.value.params)),r=x(()=>l.value>-1&&l.value===n.matched.length-1&&a9(n.params,a.value.params));function s(u={}){if(Tfe(u)){const c=t[i(e.replace)?"replace":"push"](i(e.to)).catch(Zi);return e.viewTransition&&typeof document<"u"&&"startViewTransition"in document&&document.startViewTransition(()=>c),c}return Promise.resolve()}return{route:a,href:x(()=>a.value.href),isActive:o,isExactActive:r,navigate:s}}function xfe(e){return e.length===1?e[0]:e}const kfe=K({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"},viewTransition:Boolean},useLink:g3,setup(e,{slots:t}){const n=Dt(g3(e)),{options:a}=Ve(m0),l=x(()=>({[b3(e.activeClass,a.linkActiveClass,"router-link-active")]:n.isActive,[b3(e.exactActiveClass,a.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const o=t.default&&xfe(t.default(n));return e.custom?o:Ze("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:l.value},o)}}}),Efe=kfe;function Tfe(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Mfe(e,t){for(const n in t){const a=t[n],l=e[n];if(typeof a=="string"){if(a!==l)return!1}else if(!Cl(l)||l.length!==a.length||a.some((o,r)=>o.valueOf()!==l[r].valueOf()))return!1}return!0}function y3(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const b3=(e,t,n)=>e??t??n,Ofe=K({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const a=Ve(U2),l=x(()=>e.route||a.value),o=Ve(c3,0),r=x(()=>{let c=i(o);const{matched:d}=l.value;let f;for(;(f=d[c])&&!f.components;)c++;return c}),s=x(()=>l.value.matched[r.value]);St(c3,x(()=>r.value+1)),St(afe,s),St(U2,l);const u=B();return fe(()=>[u.value,s.value,e.name],([c,d,f],[p,m,v])=>{d&&(d.instances[f]=c,m&&m!==d&&c&&c===p&&(d.leaveGuards.size||(d.leaveGuards=m.leaveGuards),d.updateGuards.size||(d.updateGuards=m.updateGuards))),c&&d&&(!m||!ni(d,m)||!p)&&(d.enterCallbacks[f]||[]).forEach(h=>h(c))},{flush:"post"}),()=>{const c=l.value,d=e.name,f=s.value,p=f&&f.components[d];if(!p)return w3(n.default,{Component:p,route:c});const m=f.props[d],v=m?m===!0?c.params:typeof m=="function"?m(c):m:null,g=Ze(p,cn({},v,t,{onVnodeUnmounted:y=>{y.component.isUnmounted&&(f.instances[d]=null)},ref:u}));return w3(n.default,{Component:g,route:c})||g}}});function w3(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const Nfe=Ofe;function Afe(e){const t=bfe(e.routes,e),n=e.parseQuery||tfe,a=e.stringifyQuery||u3,l=e.history,o=Mi(),r=Mi(),s=Mi(),u=Et(Bo);let c=Bo;Ss&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const d=pp.bind(null,Q=>""+Q),f=pp.bind(null,Bde),p=pp.bind(null,Au);function m(Q,ce){let de,Y;return l9(Q)?(de=t.getRecordMatcher(Q),Y=ce):Y=Q,t.addRoute(Y,de)}function v(Q){const ce=t.getRecordMatcher(Q);ce&&t.removeRoute(ce)}function h(){return t.getRoutes().map(Q=>Q.record)}function g(Q){return!!t.getRecordMatcher(Q)}function y(Q,ce){if(ce=cn({},ce||u.value),typeof Q=="string"){const G=hp(n,Q,ce.path),se=t.resolve({path:G.path},ce),he=l.createHref(G.fullPath);return cn(G,se,{params:p(se.params),hash:Au(G.hash),redirectedFrom:void 0,href:he})}let de;if(Q.path!=null)de=cn({},Q,{path:hp(n,Q.path,ce.path).path});else{const G=cn({},Q.params);for(const se in G)G[se]==null&&delete G[se];de=cn({},Q,{params:f(G)}),ce.params=f(ce.params)}const Y=t.resolve(de,ce),Z=Q.hash||"";Y.params=d(p(Y.params));const ie=Hde(a,cn({},Q,{hash:Ide(Z),path:Y.path})),j=l.createHref(ie);return cn({fullPath:ie,hash:Z,query:a===u3?nfe(Q.query):Q.query||{}},Y,{redirectedFrom:void 0,href:j})}function b(Q){return typeof Q=="string"?hp(n,Q,u.value.path):cn({},Q)}function w(Q,ce){if(c!==Q)return ai(Rn.NAVIGATION_CANCELLED,{from:ce,to:Q})}function _(Q){return T(Q)}function C(Q){return _(cn(b(Q),{replace:!0}))}function k(Q,ce){const de=Q.matched[Q.matched.length-1];if(de&&de.redirect){const{redirect:Y}=de;let Z=typeof Y=="function"?Y(Q,ce):Y;return typeof Z=="string"&&(Z=Z.includes("?")||Z.includes("#")?Z=b(Z):{path:Z},Z.params={}),cn({query:Q.query,hash:Q.hash,params:Z.path!=null?{}:Q.params},Z)}}function T(Q,ce){const de=c=y(Q),Y=u.value,Z=Q.state,ie=Q.force,j=Q.replace===!0,G=k(de,Y);if(G)return T(cn(b(G),{state:typeof G=="object"?cn({},Z,G.state):Z,force:ie,replace:j}),ce||de);const se=de;se.redirectedFrom=ce;let he;return!ie&&Fde(a,Y,de)&&(he=ai(Rn.NAVIGATION_DUPLICATED,{to:se,from:Y}),q(Y,Y,!0,!1)),(he?Promise.resolve(he):N(se,Y)).catch(ve=>oo(ve)?oo(ve,Rn.NAVIGATION_GUARD_REDIRECT)?ve:D(ve):L(ve,se,Y)).then(ve=>{if(ve){if(oo(ve,Rn.NAVIGATION_GUARD_REDIRECT))return T(cn({replace:j},b(ve.to),{state:typeof ve.to=="object"?cn({},Z,ve.to.state):Z,force:ie}),ce||se)}else ve=$(se,Y,!0,j,Z);return R(se,Y,ve),ve})}function E(Q,ce){const de=w(Q,ce);return de?Promise.reject(de):Promise.resolve()}function M(Q){const ce=ee.values().next().value;return ce&&typeof ce.runWithContext=="function"?ce.runWithContext(Q):Q()}function N(Q,ce){let de;const[Y,Z,ie]=lfe(Q,ce);de=mp(Y.reverse(),"beforeRouteLeave",Q,ce);for(const G of Y)G.leaveGuards.forEach(se=>{de.push(Yo(se,Q,ce))});const j=E.bind(null,Q,ce);return de.push(j),le(de).then(()=>{de=[];for(const G of o.list())de.push(Yo(G,Q,ce));return de.push(j),le(de)}).then(()=>{de=mp(Z,"beforeRouteUpdate",Q,ce);for(const G of Z)G.updateGuards.forEach(se=>{de.push(Yo(se,Q,ce))});return de.push(j),le(de)}).then(()=>{de=[];for(const G of ie)if(G.beforeEnter)if(Cl(G.beforeEnter))for(const se of G.beforeEnter)de.push(Yo(se,Q,ce));else de.push(Yo(G.beforeEnter,Q,ce));return de.push(j),le(de)}).then(()=>(Q.matched.forEach(G=>G.enterCallbacks={}),de=mp(ie,"beforeRouteEnter",Q,ce,M),de.push(j),le(de))).then(()=>{de=[];for(const G of r.list())de.push(Yo(G,Q,ce));return de.push(j),le(de)}).catch(G=>oo(G,Rn.NAVIGATION_CANCELLED)?G:Promise.reject(G))}function R(Q,ce,de){s.list().forEach(Y=>M(()=>Y(Q,ce,de)))}function $(Q,ce,de,Y,Z){const ie=w(Q,ce);if(ie)return ie;const j=ce===Bo,G=Ss?history.state:{};de&&(Y||j?l.replace(Q.fullPath,cn({scroll:j&&G&&G.scroll},Z)):l.push(Q.fullPath,Z)),u.value=Q,q(Q,ce,de,j),D()}let I;function W(){I||(I=l.listen((Q,ce,de)=>{if(!ne.listening)return;const Y=y(Q),Z=k(Y,ne.currentRoute.value);if(Z){T(cn(Z,{replace:!0,force:!0}),Y).catch(Zi);return}c=Y;const ie=u.value;Ss&&Xde(i3(ie.fullPath,de.delta),v0()),N(Y,ie).catch(j=>oo(j,Rn.NAVIGATION_ABORTED|Rn.NAVIGATION_CANCELLED)?j:oo(j,Rn.NAVIGATION_GUARD_REDIRECT)?(T(cn(b(j.to),{force:!0}),Y).then(G=>{oo(G,Rn.NAVIGATION_ABORTED|Rn.NAVIGATION_DUPLICATED)&&!de.delta&&de.type===j2.pop&&l.go(-1,!1)}).catch(Zi),Promise.reject()):(de.delta&&l.go(-de.delta,!1),L(j,Y,ie))).then(j=>{j=j||$(Y,ie,!1),j&&(de.delta&&!oo(j,Rn.NAVIGATION_CANCELLED)?l.go(-de.delta,!1):de.type===j2.pop&&oo(j,Rn.NAVIGATION_ABORTED|Rn.NAVIGATION_DUPLICATED)&&l.go(-1,!1)),R(Y,ie,j)}).catch(Zi)}))}let H=Mi(),z=Mi(),V;function L(Q,ce,de){D(Q);const Y=z.list();return Y.length?Y.forEach(Z=>Z(Q,ce,de)):console.error(Q),Promise.reject(Q)}function F(){return V&&u.value!==Bo?Promise.resolve():new Promise((Q,ce)=>{H.add([Q,ce])})}function D(Q){return V||(V=!Q,W(),H.list().forEach(([ce,de])=>Q?de(Q):ce()),H.reset()),Q}function q(Q,ce,de,Y){const{scrollBehavior:Z}=e;if(!Ss||!Z)return Promise.resolve();const ie=!de&&Zde(i3(Q.fullPath,0))||(Y||!de)&&history.state&&history.state.scroll||null;return Re().then(()=>Z(Q,ce,ie)).then(j=>j&&Gde(j)).catch(j=>L(j,Q,ce))}const U=Q=>l.go(Q);let te;const ee=new Set,ne={currentRoute:u,listening:!0,addRoute:m,removeRoute:v,clearRoutes:t.clearRoutes,hasRoute:g,getRoutes:h,resolve:y,options:e,push:_,replace:C,go:U,back:()=>U(-1),forward:()=>U(1),beforeEach:o.add,beforeResolve:r.add,afterEach:s.add,onError:z.add,isReady:F,install(Q){Q.component("RouterLink",Efe),Q.component("RouterView",Nfe),Q.config.globalProperties.$router=ne,Object.defineProperty(Q.config.globalProperties,"$route",{enumerable:!0,get:()=>i(u)}),Ss&&!te&&u.value===Bo&&(te=!0,_(l.location).catch(Y=>{}));const ce={};for(const Y in Bo)Object.defineProperty(ce,Y,{get:()=>u.value[Y],enumerable:!0});Q.provide(m0,ne),Q.provide(u1,Bu(ce)),Q.provide(U2,u);const de=Q.unmount;ee.add(Q),Q.unmount=function(){ee.delete(Q),ee.size<1&&(c=Bo,I&&I(),I=null,u.value=Bo,te=!1,V=!1),de()}}};function le(Q){return Q.reduce((ce,de)=>ce.then(()=>M(de)),Promise.resolve())}return ne}function Vfe(){return Ve(m0)}function Lfe(e){return Ve(u1)}const u9=Afe({history:ufe(),routes:[{path:"/",component:()=>gr(()=>import("./LayoutView-C9phf7in.js"),__vite__mapDeps([0,1,2,3])),children:[{path:"",name:"Dashboard",component:()=>gr(()=>import("./DashboardView-C3Hw-HpG.js"),__vite__mapDeps([4,5,6,7,2,8]))},{path:"providers",name:"Providers",component:()=>gr(()=>import("./ProvidersView-f8aZkMdw.js"),__vite__mapDeps([9,2,6,7,5,10]))},{path:"settings",name:"Settings",component:()=>gr(()=>import("./SettingsView-GvAA2vLp.js"),__vite__mapDeps([11,2,5,7,1,12]))},{path:"logs",name:"Logs",component:()=>gr(()=>import("./LogsView-BlXITEJH.js"),__vite__mapDeps([13,5,6,7,2,14]))},{path:"process",name:"Process",component:()=>gr(()=>import("./ProcessView-Bgf7-MLQ.js"),__vite__mapDeps([15,1,2,16]))}]},{path:"/login",name:"Login",component:()=>gr(()=>import("./LoginView-es6pSPgt.js"),__vite__mapDeps([17,5,18]))},{path:"/:pathMatch(.*)*",redirect:"/"}]});u9.beforeEach(e=>{const t=localStorage.getItem("webui_token");if(e.name!=="Login"&&!t)return{name:"Login"}});const yc=uu(Cde);yc.use(uk());yc.use(gde,{locale:yde});yc.use(u9);for(const[e,t]of Object.entries(lK))yc.component(e,t);yc.mount("#app");export{mD as $,h9 as A,Qr as B,en as C,st as D,tde as E,Fe as F,vo as G,ge as H,li as I,Qo as J,z9 as K,V9 as L,L9 as M,dt as N,B9 as O,S3 as P,Ra as Q,T9 as R,Ife as S,Rfe as T,$fe as U,to as V,Pfe as W,Dt as X,Qe as Y,P as Z,bde as _,Kt as a,fe as a0,C8 as a1,rn as a2,eV as a3,kF as a4,m8 as a5,ov as a6,To as a7,Hce as a8,u9 as a9,ht as b,ue as c,K as d,A as e,O as f,J as g,bt as h,re as i,ch as j,i as k,x as l,Vfe as m,Lfe as n,S as o,gt as p,_t as q,B as r,sn as s,Se as t,VS as u,Ie as v,ae as w,Y2 as x,Mn as y,$a as z};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{W as w,r as u,l as i}from"./index-GohB4JDa.js";import{u as C}from"./config-Cs3_z63C.js";const J=w("providers",()=>{const s=C(),c=u(!1),n=i(()=>s.providers),v=i(()=>s.loading),f=i(()=>n.value.length),m=i(()=>n.value.reduce((e,o)=>{var r;return e+(((r=o.models)==null?void 0:r.length)??0)},0)),t=u([]);function p(){try{const e=localStorage.getItem("provider_order");e&&(t.value=JSON.parse(e))}catch{}}function d(){try{localStorage.setItem("provider_order",JSON.stringify(t.value))}catch{}}const g=i(()=>{const e=t.value.map(r=>n.value.find(a=>a.name===r)).filter(r=>!!r);for(const r of n.value)e.find(a=>a.name===r.name)||e.push(r);const o=e.map(r=>r.name);return(o.length!==t.value.length||o.some((r,a)=>t.value[a]!==r))&&(t.value=o,d()),e});function S(e){t.value=e.map(o=>o.name),d()}function h(e){t.value=[e,...t.value.filter(o=>o!==e)],d()}async function y(){p(),await s.load()}async function l(e){return s.config.providers=e,await s.save()}async function P(e){const o=n.value.filter(r=>r.name!==e.name);return await l(o)}async function O(e){const o=n.value.map(r=>({...r,models:(r.models??[]).filter(a=>a.modelName!==e)}));return await l(o)}return{suppliers:n,loading:v,restarting:c,orderedProviders:g,providerCount:f,totalModelCount:m,loadProviders:y,saveProviders:l,deleteProvider:P,deleteModel:O,reorderProviders:S,moveProviderToTop:h}});export{J as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{a9 as f}from"./index-GohB4JDa.js";var h;const g=((h=window.__ELLM_CONFIG__)==null?void 0:h.API_BASE)??"",p=3e4;let n=null;function _(){return n===null&&(n=localStorage.getItem("webui_token")),n}function m(){n=null,localStorage.removeItem("webui_token"),f.push("/login")}async function e(t,o,s){const l={"Content-Type":"application/json"},c=_();c&&(l.Authorization=`Bearer ${c}`);const u=new AbortController,E=setTimeout(()=>u.abort(),p);try{const r=await fetch(`${g}${o}`,{method:t,headers:l,body:s!==void 0?JSON.stringify(s):void 0,signal:u.signal});if(r.status===401){const T=await r.text();let i={};try{i=T?JSON.parse(T):{}}catch{}throw o==="/api/auth/login"?new Error(i.error||"用户名或密码错误"):(m(),new Error(i.error||"未登录"))}const w=await r.text();let a;try{a=w?JSON.parse(w):{}}catch{throw new Error(`响应格式错误 (HTTP ${r.status})`)}if(!r.ok)throw new Error(a.error||`HTTP ${r.status}`);return a}catch(r){throw r.name==="AbortError"?new Error("请求超时"):r}finally{clearTimeout(E)}}const S={get:t=>e("GET",t),post:(t,o)=>e("POST",t,o),put:(t,o)=>e("PUT",t,o),del:t=>e("DELETE",t)};export{S as A};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var i;const c=((i=window.__ELLM_CONFIG__)==null?void 0:i.API_BASE)??"";function h(){try{const t=c?new URL(c):new URL(location.href);return t.port?`${t.protocol}//${t.hostname}:${Number(t.port)+1}`:null}catch{return null}}var p;const l=((p=window.__ELLM_CONFIG__)==null?void 0:p.MANAGER_BASE)??h(),E=3e4;async function e(t,u,s,d=E){if(l===null)throw new Error("无法确定管理服务地址:config.js 未配置 MANAGER_BASE,且页面经 80/443 端口访问(无法按端口+1 推导)");const a=new AbortController,T=setTimeout(()=>a.abort(),d);try{const r=await fetch(`${l}${u}`,{method:t,headers:{"Content-Type":"application/json"},body:s!==void 0?JSON.stringify(s):void 0,signal:a.signal}),n=await r.text();let o;try{o=n?JSON.parse(n):{}}catch{throw new Error(`管理服务响应格式错误 (HTTP ${r.status})`)}if(!r.ok||o.ok===!1)throw new Error(o.error||`HTTP ${r.status}`);return o}catch(r){throw r.name==="AbortError"?new Error("请求超时"):r instanceof TypeError?new Error("管理服务不可达(请确认 ellm-proxy 已启动,且本页面通过 localhost/127.0.0.1 访问)"):r}finally{clearTimeout(T)}}const w={status:()=>e("GET","/status"),processes:()=>e("GET","/processes").then(t=>t.processes),start:()=>e("POST","/start"),stop:()=>e("POST","/stop"),restart:()=>e("POST","/restart"),autostart:()=>e("GET","/autostart"),setAutostart:t=>e("POST","/autostart",{enabled:t}),update:()=>e("GET","/update"),setUpdateEnabled:t=>e("POST","/update/enabled",{enabled:t}),checkUpdate:()=>e("POST","/update/check"),applyUpdate:()=>e("POST","/update/apply",void 0,6e5),harness:()=>e("GET","/harness"),setClaudeCode:t=>e("POST","/harness/claude-code",{enabled:t}),setCodex:t=>e("POST","/harness/codex",{enabled:t}),setDsh:t=>e("POST","/harness/dsh",{enabled:t})};export{w as m};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{a8 as a}from"./index-GohB4JDa.js";function u(){function s(t,o="success"){a({message:t,type:o,duration:3e3})}return{toast:s}}export{u};
|
package/dist/web-ui/config.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// web-ui 运行时配置(部署后可直接改这个文件,无需重新构建)
|
|
2
|
-
// 标准形态:manager 管理服务托管本目录(http://127.0.0.1:<主端口+1>/),两项留空即全同源,无需修改。
|
|
3
|
-
// API_BASE:管理台 API(/api/*)的基地址。留空 = 同源(manager 已反代 /api/* 到主服务);
|
|
4
|
-
// 仅当把 dist 部署到其他静态服务器时填写,如 "http://192.168.1.10:29770"(backend 全局 CORS 已开)。
|
|
5
|
-
// MANAGER_BASE:进程/代理开关接口(/status /restart /harness…)的基地址。
|
|
6
|
-
// 留空 = 同源(manager 托管形态);独立部署且需进程管理页时填 manager 地址,如 "http://127.0.0.1:29771"。
|
|
7
|
-
window.__ELLM_CONFIG__ = {
|
|
8
|
-
API_BASE: "",
|
|
9
|
-
MANAGER_BASE: "",
|
|
10
|
-
};
|
|
1
|
+
// web-ui 运行时配置(部署后可直接改这个文件,无需重新构建)
|
|
2
|
+
// 标准形态:manager 管理服务托管本目录(http://127.0.0.1:<主端口+1>/),两项留空即全同源,无需修改。
|
|
3
|
+
// API_BASE:管理台 API(/api/*)的基地址。留空 = 同源(manager 已反代 /api/* 到主服务);
|
|
4
|
+
// 仅当把 dist 部署到其他静态服务器时填写,如 "http://192.168.1.10:29770"(backend 全局 CORS 已开)。
|
|
5
|
+
// MANAGER_BASE:进程/代理开关接口(/status /restart /harness…)的基地址。
|
|
6
|
+
// 留空 = 同源(manager 托管形态);独立部署且需进程管理页时填 manager 地址,如 "http://127.0.0.1:29771"。
|
|
7
|
+
window.__ELLM_CONFIG__ = {
|
|
8
|
+
API_BASE: "",
|
|
9
|
+
MANAGER_BASE: "",
|
|
10
|
+
};
|
package/dist/web-ui/index.html
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/vite.svg">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>ELLM - 网关管理台</title>
|
|
8
|
-
<script src="/config.js"></script>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>ELLM - 网关管理台</title>
|
|
8
|
+
<script src="/config.js"></script>
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-GohB4JDa.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/index-BecfPmag.css">
|
|
11
|
-
</head>
|
|
12
|
-
<body>
|
|
13
|
-
<div id="app"></div>
|
|
14
|
-
</body>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="app"></div>
|
|
14
|
+
</body>
|
|
15
15
|
</html>
|
package/docs/DESIGN.md
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
# ellm-proxy 发布包需求与设计文档
|
|
2
|
-
|
|
3
|
-
> 本文档整理 2026-09-01 「将 ellm 项目改造为 npm 发布包」需求的完整决策与设计,供后续维护者理解包结构与行为约束。使用说明见 [README.md](../README.md)。
|
|
4
|
-
|
|
5
|
-
## 1. 背景与目标
|
|
6
|
-
|
|
7
|
-
`ellm` monorepo(`gateway` 协议转换层 / `backend` 管理台服务 / `web-ui` Vue 前端)原为纯本地工程,目标是把 backend 单进程应用(内嵌网关 + 管理台 API)改造为可发布的 npm 包 **`ellm-proxy`**(2026-09-03 起 web-ui 由 manager 管理服务托管,backend 不再托管):
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm i -g ellm-proxy
|
|
11
|
-
ellm-proxy start # 后台启动(隐藏窗口,首次自动创建配置)
|
|
12
|
-
ellm-proxy stop # 停止后台服务
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## 2. 需求决策记录(按定案时间线)
|
|
16
|
-
|
|
17
|
-
| # | 决策 | 内容 |
|
|
18
|
-
| --- | --- | --- |
|
|
19
|
-
| 1 | 发布形态 | 完整单进程应用(网关 + 管理台 API),非 SDK。web-ui 静态托管 2026-09-03 起由 manager 管理服务承担(见决策 12) |
|
|
20
|
-
| 2 | 包名 | 坚持 `ellm-proxy`(与内部子包 `gateway` 相似性已知并接受) |
|
|
21
|
-
| 3 | 发布载体 | 新增 workspace 子包 `ellm-proxy/`,backend/web-ui 保持原职责;发布时先构建再聚合 |
|
|
22
|
-
| 4 | backend 纯净度 | backend 源码保持单模式零分支;发布差异全部由环境变量承载(经历三轮演进,见 §4) |
|
|
23
|
-
| 5 | 运行时布局 | 配置/数据/日志/PID 默认放用户目录 `~/.ellm-proxy/`,**全部可通过环境变量注入覆盖**,无则走默认 |
|
|
24
|
-
| 6 | CLI 入口 | `ellm-proxy` 命令为独立 JS CLI(`bin/ellm-proxy.js`):`-h` 内置帮助、`start`/`stop`/`restart`/`config` 子命令 |
|
|
25
|
-
| 7 | 首次配置自愈 | 首次执行时检查配置文件,不存在则从包内示例自动创建,并提示两种修改方式(编辑文件 / web-ui 在线修改) |
|
|
26
|
-
| 8 | 服务启停形态 | 实际服务由包内自带的局部 **pm2**(JS API)守护(进程名 `ellm-proxy`):崩溃自动重启、日志落盘、dump/resurrect;CLI 直接调 pm2,不再有独立启停脚本 |
|
|
27
|
-
| 9 | 配置修改渠道 | 自动创建后提示直接编辑配置文件,或启动服务后经管理台(独立部署的 web-ui)在线修改(实时热更新) |
|
|
28
|
-
| 10 | 默认端口 | 3000 太常用易冲突 → 随机定为 **29770**(避开 49152+ 系统动态端口区) |
|
|
29
|
-
| 11 | 不内嵌 node.exe | 服务要求的 Node ≥ 22.13.0 由 CLI 解析为 pm2 `interpreter`:CLI 自身 node 满足则用 `process.execPath`,否则经 **get-node**(随包依赖)解析/下载(缓存命中零下载);pm2 daemon 的 node 版本无关紧要 |
|
|
30
|
-
| 12 | web-ui 移交 manager 托管(2026-09-03) | backend 移除 `/web-ui` 静态托管(删 `webui-static.ts`);web-ui 改由 manager 管理服务托管:根路径静态资源 + 反代 `/api/*` 到主服务(端口每请求解析,主服务顺延漂移无感知),页面全接口同源零配置(`config.js` 的 `API_BASE`/`MANAGER_BASE` 留空);发布包恢复捆绑 `dist/web-ui/` 供 manager 定位(`ELLM_WEBUI_DIR` 可选覆盖,CLI 不再注入);manager 仍仅监听 127.0.0.1 |
|
|
31
|
-
|
|
32
|
-
## 3. 架构设计
|
|
33
|
-
|
|
34
|
-
### 3.1 包结构与发布流程
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
ellm-proxy/
|
|
38
|
-
├── bin/ellm-proxy.js # CLI 入口(npm bin 指向;纯 JS 零依赖)
|
|
39
|
-
├── dist/server.js # 发布版服务本体(esbuild 从 backend 源码直接构建 + bin shebang banner)
|
|
40
|
-
├── dist/web-ui/ # web-ui vite 构建产物(manager 管理服务托管于 127.0.0.1:<主端口+1>/)
|
|
41
|
-
├── manager/ # 管理服务(独立常驻):进程控制 + harness 开关 + web-ui 托管 + /api 反代
|
|
42
|
-
├── scripts/build-server.mjs # 发布构建(纯 esbuild,无任何源码替换)
|
|
43
|
-
├── scripts/build-package.mjs # 聚合 + 门禁(prepack:构建 server → 拷 web-ui → 复制 example → shebang 校验)
|
|
44
|
-
└── release/ → 已删除 # 曾存在的"包模式路径模块"已被全 env 化方案取代
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
发布命令(仓库根):`pnpm pack:proxy` = `build:web` → `npm pack ./ellm-proxy`(prepack 自动构建聚合)。
|
|
48
|
-
|
|
49
|
-
发布 manifest 要点:`engines >=22.13.0`(node:sqlite 无 flag 最低版);`bin=bin/ellm-proxy.js`;`files` 白名单(bin/、dist/、example、README、LICENSE、docs);不加 `private`;`dependencies` 为 `cac@^6`(CLI 框架)、`pm2@^7`(进程守护,局部随包分发)与 `get-node@^15`(解析满足 engines 的 node 作为 pm2 interpreter);bundle 自包含;发布时用 `npm publish`(不改写 manifest)。
|
|
50
|
-
|
|
51
|
-
### 3.2 路径全 env 化(核心设计)
|
|
52
|
-
|
|
53
|
-
所有路径「env 优先、缺省公式兜底」,相对路径按当前工作目录展开,绝对路径原样:
|
|
54
|
-
|
|
55
|
-
| 配置 | 环境变量 | 缺省公式(仓库模式自动正确) | 包模式来源 |
|
|
56
|
-
| --- | --- | --- | --- |
|
|
57
|
-
| 应用目录(配置/数据基准) | `ELLM_APP_DIR` | `backend/`(hereDir 推导) | start 脚本注入 = 工作目录(默认 `~/.ellm-proxy`) |
|
|
58
|
-
| 配置文件 | `ELLM_CONFIG` | `<APP_DIR>/ellm.config.json` | CLI `--config` 或用户 env |
|
|
59
|
-
| 数据目录 | `ELLM_DATA_DIR` | `<APP_DIR>/data` | CLI `--data-dir` 或用户 env |
|
|
60
|
-
| web-ui 静态资源 | `ELLM_WEBUI_DIR`(可选覆盖) | —(manager 按自身位置推导:包模式 `dist/web-ui` → 仓库模式 `web-ui/dist`) | manager 启动时自解析(CLI 不再注入) |
|
|
61
|
-
|
|
62
|
-
服务端不加载任何 `.env` / env 文件;环境变量一律由用户会话导出或 CLI 注入。
|
|
63
|
-
|
|
64
|
-
设计约束与边界:
|
|
65
|
-
|
|
66
|
-
- **backend 源码零模式分支**:`paths.ts` 只有「env ?? 公式」两种取值,不存在模式判断;仓库开发模式不设任何 env 即工作。
|
|
67
|
-
- **包模式必须经 CLI 启动**:绕过它裸跑 `dist/server.js` 时 env 未注入,会因找不到配置退出(报错信息指引)。官方入口始终是 `ellm-proxy start/stop`。
|
|
68
|
-
- **CLI 在转调前把路径参数统一转为绝对路径**:CLI 的 cwd 与 node 进程的工作目录不同,透传相对字符串会在另一基准下解析错位。
|
|
69
|
-
- **CLI 负责首次配置自愈**(server 端只读不建):`ensureConfig()` 查找链与 server 端一致,创建后提示编辑或 web-ui 修改。
|
|
70
|
-
|
|
71
|
-
### 3.3 后台启停设计(pm2,跨平台)
|
|
72
|
-
|
|
73
|
-
- **start**:pm2 JS API `connect → list(同名已存在则报错)→ start(数组形式一次提交主服务+管理服务)→ 并行轮询探活(waitOnline 每 300ms 查 status 至 online;errored/stopped 提前失败、15s 超时兜底,双启路径 quiet 静默轮询、结果合并为一行 ✓/✗)→ dump`。启动全程分步进度输出,等待期间不留静默空窗;管理服务已在线(部分恢复)则只补启主服务。主服务探活失败时管理服务已在线(其职责正是远程拉起主服务),失败不回收进程,错误文案给出重试路径。app 配置内联构造:`name=ellm-proxy`、`script=dist/server.js`(`--server-js` 可覆盖)、`interpreter`(CLI node 满足 engines 用 execPath,否则 get-node 解析/下载)、`cwd=appDir`、`out_file/error_file=<appDir>/logs/`、`max_restarts=10 + restart_delay=3s`(防端口占用无限重启)、`env={...process.env}`(含 ELLM_APP_DIR/CONFIG/DATA_DIR/ELLM_PORT/ELLM_MANAGER_PORT 注入,端口由 doStart 统一预算:主服务 = --port > ELLM_PORT env > 配置 port,管理服务 = 主端口+1;预算后试绑探测,被占自动顺延空闲端口——主服务 0.0.0.0+127.0.0.1 双维度、管理服务 127.0.0.1 单维度(Windows wildcard/specific 共存语义,见 utils/port.js),顺延仅本次运行生效不改配置文件;管理服务已在线时保持现有端口不重新预算)。
|
|
74
|
-
- **stop/restart**:stop = `pm2 delete`(不存在则幂等提示)+ dump 更新;restart = stop + start(保证最新参数/env 生效)。进程管理全部由 pm2 承担,无自研 PID/树杀逻辑。
|
|
75
|
-
- **开机自启(跨平台,登录时触发、均免管理员)**:`autostart on` 按平台注册自启项,内容统一为 `<nodePath> <包内 pm2 bin> resurrect`(恢复最后一次 dump;从未 start 过则静默无副作用)——Windows 写启动文件夹 `.cmd`(shell:startup;schtasks onlogon 需管理员实测被拒)、macOS 写 LaunchAgent plist + `launchctl load`、Linux 写 systemd user unit + `systemctl --user enable`(无 systemd 的 WSL/容器环境报错并提示手动处理);`autostart off` 反向移除。
|
|
76
|
-
- **日志/状态基准**:`--working-dir`(默认 `~/.ellm-proxy`,即 `ELLM_APP_DIR` 同源);运行状态用 `pm2 ls`。
|
|
77
|
-
|
|
78
|
-
### 3.4 跨包不变式(维护须知)
|
|
79
|
-
|
|
80
|
-
1. **`gateway/src/index.ts` 必须保持零顶层副作用**:backend 经 esbuild 全量内联 gateway 源码,任何 import 副作用都会执行。曾因 index.ts 顶层读 `ELLM_CONFIG` 装配 default app 与 backend 同名 env 撞车(实测踩坑)。独立网关装配已抽至 `src/standalone-app.ts`(`buildStandaloneApp()`)。
|
|
81
|
-
2. **包内布局约束**:`dist/web-ui/` 与 `dist/server.js` 同级(manager 缺省推导依赖此布局;`ELLM_WEBUI_DIR` 可覆盖)。
|
|
82
|
-
3. **`backend/ellm.config.json` 仍是配置唯一数据源**(原样读写不变式在发布包同样成立)。
|
|
83
|
-
|
|
84
|
-
## 4. 方案演进史(三轮,记录否决原因避免重走)
|
|
85
|
-
|
|
86
|
-
| 轮 | 方案 | 否决原因 |
|
|
87
|
-
| --- | --- | --- |
|
|
88
|
-
| 一 | backend 源码做双模式分支(`PACKAGE_MODE` 判据 + `BACKEND_DIR`/`REPO_ROOT` 为 null + server.ts CLI) | backend 侵入大,用户要求还原 |
|
|
89
|
-
| 二 | 保留 CLI/查找链在 backend(72 行无分支增量),发布差异收敛到一个模块(`release/runtime-dir.ts`)+ esbuild onLoad 插件构建期替换 | 用户希望路径全部 env 可传入、删除替换层 |
|
|
90
|
-
| 三(定稿) | 路径全 env 化(`ELLM_APP_DIR`/`ELLM_WEBUI_DIR` 注入由 ps1 承担),删除 release 模块与替换插件,backend 回归零分支 | — |
|
|
91
|
-
|
|
92
|
-
期间曾实现 server.ts CLI(`--config/--port/--help/--version`),随「CLI 独立为 bin 脚本 + 配置路径走 env」定案被还原删除。
|
|
93
|
-
|
|
94
|
-
## 5. 验证基线(发布前回归清单)
|
|
95
|
-
|
|
96
|
-
- `pnpm -r typecheck` + `pnpm -r test` 全绿(typecheck ×2、约 396 测试)
|
|
97
|
-
- CLI:`-h`、`config`(首次创建)、`start`(参数透传链 + env 注入 + 401/200 鉴权)、`stop`(树杀 + 端口释放)、`restart`(运行中/未运行两态)
|
|
98
|
-
- 仓库产物模式(零 env)与包模式(CLI 注入)双模式启动均正常
|
|
99
|
-
- 包目录零读写(`ls` 包目录无新增文件)、`npm pack --dry-run` 核对 tarball 无 `src/`/`test/`/真实配置
|
|
1
|
+
# ellm-proxy 发布包需求与设计文档
|
|
2
|
+
|
|
3
|
+
> 本文档整理 2026-09-01 「将 ellm 项目改造为 npm 发布包」需求的完整决策与设计,供后续维护者理解包结构与行为约束。使用说明见 [README.md](../README.md)。
|
|
4
|
+
|
|
5
|
+
## 1. 背景与目标
|
|
6
|
+
|
|
7
|
+
`ellm` monorepo(`gateway` 协议转换层 / `backend` 管理台服务 / `web-ui` Vue 前端)原为纯本地工程,目标是把 backend 单进程应用(内嵌网关 + 管理台 API)改造为可发布的 npm 包 **`ellm-proxy`**(2026-09-03 起 web-ui 由 manager 管理服务托管,backend 不再托管):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g ellm-proxy
|
|
11
|
+
ellm-proxy start # 后台启动(隐藏窗口,首次自动创建配置)
|
|
12
|
+
ellm-proxy stop # 停止后台服务
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 2. 需求决策记录(按定案时间线)
|
|
16
|
+
|
|
17
|
+
| # | 决策 | 内容 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| 1 | 发布形态 | 完整单进程应用(网关 + 管理台 API),非 SDK。web-ui 静态托管 2026-09-03 起由 manager 管理服务承担(见决策 12) |
|
|
20
|
+
| 2 | 包名 | 坚持 `ellm-proxy`(与内部子包 `gateway` 相似性已知并接受) |
|
|
21
|
+
| 3 | 发布载体 | 新增 workspace 子包 `ellm-proxy/`,backend/web-ui 保持原职责;发布时先构建再聚合 |
|
|
22
|
+
| 4 | backend 纯净度 | backend 源码保持单模式零分支;发布差异全部由环境变量承载(经历三轮演进,见 §4) |
|
|
23
|
+
| 5 | 运行时布局 | 配置/数据/日志/PID 默认放用户目录 `~/.ellm-proxy/`,**全部可通过环境变量注入覆盖**,无则走默认 |
|
|
24
|
+
| 6 | CLI 入口 | `ellm-proxy` 命令为独立 JS CLI(`bin/ellm-proxy.js`):`-h` 内置帮助、`start`/`stop`/`restart`/`config` 子命令 |
|
|
25
|
+
| 7 | 首次配置自愈 | 首次执行时检查配置文件,不存在则从包内示例自动创建,并提示两种修改方式(编辑文件 / web-ui 在线修改) |
|
|
26
|
+
| 8 | 服务启停形态 | 实际服务由包内自带的局部 **pm2**(JS API)守护(进程名 `ellm-proxy`):崩溃自动重启、日志落盘、dump/resurrect;CLI 直接调 pm2,不再有独立启停脚本 |
|
|
27
|
+
| 9 | 配置修改渠道 | 自动创建后提示直接编辑配置文件,或启动服务后经管理台(独立部署的 web-ui)在线修改(实时热更新) |
|
|
28
|
+
| 10 | 默认端口 | 3000 太常用易冲突 → 随机定为 **29770**(避开 49152+ 系统动态端口区) |
|
|
29
|
+
| 11 | 不内嵌 node.exe | 服务要求的 Node ≥ 22.13.0 由 CLI 解析为 pm2 `interpreter`:CLI 自身 node 满足则用 `process.execPath`,否则经 **get-node**(随包依赖)解析/下载(缓存命中零下载);pm2 daemon 的 node 版本无关紧要 |
|
|
30
|
+
| 12 | web-ui 移交 manager 托管(2026-09-03) | backend 移除 `/web-ui` 静态托管(删 `webui-static.ts`);web-ui 改由 manager 管理服务托管:根路径静态资源 + 反代 `/api/*` 到主服务(端口每请求解析,主服务顺延漂移无感知),页面全接口同源零配置(`config.js` 的 `API_BASE`/`MANAGER_BASE` 留空);发布包恢复捆绑 `dist/web-ui/` 供 manager 定位(`ELLM_WEBUI_DIR` 可选覆盖,CLI 不再注入);manager 仍仅监听 127.0.0.1 |
|
|
31
|
+
|
|
32
|
+
## 3. 架构设计
|
|
33
|
+
|
|
34
|
+
### 3.1 包结构与发布流程
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
ellm-proxy/
|
|
38
|
+
├── bin/ellm-proxy.js # CLI 入口(npm bin 指向;纯 JS 零依赖)
|
|
39
|
+
├── dist/server.js # 发布版服务本体(esbuild 从 backend 源码直接构建 + bin shebang banner)
|
|
40
|
+
├── dist/web-ui/ # web-ui vite 构建产物(manager 管理服务托管于 127.0.0.1:<主端口+1>/)
|
|
41
|
+
├── manager/ # 管理服务(独立常驻):进程控制 + harness 开关 + web-ui 托管 + /api 反代
|
|
42
|
+
├── scripts/build-server.mjs # 发布构建(纯 esbuild,无任何源码替换)
|
|
43
|
+
├── scripts/build-package.mjs # 聚合 + 门禁(prepack:构建 server → 拷 web-ui → 复制 example → shebang 校验)
|
|
44
|
+
└── release/ → 已删除 # 曾存在的"包模式路径模块"已被全 env 化方案取代
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
发布命令(仓库根):`pnpm pack:proxy` = `build:web` → `npm pack ./ellm-proxy`(prepack 自动构建聚合)。
|
|
48
|
+
|
|
49
|
+
发布 manifest 要点:`engines >=22.13.0`(node:sqlite 无 flag 最低版);`bin=bin/ellm-proxy.js`;`files` 白名单(bin/、dist/、example、README、LICENSE、docs);不加 `private`;`dependencies` 为 `cac@^6`(CLI 框架)、`pm2@^7`(进程守护,局部随包分发)与 `get-node@^15`(解析满足 engines 的 node 作为 pm2 interpreter);bundle 自包含;发布时用 `npm publish`(不改写 manifest)。
|
|
50
|
+
|
|
51
|
+
### 3.2 路径全 env 化(核心设计)
|
|
52
|
+
|
|
53
|
+
所有路径「env 优先、缺省公式兜底」,相对路径按当前工作目录展开,绝对路径原样:
|
|
54
|
+
|
|
55
|
+
| 配置 | 环境变量 | 缺省公式(仓库模式自动正确) | 包模式来源 |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| 应用目录(配置/数据基准) | `ELLM_APP_DIR` | `backend/`(hereDir 推导) | start 脚本注入 = 工作目录(默认 `~/.ellm-proxy`) |
|
|
58
|
+
| 配置文件 | `ELLM_CONFIG` | `<APP_DIR>/ellm.config.json` | CLI `--config` 或用户 env |
|
|
59
|
+
| 数据目录 | `ELLM_DATA_DIR` | `<APP_DIR>/data` | CLI `--data-dir` 或用户 env |
|
|
60
|
+
| web-ui 静态资源 | `ELLM_WEBUI_DIR`(可选覆盖) | —(manager 按自身位置推导:包模式 `dist/web-ui` → 仓库模式 `web-ui/dist`) | manager 启动时自解析(CLI 不再注入) |
|
|
61
|
+
|
|
62
|
+
服务端不加载任何 `.env` / env 文件;环境变量一律由用户会话导出或 CLI 注入。
|
|
63
|
+
|
|
64
|
+
设计约束与边界:
|
|
65
|
+
|
|
66
|
+
- **backend 源码零模式分支**:`paths.ts` 只有「env ?? 公式」两种取值,不存在模式判断;仓库开发模式不设任何 env 即工作。
|
|
67
|
+
- **包模式必须经 CLI 启动**:绕过它裸跑 `dist/server.js` 时 env 未注入,会因找不到配置退出(报错信息指引)。官方入口始终是 `ellm-proxy start/stop`。
|
|
68
|
+
- **CLI 在转调前把路径参数统一转为绝对路径**:CLI 的 cwd 与 node 进程的工作目录不同,透传相对字符串会在另一基准下解析错位。
|
|
69
|
+
- **CLI 负责首次配置自愈**(server 端只读不建):`ensureConfig()` 查找链与 server 端一致,创建后提示编辑或 web-ui 修改。
|
|
70
|
+
|
|
71
|
+
### 3.3 后台启停设计(pm2,跨平台)
|
|
72
|
+
|
|
73
|
+
- **start**:pm2 JS API `connect → list(同名已存在则报错)→ start(数组形式一次提交主服务+管理服务)→ 并行轮询探活(waitOnline 每 300ms 查 status 至 online;errored/stopped 提前失败、15s 超时兜底,双启路径 quiet 静默轮询、结果合并为一行 ✓/✗)→ dump`。启动全程分步进度输出,等待期间不留静默空窗;管理服务已在线(部分恢复)则只补启主服务。主服务探活失败时管理服务已在线(其职责正是远程拉起主服务),失败不回收进程,错误文案给出重试路径。app 配置内联构造:`name=ellm-proxy`、`script=dist/server.js`(`--server-js` 可覆盖)、`interpreter`(CLI node 满足 engines 用 execPath,否则 get-node 解析/下载)、`cwd=appDir`、`out_file/error_file=<appDir>/logs/`、`max_restarts=10 + restart_delay=3s`(防端口占用无限重启)、`env={...process.env}`(含 ELLM_APP_DIR/CONFIG/DATA_DIR/ELLM_PORT/ELLM_MANAGER_PORT 注入,端口由 doStart 统一预算:主服务 = --port > ELLM_PORT env > 配置 port,管理服务 = 主端口+1;预算后试绑探测,被占自动顺延空闲端口——主服务 0.0.0.0+127.0.0.1 双维度、管理服务 127.0.0.1 单维度(Windows wildcard/specific 共存语义,见 utils/port.js),顺延仅本次运行生效不改配置文件;管理服务已在线时保持现有端口不重新预算)。
|
|
74
|
+
- **stop/restart**:stop = `pm2 delete`(不存在则幂等提示)+ dump 更新;restart = stop + start(保证最新参数/env 生效)。进程管理全部由 pm2 承担,无自研 PID/树杀逻辑。
|
|
75
|
+
- **开机自启(跨平台,登录时触发、均免管理员)**:`autostart on` 按平台注册自启项,内容统一为 `<nodePath> <包内 pm2 bin> resurrect`(恢复最后一次 dump;从未 start 过则静默无副作用)——Windows 写启动文件夹 `.cmd`(shell:startup;schtasks onlogon 需管理员实测被拒)、macOS 写 LaunchAgent plist + `launchctl load`、Linux 写 systemd user unit + `systemctl --user enable`(无 systemd 的 WSL/容器环境报错并提示手动处理);`autostart off` 反向移除。
|
|
76
|
+
- **日志/状态基准**:`--working-dir`(默认 `~/.ellm-proxy`,即 `ELLM_APP_DIR` 同源);运行状态用 `pm2 ls`。
|
|
77
|
+
|
|
78
|
+
### 3.4 跨包不变式(维护须知)
|
|
79
|
+
|
|
80
|
+
1. **`gateway/src/index.ts` 必须保持零顶层副作用**:backend 经 esbuild 全量内联 gateway 源码,任何 import 副作用都会执行。曾因 index.ts 顶层读 `ELLM_CONFIG` 装配 default app 与 backend 同名 env 撞车(实测踩坑)。独立网关装配已抽至 `src/standalone-app.ts`(`buildStandaloneApp()`)。
|
|
81
|
+
2. **包内布局约束**:`dist/web-ui/` 与 `dist/server.js` 同级(manager 缺省推导依赖此布局;`ELLM_WEBUI_DIR` 可覆盖)。
|
|
82
|
+
3. **`backend/ellm.config.json` 仍是配置唯一数据源**(原样读写不变式在发布包同样成立)。
|
|
83
|
+
|
|
84
|
+
## 4. 方案演进史(三轮,记录否决原因避免重走)
|
|
85
|
+
|
|
86
|
+
| 轮 | 方案 | 否决原因 |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| 一 | backend 源码做双模式分支(`PACKAGE_MODE` 判据 + `BACKEND_DIR`/`REPO_ROOT` 为 null + server.ts CLI) | backend 侵入大,用户要求还原 |
|
|
89
|
+
| 二 | 保留 CLI/查找链在 backend(72 行无分支增量),发布差异收敛到一个模块(`release/runtime-dir.ts`)+ esbuild onLoad 插件构建期替换 | 用户希望路径全部 env 可传入、删除替换层 |
|
|
90
|
+
| 三(定稿) | 路径全 env 化(`ELLM_APP_DIR`/`ELLM_WEBUI_DIR` 注入由 ps1 承担),删除 release 模块与替换插件,backend 回归零分支 | — |
|
|
91
|
+
|
|
92
|
+
期间曾实现 server.ts CLI(`--config/--port/--help/--version`),随「CLI 独立为 bin 脚本 + 配置路径走 env」定案被还原删除。
|
|
93
|
+
|
|
94
|
+
## 5. 验证基线(发布前回归清单)
|
|
95
|
+
|
|
96
|
+
- `pnpm -r typecheck` + `pnpm -r test` 全绿(typecheck ×2、约 396 测试)
|
|
97
|
+
- CLI:`-h`、`config`(首次创建)、`start`(参数透传链 + env 注入 + 401/200 鉴权)、`stop`(树杀 + 端口释放)、`restart`(运行中/未运行两态)
|
|
98
|
+
- 仓库产物模式(零 env)与包模式(CLI 注入)双模式启动均正常
|
|
99
|
+
- 包目录零读写(`ls` 包目录无新增文件)、`npm pack --dry-run` 核对 tarball 无 `src/`/`test/`/真实配置
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
// harness 配置文件备份/还原(claude-code 与 codex 共用):
|
|
2
|
-
// 开启前备份原文件为 <file>.ellm-bak(已存在则保留最早原始配置不覆盖);关闭 = 从备份还原并删除备份。
|
|
3
|
-
import { existsSync, copyFileSync, rmSync } from "node:fs";
|
|
4
|
-
|
|
5
|
-
/** 备份原文件(<file>.ellm-bak;备份已存在则保留最早原始配置不覆盖)。返回是否新建了备份 */
|
|
6
|
-
export function backupOnce(file) {
|
|
7
|
-
const bak = `${file}.ellm-bak`;
|
|
8
|
-
if (existsSync(bak)) return false;
|
|
9
|
-
if (existsSync(file)) copyFileSync(file, bak);
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** 从备份还原并删除备份;无备份返回 false */
|
|
14
|
-
export function restoreFromBackup(file) {
|
|
15
|
-
const bak = `${file}.ellm-bak`;
|
|
16
|
-
if (!existsSync(bak)) return false;
|
|
17
|
-
copyFileSync(bak, file);
|
|
18
|
-
rmSync(bak, { force: true });
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
1
|
+
// harness 配置文件备份/还原(claude-code 与 codex 共用):
|
|
2
|
+
// 开启前备份原文件为 <file>.ellm-bak(已存在则保留最早原始配置不覆盖);关闭 = 从备份还原并删除备份。
|
|
3
|
+
import { existsSync, copyFileSync, rmSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
/** 备份原文件(<file>.ellm-bak;备份已存在则保留最早原始配置不覆盖)。返回是否新建了备份 */
|
|
6
|
+
export function backupOnce(file) {
|
|
7
|
+
const bak = `${file}.ellm-bak`;
|
|
8
|
+
if (existsSync(bak)) return false;
|
|
9
|
+
if (existsSync(file)) copyFileSync(file, bak);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 从备份还原并删除备份;无备份返回 false */
|
|
14
|
+
export function restoreFromBackup(file) {
|
|
15
|
+
const bak = `${file}.ellm-bak`;
|
|
16
|
+
if (!existsSync(bak)) return false;
|
|
17
|
+
copyFileSync(bak, file);
|
|
18
|
+
rmSync(bak, { force: true });
|
|
19
|
+
return true;
|
|
20
|
+
}
|