@hina114514/chaite 1.9.7 → 1.9.9
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/dist/{adapters-B2n5lYRi.mjs → adapters-DH0o9gIS.mjs} +2 -2
- package/dist/chunks/openai/{index.mjs-D4ZfGFNk.mjs → index.mjs-M-HPLtaS.mjs} +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{src-DHDZlyre.mjs → src-DLwIgFtm.mjs} +4 -4
- package/dist/{types-G1zCyFOu.mjs → types-rlvvkPnw.mjs} +2 -2
- package/frontend/build/index.html +939 -345
- package/package.json +1 -1
|
@@ -3,105 +3,322 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8"/>
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
6
|
-
<title>Chaite
|
|
6
|
+
<title>Chaite</title>
|
|
7
7
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
|
8
8
|
<style>
|
|
9
|
-
|
|
9
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
10
|
+
|
|
10
11
|
:root{
|
|
11
|
-
--bg:#
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
12
|
+
--c-bg:#090c15;
|
|
13
|
+
--c-surface:rgba(18,22,40,0.7);
|
|
14
|
+
--c-surface2:rgba(26,30,52,0.8);
|
|
15
|
+
--c-surface3:rgba(38,42,66,0.6);
|
|
16
|
+
--c-border:rgba(255,255,255,0.06);
|
|
17
|
+
--c-border2:rgba(255,255,255,0.1);
|
|
18
|
+
--c-text:#e4e7f0;
|
|
19
|
+
--c-dim:#7e83a0;
|
|
20
|
+
--c-faint:#52577a;
|
|
21
|
+
--c-accent:#5b8def;
|
|
22
|
+
--c-accent2:rgba(91,141,239,0.15);
|
|
23
|
+
--c-green:#4ade80;
|
|
24
|
+
--c-green2:rgba(74,222,128,0.12);
|
|
25
|
+
--c-red:#f87171;
|
|
26
|
+
--c-red2:rgba(248,113,113,0.12);
|
|
27
|
+
--c-yellow:#facc15;
|
|
28
|
+
--c-purple:#a78bfa;
|
|
29
|
+
--r:12px;
|
|
30
|
+
--r2:20px;
|
|
31
|
+
--ease: cubic-bezier(.16,1,.3,1);
|
|
32
|
+
--ease2: cubic-bezier(.7,0,.3,1);
|
|
15
33
|
}
|
|
16
|
-
body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;line-height:1.6;min-height:100vh}
|
|
17
|
-
a{color:var(--accent);text-decoration:none}
|
|
18
|
-
a:hover{text-decoration:underline}
|
|
19
|
-
.app{max-width:1280px;margin:0 auto;padding:20px}
|
|
20
34
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
body{
|
|
36
|
+
background:var(--c-bg);
|
|
37
|
+
color:var(--c-text);
|
|
38
|
+
font-family: 'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
|
|
39
|
+
font-size:14px;
|
|
40
|
+
line-height:1.5;
|
|
41
|
+
min-height:100vh;
|
|
42
|
+
overflow-x:hidden;
|
|
43
|
+
}
|
|
44
|
+
body::before{
|
|
45
|
+
content:'';position:fixed;inset:0;z-index:-1;
|
|
46
|
+
background:
|
|
47
|
+
radial-gradient(ellipse 80% 60% at 0% 0%,rgba(91,141,239,.06),transparent),
|
|
48
|
+
radial-gradient(ellipse 60% 50% at 100% 100%,rgba(91,141,239,.04),transparent);
|
|
49
|
+
pointer-events:none;
|
|
50
|
+
}
|
|
29
51
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
.
|
|
52
|
+
#app{min-height:100vh}
|
|
53
|
+
|
|
54
|
+
/* Login */
|
|
55
|
+
.login-wrap{
|
|
56
|
+
display:flex;align-items:center;justify-content:center;min-height:100vh;
|
|
57
|
+
padding:20px;
|
|
58
|
+
}
|
|
59
|
+
.login-card{
|
|
60
|
+
width:100%;max-width:400px;
|
|
61
|
+
background:var(--c-surface);
|
|
62
|
+
border:1px solid var(--c-border);
|
|
63
|
+
border-radius:var(--r2);
|
|
64
|
+
padding:48px 36px;
|
|
65
|
+
backdrop-filter:blur(20px);
|
|
66
|
+
-webkit-backdrop-filter:blur(20px);
|
|
67
|
+
text-align:center;
|
|
68
|
+
animation: fadeUp .6s var(--ease) both;
|
|
69
|
+
}
|
|
70
|
+
.login-card h1{
|
|
71
|
+
font-size:28px;font-weight:700;letter-spacing:-.5px;margin-bottom:4px;
|
|
72
|
+
background:linear-gradient(135deg,#5b8def,#8b5cf6);-webkit-background-clip:text;-webkit-text-fill-color:transparent;
|
|
73
|
+
}
|
|
74
|
+
.login-card .sub{color:var(--c-dim);font-size:13px;margin-bottom:28px}
|
|
75
|
+
.login-card input{
|
|
76
|
+
width:100%;padding:11px 14px;border-radius:10px;border:1px solid var(--c-border);
|
|
77
|
+
background:rgba(255,255,255,.03);color:var(--c-text);font-size:14px;outline:none;
|
|
78
|
+
transition:border-color .2s;
|
|
79
|
+
}
|
|
80
|
+
.login-card input:focus{border-color:var(--c-accent)}
|
|
81
|
+
.login-card .err{color:var(--c-red);font-size:13px;margin:8px 0;min-height:20px}
|
|
82
|
+
.login-card .hint{font-size:11px;color:var(--c-faint);margin-top:14px}
|
|
83
|
+
.login-card .hint code{background:var(--c-surface2);padding:2px 6px;border-radius:4px;font-size:11px}
|
|
38
84
|
|
|
39
85
|
/* Buttons */
|
|
40
|
-
.btn{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
86
|
+
.btn{
|
|
87
|
+
display:inline-flex;align-items:center;justify-content:center;gap:6px;
|
|
88
|
+
padding:7px 14px;border-radius:8px;font-size:12.5px;font-weight:500;
|
|
89
|
+
border:1px solid var(--c-border);color:var(--c-text);
|
|
90
|
+
background:var(--c-surface2);cursor:pointer;
|
|
91
|
+
transition:all .2s var(--ease);
|
|
92
|
+
white-space:nowrap;
|
|
93
|
+
}
|
|
94
|
+
.btn:hover{background:var(--c-surface3);border-color:var(--c-border2);transform:translateY(-1px)}
|
|
95
|
+
.btn:active{transform:translateY(0) scale(.98)}
|
|
96
|
+
.btn:disabled{opacity:.4;pointer-events:none}
|
|
97
|
+
.btn.pri{
|
|
98
|
+
background:var(--c-accent);border-color:var(--c-accent);color:#fff;
|
|
99
|
+
box-shadow:0 2px 12px rgba(91,141,239,.25);
|
|
100
|
+
}
|
|
101
|
+
.btn.pri:hover{box-shadow:0 4px 20px rgba(91,141,239,.4);background:#6b9df5}
|
|
102
|
+
.btn.danger{color:var(--c-red);border-color:rgba(248,113,113,.25)}
|
|
103
|
+
.btn.danger:hover{background:var(--c-red2)}
|
|
104
|
+
.btn.sm{padding:4px 10px;font-size:11px;border-radius:6px}
|
|
105
|
+
.btn.ghost{background:transparent;border-color:transparent;color:var(--c-dim)}
|
|
106
|
+
.btn.ghost:hover{color:var(--c-text);background:rgba(255,255,255,.04)}
|
|
107
|
+
.btn .spin{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.2);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite}
|
|
108
|
+
|
|
109
|
+
/* Layout */
|
|
110
|
+
.layout{display:flex;min-height:100vh}
|
|
111
|
+
.sidebar{
|
|
112
|
+
width:220px;min-width:220px;
|
|
113
|
+
background:var(--c-surface);border-right:1px solid var(--c-border);
|
|
114
|
+
display:flex;flex-direction:column;
|
|
115
|
+
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
|
|
116
|
+
transition:transform .35s var(--ease);
|
|
117
|
+
z-index:50;
|
|
118
|
+
}
|
|
119
|
+
.sidebar-logo{padding:24px 20px 20px}
|
|
120
|
+
.sidebar-logo h2{
|
|
121
|
+
font-size:22px;font-weight:700;letter-spacing:-.5px;
|
|
122
|
+
background:linear-gradient(135deg,#5b8def,#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;
|
|
123
|
+
}
|
|
124
|
+
.sidebar-logo .ver{font-size:11px;color:var(--c-faint);margin-top:2px}
|
|
125
|
+
|
|
126
|
+
.sidebar-nav{flex:1;padding:8px 12px}
|
|
127
|
+
.nav-item{
|
|
128
|
+
display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;
|
|
129
|
+
font-size:13px;color:var(--c-dim);cursor:pointer;
|
|
130
|
+
transition:all .15s var(--ease);margin-bottom:2px;
|
|
131
|
+
position:relative;
|
|
132
|
+
}
|
|
133
|
+
.nav-item:hover{color:var(--c-text);background:var(--c-surface2)}
|
|
134
|
+
.nav-item.active{color:var(--c-accent);background:var(--c-accent2)}
|
|
135
|
+
.nav-item .icon{width:18px;text-align:center;font-size:15px;line-height:1}
|
|
136
|
+
.nav-item .badge-mini{
|
|
137
|
+
margin-left:auto;font-size:10px;padding:1px 6px;border-radius:8px;
|
|
138
|
+
background:var(--c-accent2);color:var(--c-accent);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.sidebar-footer{padding:16px 20px;border-top:1px solid var(--c-border);font-size:11px;color:var(--c-faint)}
|
|
142
|
+
.sidebar-footer .dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:6px}
|
|
143
|
+
.sidebar-footer .dot.ok{background:var(--c-green);box-shadow:0 0 6px var(--c-green)}
|
|
144
|
+
|
|
145
|
+
.main{flex:1;overflow-x:hidden;display:flex;flex-direction:column}
|
|
146
|
+
.topbar{
|
|
147
|
+
padding:16px 28px;display:flex;align-items:center;justify-content:space-between;
|
|
148
|
+
border-bottom:1px solid var(--c-border);gap:12px;
|
|
149
|
+
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
|
|
150
|
+
position:sticky;top:0;z-index:40;background:rgba(9,12,21,.8);
|
|
151
|
+
}
|
|
152
|
+
.topbar .t{font-size:15px;font-weight:600;color:var(--c-text)}
|
|
153
|
+
.topbar .meta{display:flex;align-items:center;gap:10px}
|
|
154
|
+
.topbar .refresh-text{font-size:11px;color:var(--c-faint)}
|
|
155
|
+
|
|
156
|
+
.content{padding:24px 28px 40px;flex:1}
|
|
157
|
+
|
|
158
|
+
/* Toast */
|
|
159
|
+
.toast{
|
|
160
|
+
position:fixed;top:20px;right:20px;z-index:200;
|
|
161
|
+
padding:10px 18px;border-radius:10px;font-size:13px;
|
|
162
|
+
animation:slideRight .35s var(--ease) both;
|
|
163
|
+
backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
|
|
164
|
+
}
|
|
165
|
+
.toast.ok{background:rgba(74,222,128,.9);color:#000}
|
|
166
|
+
.toast.err{background:rgba(248,113,113,.9);color:#fff}
|
|
167
|
+
|
|
168
|
+
/* Mobile menu toggle */
|
|
169
|
+
.menu-toggle{
|
|
170
|
+
display:none;width:36px;height:36px;align-items:center;justify-content:center;
|
|
171
|
+
border:none;background:none;color:var(--c-text);cursor:pointer;font-size:20px;
|
|
172
|
+
}
|
|
173
|
+
.sidebar-overlay{display:none;position:fixed;inset:0;z-index:45;background:rgba(0,0,0,.5)}
|
|
48
174
|
|
|
49
175
|
/* Cards */
|
|
50
|
-
.card{
|
|
51
|
-
|
|
52
|
-
|
|
176
|
+
.card{
|
|
177
|
+
background:var(--c-surface);border:1px solid var(--c-border);
|
|
178
|
+
border-radius:var(--r2);padding:22px;margin-bottom:16px;
|
|
179
|
+
backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
|
|
180
|
+
animation: fadeUp .45s var(--ease) both;
|
|
181
|
+
}
|
|
182
|
+
.card:nth-child(1){animation-delay:0s}
|
|
183
|
+
.card:nth-child(2){animation-delay:.06s}
|
|
184
|
+
.card:nth-child(3){animation-delay:.12s}
|
|
185
|
+
.card:nth-child(4){animation-delay:.18s}
|
|
186
|
+
.card:nth-child(5){animation-delay:.24s}
|
|
187
|
+
.card-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
|
|
188
|
+
.card-hd h3{font-size:14.5px;font-weight:600;display:flex;align-items:center;gap:8px}
|
|
189
|
+
.card-hd h3 .dot{width:7px;height:7px;border-radius:50%}
|
|
53
190
|
|
|
54
|
-
/* Stat
|
|
55
|
-
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(
|
|
56
|
-
.stat{
|
|
57
|
-
|
|
58
|
-
|
|
191
|
+
/* Stat pills */
|
|
192
|
+
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:20px}
|
|
193
|
+
.stat{
|
|
194
|
+
background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r2);
|
|
195
|
+
padding:18px 20px;text-align:center;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
|
|
196
|
+
transition:transform .2s var(--ease),border-color .2s var(--ease);
|
|
197
|
+
animation: fadeUp .4s var(--ease) both;
|
|
198
|
+
}
|
|
199
|
+
.stat:hover{transform:translateY(-3px);border-color:var(--c-accent)}
|
|
200
|
+
.stat:nth-child(1){animation-delay:0s}
|
|
201
|
+
.stat:nth-child(2){animation-delay:.05s}
|
|
202
|
+
.stat:nth-child(3){animation-delay:.1s}
|
|
203
|
+
.stat:nth-child(4){animation-delay:.15s}
|
|
204
|
+
.stat:nth-child(5){animation-delay:.2s}
|
|
205
|
+
.stat:nth-child(6){animation-delay:.25s}
|
|
206
|
+
.stat .num{font-size:26px;font-weight:700;color:var(--c-accent)}
|
|
207
|
+
.stat .lbl{font-size:11px;color:var(--c-dim);margin-top:4px;text-transform:uppercase;letter-spacing:.5px}
|
|
59
208
|
|
|
60
209
|
/* Table */
|
|
210
|
+
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
61
211
|
.tbl{width:100%;border-collapse:collapse;font-size:13px}
|
|
62
|
-
.tbl th{text-align:left;padding:10px 12px;color:var(--
|
|
63
|
-
.tbl td{padding:10px 12px;border-bottom:1px solid var(--border)}
|
|
64
|
-
.tbl tr:
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.pill
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
|
|
212
|
+
.tbl th{text-align:left;padding:10px 12px;color:var(--c-faint);font-weight:500;border-bottom:1px solid var(--c-border);font-size:11px;text-transform:uppercase;letter-spacing:.5px}
|
|
213
|
+
.tbl td{padding:10px 12px;border-bottom:1px solid var(--c-border)}
|
|
214
|
+
.tbl tbody tr{transition:background .15s}
|
|
215
|
+
.tbl tbody tr:hover{background:var(--c-surface2)}
|
|
216
|
+
|
|
217
|
+
/* Pills */
|
|
218
|
+
.pill{display:inline-block;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:500}
|
|
219
|
+
.pill.on{background:var(--c-green2);color:var(--c-green)}
|
|
220
|
+
.pill.off{background:var(--c-red2);color:var(--c-red)}
|
|
221
|
+
.pill.model{background:var(--c-accent2);color:var(--c-accent);margin:2px 3px 2px 0}
|
|
222
|
+
.pill.tag{background:rgba(167,139,250,.12);color:var(--c-purple);margin:2px 3px 2px 0}
|
|
223
|
+
.pill.warn{background:rgba(250,204,21,.12);color:var(--c-yellow)}
|
|
224
|
+
|
|
225
|
+
/* Config accordion */
|
|
226
|
+
.cfg-sec{margin-bottom:10px;border:1px solid var(--c-border);border-radius:var(--r);overflow:hidden;transition:border-color .2s}
|
|
227
|
+
.cfg-sec:hover{border-color:var(--c-border2)}
|
|
228
|
+
.cfg-head{
|
|
229
|
+
display:flex;align-items:center;justify-content:space-between;padding:12px 16px;
|
|
230
|
+
background:var(--c-surface2);cursor:pointer;font-size:13px;font-weight:600;
|
|
231
|
+
transition:background .15s;user-select:none;
|
|
232
|
+
}
|
|
233
|
+
.cfg-head:hover{background:var(--c-surface3)}
|
|
234
|
+
.cfg-head .arr{transition:transform .25s var(--ease);font-size:10px;color:var(--c-faint)}
|
|
235
|
+
.cfg-head .arr.open{transform:rotate(90deg)}
|
|
236
|
+
.cfg-body{
|
|
237
|
+
padding:14px 16px;display:grid;grid-template-columns:repeat(2,1fr);gap:8px;
|
|
238
|
+
animation: slideDown .25s var(--ease) both;
|
|
239
|
+
}
|
|
240
|
+
.cfg-fld{padding:6px 10px;background:rgba(255,255,255,.02);border-radius:6px}
|
|
241
|
+
.cfg-fld .k{font-size:10px;color:var(--c-faint);display:block;margin-bottom:3px;letter-spacing:.3px}
|
|
242
|
+
.cfg-fld .v{word-break:break-all;font-size:12.5px}
|
|
243
|
+
.cfg-fld .v code{color:var(--c-green)}
|
|
244
|
+
.cfg-fld .v .num{color:var(--c-accent)}
|
|
245
|
+
.cfg-json-btn{margin-top:16px}
|
|
75
246
|
|
|
76
247
|
/* Modal */
|
|
77
|
-
.modal-mask{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.
|
|
84
|
-
|
|
248
|
+
.modal-mask{
|
|
249
|
+
position:fixed;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;
|
|
250
|
+
background:rgba(0,0,0,.55);padding:20px;
|
|
251
|
+
animation: fadeIn .2s both;
|
|
252
|
+
backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
|
|
253
|
+
}
|
|
254
|
+
.modal{
|
|
255
|
+
background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r2);
|
|
256
|
+
padding:28px;width:100%;max-width:520px;max-height:85vh;overflow-y:auto;
|
|
257
|
+
backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);
|
|
258
|
+
animation: scaleIn .3s var(--ease) both;
|
|
259
|
+
}
|
|
260
|
+
.modal h3{font-size:17px;font-weight:600;margin-bottom:20px}
|
|
261
|
+
.f-row{margin-bottom:14px}
|
|
262
|
+
.f-row label{display:block;font-size:11px;color:var(--c-dim);margin-bottom:5px;letter-spacing:.3px}
|
|
263
|
+
.f-row input,.f-row select,.f-row textarea{
|
|
264
|
+
width:100%;padding:9px 12px;background:rgba(255,255,255,.03);border:1px solid var(--c-border);
|
|
265
|
+
border-radius:8px;color:var(--c-text);font-size:13px;font-family:inherit;
|
|
266
|
+
transition:border-color .2s;
|
|
267
|
+
}
|
|
268
|
+
.f-row input:focus,.f-row select:focus,.f-row textarea:focus{outline:none;border-color:var(--c-accent)}
|
|
269
|
+
.f-row textarea{min-height:80px;resize:vertical}
|
|
270
|
+
.f-row textarea.mono{font-family:'SF Mono',Fira Code,monospace;font-size:12px}
|
|
85
271
|
.modal-btns{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}
|
|
86
272
|
|
|
87
|
-
/*
|
|
88
|
-
.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@keyframes
|
|
273
|
+
/* Page transition */
|
|
274
|
+
.page-enter{animation: fadeUp .35s var(--ease) both}
|
|
275
|
+
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
|
|
276
|
+
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
|
277
|
+
@keyframes scaleIn{from{opacity:0;transform:scale(.96) translateY(8px)}to{opacity:1;transform:none}}
|
|
278
|
+
@keyframes slideRight{from{opacity:0;transform:translateX(30px)}to{opacity:1;transform:none}}
|
|
279
|
+
@keyframes slideDown{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
|
|
280
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
281
|
+
@keyframes shimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}
|
|
282
|
+
|
|
283
|
+
.skeleton{
|
|
284
|
+
background:linear-gradient(90deg,var(--c-surface2) 25%,var(--c-surface3) 50%,var(--c-surface2) 75%);
|
|
285
|
+
background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:6px;
|
|
286
|
+
}
|
|
287
|
+
.skel-row{height:14px;margin-bottom:8px;width:70%}
|
|
288
|
+
.skel-row.short{width:40%}
|
|
289
|
+
|
|
290
|
+
.mono{font-family:'SF Mono',Fira Code,monospace}
|
|
291
|
+
code{font-family:'SF Mono',Fira Code,monospace;font-size:12px}
|
|
92
292
|
|
|
93
293
|
/* Responsive */
|
|
94
294
|
@media(max-width:768px){
|
|
95
|
-
.
|
|
96
|
-
|
|
97
|
-
|
|
295
|
+
.sidebar{
|
|
296
|
+
position:fixed;left:0;top:0;bottom:0;transform:translateX(-100%);
|
|
297
|
+
z-index:50;width:260px;
|
|
298
|
+
}
|
|
299
|
+
.sidebar.open{transform:translateX(0)}
|
|
300
|
+
.sidebar-overlay.show{display:block}
|
|
301
|
+
.menu-toggle{display:flex}
|
|
302
|
+
.topbar{padding:12px 16px}
|
|
303
|
+
.content{padding:16px}
|
|
304
|
+
.stats{grid-template-columns:repeat(2,1fr);gap:8px}
|
|
305
|
+
.stat{padding:14px 12px}
|
|
306
|
+
.stat .num{font-size:22px}
|
|
307
|
+
.card{padding:16px}
|
|
308
|
+
.cfg-body{grid-template-columns:1fr}
|
|
309
|
+
.modal{max-width:95%;padding:20px}
|
|
310
|
+
}
|
|
311
|
+
@media(max-width:480px){
|
|
312
|
+
.stats{grid-template-columns:1fr 1fr}
|
|
313
|
+
.topbar .meta{gap:6px}
|
|
314
|
+
.topbar .refresh-text{display:none}
|
|
98
315
|
}
|
|
99
316
|
</style>
|
|
100
317
|
</head>
|
|
101
318
|
<body>
|
|
102
319
|
<div id="app"></div>
|
|
103
320
|
<script>
|
|
104
|
-
const{createApp,ref,reactive,computed,onMounted,onUnmounted
|
|
321
|
+
const{createApp,ref,reactive,computed,onMounted,onUnmounted}=Vue
|
|
105
322
|
|
|
106
323
|
const API=(path,opt={})=>{
|
|
107
324
|
const t=localStorage.getItem('chaite_jwt')
|
|
@@ -116,11 +333,18 @@ createApp({
|
|
|
116
333
|
// Auth
|
|
117
334
|
const jwt=ref(localStorage.getItem('chaite_jwt')||'')
|
|
118
335
|
const loggedIn=ref(!!jwt.value)
|
|
336
|
+
const loginInput=ref('')
|
|
119
337
|
const loginLoading=ref(false)
|
|
120
338
|
const loginError=ref('')
|
|
121
339
|
|
|
122
|
-
//
|
|
123
|
-
const
|
|
340
|
+
// UI state
|
|
341
|
+
const page=ref('dashboard')
|
|
342
|
+
const sidebarOpen=ref(false)
|
|
343
|
+
const loading=ref(true)
|
|
344
|
+
const refreshTime=ref('')
|
|
345
|
+
let timer=null
|
|
346
|
+
|
|
347
|
+
// Data
|
|
124
348
|
const health=ref(null)
|
|
125
349
|
const stats=ref(null)
|
|
126
350
|
const channels=ref([])
|
|
@@ -129,15 +353,19 @@ createApp({
|
|
|
129
353
|
const processors=ref([])
|
|
130
354
|
const triggers=ref([])
|
|
131
355
|
const toolGroups=ref([])
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
356
|
+
const config=ref({})
|
|
357
|
+
const conversations=ref([])
|
|
358
|
+
const convDetail=ref(null)
|
|
135
359
|
|
|
136
360
|
// Modal
|
|
137
|
-
const modal=ref(null)
|
|
138
|
-
const editing=ref(null)
|
|
361
|
+
const modal=ref(null)
|
|
362
|
+
const editing=ref(null)
|
|
139
363
|
const form=ref({})
|
|
140
364
|
|
|
365
|
+
// Config edit
|
|
366
|
+
const editingConfig=ref(false)
|
|
367
|
+
const configForm=ref('{}')
|
|
368
|
+
|
|
141
369
|
// Toast
|
|
142
370
|
const toastMsg=ref('')
|
|
143
371
|
const toastType=ref('ok')
|
|
@@ -150,7 +378,7 @@ createApp({
|
|
|
150
378
|
try{
|
|
151
379
|
const url=new URL(location.href)
|
|
152
380
|
const urlToken=url.searchParams.get('token')
|
|
153
|
-
const token=urlToken||
|
|
381
|
+
const token=urlToken||loginInput.value
|
|
154
382
|
if(!token){loginLoading.value=false;return}
|
|
155
383
|
const d=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token})}).then(r=>r.json())
|
|
156
384
|
if(d.data?.token){
|
|
@@ -161,14 +389,13 @@ createApp({
|
|
|
161
389
|
}catch(e){loginError.value='Network error'}
|
|
162
390
|
finally{loginLoading.value=false}
|
|
163
391
|
}
|
|
164
|
-
|
|
165
392
|
function logout(){jwt.value='';localStorage.removeItem('chaite_jwt');loggedIn.value=false;clearInterval(timer)}
|
|
166
393
|
|
|
167
|
-
// Data
|
|
394
|
+
// Data fetch
|
|
168
395
|
async function refreshAll(){
|
|
169
396
|
loading.value=true
|
|
170
397
|
try{
|
|
171
|
-
const[h,s,c,t,p,pr,tr,tg]=await Promise.all([
|
|
398
|
+
const[h,s,c,t,p,pr,tr,tg,cfg,cv]=await Promise.all([
|
|
172
399
|
API('/api/system/health').catch(()=>({})),
|
|
173
400
|
API('/api/system/stats').catch(()=>({})),
|
|
174
401
|
API('/api/channels/list').catch(()=>({})),
|
|
@@ -177,6 +404,8 @@ createApp({
|
|
|
177
404
|
API('/api/processors/list').catch(()=>({})),
|
|
178
405
|
API('/api/triggers/list').catch(()=>({})),
|
|
179
406
|
API('/api/toolGroups/list').catch(()=>({})),
|
|
407
|
+
API('/api/config').catch(()=>({})),
|
|
408
|
+
API('/api/state/conversations/list').catch(()=>({})),
|
|
180
409
|
])
|
|
181
410
|
health.value=h.data||null
|
|
182
411
|
stats.value=s.data||null
|
|
@@ -186,66 +415,167 @@ createApp({
|
|
|
186
415
|
processors.value=pr.data||[]
|
|
187
416
|
triggers.value=tr.data||[]
|
|
188
417
|
toolGroups.value=tg.data||[]
|
|
418
|
+
config.value=cfg.data||{}
|
|
419
|
+
conversations.value=cv.data||[]
|
|
189
420
|
refreshTime.value=new Date().toLocaleTimeString()
|
|
190
421
|
}catch(e){console.error(e)}
|
|
191
422
|
finally{loading.value=false}
|
|
192
423
|
}
|
|
193
424
|
|
|
194
|
-
|
|
425
|
+
function navigate(p){page.value=p;sidebarOpen.value=false}
|
|
426
|
+
|
|
427
|
+
// Helpers
|
|
428
|
+
function uptimeStr(s){
|
|
429
|
+
if(!s)return '-'
|
|
430
|
+
const h=Math.floor(s/3600),m=Math.floor(s%3600/60),sec=s%60
|
|
431
|
+
return h>0?`${h}h ${m}m ${sec}s`:`${m}m ${sec}s`
|
|
432
|
+
}
|
|
433
|
+
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)}
|
|
434
|
+
function fLen(s,n){return s&&s.length>n?s.slice(0,n)+'...':s||''}
|
|
435
|
+
|
|
436
|
+
// Channel test
|
|
195
437
|
async function testChannel(id){
|
|
196
438
|
const d=await API('/api/system/test-channel',{method:'POST',body:JSON.stringify({channelId:id})})
|
|
197
|
-
if(d.data?.status==='ok')toast(
|
|
198
|
-
else toast(
|
|
439
|
+
if(d.data?.status==='ok')toast(''+d.data.channelName+' — '+d.data.latency+'ms')
|
|
440
|
+
else toast(''+(d.data?.error||d.message),'err')
|
|
199
441
|
}
|
|
200
442
|
|
|
443
|
+
// CRUD
|
|
201
444
|
function openModal(type,item=null){
|
|
202
|
-
modal.value=type
|
|
203
|
-
|
|
204
|
-
|
|
445
|
+
modal.value=type;editing.value=item
|
|
446
|
+
if(item){
|
|
447
|
+
form.value=JSON.parse(JSON.stringify(item))
|
|
448
|
+
// 为 channel 初始化 JSON 字段
|
|
449
|
+
if(type==='channel'){
|
|
450
|
+
form.value._models = (form.value.models||[]).join(', ')
|
|
451
|
+
form.value._responseModalities = JSON.stringify(form.value.sendMessageOption?.responseModalities||[])
|
|
452
|
+
form.value._safetySettings = JSON.stringify(form.value.sendMessageOption?.safetySettings||[],null,2)
|
|
453
|
+
// 确保 sendMessageOption 存在
|
|
454
|
+
if(!form.value.sendMessageOption){
|
|
455
|
+
form.value.sendMessageOption = {
|
|
456
|
+
model:'',
|
|
457
|
+
temperature:0.8,
|
|
458
|
+
maxToken:4096,
|
|
459
|
+
systemOverride:'',
|
|
460
|
+
enableReasoning:false,
|
|
461
|
+
reasoningEffort:'high',
|
|
462
|
+
reasoningBudgetTokens:0,
|
|
463
|
+
responseModalities:[],
|
|
464
|
+
safetySettings:[],
|
|
465
|
+
toolCallLimit:{maxConsecutiveCalls:8,maxConsecutiveIdenticalCalls:2}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
205
470
|
else{
|
|
206
|
-
|
|
207
|
-
channel:{name:'',adapterType:'openai',type:'openai',models:[''],options:{baseUrl:'',apiKey:''},status:'enabled',weight:1,priority:0},
|
|
208
|
-
preset:{name:'',prefix:'',sendMessageOption:{model:'',temperature:0.8,maxToken:4096,systemOverride:''}},
|
|
471
|
+
form.value={
|
|
472
|
+
channel:{name:'',adapterType:'openai',type:'openai',models:[''],options:{baseUrl:'',apiKey:''},status:'enabled',weight:1,priority:0,sendMessageOption:{model:'',temperature:0.8,maxToken:4096,systemOverride:'',enableReasoning:false,reasoningEffort:'high',reasoningBudgetTokens:0,responseModalities:[],safetySettings:[],toolCallLimit:{maxConsecutiveCalls:8,maxConsecutiveIdenticalCalls:2}}},
|
|
473
|
+
preset:{name:'',prefix:'',sendMessageOption:{model:'',temperature:0.8,maxToken:4096,systemOverride:'',enableReasoning:false,reasoningEffort:'high',disableHistoryRead:false,disableHistorySave:false}},
|
|
209
474
|
tool:{name:'',description:'',code:''},
|
|
210
475
|
processor:{name:'',type:'pre',description:'',code:''},
|
|
211
476
|
trigger:{name:'',description:'',code:''},
|
|
212
477
|
toolGroup:{name:'',description:'',toolIds:[],status:'enabled',isDefault:false},
|
|
213
|
-
}
|
|
214
|
-
form.value=defaults[type]||{}
|
|
478
|
+
}[type]||{}
|
|
215
479
|
}
|
|
216
480
|
}
|
|
217
481
|
function closeModal(){modal.value=null;editing.value=null;form.value={}}
|
|
218
482
|
|
|
219
483
|
async function saveItem(){
|
|
220
|
-
const type=modal.value
|
|
221
484
|
const apiMap={channel:'/api/channels',preset:'/api/preset',tool:'/api/tools',processor:'/api/processors',trigger:'/api/triggers',toolGroup:'/api/toolGroups'}
|
|
222
485
|
try{
|
|
486
|
+
const type=modal.value
|
|
223
487
|
const id=editing.value?.id
|
|
224
488
|
const url=apiMap[type]+(id?'/'+id:'')
|
|
225
|
-
|
|
226
|
-
|
|
489
|
+
|
|
490
|
+
// 处理 channel 的 JSON 字段
|
|
491
|
+
if(type==='channel'){
|
|
492
|
+
const formData = JSON.parse(JSON.stringify(form.value))
|
|
493
|
+
|
|
494
|
+
// 处理 models 字段
|
|
495
|
+
if(formData._models){
|
|
496
|
+
formData.models = formData._models.split(',').map(m=>m.trim()).filter(m=>m)
|
|
497
|
+
delete formData._models
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// 处理 responseModalities
|
|
501
|
+
if(formData._responseModalities){
|
|
502
|
+
try{
|
|
503
|
+
formData.sendMessageOption.responseModalities = JSON.parse(formData._responseModalities)
|
|
504
|
+
}catch(e){
|
|
505
|
+
// 如果解析失败,保持原样
|
|
506
|
+
}
|
|
507
|
+
delete formData._responseModalities
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// 处理 safetySettings
|
|
511
|
+
if(formData._safetySettings){
|
|
512
|
+
try{
|
|
513
|
+
formData.sendMessageOption.safetySettings = JSON.parse(formData._safetySettings)
|
|
514
|
+
}catch(e){
|
|
515
|
+
// 如果解析失败,保持原样
|
|
516
|
+
}
|
|
517
|
+
delete formData._safetySettings
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
form.value = formData
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const d=await API(url,{method:id?'PUT':'POST',body:JSON.stringify(form.value)})
|
|
227
524
|
if(d.data!==undefined){toast('Saved');closeModal();refreshAll()}
|
|
228
525
|
else toast(d.message||'Error','err')
|
|
229
526
|
}catch(e){toast(e.message,'err')}
|
|
230
527
|
}
|
|
231
528
|
|
|
232
529
|
async function deleteItem(type,id){
|
|
233
|
-
if(!confirm('Delete
|
|
530
|
+
if(!confirm('Delete?'))return
|
|
234
531
|
const apiMap={channel:'/api/channels',preset:'/api/preset',tool:'/api/tools',processor:'/api/processors',trigger:'/api/triggers',toolGroup:'/api/toolGroups'}
|
|
532
|
+
try{await API(apiMap[type]+'/'+id,{method:'DELETE'});toast('Deleted');refreshAll()}
|
|
533
|
+
catch(e){toast(e.message,'err')}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Config
|
|
537
|
+
const expandedCfg=ref({})
|
|
538
|
+
const SKIP_CONFIG_KEYS=new Set(['version','_saveOrigin','authKey','cloudBaseUrl','cloudApiKey'])
|
|
539
|
+
|
|
540
|
+
const cfgSections=computed(()=>{
|
|
541
|
+
const secs=[]
|
|
542
|
+
const order=['basic','bym','llm','management','chaite','memory','update']
|
|
543
|
+
for(const key of order){
|
|
544
|
+
const v=config.value[key]
|
|
545
|
+
if(v!==undefined) secs.push({key,label:key.charAt(0).toUpperCase()+key.slice(1),fields:typeof v==='object'?v:{_value:v},isObj:typeof v==='object'&&!Array.isArray(v)})
|
|
546
|
+
}
|
|
547
|
+
// Remaining keys not in order
|
|
548
|
+
for(const[k,v]of Object.entries(config.value)){
|
|
549
|
+
if(order.includes(k)||SKIP_CONFIG_KEYS.has(k))continue
|
|
550
|
+
secs.push({key:k,label:k,fields:typeof v==='object'?v:{_value:v},isObj:typeof v==='object'&&!Array.isArray(v)})
|
|
551
|
+
}
|
|
552
|
+
return secs
|
|
553
|
+
})
|
|
554
|
+
function toggleCfg(k){expandedCfg.value[k]=!expandedCfg.value[k]}
|
|
555
|
+
function editConfig(){configForm.value=JSON.stringify(config.value,null,2);editingConfig.value=true}
|
|
556
|
+
async function saveConfig(){
|
|
235
557
|
try{
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
558
|
+
const obj=JSON.parse(configForm.value)
|
|
559
|
+
const d=await API('/api/config',{method:'POST',body:JSON.stringify(obj)})
|
|
560
|
+
if(d.data!==undefined){toast('Config saved');editingConfig.value=false;refreshAll()}
|
|
561
|
+
else toast(d.message||'Error','err')
|
|
562
|
+
}catch(e){toast('Invalid JSON: '+e.message,'err')}
|
|
239
563
|
}
|
|
240
564
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
565
|
+
// Conversations
|
|
566
|
+
async function viewConv(uid){
|
|
567
|
+
try{const d=await API('/api/state/'+encodeURIComponent(uid));convDetail.value=d.data||null}
|
|
568
|
+
catch(e){toast(e.message,'err')}
|
|
569
|
+
}
|
|
570
|
+
async function clearConv(uid){
|
|
571
|
+
if(!confirm('Clear state for '+uid+'?'))return
|
|
572
|
+
try{await API('/api/state/'+encodeURIComponent(uid),{method:'DELETE'});toast('Cleared');refreshAll()}
|
|
573
|
+
catch(e){toast(e.message,'err')}
|
|
245
574
|
}
|
|
246
|
-
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)}
|
|
247
575
|
|
|
248
|
-
const
|
|
576
|
+
const isHealthy=computed(()=>health.value?.status==='ok')
|
|
577
|
+
const channelsOk=computed(()=>channels.value.filter(c=>c.status==='enabled').length)
|
|
578
|
+
const loggedInC=computed(()=>!!jwt.value)
|
|
249
579
|
|
|
250
580
|
onMounted(()=>{
|
|
251
581
|
if(loggedIn.value)refreshAll()
|
|
@@ -254,10 +584,14 @@ createApp({
|
|
|
254
584
|
onUnmounted(()=>clearInterval(timer))
|
|
255
585
|
|
|
256
586
|
return{
|
|
257
|
-
jwt,loggedIn,loginLoading,loginError,doLogin,logout,
|
|
258
|
-
|
|
587
|
+
jwt,loggedIn,loginInput,loginLoading,loginError,doLogin,logout,
|
|
588
|
+
page,sidebarOpen,navigate,loading,refreshTime,isHealthy,
|
|
589
|
+
health,stats,channels,tools,presets,processors,triggers,toolGroups,config,conversations,convDetail,
|
|
590
|
+
channelsOk,
|
|
259
591
|
modal,editing,form,openModal,closeModal,saveItem,deleteItem,testChannel,
|
|
260
|
-
|
|
592
|
+
expandedCfg,cfgSections,toggleCfg,editConfig,editingConfig,configForm,saveConfig,SKIP_CONFIG_KEYS,
|
|
593
|
+
viewConv,clearConv,
|
|
594
|
+
toastMsg,toastType,uptimeStr,fmtNum,fLen,refreshAll,
|
|
261
595
|
}
|
|
262
596
|
},
|
|
263
597
|
template:`
|
|
@@ -266,265 +600,525 @@ createApp({
|
|
|
266
600
|
|
|
267
601
|
<!-- Login -->
|
|
268
602
|
<div v-if="!loggedIn" class="login-wrap">
|
|
269
|
-
<div class="login-
|
|
270
|
-
<h1
|
|
271
|
-
<p>Management
|
|
603
|
+
<div class="login-card">
|
|
604
|
+
<h1>Chaite</h1>
|
|
605
|
+
<p class="sub">Management Console</p>
|
|
272
606
|
<div v-if="!loginLoading">
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
@keyup.enter="doLogin" autofocus
|
|
276
|
-
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"/>
|
|
277
|
-
</div>
|
|
607
|
+
<input v-model="loginInput" type="password" placeholder="Access token..."
|
|
608
|
+
@keyup.enter="doLogin" autofocus/>
|
|
278
609
|
<div class="err">{{loginError}}</div>
|
|
279
|
-
<button class="btn
|
|
280
|
-
<p
|
|
610
|
+
<button class="btn pri" @click="doLogin" style="width:100%;padding:10px;margin-top:4px">Sign In</button>
|
|
611
|
+
<p class="hint">Or visit with <code>?token=xxx</code> in URL</p>
|
|
281
612
|
</div>
|
|
282
|
-
<div v-else><div class="
|
|
613
|
+
<div v-else style="padding:20px"><div class="spin" style="display:inline-block"></div></div>
|
|
283
614
|
</div>
|
|
284
615
|
</div>
|
|
285
616
|
|
|
286
|
-
<!--
|
|
287
|
-
<div v-else class="
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
<span v-else>↻ Refresh</span>
|
|
298
|
-
</button>
|
|
299
|
-
<button class="btn" @click="logout">Logout</button>
|
|
617
|
+
<!-- Main Layout -->
|
|
618
|
+
<div v-else class="layout">
|
|
619
|
+
|
|
620
|
+
<!-- Sidebar overlay (mobile) -->
|
|
621
|
+
<div :class="['sidebar-overlay',sidebarOpen&&'show']" @click="sidebarOpen=false"></div>
|
|
622
|
+
|
|
623
|
+
<!-- Sidebar -->
|
|
624
|
+
<aside :class="['sidebar',sidebarOpen&&'open']">
|
|
625
|
+
<div class="sidebar-logo">
|
|
626
|
+
<h2>Chaite</h2>
|
|
627
|
+
<div class="ver">v{{health?.version||'...'}}</div>
|
|
300
628
|
</div>
|
|
301
|
-
|
|
629
|
+
<nav class="sidebar-nav">
|
|
630
|
+
<div :class="['nav-item',page==='dashboard'&&'active']" @click="navigate('dashboard')">
|
|
631
|
+
<span class="icon">◈</span>Overview
|
|
632
|
+
<span class="badge-mini" v-if="health">{{health.channels?.total||0}}ch</span>
|
|
633
|
+
</div>
|
|
634
|
+
<div :class="['nav-item',page==='channels'&&'active']" @click="navigate('channels')">
|
|
635
|
+
<span class="icon">▤</span>Channels
|
|
636
|
+
<span class="badge-mini" v-if="channelsOk">{{channelsOk}}</span>
|
|
637
|
+
</div>
|
|
638
|
+
<div :class="['nav-item',page==='tools'&&'active']" @click="navigate('tools')">
|
|
639
|
+
<span class="icon">⚙</span>Tools
|
|
640
|
+
<span class="badge-mini" v-if="tools.length">{{tools.length}}</span>
|
|
641
|
+
</div>
|
|
642
|
+
<div :class="['nav-item',page==='presets'&&'active']" @click="navigate('presets')">
|
|
643
|
+
<span class="icon">✎</span>Presets
|
|
644
|
+
</div>
|
|
645
|
+
<div :class="['nav-item',page==='processors'&&'active']" @click="navigate('processors')">
|
|
646
|
+
<span class="icon">⇄</span>Processors
|
|
647
|
+
</div>
|
|
648
|
+
<div :class="['nav-item',page==='triggers'&&'active']" @click="navigate('triggers')">
|
|
649
|
+
<span class="icon">⚡</span>Triggers
|
|
650
|
+
</div>
|
|
651
|
+
<div :class="['nav-item',page==='groups'&&'active']" @click="navigate('groups')">
|
|
652
|
+
<span class="icon">☰</span>Groups
|
|
653
|
+
</div>
|
|
654
|
+
<div style="margin:8px 0;border-top:1px solid var(--c-border)"></div>
|
|
655
|
+
<div :class="['nav-item',page==='conversations'&&'active']" @click="navigate('conversations')">
|
|
656
|
+
<span class="icon">◎</span>Sessions
|
|
657
|
+
<span class="badge-mini" v-if="conversations.length">{{conversations.length}}</span>
|
|
658
|
+
</div>
|
|
659
|
+
<div :class="['nav-item',page==='config'&&'active']" @click="navigate('config')">
|
|
660
|
+
<span class="icon">⚙</span>Config
|
|
661
|
+
</div>
|
|
662
|
+
</nav>
|
|
663
|
+
<div class="sidebar-footer">
|
|
664
|
+
<span :class="['dot',isHealthy?'ok':'']"></span>
|
|
665
|
+
{{isHealthy?'Online':'Offline'}}
|
|
666
|
+
<br><span style="font-size:10px">port {{health?.system?.platform||'-'}}</span>
|
|
667
|
+
</div>
|
|
668
|
+
</aside>
|
|
302
669
|
|
|
303
|
-
<!--
|
|
304
|
-
<div class="
|
|
305
|
-
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
670
|
+
<!-- Main -->
|
|
671
|
+
<div class="main">
|
|
672
|
+
<!-- Top bar -->
|
|
673
|
+
<header class="topbar">
|
|
674
|
+
<div style="display:flex;align-items:center;gap:12px">
|
|
675
|
+
<button class="menu-toggle" @click="sidebarOpen=!sidebarOpen">☰</button>
|
|
676
|
+
<span class="t">
|
|
677
|
+
{{page==='dashboard'?'Overview':page==='conversations'?'Sessions':page==='groups'?'Groups':page.charAt(0).toUpperCase()+page.slice(1)}}
|
|
678
|
+
</span>
|
|
679
|
+
</div>
|
|
680
|
+
<div class="meta">
|
|
681
|
+
<span class="refresh-text" v-if="refreshTime">↻ {{refreshTime}}</span>
|
|
682
|
+
<button class="btn ghost" @click="refreshAll" :disabled="loading">
|
|
683
|
+
<span v-if="loading" class="spin"></span>
|
|
684
|
+
<span v-else>↻</span>
|
|
685
|
+
</button>
|
|
686
|
+
<button class="btn ghost" @click="logout">⏻</button>
|
|
687
|
+
</div>
|
|
688
|
+
</header>
|
|
312
689
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
690
|
+
<div class="content" :key="page">
|
|
691
|
+
|
|
692
|
+
<!-- ========== DASHBOARD ========== -->
|
|
693
|
+
<div v-if="page==='dashboard'" class="page-enter">
|
|
694
|
+
<div class="stats" v-if="health">
|
|
695
|
+
<div class="stat"><div class="num">{{uptimeStr(health.uptime)}}</div><div class="lbl">Uptime</div></div>
|
|
696
|
+
<div class="stat"><div class="num">{{health.channels?.total||0}}</div><div class="lbl">Channels</div></div>
|
|
697
|
+
<div class="stat"><div class="num">{{health.models?.count||0}}</div><div class="lbl">Models</div></div>
|
|
698
|
+
<div class="stat"><div class="num">{{health.tools?.count||0}}</div><div class="lbl">Tools</div></div>
|
|
699
|
+
<div class="stat"><div class="num">{{health.system?.processMemory||0}}M</div><div class="lbl">Memory</div></div>
|
|
700
|
+
<div class="stat"><div class="num">{{fmtNum(stats?.summary?.totalCalls)}}</div><div class="lbl">Total Calls</div></div>
|
|
701
|
+
</div>
|
|
702
|
+
|
|
703
|
+
<div v-if="!loading&&!health" class="card" style="text-align:center;color:var(--c-dim);padding:48px">No data — is the server running?</div>
|
|
704
|
+
|
|
705
|
+
<div class="card" v-if="health">
|
|
706
|
+
<div class="card-hd"><h3><span class="dot" style="background:var(--c-green)"></span>System</h3></div>
|
|
707
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;font-size:13px">
|
|
708
|
+
<div><span style="color:var(--c-dim)">Version</span><br>{{health.version}}</div>
|
|
709
|
+
<div><span style="color:var(--c-dim)">Node</span><br>{{health.system?.nodeVersion}}</div>
|
|
710
|
+
<div><span style="color:var(--c-dim)">Platform</span><br>{{health.system?.platform}} {{health.system?.arch}}</div>
|
|
711
|
+
<div><span style="color:var(--c-dim)">CPUs</span><br>{{health.system?.cpus}}</div>
|
|
712
|
+
<div><span style="color:var(--c-dim)">Heap</span><br>{{health.system?.heapUsed}}M / {{health.system?.processMemory}}M</div>
|
|
713
|
+
<div><span style="color:var(--c-dim)">Sys Mem</span><br>{{health.system?.freeMemory}}M free</div>
|
|
714
|
+
</div>
|
|
715
|
+
</div>
|
|
323
716
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
<div
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
717
|
+
<div class="card" v-if="health?.models?.list?.length">
|
|
718
|
+
<div class="card-hd"><h3>Models</h3></div>
|
|
719
|
+
<div><span v-for="m in health.models.list" class="pill model">{{m}}</span></div>
|
|
720
|
+
</div>
|
|
721
|
+
|
|
722
|
+
<div class="card" v-if="stats?.perModel&&Object.keys(stats.perModel).length">
|
|
723
|
+
<div class="card-hd"><h3>Usage by Model</h3></div>
|
|
724
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
725
|
+
<thead><tr><th>Model</th><th>Calls</th><th>Tokens</th></tr></thead>
|
|
726
|
+
<tbody>
|
|
727
|
+
<tr v-for="(v,k) in stats.perModel" :key="k">
|
|
728
|
+
<td><span class="pill model">{{k}}</span></td>
|
|
729
|
+
<td>{{fmtNum(v.calls)}}</td><td>{{fmtNum(v.tokens)}}</td>
|
|
730
|
+
</tr>
|
|
731
|
+
</tbody>
|
|
732
|
+
</table></div>
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
<div class="card" v-if="stats?.perChannel&&Object.keys(stats.perChannel).length">
|
|
736
|
+
<div class="card-hd"><h3>Usage by Channel</h3></div>
|
|
737
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
738
|
+
<thead><tr><th>Channel</th><th>Calls</th><th>Tokens</th><th>Models</th></tr></thead>
|
|
739
|
+
<tbody>
|
|
740
|
+
<tr v-for="(v,k) in stats.perChannel" :key="k">
|
|
741
|
+
<td>{{k}}</td><td>{{fmtNum(v.calls)}}</td><td>{{fmtNum(v.tokens)}}</td>
|
|
742
|
+
<td><span v-for="m in (v.models||[])" class="pill model">{{m}}</span></td>
|
|
743
|
+
</tr>
|
|
744
|
+
</tbody>
|
|
745
|
+
</table></div>
|
|
746
|
+
</div>
|
|
747
|
+
|
|
748
|
+
<div class="card" v-if="conversations.length">
|
|
749
|
+
<div class="card-hd"><h3>{{conversations.length}} Active Sessions</h3></div>
|
|
750
|
+
<span v-for="c in conversations" class="pill tag">{{c.userId}}</span>
|
|
751
|
+
</div>
|
|
335
752
|
</div>
|
|
336
|
-
</div>
|
|
337
753
|
|
|
338
|
-
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
|
|
754
|
+
<!-- ========== CHANNELS ========== -->
|
|
755
|
+
<div v-if="page==='channels'" class="page-enter">
|
|
756
|
+
<div class="card">
|
|
757
|
+
<div class="card-hd">
|
|
758
|
+
<h3>Channels</h3>
|
|
759
|
+
<button class="btn pri sm" @click="openModal('channel')">+ New</button>
|
|
760
|
+
</div>
|
|
761
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
762
|
+
<thead><tr><th>Status</th><th>Name</th><th>Adapter</th><th>Models</th><th>Priority</th><th>Calls</th><th></th></tr></thead>
|
|
763
|
+
<tbody>
|
|
764
|
+
<tr v-for="ch in channels" :key="ch.id">
|
|
765
|
+
<td><span :class="['pill',ch.status==='enabled'?'on':'off']">{{ch.status}}</span></td>
|
|
766
|
+
<td>{{ch.name}}</td><td>{{ch.adapterType}}</td>
|
|
767
|
+
<td><span v-for="m in (ch.models||[])" class="pill model">{{m}}</span></td>
|
|
768
|
+
<td>{{ch.priority}}</td><td>{{fmtNum(ch.statistics?.callTimes)}}</td>
|
|
769
|
+
<td>
|
|
770
|
+
<button class="btn sm" @click="testChannel(ch.id)">Test</button>
|
|
771
|
+
<button class="btn sm" @click="openModal('channel',ch)">Edit</button>
|
|
772
|
+
<button class="btn sm danger" @click="deleteItem('channel',ch.id)">Del</button>
|
|
773
|
+
</td>
|
|
774
|
+
</tr>
|
|
775
|
+
<tr v-if="!channels.length"><td colspan="7" style="color:var(--c-dim);text-align:center;padding:32px">No channels</td></tr>
|
|
776
|
+
</tbody>
|
|
777
|
+
</table></div>
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
342
780
|
|
|
343
|
-
|
|
344
|
-
<
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
<
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
781
|
+
<!-- ========== TOOLS ========== -->
|
|
782
|
+
<div v-if="page==='tools'" class="page-enter">
|
|
783
|
+
<div class="card">
|
|
784
|
+
<div class="card-hd">
|
|
785
|
+
<h3>Tools</h3>
|
|
786
|
+
<button class="btn pri sm" @click="openModal('tool')">+ New</button>
|
|
787
|
+
</div>
|
|
788
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
789
|
+
<thead><tr><th>Name</th><th>Description</th><th></th></tr></thead>
|
|
790
|
+
<tbody>
|
|
791
|
+
<tr v-for="t in tools" :key="t.id">
|
|
792
|
+
<td>{{t.name||t.id}}</td>
|
|
793
|
+
<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{t.description}}</td>
|
|
794
|
+
<td>
|
|
795
|
+
<button class="btn sm" @click="openModal('tool',t)">Edit</button>
|
|
796
|
+
<button class="btn sm danger" @click="deleteItem('tool',t.id)">Del</button>
|
|
797
|
+
</td>
|
|
798
|
+
</tr>
|
|
799
|
+
<tr v-if="!tools.length"><td colspan="3" style="color:var(--c-dim);text-align:center;padding:32px">No tools</td></tr>
|
|
800
|
+
</tbody>
|
|
801
|
+
</table></div>
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
356
804
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
805
|
+
<!-- ========== PRESETS ========== -->
|
|
806
|
+
<div v-if="page==='presets'" class="page-enter">
|
|
807
|
+
<div class="card">
|
|
808
|
+
<div class="card-hd">
|
|
809
|
+
<h3>Presets</h3>
|
|
810
|
+
<button class="btn pri sm" @click="openModal('preset')">+ New</button>
|
|
811
|
+
</div>
|
|
812
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
813
|
+
<thead><tr><th>Name</th><th>Prefix</th><th>Model</th><th>Temp</th><th></th></tr></thead>
|
|
814
|
+
<tbody>
|
|
815
|
+
<tr v-for="p in presets" :key="p.id">
|
|
816
|
+
<td>{{p.name}}</td>
|
|
817
|
+
<td><code>{{p.prefix}}</code></td>
|
|
818
|
+
<td><span class="pill model">{{p.sendMessageOption?.model||'-'}}</span></td>
|
|
819
|
+
<td>{{p.sendMessageOption?.temperature}}</td>
|
|
820
|
+
<td>
|
|
821
|
+
<button class="btn sm" @click="openModal('preset',p)">Edit</button>
|
|
822
|
+
<button class="btn sm danger" @click="deleteItem('preset',p.id)">Del</button>
|
|
823
|
+
</td>
|
|
824
|
+
</tr>
|
|
825
|
+
<tr v-if="!presets.length"><td colspan="5" style="color:var(--c-dim);text-align:center;padding:32px">No presets</td></tr>
|
|
826
|
+
</tbody>
|
|
827
|
+
</table></div>
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
380
830
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
<
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
831
|
+
<!-- ========== PROCESSORS ========== -->
|
|
832
|
+
<div v-if="page==='processors'" class="page-enter">
|
|
833
|
+
<div class="card">
|
|
834
|
+
<div class="card-hd">
|
|
835
|
+
<h3>Processors</h3>
|
|
836
|
+
<button class="btn pri sm" @click="openModal('processor')">+ New</button>
|
|
837
|
+
</div>
|
|
838
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
839
|
+
<thead><tr><th>Name</th><th>Type</th><th>Description</th><th></th></tr></thead>
|
|
840
|
+
<tbody>
|
|
841
|
+
<tr v-for="p in processors" :key="p.id">
|
|
842
|
+
<td>{{p.name}}</td>
|
|
843
|
+
<td><span class="pill" :style="{background:p.type==='pre'?'rgba(167,139,250,.12)':'rgba(250,204,21,.12)',color:p.type==='pre'?'var(--c-purple)':'var(--c-yellow)'}">{{p.type}}</span></td>
|
|
844
|
+
<td>{{p.description}}</td>
|
|
845
|
+
<td>
|
|
846
|
+
<button class="btn sm" @click="openModal('processor',p)">Edit</button>
|
|
847
|
+
<button class="btn sm danger" @click="deleteItem('processor',p.id)">Del</button>
|
|
848
|
+
</td>
|
|
849
|
+
</tr>
|
|
850
|
+
<tr v-if="!processors.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">No processors</td></tr>
|
|
851
|
+
</tbody>
|
|
852
|
+
</table></div>
|
|
853
|
+
</div>
|
|
854
|
+
</div>
|
|
399
855
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
<
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
856
|
+
<!-- ========== TRIGGERS ========== -->
|
|
857
|
+
<div v-if="page==='triggers'" class="page-enter">
|
|
858
|
+
<div class="card">
|
|
859
|
+
<div class="card-hd">
|
|
860
|
+
<h3>Triggers</h3>
|
|
861
|
+
<button class="btn pri sm" @click="openModal('trigger')">+ New</button>
|
|
862
|
+
</div>
|
|
863
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
864
|
+
<thead><tr><th>Name</th><th>Description</th><th></th></tr></thead>
|
|
865
|
+
<tbody>
|
|
866
|
+
<tr v-for="t in triggers" :key="t.id">
|
|
867
|
+
<td>{{t.name||t.id}}</td><td>{{t.description}}</td>
|
|
868
|
+
<td>
|
|
869
|
+
<button class="btn sm" @click="openModal('trigger',t)">Edit</button>
|
|
870
|
+
<button class="btn sm danger" @click="deleteItem('trigger',t.id)">Del</button>
|
|
871
|
+
</td>
|
|
872
|
+
</tr>
|
|
873
|
+
<tr v-if="!triggers.length"><td colspan="3" style="color:var(--c-dim);text-align:center;padding:32px">No triggers</td></tr>
|
|
874
|
+
</tbody>
|
|
875
|
+
</table></div>
|
|
876
|
+
</div>
|
|
877
|
+
</div>
|
|
420
878
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
879
|
+
<!-- ========== GROUPS ========== -->
|
|
880
|
+
<div v-if="page==='groups'" class="page-enter">
|
|
881
|
+
<div class="card">
|
|
882
|
+
<div class="card-hd">
|
|
883
|
+
<h3>Tool Groups</h3>
|
|
884
|
+
<button class="btn pri sm" @click="openModal('toolGroup')">+ New</button>
|
|
885
|
+
</div>
|
|
886
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
887
|
+
<thead><tr><th>Name</th><th>Description</th><th>Default</th><th></th></tr></thead>
|
|
888
|
+
<tbody>
|
|
889
|
+
<tr v-for="g in toolGroups" :key="g.id">
|
|
890
|
+
<td>{{g.name}}</td><td>{{g.description}}</td>
|
|
891
|
+
<td><span v-if="g.isDefault" class="pill on">Default</span></td>
|
|
892
|
+
<td>
|
|
893
|
+
<button class="btn sm" @click="openModal('toolGroup',g)">Edit</button>
|
|
894
|
+
<button class="btn sm danger" @click="deleteItem('toolGroup',g.id)">Del</button>
|
|
895
|
+
</td>
|
|
896
|
+
</tr>
|
|
897
|
+
<tr v-if="!toolGroups.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">No groups</td></tr>
|
|
898
|
+
</tbody>
|
|
899
|
+
</table></div>
|
|
900
|
+
</div>
|
|
901
|
+
</div>
|
|
440
902
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
903
|
+
<!-- ========== SESSIONS ========== -->
|
|
904
|
+
<div v-if="page==='conversations'" class="page-enter">
|
|
905
|
+
<div class="card">
|
|
906
|
+
<div class="card-hd"><h3>Active Sessions</h3></div>
|
|
907
|
+
<div class="tbl-wrap"><table class="tbl">
|
|
908
|
+
<thead><tr><th>User ID</th><th>Conversation</th><th>Model</th><th></th></tr></thead>
|
|
909
|
+
<tbody>
|
|
910
|
+
<tr v-for="c in conversations" :key="c.userId">
|
|
911
|
+
<td>{{c.userId}}</td>
|
|
912
|
+
<td><code style="font-size:11px">{{c.conversationId||'-'}}</code></td>
|
|
913
|
+
<td><span v-if="c.currentModel" class="pill model">{{c.currentModel}}</span><span v-else style="color:var(--c-dim)">-</span></td>
|
|
914
|
+
<td>
|
|
915
|
+
<button class="btn sm" @click="viewConv(c.userId)">View</button>
|
|
916
|
+
<button class="btn sm danger" @click="clearConv(c.userId)">Clear</button>
|
|
917
|
+
</td>
|
|
918
|
+
</tr>
|
|
919
|
+
<tr v-if="!conversations.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">No active sessions</td></tr>
|
|
920
|
+
</tbody>
|
|
921
|
+
</table></div>
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
924
|
+
|
|
925
|
+
<!-- ========== CONFIG ========== -->
|
|
926
|
+
<div v-if="page==='config'" class="page-enter">
|
|
927
|
+
<div class="card">
|
|
928
|
+
<div class="card-hd"><h3>Plugin Configuration</h3></div>
|
|
929
|
+
<div v-if="cfgSections.length">
|
|
930
|
+
<div v-for="sec in cfgSections" :key="sec.key" class="cfg-sec">
|
|
931
|
+
<div class="cfg-head" @click="toggleCfg(sec.key)">
|
|
932
|
+
<span>{{sec.label}} <span style="font-weight:400;color:var(--c-faint);font-size:11px">· {{Object.keys(sec.fields||{}).length}} items</span></span>
|
|
933
|
+
<span :class="['arr',expandedCfg[sec.key]&&'open']">▶</span>
|
|
934
|
+
</div>
|
|
935
|
+
<div v-if="expandedCfg[sec.key]" class="cfg-body">
|
|
936
|
+
<div v-for="(fv,fk) in sec.fields" :key="fk" class="cfg-fld">
|
|
937
|
+
<span class="k">{{fk}}</span>
|
|
938
|
+
<span class="v">
|
|
939
|
+
<template v-if="typeof fv==='boolean'"><span :class="['pill',fv?'on':'off']">{{fv}}</span></template>
|
|
940
|
+
<template v-else-if="typeof fv==='number'"><span class="num">{{fv}}</span></template>
|
|
941
|
+
<template v-else-if="typeof fv==='string'&&fv.length<=80"><code>{{fv||'""'}}</code></template>
|
|
942
|
+
<template v-else-if="typeof fv==='string'"><code :title="fv">{{fLen(fv,60)}}</code></template>
|
|
943
|
+
<template v-else><span style="color:var(--c-faint);font-size:11px">obj</span></template>
|
|
944
|
+
</span>
|
|
945
|
+
</div>
|
|
946
|
+
</div>
|
|
947
|
+
</div>
|
|
948
|
+
</div>
|
|
949
|
+
<div v-else style="color:var(--c-dim);font-size:13px;padding:20px 0">No config data</div>
|
|
950
|
+
<div class="cfg-json-btn"><button class="btn" @click="editConfig">Edit JSON</button></div>
|
|
951
|
+
</div>
|
|
952
|
+
</div>
|
|
953
|
+
|
|
954
|
+
</div><!-- .content -->
|
|
955
|
+
</div><!-- .main -->
|
|
956
|
+
</div><!-- .layout -->
|
|
957
|
+
|
|
958
|
+
<!-- ===== MODALS ===== -->
|
|
959
|
+
|
|
960
|
+
<!-- CRUD Modal -->
|
|
961
|
+
<div v-if="modal" class="modal-mask" @click.self="closeModal">
|
|
962
|
+
<div class="modal">
|
|
963
|
+
<h3>{{editing?'Edit':'New'}} {{modal}}</h3>
|
|
964
|
+
|
|
965
|
+
<template v-if="modal==='channel'">
|
|
966
|
+
<div class="f-row"><label>Name</label><input v-model="form.name"/></div>
|
|
967
|
+
<div class="f-row"><label>Adapter</label><select v-model="form.adapterType"><option>openai</option><option>gemini</option><option>claude</option></select></div>
|
|
968
|
+
<div class="f-row"><label>Models (comma separated)</label><input v-model="form._models" :placeholder="(form.models||[]).join(', ')"/></div>
|
|
969
|
+
<div class="f-row"><label>Base URL</label><input v-model="form.options.baseUrl"/></div>
|
|
970
|
+
<div class="f-row"><label>API Key</label><input v-model="form.options.apiKey" type="password"/></div>
|
|
971
|
+
<div class="f-row"><label>Priority</label><input v-model.number="form.priority" type="number"/></div>
|
|
972
|
+
<div class="f-row"><label>Weight</label><input v-model.number="form.weight" type="number"/></div>
|
|
973
|
+
<div class="f-row"><label>Status</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
|
|
974
|
+
|
|
975
|
+
<!-- 模型高级配置 -->
|
|
976
|
+
<div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--c-border)">
|
|
977
|
+
<h4 style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--c-accent)">Model Configuration</h4>
|
|
978
|
+
|
|
979
|
+
<div class="f-row"><label>Model Name</label><input v-model="form.sendMessageOption.model"/></div>
|
|
980
|
+
<div class="f-row"><label>Temperature</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1" min="0" max="2"/></div>
|
|
981
|
+
<div class="f-row"><label>Max Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number" min="1"/></div>
|
|
982
|
+
<div class="f-row"><label>System Prompt</label><textarea v-model="form.sendMessageOption.systemOverride" placeholder="Override system prompt..."></textarea></div>
|
|
983
|
+
|
|
984
|
+
<!-- Gemini 思考配置 -->
|
|
985
|
+
<div v-if="form.adapterType==='gemini'" style="margin-top:12px;padding:12px;background:var(--c-surface2);border-radius:8px">
|
|
986
|
+
<h5 style="font-size:13px;font-weight:600;margin-bottom:8px;color:var(--c-purple)">Gemini Thinking Configuration</h5>
|
|
987
|
+
<div class="f-row">
|
|
988
|
+
<label>Enable Reasoning</label>
|
|
989
|
+
<select v-model="form.sendMessageOption.enableReasoning">
|
|
990
|
+
<option :value="false">Disabled</option>
|
|
991
|
+
<option :value="true">Enabled</option>
|
|
992
|
+
</select>
|
|
993
|
+
</div>
|
|
994
|
+
<div class="f-row" v-if="form.sendMessageOption.enableReasoning">
|
|
995
|
+
<label>Thinking Level</label>
|
|
996
|
+
<select v-model="form.sendMessageOption.reasoningEffort">
|
|
997
|
+
<option value="minimal">Minimal (Low Latency)</option>
|
|
998
|
+
<option value="low">Low</option>
|
|
999
|
+
<option value="medium">Medium (Balanced)</option>
|
|
1000
|
+
<option value="high">High (Deep Reasoning)</option>
|
|
1001
|
+
</select>
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
|
|
1005
|
+
<!-- 工具调用限制 -->
|
|
1006
|
+
<div style="margin-top:12px;padding:12px;background:var(--c-surface2);border-radius:8px">
|
|
1007
|
+
<h5 style="font-size:13px;font-weight:600;margin-bottom:8px;color:var(--c-yellow)">Tool Call Limits</h5>
|
|
1008
|
+
<div class="f-row">
|
|
1009
|
+
<label>Max Consecutive Calls</label>
|
|
1010
|
+
<input v-model.number="form.sendMessageOption.toolCallLimit.maxConsecutiveCalls" type="number" min="1"/>
|
|
1011
|
+
</div>
|
|
1012
|
+
<div class="f-row">
|
|
1013
|
+
<label>Max Consecutive Identical Calls</label>
|
|
1014
|
+
<input v-model.number="form.sendMessageOption.toolCallLimit.maxConsecutiveIdenticalCalls" type="number" min="1"/>
|
|
1015
|
+
</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
|
|
1018
|
+
<!-- 高级配置(JSON 格式) -->
|
|
1019
|
+
<div style="margin-top:12px">
|
|
1020
|
+
<div class="f-row">
|
|
1021
|
+
<label>Response Modalities (JSON array)</label>
|
|
1022
|
+
<input v-model="form._responseModalities" :placeholder="JSON.stringify(form.sendMessageOption.responseModalities||[])"/>
|
|
1023
|
+
</div>
|
|
1024
|
+
<div class="f-row">
|
|
1025
|
+
<label>Safety Settings (JSON array)</label>
|
|
1026
|
+
<textarea v-model="form._safetySettings" class="mono" style="min-height:60px" :placeholder="JSON.stringify(form.sendMessageOption.safetySettings||[],null,2)"></textarea>
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
</div>
|
|
1030
|
+
</template>
|
|
1031
|
+
|
|
1032
|
+
<template v-if="modal==='preset'">
|
|
1033
|
+
<div class="f-row"><label>Name</label><input v-model="form.name"/></div>
|
|
1034
|
+
<div class="f-row"><label>Prefix</label><input v-model="form.prefix"/></div>
|
|
1035
|
+
<div class="f-row"><label>Model</label><input v-model="form.sendMessageOption.model"/></div>
|
|
1036
|
+
<div class="f-row"><label>Temperature</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1" min="0" max="2"/></div>
|
|
1037
|
+
<div class="f-row"><label>Max Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number" min="1"/></div>
|
|
1038
|
+
<div class="f-row"><label>System Prompt</label><textarea v-model="form.sendMessageOption.systemOverride" placeholder="System prompt override..."></textarea></div>
|
|
1039
|
+
|
|
1040
|
+
<!-- 高级配置 -->
|
|
1041
|
+
<div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--c-border)">
|
|
1042
|
+
<h4 style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--c-accent)">Advanced Configuration</h4>
|
|
1043
|
+
|
|
1044
|
+
<div class="f-row">
|
|
1045
|
+
<label>Enable Reasoning</label>
|
|
1046
|
+
<select v-model="form.sendMessageOption.enableReasoning">
|
|
1047
|
+
<option :value="false">Disabled</option>
|
|
1048
|
+
<option :value="true">Enabled</option>
|
|
1049
|
+
</select>
|
|
1050
|
+
</div>
|
|
1051
|
+
|
|
1052
|
+
<div class="f-row" v-if="form.sendMessageOption.enableReasoning">
|
|
1053
|
+
<label>Reasoning Effort</label>
|
|
1054
|
+
<select v-model="form.sendMessageOption.reasoningEffort">
|
|
1055
|
+
<option value="minimal">Minimal</option>
|
|
1056
|
+
<option value="low">Low</option>
|
|
1057
|
+
<option value="medium">Medium</option>
|
|
1058
|
+
<option value="high">High</option>
|
|
1059
|
+
</select>
|
|
1060
|
+
</div>
|
|
1061
|
+
|
|
1062
|
+
<div class="f-row">
|
|
1063
|
+
<label>Disable History Read</label>
|
|
1064
|
+
<select v-model="form.sendMessageOption.disableHistoryRead">
|
|
1065
|
+
<option :value="false">No</option>
|
|
1066
|
+
<option :value="true">Yes</option>
|
|
1067
|
+
</select>
|
|
1068
|
+
</div>
|
|
1069
|
+
|
|
1070
|
+
<div class="f-row">
|
|
1071
|
+
<label>Disable History Save</label>
|
|
1072
|
+
<select v-model="form.sendMessageOption.disableHistorySave">
|
|
1073
|
+
<option :value="false">No</option>
|
|
1074
|
+
<option :value="true">Yes</option>
|
|
1075
|
+
</select>
|
|
1076
|
+
</div>
|
|
1077
|
+
</div>
|
|
1078
|
+
</template>
|
|
1079
|
+
|
|
1080
|
+
<template v-if="modal==='tool'||modal==='processor'||modal==='trigger'">
|
|
1081
|
+
<div class="f-row"><label>Name</label><input v-model="form.name"/></div>
|
|
1082
|
+
<div class="f-row" v-if="modal==='processor'"><label>Type</label><select v-model="form.type"><option>pre</option><option>post</option></select></div>
|
|
1083
|
+
<div class="f-row"><label>Description</label><input v-model="form.description"/></div>
|
|
1084
|
+
<div class="f-row"><label>Code</label><textarea v-model="form.code" class="mono" style="min-height:200px"></textarea></div>
|
|
1085
|
+
</template>
|
|
1086
|
+
|
|
1087
|
+
<template v-if="modal==='toolGroup'">
|
|
1088
|
+
<div class="f-row"><label>Name</label><input v-model="form.name"/></div>
|
|
1089
|
+
<div class="f-row"><label>Description</label><input v-model="form.description"/></div>
|
|
1090
|
+
<div class="f-row"><label>Tool IDs (comma)</label><input v-model="form._toolIds" :placeholder="(form.toolIds||[]).join(', ')"/></div>
|
|
1091
|
+
<div class="f-row"><label>Status</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
|
|
1092
|
+
</template>
|
|
1093
|
+
|
|
1094
|
+
<div class="modal-btns">
|
|
1095
|
+
<button class="btn" @click="closeModal">Cancel</button>
|
|
1096
|
+
<button class="btn pri" @click="saveItem">Save</button>
|
|
457
1097
|
</div>
|
|
458
1098
|
</div>
|
|
1099
|
+
</div>
|
|
459
1100
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
<td>{{g.description}}</td>
|
|
469
|
-
<td><span v-if="g.isDefault" class="pill on">Default</span></td>
|
|
470
|
-
<td>
|
|
471
|
-
<button class="btn sm" @click="openModal('toolGroup',g)">Edit</button>
|
|
472
|
-
<button class="btn sm danger" @click="deleteItem('toolGroup',g.id)">Del</button>
|
|
473
|
-
</td>
|
|
474
|
-
</tr>
|
|
475
|
-
<tr v-if="!toolGroups.length"><td colspan="4" style="color:var(--text2)">No groups</td></tr>
|
|
476
|
-
</table>
|
|
1101
|
+
<!-- Config JSON Edit Modal -->
|
|
1102
|
+
<div v-if="editingConfig" class="modal-mask" @click.self="editingConfig=false">
|
|
1103
|
+
<div class="modal">
|
|
1104
|
+
<h3>Edit Configuration</h3>
|
|
1105
|
+
<div class="f-row"><label>JSON</label><textarea v-model="configForm" class="mono" style="min-height:360px"></textarea></div>
|
|
1106
|
+
<div class="modal-btns">
|
|
1107
|
+
<button class="btn" @click="editingConfig=false">Cancel</button>
|
|
1108
|
+
<button class="btn pri" @click="saveConfig">Save</button>
|
|
477
1109
|
</div>
|
|
478
1110
|
</div>
|
|
1111
|
+
</div>
|
|
479
1112
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
<div class="form-row"><label>Models (comma separated)</label><input v-model="form._models" :placeholder="(form.models||[]).join(', ')"/></div>
|
|
490
|
-
<div class="form-row"><label>Base URL</label><input v-model="form.options.baseUrl"/></div>
|
|
491
|
-
<div class="form-row"><label>API Key</label><input v-model="form.options.apiKey" type="password"/></div>
|
|
492
|
-
<div class="form-row"><label>Priority</label><input v-model.number="form.priority" type="number"/></div>
|
|
493
|
-
<div class="form-row"><label>Weight</label><input v-model.number="form.weight" type="number"/></div>
|
|
494
|
-
<div class="form-row"><label>Status</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
|
|
495
|
-
</template>
|
|
496
|
-
|
|
497
|
-
<!-- Preset form -->
|
|
498
|
-
<template v-if="modal==='preset'">
|
|
499
|
-
<div class="form-row"><label>Name</label><input v-model="form.name"/></div>
|
|
500
|
-
<div class="form-row"><label>Prefix</label><input v-model="form.prefix"/></div>
|
|
501
|
-
<div class="form-row"><label>Model</label><input v-model="form.sendMessageOption.model"/></div>
|
|
502
|
-
<div class="form-row"><label>Temperature</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1"/></div>
|
|
503
|
-
<div class="form-row"><label>Max Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number"/></div>
|
|
504
|
-
<div class="form-row"><label>System Prompt</label><textarea v-model="form.sendMessageOption.systemOverride"></textarea></div>
|
|
505
|
-
</template>
|
|
506
|
-
|
|
507
|
-
<!-- Tool / Processor / Trigger form -->
|
|
508
|
-
<template v-if="modal==='tool'||modal==='processor'||modal==='trigger'">
|
|
509
|
-
<div class="form-row"><label>Name</label><input v-model="form.name"/></div>
|
|
510
|
-
<div class="form-row" v-if="modal==='processor'"><label>Type</label><select v-model="form.type"><option>pre</option><option>post</option></select></div>
|
|
511
|
-
<div class="form-row"><label>Description</label><input v-model="form.description"/></div>
|
|
512
|
-
<div class="form-row"><label>Code</label><textarea v-model="form.code" style="min-height:200px;font-family:monospace;font-size:12px"></textarea></div>
|
|
513
|
-
</template>
|
|
514
|
-
|
|
515
|
-
<!-- Tool Group form -->
|
|
516
|
-
<template v-if="modal==='toolGroup'">
|
|
517
|
-
<div class="form-row"><label>Name</label><input v-model="form.name"/></div>
|
|
518
|
-
<div class="form-row"><label>Description</label><input v-model="form.description"/></div>
|
|
519
|
-
<div class="form-row"><label>Tool IDs (comma separated)</label><input v-model="form._toolIds" :placeholder="(form.toolIds||[]).join(', ')"/></div>
|
|
520
|
-
<div class="form-row"><label>Status</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
|
|
521
|
-
</template>
|
|
522
|
-
|
|
523
|
-
<div class="modal-btns">
|
|
524
|
-
<button class="btn" @click="closeModal">Cancel</button>
|
|
525
|
-
<button class="btn primary" @click="saveItem">Save</button>
|
|
526
|
-
</div>
|
|
527
|
-
</div>
|
|
1113
|
+
<!-- Session Detail Modal -->
|
|
1114
|
+
<div v-if="convDetail" class="modal-mask" @click.self="convDetail=null">
|
|
1115
|
+
<div class="modal">
|
|
1116
|
+
<h3>Session: {{convDetail.userId}}</h3>
|
|
1117
|
+
<div class="f-row"><label>Conversation ID</label><input :value="convDetail.current?.conversationId||'-'" readonly/></div>
|
|
1118
|
+
<div class="f-row"><label>Model</label><input :value="convDetail.current?.model||'-'" readonly/></div>
|
|
1119
|
+
<div class="f-row"><label>Preset</label><input :value="convDetail.current?.presetId||'-'" readonly/></div>
|
|
1120
|
+
<div class="f-row"><label>Full State</label><textarea readonly :value="JSON.stringify(convDetail,null,2)" class="mono" style="min-height:200px"></textarea></div>
|
|
1121
|
+
<div class="modal-btns"><button class="btn" @click="convDetail=null">Close</button></div>
|
|
528
1122
|
</div>
|
|
529
1123
|
</div>
|
|
530
1124
|
`
|