ellm-proxy 0.0.6 → 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/bin/commands/credentials.js +100 -0
- package/bin/ellm-proxy.js +4 -1
- package/dist/server.js +108 -2
- package/dist/web-ui/assets/DashboardView-BCwrFrE2.js +1 -0
- package/dist/web-ui/assets/{LayoutView-C9phf7in.js → LayoutView-BePJfgia.js} +1 -1
- package/dist/web-ui/assets/LoginView-Dp7NmXtf.js +1 -0
- package/dist/web-ui/assets/{LogsView-BlXITEJH.js → LogsView-Dyy_zkgg.js} +2 -2
- package/dist/web-ui/assets/{ProcessView-Bgf7-MLQ.js → ProcessView-CpPpMtFg.js} +1 -1
- package/dist/web-ui/assets/{ProvidersView-f8aZkMdw.js → ProvidersView-CYFs76wJ.js} +2 -2
- 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-GohB4JDa.js → index-X5PdkJlA.js} +28 -28
- package/dist/web-ui/assets/{providers-Djtdy2tv.js → providers-H_9x778D.js} +1 -1
- package/dist/web-ui/assets/{useToast-C0UMTbkx.js → useToast-MtbkGJiM.js} +1 -1
- package/dist/web-ui/index.html +1 -1
- package/ellm.config.example.json +2 -0
- package/manager/index.js +1 -1
- package/package.json +1 -1
- package/utils/updater.js +31 -8
- package/dist/web-ui/assets/DashboardView-C3Hw-HpG.js +0 -1
- package/dist/web-ui/assets/LoginView-es6pSPgt.js +0 -1
- package/dist/web-ui/assets/SettingsView-GvAA2vLp.js +0 -1
- package/dist/web-ui/assets/config-Cs3_z63C.js +0 -1
- package/dist/web-ui/assets/useApi-D_iJ2Mca.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{X as w,r as u,l as i}from"./index-X5PdkJlA.js";import{u as C}from"./config-CRWaUFQc.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 a}from"./index-X5PdkJlA.js";function u(){function s(t,o="success"){a({message:t,type:o,duration:3e3})}return{toast:s}}export{u};
|
package/dist/web-ui/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>ELLM - 网关管理台</title>
|
|
8
8
|
<script src="/config.js"></script>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-X5PdkJlA.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/index-BecfPmag.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
package/ellm.config.example.json
CHANGED
package/manager/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// POST /start | /stop | /restart pm2 启动/停止/重启 ellm-proxy(复用 utils/service.js,含探活)
|
|
17
17
|
// GET|POST /autostart 开机自启状态 / 开关(body {enabled};utils/autostart.js,opt-out 尊重用户选择)
|
|
18
18
|
// GET /update 自动更新状态(开关/上次检查/上次更新/supported)
|
|
19
|
-
// POST /update/enabled body {enabled}
|
|
19
|
+
// POST /update/enabled body {enabled};自动更新开关(ellm.config.json 顶层 autoUpdate)
|
|
20
20
|
// POST /update/check 手动检查更新(npm view 私有源)
|
|
21
21
|
// POST /update/apply 手动执行更新(停主服务 → npm i -g → 重启主服务 → manager 延迟自重启)
|
|
22
22
|
// GET /harness Claude Code / Codex / DeepSeek Harness 代理开关状态
|
package/package.json
CHANGED
package/utils/updater.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
// 检查源 = npm 官方 registry(ELLM_UPDATE_REGISTRY 可覆盖);包名取 pkg.name(ellm-proxy)。
|
|
4
4
|
// 更新流程:stopMainOnly(停主服务释放文件句柄,manager 保持存活)→ npm i -g <pkg>@latest →
|
|
5
5
|
// doStart(startOpts)(主服务即新代码)→ 延迟 3s pm2 restart manager 自身(daemon 负责拉起,不依赖自身存活)。
|
|
6
|
-
//
|
|
6
|
+
// 开关状态:enabled 存 ellm.config.json 顶层 autoUpdate(统一配置层),lastCheck/lastApply 属
|
|
7
|
+
// 运行状态仍存 <appDir>/updater.json;兼容老安装——配置未显式设置 autoUpdate 时回退 updater.json
|
|
8
|
+
// (两处都没有 = 默认开启)。
|
|
7
9
|
// 仓库 dev 模式防护:pkgRoot 父目录不是 node_modules(源码运行)时更新域 unsupported,看门狗不启动。
|
|
8
10
|
import { spawn } from "node:child_process";
|
|
9
11
|
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
10
12
|
import { basename, dirname, join } from "node:path";
|
|
11
|
-
import { pkg, pkgRoot, defaultAppDir, MANAGER_APP_NAME } from "./paths.js";
|
|
13
|
+
import { pkg, pkgRoot, defaultAppDir, configPath, MANAGER_APP_NAME } from "./paths.js";
|
|
12
14
|
import { doStart, stopMainOnly } from "./service.js";
|
|
13
15
|
import { pm2cb, withPm2 } from "./pm2.js";
|
|
14
16
|
|
|
@@ -17,27 +19,48 @@ const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
|
17
19
|
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 每小时检查
|
|
18
20
|
const FIRST_CHECK_DELAY_MS = 30 * 1000; // 启动后 30s 首查(错开启动高峰)
|
|
19
21
|
|
|
20
|
-
/**
|
|
22
|
+
/** 更新开关配置路径(lastCheck/lastApply 状态文件) */
|
|
21
23
|
function settingsPath() {
|
|
22
24
|
return join(defaultAppDir, UPDATER_SETTINGS_FILE);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
/**
|
|
27
|
+
/** 配置文件里的更新开关(顶层 autoUpdate;未显式设置/文件缺失/损坏返回 null,由调用方回退) */
|
|
28
|
+
function readAutoUpdateFromConfig() {
|
|
29
|
+
try {
|
|
30
|
+
const value = JSON.parse(readFileSync(configPath(), "utf-8")).autoUpdate;
|
|
31
|
+
return typeof value === "boolean" ? value : null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 读取开关设置:enabled = 配置文件 autoUpdate(显式设置优先)→ 旧 updater.json enabled → 默认开启 */
|
|
26
38
|
export function readUpdaterSettings() {
|
|
27
39
|
try {
|
|
28
40
|
const raw = JSON.parse(readFileSync(settingsPath(), "utf-8"));
|
|
29
|
-
return { enabled: raw.enabled !== false, lastCheck: raw.lastCheck ?? null, lastApply: raw.lastApply ?? null };
|
|
41
|
+
return { enabled: readAutoUpdateFromConfig() ?? raw.enabled !== false, lastCheck: raw.lastCheck ?? null, lastApply: raw.lastApply ?? null };
|
|
30
42
|
} catch {
|
|
31
|
-
return { enabled: true, lastCheck: null, lastApply: null };
|
|
43
|
+
return { enabled: readAutoUpdateFromConfig() ?? true, lastCheck: null, lastApply: null };
|
|
32
44
|
}
|
|
33
45
|
}
|
|
34
46
|
|
|
35
|
-
/**
|
|
47
|
+
/** 合并写回设置:enabled 写配置文件顶层 autoUpdate;lastCheck/lastApply 浅合并写 updater.json
|
|
48
|
+
* (写盘失败不 throw——落盘失败不影响主流程) */
|
|
36
49
|
export function writeUpdaterSettings(patch) {
|
|
50
|
+
if (typeof patch.enabled === "boolean") {
|
|
51
|
+
try {
|
|
52
|
+
const path = configPath();
|
|
53
|
+
const config = JSON.parse(readFileSync(path, "utf-8"));
|
|
54
|
+
writeFileSync(path, JSON.stringify({ ...config, autoUpdate: patch.enabled }, null, 2) + "\n", "utf-8");
|
|
55
|
+
} catch {
|
|
56
|
+
/* 配置只读/缺失等场景容忍:内存态仍可用,下次 toggle 重试 */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
37
59
|
const next = { ...readUpdaterSettings(), ...patch };
|
|
60
|
+
const { enabled: _enabled, ...state } = next;
|
|
38
61
|
try {
|
|
39
62
|
mkdirSync(defaultAppDir, { recursive: true });
|
|
40
|
-
writeFileSync(settingsPath(), JSON.stringify(
|
|
63
|
+
writeFileSync(settingsPath(), JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
41
64
|
} catch {
|
|
42
65
|
/* 只读盘等场景容忍:内存态仍可用 */
|
|
43
66
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{A as T}from"./useApi-D_iJ2Mca.js";import{u as H}from"./providers-Djtdy2tv.js";import{u as J}from"./useToast-C0UMTbkx.js";import{d as Q,p as W,e as h,g as o,w as s,c as V,i as B,r as y,a as _,j as X,o as c,b as Z,k as Y,t as u,f as l,h as p,F as ee,q as te,l as N,_ as ae}from"./index-GohB4JDa.js";import"./config-Cs3_z63C.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),q=N(()=>`http://${L}${x.value?`:${x.value}`:""}`),g=a=>a.toLocaleString(),E=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"),j=_("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:q.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(j,{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(E(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-de31ace5"]]);export{Pe as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{d as h,e as b,g as o,w as l,a,o as _,f as k,Y as x,a2 as f,c as C,i as L,h as B,r as i,m as N,_ as I}from"./index-GohB4JDa.js";import{A as K}from"./useApi-D_iJ2Mca.js";const A={class:"login-wrap"},E=h({__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 _(),b("div",A,[o(y,{shadow:"hover",class:"login-card"},{default:l(()=>[e[3]||(e[3]=k("h2",{class:"login-title"},"ELLM",-1)),o(V,{"label-position":"top",onSubmit:x(r,["prevent"])},{default:l(()=>[o(m,{label:"用户名"},{default:l(()=>[o(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})])}}}),P=I(E,[["__scopeId","data-v-c944978e"]]);export{P as default};
|
|
@@ -1 +0,0 @@
|
|
|
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};
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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};
|