@hina114514/chaite 1.9.4 → 1.9.5

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.
@@ -1,4 +1,4 @@
1
- import { d as init_adapters, f as createClient, g as AbstractClient, h as OpenAIClient, m as ClaudeClient, p as GeminiClient } from "./src-DGgOl4PZ.mjs";
1
+ import { d as init_adapters, f as createClient, g as AbstractClient, h as OpenAIClient, m as ClaudeClient, p as GeminiClient } from "./src-DpLgiiEQ.mjs";
2
2
  import "./chunks/lop/index.mjs-BWbOMJZv.mjs";
3
3
  import "./chunks/bluebird/index.mjs-D4zFjek8.mjs";
4
4
  import "./chunks/mammoth/index.mjs-C9xwBIgf.mjs";
package/dist/index.d.ts CHANGED
@@ -1567,7 +1567,7 @@ declare const PROCESSOR_TYPE_MAP: {
1567
1567
  };
1568
1568
  //#endregion
1569
1569
  //#region src/version.d.ts
1570
- declare const VERSION = "1.9.4";
1570
+ declare const VERSION = "1.9.5";
1571
1571
  //#endregion
1572
1572
  //#region src/controllers/index.d.ts
1573
1573
  declare function createApp(configure?: (app: Application) => void): Application;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as ChaiteStorage, A as FrontEndAuthHandler, B as VectorizerImpl, C as Channel, D as ExecutableShareableManager, E as ToolManager, F as PureTextFileParser, G as KVHistoryManager, H as RAGManager, I as DocxBufferParser, J as getKey, K as asyncLocalStorage, L as DocxFileParser, M as DEFAULT_PORT, N as GlobalConfig, O as NonExecutableShareableManager, P as PureTextBufferParser, Q as ChaiteResponse, R as PdfBufferParser, S as ChatPreset, T as DefaultCloudService, U as TextProcessor, V as AbstractVectorDatabase, W as InMemoryHistoryManager, X as useEvent, Y as saveAndLoadModule, Z as AbstractUserModeSelector, _ as TriggerManager, _t as AbstractShareable, a as EventTrigger, at as AbstractHistoryManager, b as ProcessorsManager, c as createApp, ct as DocumentFileParser, dt as ToolDTO, et as PostProcessor, f as createClient, ft as ToolsGroupDTO, g as AbstractClient, gt as MultipleKeyStrategyChoice, h as OpenAIClient, ht as DefaultLogger, i as CronTrigger, it as PROCESSOR_TYPE_MAP, j as DEFAULT_HOST, k as getLogger, l as runServer, lt as DocumentPathParser, m as ClaudeClient, mt as ChaiteContext, nt as ProcessorDTO, o as TriggerDTO, ot as SendMessageOption, p as GeminiClient, pt as BaseClientOptions, q as extractClassName, r as BaseTrigger, rt as CHANNEL_STATUS_MAP, s as Chaite, st as ChannelStatistics, t as init_src, tt as PreProcessor, u as VERSION, ut as CustomTool, v as ToolsGroupManager, w as DefaultChannelLoadBalancer, x as ChannelsManager, y as ChatPresetManager, z as PdfFileParser } from "./src-DGgOl4PZ.mjs";
1
+ import { $ as ChaiteStorage, A as FrontEndAuthHandler, B as VectorizerImpl, C as Channel, D as ExecutableShareableManager, E as ToolManager, F as PureTextFileParser, G as KVHistoryManager, H as RAGManager, I as DocxBufferParser, J as getKey, K as asyncLocalStorage, L as DocxFileParser, M as DEFAULT_PORT, N as GlobalConfig, O as NonExecutableShareableManager, P as PureTextBufferParser, Q as ChaiteResponse, R as PdfBufferParser, S as ChatPreset, T as DefaultCloudService, U as TextProcessor, V as AbstractVectorDatabase, W as InMemoryHistoryManager, X as useEvent, Y as saveAndLoadModule, Z as AbstractUserModeSelector, _ as TriggerManager, _t as AbstractShareable, a as EventTrigger, at as AbstractHistoryManager, b as ProcessorsManager, c as createApp, ct as DocumentFileParser, dt as ToolDTO, et as PostProcessor, f as createClient, ft as ToolsGroupDTO, g as AbstractClient, gt as MultipleKeyStrategyChoice, h as OpenAIClient, ht as DefaultLogger, i as CronTrigger, it as PROCESSOR_TYPE_MAP, j as DEFAULT_HOST, k as getLogger, l as runServer, lt as DocumentPathParser, m as ClaudeClient, mt as ChaiteContext, nt as ProcessorDTO, o as TriggerDTO, ot as SendMessageOption, p as GeminiClient, pt as BaseClientOptions, q as extractClassName, r as BaseTrigger, rt as CHANNEL_STATUS_MAP, s as Chaite, st as ChannelStatistics, t as init_src, tt as PreProcessor, u as VERSION, ut as CustomTool, v as ToolsGroupManager, w as DefaultChannelLoadBalancer, x as ChannelsManager, y as ChatPresetManager, z as PdfFileParser } from "./src-DpLgiiEQ.mjs";
2
2
  import "./chunks/lop/index.mjs-BWbOMJZv.mjs";
