ellm-proxy 0.0.3 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -60
- package/bin/commands/autostart.js +37 -213
- package/bin/commands/config.js +8 -8
- package/bin/commands/credentials.js +100 -0
- package/bin/commands/pm2.js +28 -28
- package/bin/commands/service.js +107 -107
- package/bin/ellm-proxy.js +64 -61
- package/dist/server.js +141 -4
- package/dist/web-ui/assets/DashboardView-BCwrFrE2.js +1 -0
- package/dist/web-ui/assets/DashboardView-BHG4xIdN.css +1 -0
- package/dist/web-ui/assets/LayoutView-BUA8-IzK.css +1 -0
- package/dist/web-ui/assets/LayoutView-BePJfgia.js +1 -0
- package/dist/web-ui/assets/LoginView-Dp7NmXtf.js +1 -0
- package/dist/web-ui/assets/LoginView-DrhfslIY.css +1 -0
- package/dist/web-ui/assets/{LogsView-B2ctrpv4.js → LogsView-Dyy_zkgg.js} +2 -2
- package/dist/web-ui/assets/ProcessView-CpPpMtFg.js +1 -0
- package/dist/web-ui/assets/ProcessView-DK0TvDpR.css +1 -0
- package/dist/web-ui/assets/{ProvidersView-mb0KU16n.js → ProvidersView-CYFs76wJ.js} +2 -2
- package/dist/web-ui/assets/SettingsView-BkwyoOUb.css +1 -0
- package/dist/web-ui/assets/SettingsView-mPcaVAD2.js +1 -0
- package/dist/web-ui/assets/SetupView-DX6S_KpK.js +1 -0
- package/dist/web-ui/assets/SetupView-kPJXJ7dX.css +1 -0
- package/dist/web-ui/assets/config-CRWaUFQc.js +1 -0
- package/dist/web-ui/assets/{index-dPeMJjrz.js → index-X5PdkJlA.js} +28 -28
- package/dist/web-ui/assets/{providers-Dmz2p4Wd.js → providers-H_9x778D.js} +1 -1
- package/dist/web-ui/assets/useManagerApi-C1_COxKg.js +1 -0
- package/dist/web-ui/assets/{useToast-B3quFZyv.js → useToast-MtbkGJiM.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/ellm.config.example.json +2 -0
- 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 +233 -0
- package/dist/web-ui/assets/DashboardView-B71Dy3px.js +0 -1
- 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/useApi-C1Ngt_o5.js +0 -1
- package/dist/web-ui/assets/useManagerApi-BDWJz5o6.js +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{A as T}from"./useApi-C1Ngt_o5.js";import{u as H}from"./providers-Dmz2p4Wd.js";import{u as J}from"./useToast-B3quFZyv.js";import{d as Q,i as W,c as h,a as o,w as s,j as V,k as B,l as y,r as _,m as X,o as c,n as Z,u as Y,t as u,b as l,e as p,F as ee,p as te,f as N,_ as ae}from"./index-dPeMJjrz.js";import"./config-BRaXq0LL.js";const le={class:"flex-col-gap"},oe=["onClick"],se={key:1},ne={class:"flex-between"},de={class:"flex-gap-8"},ue={class:"usage-grid"},ie={class:"usage-cell"},re={class:"usage-num orange"},ce={class:"usage-cell"},ve={class:"usage-num blue"},_e={class:"usage-cell"},pe={class:"usage-num green"},ge={class:"usage-cell"},me={class:"usage-num purple"},fe={class:"usage-cell"},he={class:"usage-num purple"},ye={key:0,class:"usage-cell"},be={class:"usage-num gray"},we={key:0},ke=["onClick"],De=Q({__name:"DashboardView",setup(Ce){const k=H(),{toast:M}=J(),i=y(null),b=y(!0),D=y(""),v=y(null),w=y("today"),C=a=>{const e=a.getFullYear(),t=String(a.getMonth()+1).padStart(2,"0"),r=String(a.getDate()).padStart(2,"0");return`${e}-${t}-${r}`},F=a=>{const e=new Date;let t,r=e;if(a==="today")t=e;else if(a==="yesterday")t=new Date(e),t.setDate(e.getDate()-1),r=t;else if(a==="beforeYesterday")t=new Date(e),t.setDate(e.getDate()-2),r=t;else if(a==="last3days")t=new Date(e),t.setDate(e.getDate()-2);else if(a==="week"){t=new Date(e);const S=(e.getDay()+6)%7;t.setDate(e.getDate()-S)}else if(a==="month")t=new Date(e.getFullYear(),e.getMonth(),1);else return;w.value=a,v.value=[C(t),C(r)],f()},U=a=>{a?F(a):(w.value="",v.value=null,f())},A=()=>{w.value="",f()},I=N(()=>{var e;const a=new Set;for(const t of k.suppliers)for(const r of t.models)a.add(r.modelName);if((e=i.value)!=null&&e.models)for(const t of i.value.models)a.add(t.model);return[...a].sort()}),f=async()=>{var a;b.value=!0;try{const e=new URLSearchParams;D.value&&e.set("model",D.value),((a=v.value)==null?void 0:a.length)===2&&(e.set("startDate",v.value[0]),e.set("endDate",v.value[1]));const t=e.toString();i.value=await T.get(`/api/usage${t?`?${t}`:""}`)}catch(e){console.error(e)}finally{b.value=!1}},R=async()=>{var a;try{const[e]=await Promise.all([T.get("/api/usage"),k.loadProviders(),T.get("/api/status").then(t=>x.value=t.listenPort).catch(()=>x.value)]);if(i.value=e,!((a=v.value)!=null&&a.length)){const t=new Date;v.value=[C(t),C(t)],await f()}}catch(e){console.error(e)}finally{b.value=!1}},L=window.location.hostname,x=y(Number(window.location.port)||null),E=N(()=>`http://${L}${x.value?`:${x.value}`:""}`),g=a=>a.toLocaleString(),j=a=>a==null||a<=0?"-":a>=1e3?(a/1e3).toFixed(2)+"s":Math.round(a)+"ms",$=async a=>{try{await navigator.clipboard.writeText(a),M("已复制到剪贴板","success")}catch{const e=document.createElement("textarea");e.value=a,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select();try{document.execCommand("copy"),M("已复制到剪贴板","success")}catch{M("复制失败","error")}document.body.removeChild(e)}};return W(R),(a,e)=>{const t=_("el-table-column"),r=_("el-table"),S=_("el-card"),m=_("el-option"),P=_("el-select"),q=_("el-date-picker"),z=_("el-button"),K=_("el-tag"),O=_("el-empty"),G=X("loading");return c(),h("div",le,[o(S,{shadow:"never"},{header:s(()=>[...e[3]||(e[3]=[l("span",{class:"fw-600"},"服务信息",-1)])]),default:s(()=>[Z((c(),V(r,{border:"",data:[{key:"API 地址",value:E.value},{key:"供应商数量",value:String(Y(k).providerCount)},{key:"模型总数",value:String(Y(k).totalModelCount)}],"show-header":!1,class:"full-width"},{default:s(()=>[o(t,{prop:"key",width:"160"}),o(t,null,{default:s(({row:d})=>[d.key==="API 地址"?(c(),h("span",{key:0,class:"copy-text",onClick:n=>$(d.value)},u(d.value),9,oe)):(c(),h("span",se,u(d.value),1))]),_:1})]),_:1},8,["data"])),[[G,b.value]])]),_:1}),i.value?(c(),V(S,{key:0,shadow:"never"},{header:s(()=>[l("div",ne,[e[5]||(e[5]=l("span",{class:"fw-600"},"用量统计",-1)),l("div",de,[o(P,{modelValue:D.value,"onUpdate:modelValue":e[0]||(e[0]=d=>D.value=d),placeholder:"全部模型",clearable:"",filterable:"",style:{width:"220px"},onChange:f},{default:s(()=>[(c(!0),h(ee,null,te(I.value,d=>(c(),V(m,{key:d,label:d,value:d},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),o(P,{modelValue:w.value,"onUpdate:modelValue":e[1]||(e[1]=d=>w.value=d),placeholder:"时间范围",clearable:"",style:{width:"130px"},onChange:U},{default:s(()=>[o(m,{label:"今天",value:"today"}),o(m,{label:"昨天",value:"yesterday"}),o(m,{label:"前天",value:"beforeYesterday"}),o(m,{label:"近三天",value:"last3days"}),o(m,{label:"本周",value:"week"}),o(m,{label:"本月",value:"month"})]),_:1},8,["modelValue"]),o(q,{modelValue:v.value,"onUpdate:modelValue":e[2]||(e[2]=d=>v.value=d),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",style:{width:"260px"},onChange:A},null,8,["modelValue"]),o(z,{loading:b.value,onClick:f},{default:s(()=>[...e[4]||(e[4]=[p("刷新",-1)])]),_:1},8,["loading"])]),o(K,{type:"info"},{default:s(()=>[p(u(i.value.total.calls)+" 次调用",1)]),_:1})])]),default:s(()=>{var d;return[l("div",ue,[l("div",ie,[l("div",re,u(g(i.value.total.total_tokens)),1),e[6]||(e[6]=l("div",{class:"usage-label"},"总 Token",-1))]),l("div",ce,[l("div",ve,u(g(i.value.total.input_tokens)),1),e[7]||(e[7]=l("div",{class:"usage-label"},"输入 Token",-1))]),l("div",_e,[l("div",pe,u(g(i.value.total.output_tokens)),1),e[8]||(e[8]=l("div",{class:"usage-label"},"输出 Token",-1))]),l("div",ge,[l("div",me,u(g(i.value.total.cache_read_tokens)),1),e[9]||(e[9]=l("div",{class:"usage-label"},"缓存读取",-1))]),l("div",fe,[l("div",he,u(i.value.total.cache_rate!=null?i.value.total.cache_rate+"%":"-"),1),e[10]||(e[10]=l("div",{class:"usage-label"},"缓存命中率",-1))]),i.value.logStorageMB!=null?(c(),h("div",ye,[l("div",be,u(i.value.logStorageMB),1),e[11]||(e[11]=l("div",{class:"usage-label"},"日志占用 (MB)",-1))])):B("",!0)]),(d=i.value.models)!=null&&d.length?(c(),h("div",we,[e[12]||(e[12]=l("div",{class:"section-title"},"模型用量",-1)),o(r,{data:i.value.models,border:"",stripe:"",class:"full-width"},{default:s(()=>[o(t,{prop:"model",label:"模型","min-width":"200"},{default:s(({row:n})=>[l("span",{class:"copy-text",onClick:xe=>$(n.model)},u(n.model),9,ke)]),_:1}),o(t,{label:"调用次数",width:"90",align:"center"},{default:s(({row:n})=>[p(u(n.calls),1)]),_:1}),o(t,{label:"平均耗时",width:"110",align:"center"},{default:s(({row:n})=>[p(u(j(n.avg_duration)),1)]),_:1}),o(t,{label:"总 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.total_tokens)),1)]),_:1}),o(t,{label:"输入 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.input_tokens)),1)]),_:1}),o(t,{label:"输出 Token",width:"140",align:"center"},{default:s(({row:n})=>[p(u(g(n.output_tokens)),1)]),_:1}),o(t,{label:"缓存读取",width:"130",align:"center"},{default:s(({row:n})=>[p(u(g(n.cache_read_tokens)),1)]),_:1}),o(t,{label:"缓存命中率",width:"110",align:"center"},{default:s(({row:n})=>[p(u(n.cache_rate!=null?n.cache_rate+"%":"-"),1)]),_:1})]),_:1},8,["data"])])):(c(),V(O,{key:1,description:"暂无调用记录","image-size":60}))]}),_:1})):B("",!0)])}}}),Pe=ae(De,[["__scopeId","data-v-a652207c"]]);export{Pe as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.copy-text[data-v-a652207c]{color:#409eff;cursor:pointer}.copy-text[data-v-a652207c]:hover{text-decoration:underline}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as C,c as y,a as t,w as o,b as l,r as s,o as V,e as r,t as W,u as B,f as D,g as L,h as N}from"./index-dPeMJjrz.js";const S={class:"layout"},A={class:"header-nav"},E={class:"header-actions"},I={class:"main"},R={class:"page"},M=C({__name:"LayoutView",setup(T){const d=N(),i=L(),u=(()=>{const n=localStorage.getItem("webui_token")||"";try{return atob(n).split(":")[0]||"用户"}catch{return"用户"}})(),_=D(()=>{const n=d.path;return n==="/"?"/":n.startsWith("/providers")?"/providers":n.startsWith("/settings")?"/settings":n.startsWith("/logs")?"/logs":n.startsWith("/process")?"/process":"/"}),c=()=>{localStorage.removeItem("webui_token"),i.push("/login")},m=n=>{n==="logout"&&c()};return(n,e)=>{const a=s("el-menu-item"),p=s("el-menu"),f=s("ArrowDown"),g=s("el-icon"),w=s("el-button"),v=s("el-dropdown-item"),h=s("el-dropdown-menu"),x=s("el-dropdown"),b=s("el-header"),k=s("router-view");return V(),y("div",S,[t(b,{class:"header"},{default:o(()=>[e[6]||(e[6]=l("h1",null,[l("span",null,"E"),r("LLM")],-1)),l("nav",A,[t(p,{mode:"horizontal","default-active":_.value,ellipsis:!1,router:"",class:"header-menu"},{default:o(()=>[t(a,{index:"/"},{default:o(()=>[...e[0]||(e[0]=[r("首页",-1)])]),_:1}),t(a,{index:"/providers"},{default:o(()=>[...e[1]||(e[1]=[r("供应商管理",-1)])]),_:1}),t(a,{index:"/logs"},{default:o(()=>[...e[2]||(e[2]=[r("日志",-1)])]),_:1}),t(a,{index:"/settings"},{default:o(()=>[...e[3]||(e[3]=[r("设置",-1)])]),_:1}),t(a,{index:"/process"},{default:o(()=>[...e[4]||(e[4]=[r("进程管理",-1)])]),_:1})]),_:1},8,["default-active"])]),l("div",E,[t(x,{trigger:"click",onCommand:m,class:"ml-12"},{dropdown:o(()=>[t(h,null,{default:o(()=>[t(v,{command:"logout"},{default:o(()=>[...e[5]||(e[5]=[r("退出",-1)])]),_:1})]),_:1})]),default:o(()=>[t(w,null,{default:o(()=>[r(W(B(u))+" ",1),t(g,{class:"el-icon--right"},{default:o(()=>[t(f)]),_:1})]),_:1})]),_:1})])]),_:1}),l("main",I,[l("div",R,[t(k)])])])}}});export{M as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.login-card[data-v-d5b6a90c]{max-width:400px;width:100%}.login-title[data-v-d5b6a90c]{text-align:center;margin-bottom:24px;color:#3b82f6}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as b,c as h,a as o,w as l,r as a,o as _,b as k,W as x,a0 as f,j as C,k as L,e as B,l as i,g as N,_ as I}from"./index-dPeMJjrz.js";import{A as K}from"./useApi-C1Ngt_o5.js";const A={class:"login-wrap"},E=b({__name:"LoginView",setup(M){const v=N(),n=i(""),s=i(""),t=i(""),d=i(!1);async function r(){if(!n.value){t.value="请输入用户名";return}if(!s.value){t.value="请输入密码";return}d.value=!0,t.value="";try{const u=await K.post("/api/auth/login",{username:n.value.trim(),password:s.value});localStorage.setItem("webui_token",u.token),v.push("/")}catch(u){t.value=u.message||"登录失败"}finally{d.value=!1}}return(u,e)=>{const c=a("el-input"),m=a("el-form-item"),w=a("el-alert"),g=a("el-button"),V=a("el-form"),y=a("el-card");return _(),h("div",A,[o(y,{shadow:"hover",class:"login-card"},{default:l(()=>[e[3]||(e[3]=k("h2",{class:"login-title"},"ELLM",-1)),o(V,{"label-position":"top",onSubmit:x(r,["prevent"])},{default:l(()=>[o(m,{label:"用户名"},{default:l(()=>[o(c,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=p=>n.value=p),placeholder:"请输入用户名",onKeyup:f(r,["enter"])},null,8,["modelValue"])]),_:1}),o(m,{label:"密码"},{default:l(()=>[o(c,{modelValue:s.value,"onUpdate:modelValue":e[1]||(e[1]=p=>s.value=p),type:"password",placeholder:"请输入密码","show-password":"",onKeyup:f(r,["enter"])},null,8,["modelValue"])]),_:1}),t.value?(_(),C(w,{key:0,title:t.value,type:"error","show-icon":"",class:"mb-16"},null,8,["title"])):L("",!0),o(g,{type:"primary",class:"full-width",loading:d.value,onClick:r},{default:l(()=>[...e[2]||(e[2]=[B(" 登录 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1})])}}}),j=I(E,[["__scopeId","data-v-d5b6a90c"]]);export{j as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as K,i as q,a6 as H,n as J,c as D,a as n,w as l,l as i,r as c,m as Q,o as w,b as d,X as W,e as o,t as r,j as Y,a1 as Z,_ as ee}from"./index-dPeMJjrz.js";import{m as I}from"./useManagerApi-BDWJz5o6.js";import{u as te}from"./useToast-B3quFZyv.js";const ae={class:"flex-col-gap"},le={class:"flex-between"},ne={class:"proc-name"},se={class:"proc-sub"},oe=["onClick"],re={key:1,class:"muted-sm"},ie={class:"desc-full"},de=K({__name:"ProcessView",setup(ue){const{toast:b}=te(),v=i(!0),g=i(!1),y=i([]),x=i(null),p=i(null),h=i(Date.now()),P={daemon:"pm2 守护进程",manager:"管理服务",main:"代理服务"},C=e=>P[e.role]??e.role,k={daemon:"pm2 守护进程:托管代理服务与管理服务的后台守护,进程崩溃时自动拉起;自身不监听端口、不处理业务请求,不允许远程操控(停止它将失去自动恢复能力)。",manager:"管理服务(ellm-proxy-manager):独立常驻的轻量服务,提供进程启停接口与代理开关接口;代理服务停止时保持存活,保证还能在本页远程拉起代理服务。",main:"代理服务(ellm-proxy):网关本体,转发 /v1/* 模型请求,同时承载管理台页面与 API;本页的重启操作即作用于该进程,重启期间网关与管理台 API 短暂不可用(代理服务停止时重启等效于启动)。"},A=e=>k[e.role],_=i(!1),T=i(""),M=i(""),E=e=>{T.value=`${C(e)}(${$(e)})`,M.value=k[e.role],_.value=!0},$=e=>e.role==="daemon"?"pm2":e.role==="manager"?"ellm-proxy-manager":"ellm-proxy",O=e=>e.status==="online",N=e=>e.status?e.status==="online"?"running":e.status==="errored"?"abnormal":"stopped":"stopped",V=e=>{switch(e.status){case"online":return{text:"运行中",type:"success"};case"stopped":return{text:"已停止",type:"info"};case"errored":return{text:"异常",type:"danger"};case"waiting restart":case"launching":return{text:"重启中",type:"warning"};default:return{text:e.status||"未知",type:"info"}}},R=e=>{if(!O(e)||!e.startedAt)return"-";let t=Math.max(0,Math.floor((h.value-e.startedAt)/1e3));const m=Math.floor(t/86400);t-=m*86400;const u=Math.floor(t/3600);t-=u*3600;const s=Math.floor(t/60);return t-=s*60,[m?`${m}天`:"",u?`${u}时`:"",s?`${s}分`:"",`${t}秒`].filter(Boolean).join(" ")},S=e=>e?e>=1024**3?(e/1024**3).toFixed(1)+"G":e>=1024**2?(e/1024**2).toFixed(1)+"M":Math.round(e/1024)+"K":"-",f=async e=>{e||(y.value.length?g.value=!0:v.value=!0);try{y.value=await I.processes(),h.value=Date.now(),x.value=null}catch(t){e||(x.value=t.message)}finally{e||(v.value=!1,g.value=!1)}};let B;const j=()=>{p.value||document.hidden||f(!0)};q(()=>{f(!1),B=window.setInterval(j,5e3)}),H(()=>window.clearInterval(B));const z=async()=>{try{await Z.confirm("确定重启 ellm-proxy 代理服务?重启期间网关与管理台 API 将短暂不可用(代理服务已停止时重启等效于启动)。","重启确认",{confirmButtonText:"重启",cancelButtonText:"取消",type:"warning"})}catch{return}p.value="restart";try{await I.restart(),b("重启完成","success")}catch(e){b(e.message||"操作失败","error")}finally{p.value=null,await f(!0)}};return(e,t)=>{const m=c("el-tag"),u=c("el-button"),s=c("el-table-column"),F=c("el-empty"),L=c("el-table"),U=c("el-card"),X=c("el-dialog"),G=Q("loading");return J((w(),D("div",ae,[n(U,{shadow:"never"},{header:l(()=>[d("div",le,[t[6]||(t[6]=d("span",{class:"fw-600"},"进程管理",-1)),d("div",null,[n(m,{type:"info",class:"mr-8"},{default:l(()=>[...t[4]||(t[4]=[o("刷新间隔 5s",-1)])]),_:1}),n(u,{loading:g.value,onClick:t[0]||(t[0]=a=>f(!1))},{default:l(()=>[...t[5]||(t[5]=[o("刷新",-1)])]),_:1},8,["loading"])])])]),default:l(()=>[n(L,{data:y.value,border:"",stripe:"",class:"full-width"},{empty:l(()=>[n(F,{description:x.value||"暂无进程数据"},{default:l(()=>[n(u,{type:"primary",onClick:t[1]||(t[1]=a=>f(!1))},{default:l(()=>[...t[8]||(t[8]=[o("重试",-1)])]),_:1})]),_:1},8,["description"])]),default:l(()=>[n(s,{label:"进程","min-width":"180"},{default:l(({row:a})=>[d("div",ne,[d("span",{class:W(["status-dot",N(a)])},null,2),o(" "+r(C(a))+" ",1),d("span",se,r($(a)),1)])]),_:1}),n(s,{label:"运行状态",width:"100",align:"center"},{default:l(({row:a})=>[n(m,{type:V(a).type,"disable-transitions":""},{default:l(()=>[o(r(V(a).text),1)]),_:2},1032,["type"])]),_:1}),n(s,{label:"PID",width:"90",align:"center"},{default:l(({row:a})=>[o(r(a.pid??"-"),1)]),_:1}),n(s,{label:"端口",width:"90",align:"center"},{default:l(({row:a})=>[o(r(a.port??"-"),1)]),_:1}),n(s,{label:"运行时长",width:"130",align:"center"},{default:l(({row:a})=>[o(r(R(a)),1)]),_:1}),n(s,{label:"地址","min-width":"170"},{default:l(({row:a})=>[o(r(a.address??"-"),1)]),_:1}),n(s,{label:"内存",width:"90",align:"center"},{default:l(({row:a})=>[o(r(S(a.memory)),1)]),_:1}),n(s,{label:"描述","min-width":"220"},{default:l(({row:a})=>[d("span",{class:"desc-cell",title:"点击查看详情",onClick:ce=>E(a)},r(A(a)),9,oe)]),_:1}),n(s,{label:"操作",width:"90",align:"center"},{default:l(({row:a})=>[a.role==="main"?(w(),Y(u,{key:0,type:"primary",size:"small",loading:p.value==="restart",disabled:p.value!==null,onClick:z},{default:l(()=>[...t[7]||(t[7]=[o(" 重启 ",-1)])]),_:1},8,["loading","disabled"])):(w(),D("span",re,"不可操作"))]),_:1})]),_:1},8,["data"]),t[9]||(t[9]=d("div",{class:"muted-sm"}," 进程控制由管理服务(manager)执行,仅代理服务(ellm-proxy)可重启(已停止时重启等效于启动): 重启期间网关与管理台 API 短暂不可用。 pm2 守护进程与管理服务不允许远程操控(停止它们将失去远程恢复能力)。 ",-1))]),_:1}),n(X,{modelValue:_.value,"onUpdate:modelValue":t[3]||(t[3]=a=>_.value=a),title:T.value,width:"520px"},{footer:l(()=>[n(u,{type:"primary",onClick:t[2]||(t[2]=a=>_.value=!1)},{default:l(()=>[...t[10]||(t[10]=[o("知道了",-1)])]),_:1})]),default:l(()=>[d("p",ie,r(M.value),1)]),_:1},8,["modelValue","title"])])),[[G,v.value]])}}}),_e=ee(de,[["__scopeId","data-v-1404bf7a"]]);export{_e as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@charset "UTF-8";.proc-name[data-v-1404bf7a]{display:flex;align-items:center;gap:8px;font-weight:600}.proc-name .proc-sub[data-v-1404bf7a]{font-weight:400;color:var(--el-text-color-secondary);font-size:12px}.status-dot[data-v-1404bf7a]{display:inline-block;width:8px;height:8px;border-radius:50%}.status-dot.running[data-v-1404bf7a]{background:var(--el-color-success);box-shadow:0 0 4px var(--el-color-success)}.status-dot.stopped[data-v-1404bf7a]{background:var(--el-color-info)}.status-dot.abnormal[data-v-1404bf7a]{background:var(--el-color-danger)}.desc-cell[data-v-1404bf7a]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.desc-full[data-v-1404bf7a]{margin:0;line-height:1.8}.mr-8[data-v-1404bf7a]{margin-right:8px}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.switch-row[data-v-e8a0f661]{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:4px 0}.switch-title[data-v-e8a0f661]{display:flex;align-items:center;gap:8px;font-weight:600;margin-bottom:4px}.switch-desc[data-v-e8a0f661]{color:var(--el-text-color-secondary);font-size:13px;line-height:1.6}.switch-desc code[data-v-e8a0f661]{background:var(--el-fill-color-light);padding:1px 6px;border-radius:4px;font-size:12px}.switch-divider[data-v-e8a0f661]{margin:16px 0}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{u as D}from"./useToast-B3quFZyv.js";import{A as q}from"./useApi-C1Ngt_o5.js";import{u as G}from"./config-BRaXq0LL.js";import{d as N,i as H,n as R,o as x,j as T,w as o,c as U,b as t,t as A,k as B,e as n,a as s,F as L,l as d,a1 as J,r as i,m as M,_ as Q}from"./index-dPeMJjrz.js";import{m as P}from"./useManagerApi-BDWJz5o6.js";const Z={class:"flex-between"},ee={class:"muted-sm mb-8"},le={class:"switch-row"},se={class:"switch-info"},te={class:"switch-title"},oe={class:"switch-row"},ae={class:"switch-info"},ne={class:"switch-title"},ie={class:"switch-row"},ue={class:"switch-info"},de={class:"switch-title"},re=N({__name:"HarnessSwitches",setup(O){const{toast:h}=D(),v=d(!0),k=d(!1),u=d(null),g=d(null),c=d(null),p=d(null),y=d("success"),w=async m=>{m||(u.value?k.value=!0:v.value=!0);try{u.value=await P.harness(),g.value=null}catch(l){m||(g.value=l.message)}finally{m||(v.value=!1,k.value=!1)}};H(()=>w(!1));const E={claudeCode:{label:"Claude Code",file:"~/.claude/settings.json",toggle:P.setClaudeCode},codex:{label:"Codex CLI",file:"~/.codex/config.toml",toggle:P.setCodex},dsh:{label:"DeepSeek Harness",file:"~/.dsh/settings.yaml",toggle:P.setDsh}},b=async(m,l)=>{if(!u.value||c.value)return;const _=E[m],a=l?`确定开启 ${_.label} 代理?将改写 ${_.file}(开启前自动备份,关闭时从备份还原)。`:`确定关闭 ${_.label} 代理?将从备份还原 ${_.file}(无备份时移除代理相关配置)。`;try{await J.confirm(a,l?"开启确认":"关闭确认",{confirmButtonText:l?"开启":"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}c.value=m;try{const e=await _.toggle(l);p.value=e.note??null,y.value=l?"success":"info",h(l?"已开启代理":"已关闭代理","success")}catch(e){h(e.message||"操作失败","error")}finally{c.value=null,await w(!0)}};return(m,l)=>{const _=i("el-button"),a=i("el-alert"),e=i("el-tag"),V=i("el-switch"),S=i("el-divider"),C=i("el-empty"),$=i("el-card"),I=M("loading");return R((x(),T($,{shadow:"never"},{header:o(()=>[t("div",Z,[l[7]||(l[7]=t("span",{class:"fw-600"},"代理开关",-1)),s(_,{loading:k.value,onClick:l[0]||(l[0]=f=>w(!1))},{default:o(()=>[...l[6]||(l[6]=[n("刷新",-1)])]),_:1},8,["loading"])])]),default:o(()=>[u.value?(x(),U(L,{key:0},[t("div",ee," 将本机 CLI 工具的流量一键指向 ellm-proxy 网关("+A(u.value.gatewayBase)+")。 开启前自动备份原配置为 *.ellm-bak(保留最早的原始备份),关闭时从备份还原。 ",1),p.value?(x(),T(a,{key:0,title:p.value,type:y.value,"show-icon":"",class:"mb-8",onClose:l[1]||(l[1]=f=>p.value=null)},null,8,["title","type"])):B("",!0),t("div",le,[t("div",se,[t("div",te,[l[8]||(l[8]=n(" Claude Code ",-1)),s(e,{type:u.value.claudeCode?"success":"info",size:"small","disable-transitions":""},{default:o(()=>[n(A(u.value.claudeCode?"已开启":"未开启"),1)]),_:1},8,["type"])]),l[9]||(l[9]=t("div",{class:"switch-desc"},[n(" 写入 "),t("code",null,"~/.claude/settings.json"),n(" 的 env.ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN, 鉴权取网关配置的 masterKey(未设置将开启失败);其余字段原样保留,重启 Claude Code 会话后生效。 ")],-1))]),s(V,{"model-value":u.value.claudeCode,loading:c.value==="claudeCode",onChange:l[2]||(l[2]=f=>b("claudeCode",!!f))},null,8,["model-value","loading"])]),s(S,{class:"switch-divider"}),t("div",oe,[t("div",ae,[t("div",ne,[l[10]||(l[10]=n(" Codex CLI ",-1)),s(e,{type:u.value.codex?"success":"info",size:"small","disable-transitions":""},{default:o(()=>[n(A(u.value.codex?"已开启":"未开启"),1)]),_:1},8,["type"])]),l[11]||(l[11]=t("div",{class:"switch-desc"},[n(" 写入 "),t("code",null,"~/.codex/config.toml"),n(" 的 model_provider 与 [model_providers.ellm-proxy] 段 (保留用户注释与排版);开启后需按提示设置环境变量 ELLM_PROXY_API_KEY 供 Codex 鉴权。 ")],-1))]),s(V,{"model-value":u.value.codex,loading:c.value==="codex",onChange:l[3]||(l[3]=f=>b("codex",!!f))},null,8,["model-value","loading"])]),s(S,{class:"switch-divider"}),t("div",ie,[t("div",ue,[t("div",de,[l[12]||(l[12]=n(" DeepSeek Harness ",-1)),s(e,{type:u.value.dsh?"success":"info",size:"small","disable-transitions":""},{default:o(()=>[n(A(u.value.dsh?"已开启":"未开启"),1)]),_:1},8,["type"])]),l[13]||(l[13]=t("div",{class:"switch-desc"},[n(" 写入 "),t("code",null,"~/.dsh/settings.yaml"),n(" 的 llm-deepseek.baseURL 与 "),t("code",null,"~/.dsh/.credentials.yaml"),n(" 的 DEEPSEEK_API_KEY(取网关 masterKey,未设置则写占位值);模型请在 dsh 内选择与网关部署名一致的模型。 dsh 为 developer preview,配置格式可能随版本变化。 ")],-1))]),s(V,{"model-value":u.value.dsh,loading:c.value==="dsh",onChange:l[4]||(l[4]=f=>b("dsh",!!f))},null,8,["model-value","loading"])])],64)):(x(),T(C,{key:1,description:g.value||"暂无开关状态"},{default:o(()=>[s(_,{type:"primary",onClick:l[5]||(l[5]=f=>w(!1))},{default:o(()=>[...l[14]||(l[14]=[n("重试",-1)])]),_:1})]),_:1},8,["description"]))]),_:1})),[[I,v.value]])}}}),me=Q(re,[["__scopeId","data-v-e8a0f661"]]),ve={class:"flex-col-gap"},ce={class:"flex-between"},pe={class:"muted-sm"},_e={key:0},fe={class:"flex-end"},xe=N({__name:"SettingsView",setup(O){const{toast:h}=D(),v=G(),k=d("basic"),u=d(!0),g=d(!1),c=d(""),p=d(29770),y=d(null),w=d("passthrough"),E=d(!1),b=d(""),m=d(""),l=async()=>{var a;try{await v.load(),c.value=v.config.masterKey||"",p.value=v.config.port??29770,w.value=v.config.compression==="plain"?"plain":"passthrough",E.value=v.config.logContent===!0;const e=(a=v.config["web-ui"])==null?void 0:a["login-account"];b.value=(e==null?void 0:e.username)||"",m.value=(e==null?void 0:e.password)||"";try{y.value=await q.get("/api/status").then(V=>V.listenPort)}catch{y.value=null}}catch(e){h(e.message,"error")}finally{u.value=!1}},_=async()=>{g.value=!0;try{await v.update(a=>{var e;a.masterKey=c.value.trim(),a.port=p.value,a.compression=w.value,a.logContent=E.value,a["web-ui"]??(a["web-ui"]={}),(e=a["web-ui"])["login-account"]??(e["login-account"]={}),a["web-ui"]["login-account"].username=b.value.trim(),a["web-ui"]["login-account"].password=m.value}),h("设置已保存并生效","success")}catch(a){h(a.message,"error")}finally{g.value=!1}};return H(l),(a,e)=>{const V=i("el-tag"),S=i("el-input"),C=i("el-form-item"),$=i("el-input-number"),I=i("el-option"),f=i("el-select"),z=i("el-switch"),W=i("el-form"),j=i("el-card"),Y=i("el-button"),K=i("el-tab-pane"),F=i("el-tabs"),X=M("loading");return x(),T(F,{modelValue:k.value,"onUpdate:modelValue":e[6]||(e[6]=r=>k.value=r),class:"settings-tabs"},{default:o(()=>[s(K,{label:"基本设置",name:"basic"},{default:o(()=>[R((x(),U("div",ve,[s(j,{shadow:"never"},{header:o(()=>[t("div",ce,[e[8]||(e[8]=t("span",{class:"fw-600"},"基本设置",-1)),s(V,{type:"success"},{default:o(()=>[...e[7]||(e[7]=[n("运行中",-1)])]),_:1})])]),default:o(()=>[s(W,{"label-position":"top"},{default:o(()=>[s(C,{label:"Master Key"},{default:o(()=>[s(S,{modelValue:c.value,"onUpdate:modelValue":e[0]||(e[0]=r=>c.value=r),type:"password",placeholder:"sk-1234","show-password":""},null,8,["modelValue"]),e[9]||(e[9]=t("div",{class:"muted-sm"}," 网关 API 认证密钥(/v1/* 请求需携带 Bearer <masterKey>;留空 = 本地放行) ",-1))]),_:1}),s(C,{label:"监听端口 (port)"},{default:o(()=>[s($,{modelValue:p.value,"onUpdate:modelValue":e[1]||(e[1]=r=>p.value=r),min:1,max:65535,step:1,class:"full-width"},null,8,["modelValue"]),t("div",pe,[e[11]||(e[11]=n(" 网关与管理台 API 的监听端口,保存后下次重启生效 ",-1)),y.value!==null?(x(),U(L,{key:0},[e[10]||(e[10]=n(" ;当前实际监听 ",-1)),t("b",null,A(y.value),1),y.value!==p.value?(x(),U("span",_e,"(由 PORT 环境变量覆盖,此配置值不生效)")):B("",!0)],64)):B("",!0)])]),_:1}),s(C,{label:"压缩语义 (compression)"},{default:o(()=>[s(f,{modelValue:w.value,"onUpdate:modelValue":e[2]||(e[2]=r=>w.value=r),class:"full-width"},{default:o(()=>[s(I,{label:"透传 (passthrough)",value:"passthrough"}),s(I,{label:"明文 (plain)",value:"plain"})]),_:1},8,["modelValue"]),e[12]||(e[12]=t("div",{class:"muted-sm"},"上游响应压缩处理方式,保存后热更新生效",-1))]),_:1}),s(C,{label:"日志内容捕获 (logContent)"},{default:o(()=>[s(z,{modelValue:E.value,"onUpdate:modelValue":e[3]||(e[3]=r=>E.value=r)},null,8,["modelValue"]),e[13]||(e[13]=t("div",{class:"muted-sm"}," 记录完整请求/响应(含上游原始与转换后内容)进日志详情,用于排查定位问题;开启后有少量内存与存储开销,保存后热更新生效 ",-1))]),_:1}),s(C,{label:"管理台用户名 (web-ui.login-account.username)"},{default:o(()=>[s(S,{modelValue:b.value,"onUpdate:modelValue":e[4]||(e[4]=r=>b.value=r),placeholder:"admin"},null,8,["modelValue"]),e[14]||(e[14]=t("div",{class:"muted-sm"}," 管理台登录用户名,留空回退 WEBUI_USERNAME 环境变量或默认 admin ",-1))]),_:1}),s(C,{label:"管理台密码 (web-ui.login-account.password)"},{default:o(()=>[s(S,{modelValue:m.value,"onUpdate:modelValue":e[5]||(e[5]=r=>m.value=r),type:"password",placeholder:"admin","show-password":""},null,8,["modelValue"]),e[15]||(e[15]=t("div",{class:"muted-sm"}," 管理台登录密码,留空回退 WEBUI_PASSWORD 环境变量或默认 admin;修改保存后立即生效,需用新账号重新登录 ",-1))]),_:1})]),_:1})]),_:1}),t("div",fe,[s(Y,{type:"primary",loading:g.value,disabled:g.value,onClick:_},{default:o(()=>[n(A(g.value?"保存中...":"保存设置"),1)]),_:1},8,["loading","disabled"])])])),[[X,u.value]])]),_:1}),s(K,{label:"代理开关",name:"harness",lazy:""},{default:o(()=>[s(me)]),_:1})]),_:1},8,["modelValue"])}}});export{xe as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as p,l as u,f as n}from"./index-dPeMJjrz.js";import{A as i}from"./useApi-C1Ngt_o5.js";const m=p("config",()=>{const e=u({}),o=u(!1),t=n(()=>Array.isArray(e.value.providers)?e.value.providers:[]),l=n(()=>t.value.length),c=n(()=>t.value.reduce((a,d)=>{var s;return a+(((s=d.models)==null?void 0:s.length)??0)},0));async function f(){o.value=!0;try{e.value=await i.get("/api/config")}finally{o.value=!1}}async function r(){return await i.put("/api/config",e.value)}async function v(a){return a(e.value),await r()}return{config:e,loading:o,providers:t,providerCount:l,totalModelCount:c,load:f,save:r,update:v}});export{m as u};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a8 as f}from"./index-dPeMJjrz.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};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var l;const c=((l=window.__ELLM_CONFIG__)==null?void 0:l.API_BASE)??"";function u(){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 h;const i=((h=window.__ELLM_CONFIG__)==null?void 0:h.MANAGER_BASE)??u(),T=3e4;async function r(t,E,s){if(i===null)throw new Error("无法确定管理服务地址:config.js 未配置 MANAGER_BASE,且页面经 80/443 端口访问(无法按端口+1 推导)");const n=new AbortController,w=setTimeout(()=>n.abort(),T);try{const e=await fetch(`${i}${E}`,{method:t,headers:{"Content-Type":"application/json"},body:s!==void 0?JSON.stringify(s):void 0,signal:n.signal}),a=await e.text();let o;try{o=a?JSON.parse(a):{}}catch{throw new Error(`管理服务响应格式错误 (HTTP ${e.status})`)}if(!e.ok||o.ok===!1)throw new Error(o.error||`HTTP ${e.status}`);return o}catch(e){throw e.name==="AbortError"?new Error("请求超时"):e instanceof TypeError?new Error("管理服务不可达(请确认 ellm-proxy 已启动,且本页面通过 localhost/127.0.0.1 访问)"):e}finally{clearTimeout(w)}}const p={status:()=>r("GET","/status"),processes:()=>r("GET","/processes").then(t=>t.processes),start:()=>r("POST","/start"),stop:()=>r("POST","/stop"),restart:()=>r("POST","/restart"),harness:()=>r("GET","/harness"),setClaudeCode:t=>r("POST","/harness/claude-code",{enabled:t}),setCodex:t=>r("POST","/harness/codex",{enabled:t}),setDsh:t=>r("POST","/harness/dsh",{enabled:t})};export{p as m};
|