3
3
  import "./chunks/bluebird/index.mjs-D4zFjek8.mjs";
4
4
  import "./chunks/mammoth/index.mjs-C9xwBIgf.mjs";
@@ -4441,7 +4441,7 @@ var init_trigger$1 = __esm({ "src/controllers/trigger.ts": (() => {
4441
4441
  //#region src/version.ts
4442
4442
  var VERSION;
4443
4443
  var init_version = __esm({ "src/version.ts": (() => {
4444
- VERSION = "1.9.4";
4444
+ VERSION = "1.9.5";
4445
4445
  }) });
4446
4446
 
4447
4447
  //#endregion
@@ -4567,8 +4567,8 @@ var init_basic = __esm({ "src/controllers/basic.ts": (() => {
4567
4567
  res.status(404).json(ChaiteResponse.fail(null, "Channel not found"));
4568
4568
  return;
4569
4569
  }
4570
- const { createClient: createClient$1 } = await import("./adapters-DLb1sL9p.mjs");
4571
- const { ChaiteContext: ChaiteContext$1 } = await import("./types-BN6lBxWd.mjs");
4570
+ const { createClient: createClient$1 } = await import("./adapters-BtoEz02k.mjs");
4571
+ const { ChaiteContext: ChaiteContext$1 } = await import("./types-kzT5dZX7.mjs");
4572
4572
  const ctx = new ChaiteContext$1(chaite.getLogger());
4573
4573
  ctx.setChaite(chaite);
4574
4574
  const client = createClient$1(channel.adapterType, channel.options, ctx);
@@ -1,4 +1,4 @@
1
- import { $ as ChaiteStorage, Q as ChaiteResponse, Z as AbstractUserModeSelector, _t as AbstractShareable, a as EventTrigger, at as AbstractHistoryManager, ct as DocumentFileParser, dt as ToolDTO, et as PostProcessor, ft as ToolsGroupDTO, gt as MultipleKeyStrategyChoice, ht as DefaultLogger, i as CronTrigger, lt as DocumentPathParser, mt as ChaiteContext, n as init_types, nt as ProcessorDTO, o as TriggerDTO, ot as SendMessageOption, pt as BaseClientOptions, r as BaseTrigger, st as ChannelStatistics, tt as PreProcessor, ut as CustomTool } from "./src-DGgOl4PZ.mjs";
1
+ import { $ as ChaiteStorage, Q as ChaiteResponse, Z as AbstractUserModeSelector, _t as AbstractShareable, a as EventTrigger, at as AbstractHistoryManager, ct as DocumentFileParser, dt as ToolDTO, et as PostProcessor, ft as ToolsGroupDTO, gt as MultipleKeyStrategyChoice, ht as DefaultLogger, i as CronTrigger, lt as DocumentPathParser, mt as ChaiteContext, n as init_types, nt as ProcessorDTO, o as TriggerDTO, ot as SendMessageOption, pt as BaseClientOptions, r as BaseTrigger, st as ChannelStatistics, tt as PreProcessor, ut as CustomTool } from "./src-DpLgiiEQ.mjs";
2
2
  import "./chunks/lop/index.mjs-BWbOMJZv.mjs";
3
3
  import "./chunks/bluebird/index.mjs-D4zFjek8.mjs";
4
4
  import "./chunks/mammoth/index.mjs-C9xwBIgf.mjs";
@@ -1,18 +1,533 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
3
-
2
+ <html lang="zh-CN">
4
3
  <head>
5
- <meta charset="UTF-8" />
6
- <link rel="icon" href="/favicon.svg" />
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
- <title>Chaite Dashboard</title>
9
- <script type="module" crossorigin src="/assets/index-DzBQLoNi.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-Dn5ZzrRm.css">
11
- </head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
6
+ <title>Chaite Dashboard</title>
7
+ <style>
8
+ *{margin:0;padding:0;box-sizing:border-box}
9
+ :root{
10
+ --bg:#0c0e14;--surface:#141720;--surface2:#1a1e2a;--surface3:#222738;
11
+ --border:#2a2f42;--text:#e0e4ef;--text2:#8b92a8;--accent:#6c8cff;--accent2:#4a6aef;
12
+ --green:#4ade80;--red:#f87171;--yellow:#facc15;--purple:#a78bfa;
13
+ --radius:10px;--shadow:0 2px 12px rgba(0,0,0,.4);
14
+ }
15
+ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;line-height:1.6;min-height:100vh}
16
+ a{color:var(--accent);text-decoration:none}
17
+ a:hover{text-decoration:underline}
18
+ .app{max-width:1280px;margin:0 auto;padding:20px}
19
+
20
+ /* Login */
21
+ .login-wrap{display:flex;align-items:center;justify-content:center;min-height:100vh}
22
+ .login-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:48px 40px;width:380px;text-align:center;box-shadow:var(--shadow)}
23
+ .login-box h1{font-size:24px;margin-bottom:8px}
24
+ .login-box p{color:var(--text2);font-size:14px;margin-bottom:32px}
25
+ .login-box .spinner{display:inline-block;width:18px;height:18px;border:2px solid var(--text2);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite}
26
+ .login-box .err{color:var(--red);font-size:13px;margin-bottom:12px;min-height:20px}
27
+ @keyframes spin{to{transform:rotate(360deg)}}
28
+
29
+ /* Header */
30
+ .header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:24px}
31
+ .header h1{font-size:20px;display:flex;align-items:center;gap:8px}
32
+ .header h1 span{color:var(--accent)}
33
+ .header .meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
34
+ .badge{background:var(--surface2);border:1px solid var(--border);border-radius:20px;padding:3px 12px;font-size:12px;color:var(--text2)}
35
+ .badge.ok{border-color:var(--green);color:var(--green)}
36
+ .badge.err{border-color:var(--red);color:var(--red)}
37
+
38
+ /* Buttons */
39
+ .btn{background:var(--surface2);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:6px 14px;font-size:13px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:all .15s}
40
+ .btn:hover{background:var(--surface3);border-color:var(--accent)}
41
+ .btn:disabled{opacity:.5;cursor:not-allowed}
42
+ .btn.primary{background:var(--accent2);border-color:var(--accent);color:#fff}
43
+ .btn.primary:hover{background:var(--accent)}
44
+ .btn.danger{border-color:var(--red);color:var(--red)}
45
+ .btn.danger:hover{background:rgba(248,113,113,.15)}
46
+ .btn.sm{padding:3px 10px;font-size:12px}
47
+
48
+ /* Cards */
49
+ .card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:16px;box-shadow:var(--shadow)}
50
+ .card h3{font-size:15px;margin-bottom:14px;display:flex;align-items:center;gap:8px}
51
+ .card h3 .dot{width:8px;height:8px;border-radius:50%}
12
52
 
53
+ /* Stat cards */
54
+ .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:20px}
55
+ .stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px;text-align:center}
56
+ .stat .val{font-size:28px;font-weight:700;color:var(--accent)}
57
+ .stat .label{font-size:12px;color:var(--text2);margin-top:4px}
58
+
59
+ /* Table */
60
+ .tbl{width:100%;border-collapse:collapse;font-size:13px}
61
+ .tbl th{text-align:left;padding:10px 12px;color:var(--text2);font-weight:600;border-bottom:1px solid var(--border);font-size:12px;text-transform:uppercase;letter-spacing:.5px}
62
+ .tbl td{padding:10px 12px;border-bottom:1px solid var(--border)}
63
+ .tbl tr:hover td{background:var(--surface2)}
64
+ .pill{display:inline-block;padding:2px 10px;border-radius:12px;font-size:11px;font-weight:600}
65
+ .pill.on{background:rgba(74,222,128,.15);color:var(--green)}
66
+ .pill.off{background:rgba(248,113,113,.12);color:var(--red)}
67
+ .pill.model{background:rgba(108,140,255,.12);color:var(--accent);margin:2px 3px 2px 0}
68
+
69
+ /* Tabs */
70
+ .tabs{display:flex;gap:0;margin-bottom:20px;border-bottom:1px solid var(--border)}
71
+ .tab{padding:10px 20px;font-size:13px;cursor:pointer;color:var(--text2);border-bottom:2px solid transparent;transition:all .15s}
72
+ .tab:hover{color:var(--text)}
73
+ .tab.active{color:var(--accent);border-bottom-color:var(--accent)}
74
+
75
+ /* Modal */
76
+ .modal-mask{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:100}
77
+ .modal{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:28px;width:520px;max-height:85vh;overflow-y:auto;box-shadow:0 8px 32px rgba(0,0,0,.5)}
78
+ .modal h3{margin-bottom:20px;font-size:17px}
79
+ .form-row{margin-bottom:14px}
80
+ .form-row label{display:block;font-size:12px;color:var(--text2);margin-bottom:5px}
81
+ .form-row input,.form-row select,.form-row textarea{width:100%;padding:8px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;font-family:inherit}
82
+ .form-row textarea{min-height:80px;resize:vertical}
83
+ .form-row input:focus,.form-row select:focus,.form-row textarea:focus{outline:none;border-color:var(--accent)}
84
+ .modal-btns{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}
85
+
86
+ /* Toast */
87
+ .toast{position:fixed;top:20px;right:20px;padding:10px 20px;border-radius:8px;font-size:13px;z-index:200;animation:slideIn .3s}
88
+ .toast.ok{background:var(--green);color:#000}
89
+ .toast.err{background:var(--red);color:#fff}
90
+ @keyframes slideIn{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
91
+
92
+ /* Responsive */
93
+ @media(max-width:768px){
94
+ .stats{grid-template-columns:repeat(2,1fr)}
95
+ .modal{width:95%;margin:10px}
96
+ .header{flex-direction:column;align-items:flex-start}
97
+ }
98
+ </style>
99
+ </head>
13
100
  <body>
14
- <div id="appLoading"></div>
15
- <div id="app"></div>
16
- </body>
101
+ <div id="app"></div>
102
+ <script>
103
+ const{createApp,ref,reactive,computed,onMounted,onUnmounted,watch}=Vue
104
+
105
+ const API=(path,opt={})=>{
106
+ const t=localStorage.getItem('chaite_jwt')
107
+ return fetch(path,{...opt,headers:{'Content-Type':'application/json','Authorization':t?'Bearer '+t:'',...(opt.headers||{})}}).then(async r=>{
108
+ if(r.status===401){localStorage.removeItem('chaite_jwt');location.reload()}
109
+ return r.json()
110
+ })
111
+ }
112
+
113
+ createApp({
114
+ setup(){
115
+ // Auth
116
+ const jwt=ref(localStorage.getItem('chaite_jwt')||'')
117
+ const loggedIn=ref(!!jwt.value)
118
+ const loginLoading=ref(false)
119
+ const loginError=ref('')
120
+
121
+ // State
122
+ const tab=ref('overview')
123
+ const health=ref(null)
124
+ const stats=ref(null)
125
+ const channels=ref([])
126
+ const tools=ref([])
127
+ const presets=ref([])
128
+ const processors=ref([])
129
+ const triggers=ref([])
130
+ const toolGroups=ref([])
131
+ const loading=ref(true)
132
+ const refreshTime=ref('')
133
+ let timer=null
134
+
135
+ // Modal
136
+ const modal=ref(null) // 'channel'|'preset'|'tool'|'processor'|'trigger'|'toolGroup'|null
137
+ const editing=ref(null) // object being edited
138
+ const form=ref({})
139
+
140
+ // Toast
141
+ const toastMsg=ref('')
142
+ const toastType=ref('ok')
143
+ let toastTimer=null
144
+ function toast(msg,type='ok'){toastMsg.value=msg;toastType.value=type;clearTimeout(toastTimer);toastTimer=setTimeout(()=>toastMsg.value='',3000)}
145
+
146
+ // Login
147
+ async function doLogin(){
148
+ loginError.value='';loginLoading.value=true
149
+ try{
150
+ const url=new URL(location.href)
151
+ const urlToken=url.searchParams.get('token')
152
+ const token=urlToken||prompt('Enter access token:')
153
+ if(!token){loginLoading.value=false;return}
154
+ const d=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token})}).then(r=>r.json())
155
+ if(d.data?.token){
156
+ jwt.value=d.data.token;localStorage.setItem('chaite_jwt',d.data.token);loggedIn.value=true
157
+ url.searchParams.delete('token');history.replaceState(null,'',url.pathname+url.search)
158
+ refreshAll()
159
+ }else{loginError.value=d.message||'Login failed'}
160
+ }catch(e){loginError.value='Network error'}
161
+ finally{loginLoading.value=false}
162
+ }
163
+
164
+ function logout(){jwt.value='';localStorage.removeItem('chaite_jwt');loggedIn.value=false;clearInterval(timer)}
165
+
166
+ // Data fetching
167
+ async function refreshAll(){
168
+ loading.value=true
169
+ try{
170
+ const[h,s,c,t,p,pr,tr,tg]=await Promise.all([
171
+ API('/api/system/health').catch(()=>({})),
172
+ API('/api/system/stats').catch(()=>({})),
173
+ API('/api/channels/list').catch(()=>({})),
174
+ API('/api/tools/list').catch(()=>({})),
175
+ API('/api/preset/list').catch(()=>({})),
176
+ API('/api/processors/list').catch(()=>({})),
177
+ API('/api/triggers/list').catch(()=>({})),
178
+ API('/api/toolGroups/list').catch(()=>({})),
179
+ ])
180
+ health.value=h.data||null
181
+ stats.value=s.data||null
182
+ channels.value=c.data||[]
183
+ tools.value=t.data||[]
184
+ presets.value=p.data||[]
185
+ processors.value=pr.data||[]
186
+ triggers.value=tr.data||[]
187
+ toolGroups.value=tg.data||[]
188
+ refreshTime.value=new Date().toLocaleTimeString()
189
+ }catch(e){console.error(e)}
190
+ finally{loading.value=false}
191
+ }
192
+
193
+ // CRUD helpers
194
+ async function testChannel(id){
195
+ const d=await API('/api/system/test-channel',{method:'POST',body:JSON.stringify({channelId:id})})
196
+ if(d.data?.status==='ok')toast(`✅ ${d.data.channelName} — ${d.data.latency}ms`)
197
+ else toast(`❌ ${d.data?.error||d.message}`,'err')
198
+ }
199
+
200
+ function openModal(type,item=null){
201
+ modal.value=type
202
+ editing.value=item
203
+ if(item)form.value=JSON.parse(JSON.stringify(item))
204
+ else{
205
+ const defaults={
206
+ channel:{name:'',adapterType:'openai',type:'openai',models:[''],options:{baseUrl:'',apiKey:''},status:'enabled',weight:1,priority:0},
207
+ preset:{name:'',prefix:'',sendMessageOption:{model:'',temperature:0.8,maxToken:4096,systemOverride:''}},
208
+ tool:{name:'',description:'',code:''},
209
+ processor:{name:'',type:'pre',description:'',code:''},
210
+ trigger:{name:'',description:'',code:''},
211
+ toolGroup:{name:'',description:'',toolIds:[],status:'enabled',isDefault:false},
212
+ }
213
+ form.value=defaults[type]||{}
214
+ }
215
+ }
216
+ function closeModal(){modal.value=null;editing.value=null;form.value={}}
217
+
218
+ async function saveItem(){
219
+ const type=modal.value
220
+ const apiMap={channel:'/api/channels',preset:'/api/preset',tool:'/api/tools',processor:'/api/processors',trigger:'/api/triggers',toolGroup:'/api/toolGroups'}
221
+ try{
222
+ const id=editing.value?.id
223
+ const url=apiMap[type]+(id?'/'+id:'')
224
+ const method=id?'PUT':'POST'
225
+ const d=await API(url,{method,body:JSON.stringify(form.value)})
226
+ if(d.data!==undefined){toast('Saved');closeModal();refreshAll()}
227
+ else toast(d.message||'Error','err')
228
+ }catch(e){toast(e.message,'err')}
229
+ }
17
230
 
231
+ async function deleteItem(type,id){
232
+ if(!confirm('Delete this item?'))return
233
+ const apiMap={channel:'/api/channels',preset:'/api/preset',tool:'/api/tools',processor:'/api/processors',trigger:'/api/triggers',toolGroup:'/api/toolGroups'}
234
+ try{
235
+ await API(apiMap[type]+'/'+id,{method:'DELETE'})
236
+ toast('Deleted');refreshAll()
237
+ }catch(e){toast(e.message,'err')}
238
+ }
239
+
240
+ function uptimeStr(s){
241
+ if(!s)return '-'
242
+ const h=Math.floor(s/3600),m=Math.floor(s%3600/60),sec=s%60
243
+ return h>0?`${h}h ${m}m ${sec}s`:`${m}m ${sec}s`
244
+ }
245
+ function fmtNum(n){if(n>=1e6)return(n/1e6).toFixed(1)+'M';if(n>=1e3)return(n/1e3).toFixed(1)+'K';return String(n||0)}
246
+
247
+ onMounted(()=>{
248
+ if(loggedIn.value)refreshAll()
249
+ timer=setInterval(()=>{if(logged.value)refreshAll()},30000)
250
+ })
251
+ onUnmounted(()=>clearInterval(timer))
252
+
253
+ const loggedIn=computed(()=>!!jwt.value)
254
+
255
+ return{
256
+ jwt,loggedIn,loginLoading,loginError,doLogin,logout,
257
+ tab,health,stats,channels,tools,presets,processors,triggers,toolGroups,loading,refreshTime,
258
+ modal,editing,form,openModal,closeModal,saveItem,deleteItem,testChannel,
259
+ toastMsg,toastType,uptimeStr,fmtNum,refreshAll,
260
+ }
261
+ },
262
+ template:`
263
+ <!-- Toast -->
264
+ <div v-if="toastMsg" :class="['toast',toastType]">{{toastMsg}}</div>
265
+
266
+ <!-- Login -->
267
+ <div v-if="!loggedIn" class="login-wrap">
268
+ <div class="login-box">
269
+ <h1>⚡ <span style="color:var(--accent)">Chaite</span></h1>
270
+ <p>Management Dashboard</p>
271
+ <div v-if="!loginLoading">
272
+ <div style="margin-bottom:16px">
273
+ <input v-model="loginInput" type="password" placeholder="Paste access token here..."
274
+ @keyup.enter="doLogin" autofocus
275
+ style="width:100%;padding:10px 14px;border-radius:8px;border:1px solid var(--border);background:var(--surface2);color:var(--text);font-size:14px;outline:none"/>
276
+ </div>
277
+ <div class="err">{{loginError}}</div>
278
+ <button class="btn primary" @click="doLogin" style="width:100%;padding:10px">Login</button>
279
+ <p style="margin-top:16px;font-size:12px;color:var(--text2)">Or append <code>?token=xxx</code> to URL</p>
280
+ </div>
281
+ <div v-else><div class="spinner"></div></div>
282
+ </div>
283
+ </div>
284
+
285
+ <!-- Dashboard -->
286
+ <div v-else class="app">
287
+ <!-- Header -->
288
+ <div class="header">
289
+ <h1>⚡ <span>Chaite</span> Dashboard</h1>
290
+ <div class="meta">
291
+ <span class="badge" v-if="refreshTime">↻ {{refreshTime}}</span>
292
+ <span class="badge ok" v-if="health?.status==='ok'">Healthy</span>
293
+ <span class="badge err" v-else-if="health">Error</span>
294
+ <button class="btn" @click="refreshAll" :disabled="loading">
295
+ <span v-if="loading" style="display:inline-block;width:14px;height:14px;border:2px solid var(--text2);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite"></span>
296
+ <span v-else>↻ Refresh</span>
297
+ </button>
298
+ <button class="btn" @click="logout">Logout</button>
299
+ </div>
300
+ </div>
301
+
302
+ <!-- Stats -->
303
+ <div class="stats" v-if="health">
304
+ <div class="stat"><div class="val">{{uptimeStr(health.uptime)}}</div><div class="label">Uptime</div></div>
305
+ <div class="stat"><div class="val">{{health.channels?.total||0}}</div><div class="label">Channels</div></div>
306
+ <div class="stat"><div class="val">{{health.models?.count||0}}</div><div class="label">Models</div></div>
307
+ <div class="stat"><div class="val">{{health.tools?.count||0}}</div><div class="label">Tools</div></div>
308
+ <div class="stat"><div class="val">{{health.system?.processMemory||0}}MB</div><div class="label">Memory</div></div>
309
+ <div class="stat"><div class="val">{{fmtNum(stats?.summary?.totalCalls)}}</div><div class="label">Total Calls</div></div>
310
+ </div>
311
+
312
+ <!-- Tabs -->
313
+ <div class="tabs">
314
+ <div :class="['tab',tab==='overview'&&'active']" @click="tab='overview'">Overview</div>
315
+ <div :class="['tab',tab==='channels'&&'active']" @click="tab='channels'">Channels</div>
316
+ <div :class="['tab',tab==='tools'&&'active']" @click="tab='tools'">Tools</div>
317
+ <div :class="['tab',tab==='presets'&&'active']" @click="tab='presets'">Presets</div>
318
+ <div :class="['tab',tab==='processors'&&'active']" @click="tab='processors'">Processors</div>
319
+ <div :class="['tab',tab==='triggers'&&'active']" @click="tab='triggers'">Triggers</div>
320
+ <div :class="['tab',tab==='groups'&&'active']" @click="tab='groups'">Groups</div>
321
+ </div>
322
+
323
+ <!-- Overview -->
324
+ <div v-if="tab==='overview'">
325
+ <div class="card" v-if="health">
326
+ <h3><span class="dot" style="background:var(--green)"></span> System</h3>
327
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;font-size:13px">
328
+ <div><span style="color:var(--text2)">Version:</span> {{health.version}}</div>
329
+ <div><span style="color:var(--text2)">Node:</span> {{health.system?.nodeVersion}}</div>
330
+ <div><span style="color:var(--text2)">Platform:</span> {{health.system?.platform}} {{health.system?.arch}}</div>
331
+ <div><span style="color:var(--text2)">CPUs:</span> {{health.system?.cpus}}</div>
332
+ <div><span style="color:var(--text2)">Heap:</span> {{health.system?.heapUsed}}MB / {{health.system?.processMemory}}MB</div>
333
+ <div><span style="color:var(--text2)">System Mem:</span> {{health.system?.freeMemory}}MB free / {{health.system?.totalMemory}}MB</div>
334
+ </div>
335
+ </div>
336
+
337
+ <div class="card" v-if="health?.models?.list?.length">
338
+ <h3>Available Models</h3>
339
+ <div><span v-for="m in health.models.list" class="pill model">{{m}}</span></div>
340
+ </div>
341
+
342
+ <div class="card" v-if="stats">
343
+ <h3>Usage by Model</h3>
344
+ <table class="tbl">
345
+ <tr><th>Model</th><th>Calls</th><th>Tokens</th></tr>
346
+ <tr v-for="(v,k) in stats.perModel" :key="k">
347
+ <td><span class="pill model">{{k}}</span></td>
348
+ <td>{{fmtNum(v.calls)}}</td>
349
+ <td>{{fmtNum(v.tokens)}}</td>
350
+ </tr>
351
+ <tr v-if="!Object.keys(stats.perModel||{}).length"><td colspan="3" style="color:var(--text2)">No usage data</td></tr>
352
+ </table>
353
+ </div>
354
+ </div>
355
+
356
+ <!-- Channels -->
357
+ <div v-if="tab==='channels'">
358
+ <div class="card">
359
+ <h3 style="justify-content:space-between">Channels <button class="btn primary sm" @click="openModal('channel')">+ New</button></h3>
360
+ <table class="tbl">
361
+ <tr><th>Status</th><th>Name</th><th>Adapter</th><th>Models</th><th>Priority</th><th>Calls</th><th>Actions</th></tr>
362
+ <tr v-for="ch in channels" :key="ch.id">
363
+ <td><span :class="['pill',ch.status==='enabled'?'on':'off']">{{ch.status}}</span></td>
364
+ <td>{{ch.name}}</td>
365
+ <td>{{ch.adapterType}}</td>
366
+ <td><span v-for="m in (ch.models||[])" class="pill model">{{m}}</span></td>
367
+ <td>{{ch.priority}}</td>
368
+ <td>{{fmtNum(ch.statistics?.callTimes)}}</td>
369
+ <td>
370
+ <button class="btn sm" @click="testChannel(ch.id)">Test</button>
371
+ <button class="btn sm" @click="openModal('channel',ch)">Edit</button>
372
+ <button class="btn sm danger" @click="deleteItem('channel',ch.id)">Del</button>
373
+ </td>
374
+ </tr>
375
+ <tr v-if="!channels.length"><td colspan="7" style="color:var(--text2)">No channels</td></tr>
376
+ </table>
377
+ </div>
378
+ </div>
379
+
380
+ <!-- Tools -->
381
+ <div v-if="tab==='tools'">
382
+ <div class="card">
383
+ <h3 style="justify-content:space-between">Tools <button class="btn primary sm" @click="openModal('tool')">+ New</button></h3>
384
+ <table class="tbl">
385
+ <tr><th>Name</th><th>Description</th><th>Actions</th></tr>
386
+ <tr v-for="t in tools" :key="t.id">
387
+ <td>{{t.name||t.id}}</td>
388
+ <td style="max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{t.description}}</td>
389
+ <td>
390
+ <button class="btn sm" @click="openModal('tool',t)">Edit</button>
391
+ <button class="btn sm danger" @click="deleteItem('tool',t.id)">Del</button>
392
+ </td>
393
+ </tr>
394
+ <tr v-if="!tools.length"><td colspan="3" style="color:var(--text2)">No tools</td></tr>
395
+ </table>
396
+ </div>
397
+ </div>
398
+
399
+ <!-- Presets -->
400
+ <div v-if="tab==='presets'">
401
+ <div class="card">
402
+ <h3 style="justify-content:space-between">Presets <button class="btn primary sm" @click="openModal('preset')">+ New</button></h3>
403
+ <table class="tbl">
404
+ <tr><th>Name</th><th>Prefix</th><th>Model</th><th>Temp</th><th>Actions</th></tr>
405
+ <tr v-for="p in presets" :key="p.id">
406
+ <td>{{p.name}}</td>
407
+ <td><code>{{p.prefix}}</code></td>
408
+ <td><span class="pill model">{{p.sendMessageOption?.model||'-'}}</span></td>
409
+ <td>{{p.sendMessageOption?.temperature}}</td>
410
+ <td>
411
+ <button class="btn sm" @click="openModal('preset',p)">Edit</button>
412
+ <button class="btn sm danger" @click="deleteItem('preset',p.id)">Del</button>
413
+ </td>
414
+ </tr>
415
+ <tr v-if="!presets.length"><td colspan="5" style="color:var(--text2)">No presets</td></tr>
416
+ </table>
417
+ </div>
418
+ </div>
419
+
420
+ <!-- Processors -->
421
+ <div v-if="tab==='processors'">
422
+ <div class="card">
423
+ <h3 style="justify-content:space-between">Processors <button class="btn primary sm" @click="openModal('processor')">+ New</button></h3>
424
+ <table class="tbl">
425
+ <tr><th>Name</th><th>Type</th><th>Description</th><th>Actions</th></tr>
426
+ <tr v-for="p in processors" :key="p.id">
427
+ <td>{{p.name}}</td>
428
+ <td><span class="pill" :style="{background:p.type==='pre'?'rgba(167,139,250,.15)':'rgba(250,204,21,.15)',color:p.type==='pre'?'var(--purple)':'var(--yellow)'}">{{p.type}}</span></td>
429
+ <td>{{p.description}}</td>
430
+ <td>
431
+ <button class="btn sm" @click="openModal('processor',p)">Edit</button>
432
+ <button class="btn sm danger" @click="deleteItem('processor',p.id)">Del</button>
433
+ </td>
434
+ </tr>
435
+ <tr v-if="!processors.length"><td colspan="4" style="color:var(--text2)">No processors</td></tr>
436
+ </table>
437
+ </div>
438
+ </div>
439
+
440
+ <!-- Triggers -->
441
+ <div v-if="tab==='triggers'">
442
+ <div class="card">
443
+ <h3 style="justify-content:space-between">Triggers <button class="btn primary sm" @click="openModal('trigger')">+ New</button></h3>
444
+ <table class="tbl">
445
+ <tr><th>Name</th><th>Description</th><th>Actions</th></tr>
446
+ <tr v-for="t in triggers" :key="t.id">
447
+ <td>{{t.name||t.id}}</td>
448
+ <td>{{t.description}}</td>
449
+ <td>
450
+ <button class="btn sm" @click="openModal('trigger',t)">Edit</button>
451
+ <button class="btn sm danger" @click="deleteItem('trigger',t.id)">Del</button>
452
+ </td>
453
+ </tr>
454
+ <tr v-if="!triggers.length"><td colspan="3" style="color:var(--text2)">No triggers</td></tr>
455
+ </table>
456
+ </div>
457
+ </div>
458
+
459
+ <!-- Tool Groups -->
460
+ <div v-if="tab==='groups'">
461
+ <div class="card">
462
+ <h3 style="justify-content:space-between">Tool Groups <button class="btn primary sm" @click="openModal('toolGroup')">+ New</button></h3>
463
+ <table class="tbl">
464
+ <tr><th>Name</th><th>Description</th><th>Default</th><th>Actions</th></tr>
465
+ <tr v-for="g in toolGroups" :key="g.id">
466
+ <td>{{g.name}}</td>
467
+ <td>{{g.description}}</td>
468
+ <td><span v-if="g.isDefault" class="pill on">Default</span></td>
469
+ <td>
470
+ <button class="btn sm" @click="openModal('toolGroup',g)">Edit</button>
471
+ <button class="btn sm danger" @click="deleteItem('toolGroup',g.id)">Del</button>
472
+ </td>
473
+ </tr>
474
+ <tr v-if="!toolGroups.length"><td colspan="4" style="color:var(--text2)">No groups</td></tr>
475
+ </table>
476
+ </div>
477
+ </div>
478
+
479
+ <!-- Modal -->
480
+ <div v-if="modal" class="modal-mask" @click.self="closeModal">
481
+ <div class="modal">
482
+ <h3>{{editing?'Edit':'New'}} {{modal}}</h3>
483
+
484
+ <!-- Channel form -->
485
+ <template v-if="modal==='channel'">
486
+ <div class="form-row"><label>Name</label><input v-model="form.name"/></div>
487
+ <div class="form-row"><label>Adapter Type</label><select v-model="form.adapterType"><option>openai</option><option>gemini</option><option>claude</option></select></div>
488
+ <div class="form-row"><label>Models (comma separated)</label><input v-model="form._models" :placeholder="(form.models||[]).join(', ')"/></div>
489
+ <div class="form-row"><label>Base URL</label><input v-model="form.options.baseUrl"/></div>
490
+ <div class="form-row"><label>API Key</label><input v-model="form.options.apiKey" type="password"/></div>
491
+ <div class="form-row"><label>Priority</label><input v-model.number="form.priority" type="number"/></div>
492
+ <div class="form-row"><label>Weight</label><input v-model.number="form.weight" type="number"/></div>
493
+ <div class="form-row"><label>Status</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
494
+ </template>
495
+
496
+ <!-- Preset form -->
497
+ <template v-if="modal==='preset'">
498
+ <div class="form-row"><label>Name</label><input v-model="form.name"/></div>
499
+ <div class="form-row"><label>Prefix</label><input v-model="form.prefix"/></div>
500
+ <div class="form-row"><label>Model</label><input v-model="form.sendMessageOption.model"/></div>
501
+ <div class="form-row"><label>Temperature</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1"/></div>
502
+ <div class="form-row"><label>Max Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number"/></div>
503
+ <div class="form-row"><label>System Prompt</label><textarea v-model="form.sendMessageOption.systemOverride"></textarea></div>
504
+ </template>
505
+
506
+ <!-- Tool / Processor / Trigger form -->
507
+ <template v-if="modal==='tool'||modal==='processor'||modal==='trigger'">
508
+ <div class="form-row"><label>Name</label><input v-model="form.name"/></div>
509
+ <div class="form-row" v-if="modal==='processor'"><label>Type</label><select v-model="form.type"><option>pre</option><option>post</option></select></div>
510
+ <div class="form-row"><label>Description</label><input v-model="form.description"/></div>
511
+ <div class="form-row"><label>Code</label><textarea v-model="form.code" style="min-height:200px;font-family:monospace;font-size:12px"></textarea></div>
512
+ </template>
513
+
514
+ <!-- Tool Group form -->
515
+ <template v-if="modal==='toolGroup'">
516
+ <div class="form-row"><label>Name</label><input v-model="form.name"/></div>
517
+ <div class="form-row"><label>Description</label><input v-model="form.description"/></div>
518
+ <div class="form-row"><label>Tool IDs (comma separated)</label><input v-model="form._toolIds" :placeholder="(form.toolIds||[]).join(', ')"/></div>
519
+ <div class="form-row"><label>Status</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
520
+ </template>
521
+
522
+ <div class="modal-btns">
523
+ <button class="btn" @click="closeModal">Cancel</button>
524
+ <button class="btn primary" @click="saveItem">Save</button>
525
+ </div>
526
+ </div>
527
+ </div>
528
+ </div>
529
+ `
530
+ }).mount('#app')
531
+ </script>
532
+ </body>
18
533
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hina114514/chaite",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
4
4
  "description": "core for chatgpt-plugin and karin-plugin-chatgpt",
5
5
  "keywords": [
6
6
  "yunzai",