@hina114514/chaite 1.9.8 → 2.0.0

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.
@@ -9,25 +9,26 @@
9
9
  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
10
10
 
11
11
  :root{
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);
12
+ --c-bg:#070b14;
13
+ --c-surface:rgba(16,20,38,0.75);
14
+ --c-surface2:rgba(24,28,52,0.85);
15
+ --c-surface3:rgba(34,40,68,0.65);
16
+ --c-border:rgba(255,255,255,0.05);
17
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);
18
+ --c-text:#e8eaf2;
19
+ --c-dim:#8589a6;
20
+ --c-faint:#565b82;
21
+ --c-accent:#6198ef;
22
+ --c-accent-glow:rgba(97,152,239,0.2);
23
+ --c-accent2:rgba(97,152,239,0.12);
23
24
  --c-green:#4ade80;
24
25
  --c-green2:rgba(74,222,128,0.12);
25
26
  --c-red:#f87171;
26
27
  --c-red2:rgba(248,113,113,0.12);
27
28
  --c-yellow:#facc15;
28
29
  --c-purple:#a78bfa;
29
- --r:12px;
30
- --r2:20px;
30
+ --r:10px;
31
+ --r2:16px;
31
32
  --ease: cubic-bezier(.16,1,.3,1);
32
33
  --ease2: cubic-bezier(.7,0,.3,1);
33
34
  }
@@ -37,276 +38,196 @@ body{
37
38
  color:var(--c-text);
38
39
  font-family: 'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
39
40
  font-size:14px;
40
- line-height:1.5;
41
+ line-height:1.6;
41
42
  min-height:100vh;
42
43
  overflow-x:hidden;
44
+ -webkit-font-smoothing:antialiased;
43
45
  }
44
46
  body::before{
45
47
  content:'';position:fixed;inset:0;z-index:-1;
46
48
  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
+ radial-gradient(ellipse 90% 50% at 10% 0%,rgba(97,152,239,.07),transparent),
50
+ radial-gradient(ellipse 50% 40% at 90% 20%,rgba(97,152,239,.04),transparent),
51
+ radial-gradient(ellipse 60% 50% at 50% 80%,rgba(167,139,250,.04),transparent);
49
52
  pointer-events:none;
50
53
  }
51
54
 
52
55
  #app{min-height:100vh}
53
56
 
54
57
  /* Login */
55
- .login-wrap{
56
- display:flex;align-items:center;justify-content:center;min-height:100vh;
57
- padding:20px;
58
- }
58
+ .login-wrap{display:flex;align-items:center;justify-content:center;min-height:100vh;padding:20px}
59
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;
60
+ width:100%;max-width:420px;background:var(--c-surface);border:1px solid var(--c-border);
61
+ border-radius:var(--r2);padding:52px 40px;
62
+ backdrop-filter:blur(24px) saturate(1.4);-webkit-backdrop-filter:blur(24px) saturate(1.4);
63
+ box-shadow:0 24px 80px rgba(0,0,0,.45),inset 0 1px 0 rgba(255,255,255,.04);
64
+ text-align:center;animation:fadeUp .5s var(--ease) both
79
65
  }
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}
66
+ .login-card h1{font-size:30px;font-weight:700;letter-spacing:-.5px;margin-bottom:4px;background:linear-gradient(135deg,#6198ef,#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
67
+ .login-card .sub{color:var(--c-dim);font-size:13px;margin-bottom:32px;letter-spacing:.2px}
68
+ .login-card input{width:100%;padding:12px 16px;border-radius:10px;border:1px solid var(--c-border);background:rgba(255,255,255,.03);color:var(--c-text);font-size:14px;outline:none;transition:all .2s var(--ease)}
69
+ .login-card input:focus{border-color:var(--c-accent);box-shadow:0 0 0 3px var(--c-accent-glow)}
70
+ .login-card .err{color:var(--c-red);font-size:13px;margin:10px 0;min-height:20px}
71
+ .login-card .hint{font-size:11px;color:var(--c-faint);margin-top:16px}
72
+ .login-card .hint code{background:var(--c-surface2);padding:2px 8px;border-radius:4px;font-size:11px}
84
73
 
85
74
  /* Buttons */
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
- }
75
+ .btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 16px;border-radius:8px;font-size:13px;font-weight:500;border:1px solid var(--c-border);color:var(--c-text);background:var(--c-surface2);cursor:pointer;transition:all .2s var(--ease);white-space:nowrap;letter-spacing:.2px}
94
76
  .btn:hover{background:var(--c-surface3);border-color:var(--c-border2);transform:translateY(-1px)}
95
77
  .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)}
78
+ .btn:disabled{opacity:.35;pointer-events:none}
79
+ .btn.pri{background:linear-gradient(135deg,#6198ef,#4a82df);border-color:transparent;color:#fff;box-shadow:0 2px 14px rgba(97,152,239,.3)}
80
+ .btn.pri:hover{box-shadow:0 4px 24px rgba(97,152,239,.45);background:linear-gradient(135deg,#72a3f5,#5b8def)}
81
+ .btn.danger{color:var(--c-red);border-color:rgba(248,113,113,.2)}
103
82
  .btn.danger:hover{background:var(--c-red2)}
104
- .btn.sm{padding:4px 10px;font-size:11px;border-radius:6px}
83
+ .btn.sm{padding:5px 11px;font-size:11.5px;border-radius:6px}
105
84
  .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)}
85
+ .btn.ghost:hover{color:var(--c-text);background:rgba(255,255,255,.03)}
107
86
  .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
87
 
109
88
  /* Layout */
110
89
  .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
- }
90
+ .sidebar{width:224px;min-width:224px;background:var(--c-surface);border-right:1px solid var(--c-border);display:flex;flex-direction:column;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);box-shadow:2px 0 40px rgba(0,0,0,.3);transition:transform .35s var(--ease);z-index:50}
119
91
  .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
- }
92
+ .sidebar-logo h2{font-size:22px;font-weight:700;letter-spacing:-.5px;background:linear-gradient(135deg,#6198ef,#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
124
93
  .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
- }
94
+ .sidebar-nav{flex:1;padding:8px 10px}
95
+ .nav-item{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;font-size:13px;color:var(--c-dim);cursor:pointer;transition:all .15s var(--ease);margin-bottom:1px;position:relative;letter-spacing:.2px}
133
96
  .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
-
97
+ .nav-item.active{color:var(--c-accent);background:var(--c-accent2);font-weight:500}
98
+ .nav-item .icon{width:18px;text-align:center;font-size:14px;line-height:1;opacity:.8}
99
+ .nav-item.active .icon{opacity:1}
100
+ .nav-item .badge-mini{margin-left:auto;font-size:10px;padding:1px 7px;border-radius:8px;background:var(--c-accent2);color:var(--c-accent);font-weight:500}
141
101
  .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)}
102
+ .sidebar-footer .dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px}
103
+ .sidebar-footer .dot.ok{background:var(--c-green);box-shadow:0 0 8px var(--c-green);animation:pulse-dot 2s infinite}
144
104
 
145
105
  .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)}
106
+ .topbar{padding:16px 28px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--c-border);gap:12px;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);position:sticky;top:0;z-index:40;background:rgba(7,11,20,.85)}
107
+ .topbar .t{font-size:15px;font-weight:600;color:var(--c-text);letter-spacing:-.3px}
153
108
  .topbar .meta{display:flex;align-items:center;gap:10px}
154
109
  .topbar .refresh-text{font-size:11px;color:var(--c-faint)}
155
-
156
- .content{padding:24px 28px 40px;flex:1}
110
+ .content{padding:28px 28px 48px;flex:1}
157
111
 
158
112
  /* 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}
113
+ .toast{position:fixed;top:24px;right:24px;z-index:200;padding:11px 20px;border-radius:10px;font-size:13px;font-weight:500;animation:slideRight .35s var(--ease) both;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);box-shadow:0 8px 32px rgba(0,0,0,.4)}
114
+ .toast.ok{background:rgba(74,222,128,.9);color:#052e16}
166
115
  .toast.err{background:rgba(248,113,113,.9);color:#fff}
167
116
 
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)}
117
+ .menu-toggle{display:none;width:36px;height:36px;align-items:center;justify-content:center;border:none;background:none;color:var(--c-text);cursor:pointer;font-size:20px}
118
+ .sidebar-overlay{display:none;position:fixed;inset:0;z-index:45;background:rgba(0,0,0,.5);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}
174
119
 
175
120
  /* Cards */
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
- }
121
+ .card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r2);padding:24px;margin-bottom:18px;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);box-shadow:0 4px 24px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,255,255,.03);animation:fadeUp .45s var(--ease) both;transition:border-color .3s var(--ease)}
122
+ .card:hover{border-color:rgba(255,255,255,.08)}
182
123
  .card:nth-child(1){animation-delay:0s}
183
124
  .card:nth-child(2){animation-delay:.06s}
184
125
  .card:nth-child(3){animation-delay:.12s}
185
126
  .card:nth-child(4){animation-delay:.18s}
186
127
  .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}
128
+ .card-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}
129
+ .card-hd h3{font-size:15px;font-weight:600;display:flex;align-items:center;gap:8px;letter-spacing:-.2px}
189
130
  .card-hd h3 .dot{width:7px;height:7px;border-radius:50%}
190
131
 
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)}
132
+ /* Stats */
133
+ .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:14px;margin-bottom:22px}
134
+ .stat{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r2);padding:20px 22px;text-align:center;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);box-shadow:0 2px 16px rgba(0,0,0,.2),inset 0 1px 0 rgba(255,255,255,.02);transition:all .25s var(--ease);cursor:default;animation:fadeUp .4s var(--ease) both}
135
+ .stat:hover{transform:translateY(-3px);border-color:var(--c-accent);box-shadow:0 8px 28px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.04)}
200
136
  .stat:nth-child(1){animation-delay:0s}
201
137
  .stat:nth-child(2){animation-delay:.05s}
202
138
  .stat:nth-child(3){animation-delay:.1s}
203
139
  .stat:nth-child(4){animation-delay:.15s}
204
140
  .stat:nth-child(5){animation-delay:.2s}
205
141
  .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}
142
+ .stat .num{font-size:28px;font-weight:700;color:var(--c-accent);letter-spacing:-1px}
143
+ .stat .lbl{font-size:11px;color:var(--c-dim);margin-top:5px;text-transform:uppercase;letter-spacing:.8px;font-weight:500}
208
144
 
209
145
  /* Table */
210
- .tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
146
+ .tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;border-radius:8px}
211
147
  .tbl{width:100%;border-collapse:collapse;font-size:13px}
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)}
148
+ .tbl th{text-align:left;padding:10px 14px;color:var(--c-faint);font-weight:500;border-bottom:1px solid var(--c-border);font-size:11px;text-transform:uppercase;letter-spacing:.6px}
149
+ .tbl td{padding:10px 14px;border-bottom:1px solid var(--c-border)}
214
150
  .tbl tbody tr{transition:background .15s}
215
- .tbl tbody tr:hover{background:var(--c-surface2)}
151
+ .tbl tbody tr:hover{background:rgba(255,255,255,.02)}
216
152
 
217
153
  /* Pills */
218
- .pill{display:inline-block;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:500}
154
+ .pill{display:inline-block;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:500;letter-spacing:.2px}
219
155
  .pill.on{background:var(--c-green2);color:var(--c-green)}
220
156
  .pill.off{background:var(--c-red2);color:var(--c-red)}
221
157
  .pill.model{background:var(--c-accent2);color:var(--c-accent);margin:2px 3px 2px 0}
222
158
  .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}
159
+
160
+ /* Config grid cards */
161
+ .cfg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px;margin-top:16px}
162
+ .cfg-card{background:rgba(255,255,255,.015);border:1px solid var(--c-border);border-radius:12px;overflow:hidden;transition:all .2s var(--ease)}
163
+ .cfg-card:hover{border-color:rgba(255,255,255,.08);background:rgba(255,255,255,.022)}
164
+ .cfg-card-head{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;background:rgba(255,255,255,.015);cursor:pointer;font-size:14px;font-weight:600;letter-spacing:-.2px;user-select:none;gap:8px}
165
+ .cfg-card-head:hover{background:rgba(255,255,255,.025)}
166
+ .cfg-card-head .arr{font-size:10px;color:var(--c-faint);transition:transform .25s var(--ease);flex-shrink:0}
167
+ .cfg-card-head .arr.open{transform:rotate(90deg)}
168
+ .cfg-card-body{display:none;padding:0 18px 16px}
169
+ .cfg-card-body.show{display:block;animation:slideDown .2s var(--ease) both}
170
+ .cfg-field{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;margin-bottom:4px;border-radius:8px;background:rgba(255,255,255,.012);gap:8px;transition:all .15s var(--ease);position:relative}
171
+ .cfg-field:hover{background:rgba(255,255,255,.025)}
172
+ .cfg-field .k{font-size:11px;color:var(--c-faint);font-weight:500;letter-spacing:.3px;flex-shrink:0}
173
+ .cfg-field .v{font-size:12.5px;text-align:right;word-break:break-all;min-width:0;flex:1}
174
+ .cfg-field .v code{color:var(--c-green);font-family:'SF Mono',Fira Code,monospace;font-size:11.5px}
175
+ .cfg-field .v .num{color:var(--c-accent)}
176
+ .cfg-field .edit-btn{flex-shrink:0;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--c-faint);cursor:pointer;font-size:12px;transition:all .15s var(--ease);opacity:0}
177
+ .cfg-field:hover .edit-btn{opacity:1;border-color:var(--c-border)}
178
+ .cfg-field .edit-btn:hover{color:var(--c-accent);border-color:var(--c-accent);background:var(--c-accent2)}
179
+ .cfg-field.edit-mode{background:rgba(97,152,239,.06);border:1px solid rgba(97,152,239,.15);border-radius:8px;flex-wrap:wrap;padding:10px 12px}
180
+ .cfg-field.edit-mode .v{display:none}
181
+ .cfg-field.edit-mode .edit-btn{display:none}
182
+ .cfg-field .edit-row{display:none;width:100%;gap:6px}
183
+ .cfg-field.edit-mode .edit-row{display:flex;align-items:center}
184
+ .cfg-field .edit-row input,.cfg-field .edit-row select{flex:1;padding:7px 10px;background:rgba(0,0,0,.25);border:1px solid var(--c-border);border-radius:6px;color:var(--c-text);font-size:12.5px;font-family:inherit;outline:none;transition:border-color .15s}
185
+ .cfg-field .edit-row input:focus,.cfg-field .edit-row select:focus{border-color:var(--c-accent)}
186
+ .cfg-field .edit-row .btn-xs{padding:5px 10px;font-size:11px;border-radius:6px;border:1px solid var(--c-border);background:var(--c-surface2);color:var(--c-text);cursor:pointer;transition:all .15s;white-space:nowrap}
187
+ .cfg-field .edit-row .btn-xs.save{background:var(--c-accent);border-color:var(--c-accent);color:#fff}
188
+ .cfg-field .edit-row .btn-xs:hover{opacity:.85}
189
+ .cfg-actions{display:flex;gap:8px;margin-top:20px;padding-top:16px;border-top:1px solid var(--c-border)}
246
190
 
247
191
  /* Modal */
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}
192
+ .modal-mask{position:fixed;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.55);padding:24px;animation:fadeIn .2s both;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}
193
+ .modal{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r2);padding:32px;width:100%;max-width:540px;max-height:85vh;overflow-y:auto;backdrop-filter:blur(28px);-webkit-backdrop-filter:blur(28px);box-shadow:0 24px 80px rgba(0,0,0,.5);animation:scaleIn .3s var(--ease) both}
194
+ .modal h3{font-size:18px;font-weight:600;margin-bottom:24px;letter-spacing:-.3px}
261
195
  .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}
196
+ .f-row label{display:block;font-size:11px;color:var(--c-dim);margin-bottom:6px;letter-spacing:.5px;font-weight:500}
197
+ .f-row input,.f-row select,.f-row textarea{width:100%;padding:10px 13px;background:rgba(255,255,255,.03);border:1px solid var(--c-border);border-radius:8px;color:var(--c-text);font-size:13px;font-family:inherit;transition:all .2s var(--ease);resize:vertical}
198
+ .f-row input:focus,.f-row select:focus,.f-row textarea:focus{outline:none;border-color:var(--c-accent);box-shadow:0 0 0 3px var(--c-accent-glow)}
199
+ .f-row textarea{min-height:80px}
270
200
  .f-row textarea.mono{font-family:'SF Mono',Fira Code,monospace;font-size:12px}
271
- .modal-btns{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}
201
+ .modal-btns{display:flex;justify-content:flex-end;gap:8px;margin-top:24px}
272
202
 
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}}
203
+ /* Animations */
204
+ .page-enter{animation:fadeUp .35s var(--ease) both}
205
+ @keyframes fadeUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
276
206
  @keyframes fadeIn{from{opacity:0}to{opacity:1}}
277
207
  @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}}
208
+ @keyframes slideRight{from{opacity:0;transform:translateX(20px)}to{opacity:1;transform:none}}
209
+ @keyframes slideDown{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
280
210
  @keyframes spin{to{transform:rotate(360deg)}}
281
211
  @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%}
212
+ @keyframes pulse-dot{0%,100%{opacity:1}50%{opacity:.4}}
289
213
 
290
214
  .mono{font-family:'SF Mono',Fira Code,monospace}
291
215
  code{font-family:'SF Mono',Fira Code,monospace;font-size:12px}
292
216
 
293
217
  /* Responsive */
294
218
  @media(max-width:768px){
295
- .sidebar{
296
- position:fixed;left:0;top:0;bottom:0;transform:translateX(-100%);
297
- z-index:50;width:260px;
298
- }
219
+ .sidebar{position:fixed;left:0;top:0;bottom:0;transform:translateX(-100%);z-index:50;width:260px}
299
220
  .sidebar.open{transform:translateX(0)}
300
221
  .sidebar-overlay.show{display:block}
301
222
  .menu-toggle{display:flex}
302
223
  .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}
224
+ .content{padding:16px 16px 32px}
225
+ .stats{grid-template-columns:repeat(2,1fr);gap:10px}
226
+ .stat{padding:16px 14px}
227
+ .stat .num{font-size:24px}
228
+ .card{padding:18px}
229
+ .cfg-grid{grid-template-columns:1fr}
230
+ .modal{max-width:95%;padding:24px}
310
231
  }
311
232
  @media(max-width:480px){
312
233
  .stats{grid-template-columns:1fr 1fr}
@@ -365,6 +286,7 @@ createApp({
365
286
  // Config edit
366
287
  const editingConfig=ref(false)
367
288
  const configForm=ref('{}')
289
+ const editingCfgField=ref(null)
368
290
 
369
291
  // Toast
370
292
  const toastMsg=ref('')
@@ -385,8 +307,8 @@ createApp({
385
307
  jwt.value=d.data.token;localStorage.setItem('chaite_jwt',d.data.token);loggedIn.value=true
386
308
  url.searchParams.delete('token');history.replaceState(null,'',url.pathname+url.search)
387
309
  refreshAll()
388
- }else{loginError.value=d.message||'Login failed'}
389
- }catch(e){loginError.value='Network error'}
310
+ }else{loginError.value=d.message||'登录失败'}
311
+ }catch(e){loginError.value='网络错误'}
390
312
  finally{loginLoading.value=false}
391
313
  }
392
314
  function logout(){jwt.value='';localStorage.removeItem('chaite_jwt');loggedIn.value=false;clearInterval(timer)}
@@ -445,29 +367,13 @@ createApp({
445
367
  modal.value=type;editing.value=item
446
368
  if(item){
447
369
  form.value=JSON.parse(JSON.stringify(item))
448
- // 为 channel 初始化 JSON 字段
449
370
  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
- }
371
+ form.value._models=(form.value.models||[]).join(', ')
372
+ form.value._responseModalities=JSON.stringify(form.value.sendMessageOption?.responseModalities||[])
373
+ form.value._safetySettings=JSON.stringify(form.value.sendMessageOption?.safetySettings||[],null,2)
374
+ if(!form.value.sendMessageOption)form.value.sendMessageOption={model:'',temperature:0.8,maxToken:4096,systemOverride:'',enableReasoning:false,reasoningEffort:'high',reasoningBudgetTokens:0,responseModalities:[],safetySettings:[],toolCallLimit:{maxConsecutiveCalls:8,maxConsecutiveIdenticalCalls:2}}
468
375
  }
469
- }
470
- else{
376
+ }else{
471
377
  form.value={
472
378
  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
379
  preset:{name:'',prefix:'',sendMessageOption:{model:'',temperature:0.8,maxToken:4096,systemOverride:'',enableReasoning:false,reasoningEffort:'high',disableHistoryRead:false,disableHistorySave:false}},
@@ -486,50 +392,23 @@ createApp({
486
392
  const type=modal.value
487
393
  const id=editing.value?.id
488
394
  const url=apiMap[type]+(id?'/'+id:'')
489
-
490
- // 处理 channel 的 JSON 字段
491
395
  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
396
+ const formData=JSON.parse(JSON.stringify(form.value))
397
+ if(formData._models){formData.models=formData._models.split(',').map(m=>m.trim()).filter(m=>m);delete formData._models}
398
+ if(formData._responseModalities){try{formData.sendMessageOption.responseModalities=JSON.parse(formData._responseModalities)}catch(e){};delete formData._responseModalities}
399
+ if(formData._safetySettings){try{formData.sendMessageOption.safetySettings=JSON.parse(formData._safetySettings)}catch(e){};delete formData._safetySettings}
400
+ form.value=formData
521
401
  }
522
-
523
402
  const d=await API(url,{method:id?'PUT':'POST',body:JSON.stringify(form.value)})
524
- if(d.data!==undefined){toast('Saved');closeModal();refreshAll()}
525
- else toast(d.message||'Error','err')
403
+ if(d.data!==undefined){toast('已保存');closeModal();refreshAll()}
404
+ else toast(d.message||'错误','err')
526
405
  }catch(e){toast(e.message,'err')}
527
406
  }
528
407
 
529
408
  async function deleteItem(type,id){
530
- if(!confirm('Delete?'))return
409
+ if(!confirm('确认删除?'))return
531
410
  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()}
411
+ try{await API(apiMap[type]+'/'+id,{method:'DELETE'});toast('已删除');refreshAll()}
533
412
  catch(e){toast(e.message,'err')}
534
413
  }
535
414
 
@@ -537,29 +416,75 @@ createApp({
537
416
  const expandedCfg=ref({})
538
417
  const SKIP_CONFIG_KEYS=new Set(['version','_saveOrigin','authKey','cloudBaseUrl','cloudApiKey'])
539
418
 
419
+ // 配置键名翻译表
420
+ const CFG_LABELS={
421
+ basic:'基础配置',bym:'伪人配置',llm:'大模型配置',management:'管理配置',
422
+ chaite:'Chaite',memory:'记忆配置',update:'更新配置',
423
+ commandPrefix:'命令前缀',apiPrefix:'API前缀',host:'主机',port:'端口',
424
+ accessToken:'访问令牌',tokenExpiresIn:'令牌有效期',
425
+ defaultChatPresetId:'默认预设ID',defaultBymPresetId:'伪人默认预设',
426
+ model:'模型',temperature:'温度',maxToken:'最大Token',systemPrompt:'系统提示词',
427
+ enableReasoning:'启用推理',reasoningEffort:'推理强度',
428
+ default:'默认',command:'指令',prefix:'前缀',
429
+ systemOverride:'系统提示词覆盖',disableHistoryRead:'禁用读历史',disableHistorySave:'禁用写历史',
430
+ userSettings:'用户设置',rateLimit:'速率限制',
431
+ maxConsecutiveCalls:'最大连续调用',maxConsecutiveIdenticalCalls:'最大连续相同调用',
432
+ debug:'调试模式',blackGroups:'群黑名单',whiteGroups:'群白名单',
433
+ blackUsers:'用户黑名单',whiteUsers:'用户白名单',
434
+ promptBlockWords:'输入屏蔽词',responseBlockWords:'输出屏蔽词',
435
+ customPresetUserBlackList:'预设切换用户黑名单',customPresetUserWhiteList:'预设切换用户白名单',
436
+ summary:'摘要',perModel:'按模型',perChannel:'按渠道',
437
+ totalCalls:'总调用',totalTokens:'总Token',
438
+ }
439
+ function cfgLabel(k){
440
+ return CFG_LABELS[k]||k.charAt(0).toUpperCase()+k.slice(1)
441
+ }
442
+
540
443
  const cfgSections=computed(()=>{
541
444
  const secs=[]
542
445
  const order=['basic','bym','llm','management','chaite','memory','update']
543
446
  for(const key of order){
544
447
  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)})
448
+ if(v!==undefined) secs.push({key,label:cfgLabel(key),fields:typeof v==='object'?v:{_value:v},isObj:typeof v==='object'&&!Array.isArray(v)})
546
449
  }
547
- // Remaining keys not in order
548
450
  for(const[k,v]of Object.entries(config.value)){
549
451
  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)})
452
+ secs.push({key:k,label:cfgLabel(k),fields:typeof v==='object'?v:{_value:v},isObj:typeof v==='object'&&!Array.isArray(v)})
551
453
  }
552
454
  return secs
553
455
  })
554
456
  function toggleCfg(k){expandedCfg.value[k]=!expandedCfg.value[k]}
457
+
458
+ // Inline config field editing
459
+ function startEditField(secKey, fieldKey, currentVal){
460
+ const raw=typeof currentVal==='string'?currentVal:JSON.stringify(currentVal)
461
+ editingCfgField.value={sec:secKey,key:fieldKey,val:raw,type:typeof currentVal==='boolean'?'bool':typeof currentVal==='number'?'num':'str'}
462
+ }
463
+ function cancelEditField(){editingCfgField.value=null}
464
+ async function saveEditField(secKey, fieldKey){
465
+ if(!editingCfgField.value)return
466
+ let newVal=editingCfgField.value.val
467
+ const type=editingCfgField.value.type
468
+ if(type==='bool') newVal=newVal==='true'||newVal===true
469
+ else if(type==='num') newVal=Number(newVal)
470
+ try{
471
+ // Deep set the nested key
472
+ const cfgCopy=JSON.parse(JSON.stringify(config.value))
473
+ cfgCopy[secKey][fieldKey]=newVal
474
+ const d=await API('/api/config',{method:'POST',body:JSON.stringify(cfgCopy)})
475
+ if(d.data!==undefined){toast('配置已保存');cancelEditField();refreshAll()}
476
+ else toast(d.message||'错误','err')
477
+ }catch(e){toast('保存失败: '+e.message,'err')}
478
+ }
479
+
555
480
  function editConfig(){configForm.value=JSON.stringify(config.value,null,2);editingConfig.value=true}
556
481
  async function saveConfig(){
557
482
  try{
558
483
  const obj=JSON.parse(configForm.value)
559
484
  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')}
485
+ if(d.data!==undefined){toast('配置已保存');editingConfig.value=false;refreshAll()}
486
+ else toast(d.message||'错误','err')
487
+ }catch(e){toast('无效 JSON: '+e.message,'err')}
563
488
  }
564
489
 
565
490
  // Conversations
@@ -568,8 +493,8 @@ createApp({
568
493
  catch(e){toast(e.message,'err')}
569
494
  }
570
495
  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()}
496
+ if(!confirm('清除用户 '+uid+' 的状态?'))return
497
+ try{await API('/api/state/'+encodeURIComponent(uid),{method:'DELETE'});toast('已清除');refreshAll()}
573
498
  catch(e){toast(e.message,'err')}
574
499
  }
575
500
 
@@ -590,6 +515,7 @@ createApp({
590
515
  channelsOk,
591
516
  modal,editing,form,openModal,closeModal,saveItem,deleteItem,testChannel,
592
517
  expandedCfg,cfgSections,toggleCfg,editConfig,editingConfig,configForm,saveConfig,SKIP_CONFIG_KEYS,
518
+ editingCfgField,startEditField,cancelEditField,saveEditField,cfgLabel,
593
519
  viewConv,clearConv,
594
520
  toastMsg,toastType,uptimeStr,fmtNum,fLen,refreshAll,
595
521
  }
@@ -602,13 +528,13 @@ createApp({
602
528
  <div v-if="!loggedIn" class="login-wrap">
603
529
  <div class="login-card">
604
530
  <h1>Chaite</h1>
605
- <p class="sub">Management Console</p>
531
+ <p class="sub">管理控制台</p>
606
532
  <div v-if="!loginLoading">
607
- <input v-model="loginInput" type="password" placeholder="Access token..."
533
+ <input v-model="loginInput" type="password" placeholder="输入访问令牌..."
608
534
  @keyup.enter="doLogin" autofocus/>
609
535
  <div class="err">{{loginError}}</div>
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>
536
+ <button class="btn pri" @click="doLogin" style="width:100%;padding:10px;margin-top:4px">登录</button>
537
+ <p class="hint">或在 URL 中携带 <code>?token=xxx</code> 访问</p>
612
538
  </div>
613
539
  <div v-else style="padding:20px"><div class="spin" style="display:inline-block"></div></div>
614
540
  </div>
@@ -628,42 +554,42 @@ createApp({
628
554
  </div>
629
555
  <nav class="sidebar-nav">
630
556
  <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>
557
+ <span class="icon">◈</span>概览
558
+ <span class="badge-mini" v-if="health">{{health.channels?.total||0}}渠道</span>
633
559
  </div>
634
560
  <div :class="['nav-item',page==='channels'&&'active']" @click="navigate('channels')">
635
- <span class="icon">▤</span>Channels
561
+ <span class="icon">▤</span>渠道
636
562
  <span class="badge-mini" v-if="channelsOk">{{channelsOk}}</span>
637
563
  </div>
638
564
  <div :class="['nav-item',page==='tools'&&'active']" @click="navigate('tools')">
639
- <span class="icon">⚙</span>Tools
565
+ <span class="icon">⚙</span>工具
640
566
  <span class="badge-mini" v-if="tools.length">{{tools.length}}</span>
641
567
  </div>
642
568
  <div :class="['nav-item',page==='presets'&&'active']" @click="navigate('presets')">
643
- <span class="icon">✎</span>Presets
569
+ <span class="icon">✎</span>预设
644
570
  </div>
645
571
  <div :class="['nav-item',page==='processors'&&'active']" @click="navigate('processors')">
646
- <span class="icon">⇄</span>Processors
572
+ <span class="icon">⇄</span>处理器
647
573
  </div>
648
574
  <div :class="['nav-item',page==='triggers'&&'active']" @click="navigate('triggers')">
649
- <span class="icon">⚡</span>Triggers
575
+ <span class="icon">⚡</span>触发器
650
576
  </div>
651
577
  <div :class="['nav-item',page==='groups'&&'active']" @click="navigate('groups')">
652
- <span class="icon">☰</span>Groups
578
+ <span class="icon">☰</span>工具组
653
579
  </div>
654
580
  <div style="margin:8px 0;border-top:1px solid var(--c-border)"></div>
655
581
  <div :class="['nav-item',page==='conversations'&&'active']" @click="navigate('conversations')">
656
- <span class="icon">◎</span>Sessions
582
+ <span class="icon">◎</span>会话
657
583
  <span class="badge-mini" v-if="conversations.length">{{conversations.length}}</span>
658
584
  </div>
659
585
  <div :class="['nav-item',page==='config'&&'active']" @click="navigate('config')">
660
- <span class="icon">⚙</span>Config
586
+ <span class="icon">⚙</span>配置
661
587
  </div>
662
588
  </nav>
663
589
  <div class="sidebar-footer">
664
590
  <span :class="['dot',isHealthy?'ok':'']"></span>
665
- {{isHealthy?'Online':'Offline'}}
666
- <br><span style="font-size:10px">port {{health?.system?.platform||'-'}}</span>
591
+ {{isHealthy?'在线':'离线'}}
592
+ <br><span style="font-size:10px">端口 {{health?.system?.platform||'-'}}</span>
667
593
  </div>
668
594
  </aside>
669
595
 
@@ -674,7 +600,7 @@ createApp({
674
600
  <div style="display:flex;align-items:center;gap:12px">
675
601
  <button class="menu-toggle" @click="sidebarOpen=!sidebarOpen">☰</button>
676
602
  <span class="t">
677
- {{page==='dashboard'?'Overview':page==='conversations'?'Sessions':page==='groups'?'Groups':page.charAt(0).toUpperCase()+page.slice(1)}}
603
+ {{page==='dashboard'?'概览':page==='conversations'?'会话':page==='groups'?'工具组':page.charAt(0).toUpperCase()+page.slice(1)}}
678
604
  </span>
679
605
  </div>
680
606
  <div class="meta">
@@ -692,37 +618,37 @@ createApp({
692
618
  <!-- ========== DASHBOARD ========== -->
693
619
  <div v-if="page==='dashboard'" class="page-enter">
694
620
  <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>
621
+ <div class="stat"><div class="num">{{uptimeStr(health.uptime)}}</div><div class="lbl">运行时长</div></div>
622
+ <div class="stat"><div class="num">{{health.channels?.total||0}}</div><div class="lbl">渠道</div></div>
623
+ <div class="stat"><div class="num">{{health.models?.count||0}}</div><div class="lbl">模型</div></div>
624
+ <div class="stat"><div class="num">{{health.tools?.count||0}}</div><div class="lbl">工具</div></div>
625
+ <div class="stat"><div class="num">{{health.system?.processMemory||0}}M</div><div class="lbl">内存</div></div>
626
+ <div class="stat"><div class="num">{{fmtNum(stats?.summary?.totalCalls)}}</div><div class="lbl">总调用次数</div></div>
701
627
  </div>
702
628
 
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>
629
+ <div v-if="!loading&&!health" class="card" style="text-align:center;color:var(--c-dim);padding:48px">无数据服务器是否运行?</div>
704
630
 
705
631
  <div class="card" v-if="health">
706
- <div class="card-hd"><h3><span class="dot" style="background:var(--c-green)"></span>System</h3></div>
632
+ <div class="card-hd"><h3><span class="dot" style="background:var(--c-green)"></span>系统信息</h3></div>
707
633
  <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>
634
+ <div><span style="color:var(--c-dim)">版本</span><br>{{health.version}}</div>
709
635
  <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>
636
+ <div><span style="color:var(--c-dim)">平台</span><br>{{health.system?.platform}} {{health.system?.arch}}</div>
637
+ <div><span style="color:var(--c-dim)">CPU</span><br>{{health.system?.cpus}}</div>
638
+ <div><span style="color:var(--c-dim)">堆内存</span><br>{{health.system?.heapUsed}}M / {{health.system?.processMemory}}M</div>
639
+ <div><span style="color:var(--c-dim)">系统内存</span><br>{{health.system?.freeMemory}}M 可用</div>
714
640
  </div>
715
641
  </div>
716
642
 
717
643
  <div class="card" v-if="health?.models?.list?.length">
718
- <div class="card-hd"><h3>Models</h3></div>
644
+ <div class="card-hd"><h3>模型列表</h3></div>
719
645
  <div><span v-for="m in health.models.list" class="pill model">{{m}}</span></div>
720
646
  </div>
721
647
 
722
648
  <div class="card" v-if="stats?.perModel&&Object.keys(stats.perModel).length">
723
- <div class="card-hd"><h3>Usage by Model</h3></div>
649
+ <div class="card-hd"><h3>模型使用统计</h3></div>
724
650
  <div class="tbl-wrap"><table class="tbl">
725
- <thead><tr><th>Model</th><th>Calls</th><th>Tokens</th></tr></thead>
651
+ <thead><tr><th>模型</th><th>调用次数</th><th>Token</th></tr></thead>
726
652
  <tbody>
727
653
  <tr v-for="(v,k) in stats.perModel" :key="k">
728
654
  <td><span class="pill model">{{k}}</span></td>
@@ -733,9 +659,9 @@ createApp({
733
659
  </div>
734
660
 
735
661
  <div class="card" v-if="stats?.perChannel&&Object.keys(stats.perChannel).length">
736
- <div class="card-hd"><h3>Usage by Channel</h3></div>
662
+ <div class="card-hd"><h3>渠道使用统计</h3></div>
737
663
  <div class="tbl-wrap"><table class="tbl">
738
- <thead><tr><th>Channel</th><th>Calls</th><th>Tokens</th><th>Models</th></tr></thead>
664
+ <thead><tr><th>渠道</th><th>调用次数</th><th>Token</th><th>模型</th></tr></thead>
739
665
  <tbody>
740
666
  <tr v-for="(v,k) in stats.perChannel" :key="k">
741
667
  <td>{{k}}</td><td>{{fmtNum(v.calls)}}</td><td>{{fmtNum(v.tokens)}}</td>
@@ -746,7 +672,7 @@ createApp({
746
672
  </div>
747
673
 
748
674
  <div class="card" v-if="conversations.length">
749
- <div class="card-hd"><h3>{{conversations.length}} Active Sessions</h3></div>
675
+ <div class="card-hd"><h3>{{conversations.length}} 个活跃会话</h3></div>
750
676
  <span v-for="c in conversations" class="pill tag">{{c.userId}}</span>
751
677
  </div>
752
678
  </div>
@@ -754,25 +680,22 @@ createApp({
754
680
  <!-- ========== CHANNELS ========== -->
755
681
  <div v-if="page==='channels'" class="page-enter">
756
682
  <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>
683
+ <div class="card-hd"><h3>渠道</h3><button class="btn pri sm" @click="openModal('channel')">+ 新建</button></div>
761
684
  <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>
685
+ <thead><tr><th>状态</th><th>名称</th><th>适配器</th><th>模型</th><th>优先级</th><th>调用次数</th><th></th></tr></thead>
763
686
  <tbody>
764
687
  <tr v-for="ch in channels" :key="ch.id">
765
- <td><span :class="['pill',ch.status==='enabled'?'on':'off']">{{ch.status}}</span></td>
688
+ <td><span :class="['pill',ch.status==='enabled'?'on':'off']">{{ch.status==='enabled'?'启用':'禁用'}}</span></td>
766
689
  <td>{{ch.name}}</td><td>{{ch.adapterType}}</td>
767
690
  <td><span v-for="m in (ch.models||[])" class="pill model">{{m}}</span></td>
768
691
  <td>{{ch.priority}}</td><td>{{fmtNum(ch.statistics?.callTimes)}}</td>
769
692
  <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>
693
+ <button class="btn sm" @click="testChannel(ch.id)">测试</button>
694
+ <button class="btn sm" @click="openModal('channel',ch)">编辑</button>
695
+ <button class="btn sm danger" @click="deleteItem('channel',ch.id)">删除</button>
773
696
  </td>
774
697
  </tr>
775
- <tr v-if="!channels.length"><td colspan="7" style="color:var(--c-dim);text-align:center;padding:32px">No channels</td></tr>
698
+ <tr v-if="!channels.length"><td colspan="7" style="color:var(--c-dim);text-align:center;padding:32px">暂无渠道</td></tr>
776
699
  </tbody>
777
700
  </table></div>
778
701
  </div>
@@ -781,22 +704,15 @@ createApp({
781
704
  <!-- ========== TOOLS ========== -->
782
705
  <div v-if="page==='tools'" class="page-enter">
783
706
  <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>
707
+ <div class="card-hd"><h3>工具</h3><button class="btn pri sm" @click="openModal('tool')">+ 新建</button></div>
788
708
  <div class="tbl-wrap"><table class="tbl">
789
- <thead><tr><th>Name</th><th>Description</th><th></th></tr></thead>
709
+ <thead><tr><th>名称</th><th>描述</th><th></th></tr></thead>
790
710
  <tbody>
791
711
  <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>
712
+ <td>{{t.name||t.id}}</td><td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{t.description}}</td>
713
+ <td><button class="btn sm" @click="openModal('tool',t)">编辑</button><button class="btn sm danger" @click="deleteItem('tool',t.id)">删除</button></td>
798
714
  </tr>
799
- <tr v-if="!tools.length"><td colspan="3" style="color:var(--c-dim);text-align:center;padding:32px">No tools</td></tr>
715
+ <tr v-if="!tools.length"><td colspan="3" style="color:var(--c-dim);text-align:center;padding:32px">暂无工具</td></tr>
800
716
  </tbody>
801
717
  </table></div>
802
718
  </div>
@@ -805,24 +721,17 @@ createApp({
805
721
  <!-- ========== PRESETS ========== -->
806
722
  <div v-if="page==='presets'" class="page-enter">
807
723
  <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>
724
+ <div class="card-hd"><h3>预设</h3><button class="btn pri sm" @click="openModal('preset')">+ 新建</button></div>
812
725
  <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>
726
+ <thead><tr><th>名称</th><th>前缀</th><th>模型</th><th>温度</th><th></th></tr></thead>
814
727
  <tbody>
815
728
  <tr v-for="p in presets" :key="p.id">
816
- <td>{{p.name}}</td>
817
- <td><code>{{p.prefix}}</code></td>
729
+ <td>{{p.name}}</td><td><code>{{p.prefix}}</code></td>
818
730
  <td><span class="pill model">{{p.sendMessageOption?.model||'-'}}</span></td>
819
731
  <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>
732
+ <td><button class="btn sm" @click="openModal('preset',p)">编辑</button><button class="btn sm danger" @click="deleteItem('preset',p.id)">删除</button></td>
824
733
  </tr>
825
- <tr v-if="!presets.length"><td colspan="5" style="color:var(--c-dim);text-align:center;padding:32px">No presets</td></tr>
734
+ <tr v-if="!presets.length"><td colspan="5" style="color:var(--c-dim);text-align:center;padding:32px">暂无预设</td></tr>
826
735
  </tbody>
827
736
  </table></div>
828
737
  </div>
@@ -831,23 +740,17 @@ createApp({
831
740
  <!-- ========== PROCESSORS ========== -->
832
741
  <div v-if="page==='processors'" class="page-enter">
833
742
  <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>
743
+ <div class="card-hd"><h3>处理器</h3><button class="btn pri sm" @click="openModal('processor')">+ 新建</button></div>
838
744
  <div class="tbl-wrap"><table class="tbl">
839
- <thead><tr><th>Name</th><th>Type</th><th>Description</th><th></th></tr></thead>
745
+ <thead><tr><th>名称</th><th>类型</th><th>描述</th><th></th></tr></thead>
840
746
  <tbody>
841
747
  <tr v-for="p in processors" :key="p.id">
842
748
  <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>
749
+ <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==='pre'?'前置':'后置'}}</span></td>
844
750
  <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>
751
+ <td><button class="btn sm" @click="openModal('processor',p)">编辑</button><button class="btn sm danger" @click="deleteItem('processor',p.id)">删除</button></td>
849
752
  </tr>
850
- <tr v-if="!processors.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">No processors</td></tr>
753
+ <tr v-if="!processors.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">暂无处理器</td></tr>
851
754
  </tbody>
852
755
  </table></div>
853
756
  </div>
@@ -856,21 +759,15 @@ createApp({
856
759
  <!-- ========== TRIGGERS ========== -->
857
760
  <div v-if="page==='triggers'" class="page-enter">
858
761
  <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>
762
+ <div class="card-hd"><h3>触发器</h3><button class="btn pri sm" @click="openModal('trigger')">+ 新建</button></div>
863
763
  <div class="tbl-wrap"><table class="tbl">
864
- <thead><tr><th>Name</th><th>Description</th><th></th></tr></thead>
764
+ <thead><tr><th>名称</th><th>描述</th><th></th></tr></thead>
865
765
  <tbody>
866
766
  <tr v-for="t in triggers" :key="t.id">
867
767
  <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>
768
+ <td><button class="btn sm" @click="openModal('trigger',t)">编辑</button><button class="btn sm danger" @click="deleteItem('trigger',t.id)">删除</button></td>
872
769
  </tr>
873
- <tr v-if="!triggers.length"><td colspan="3" style="color:var(--c-dim);text-align:center;padding:32px">No triggers</td></tr>
770
+ <tr v-if="!triggers.length"><td colspan="3" style="color:var(--c-dim);text-align:center;padding:32px">暂无触发器</td></tr>
874
771
  </tbody>
875
772
  </table></div>
876
773
  </div>
@@ -879,22 +776,16 @@ createApp({
879
776
  <!-- ========== GROUPS ========== -->
880
777
  <div v-if="page==='groups'" class="page-enter">
881
778
  <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>
779
+ <div class="card-hd"><h3>工具组</h3><button class="btn pri sm" @click="openModal('toolGroup')">+ 新建</button></div>
886
780
  <div class="tbl-wrap"><table class="tbl">
887
- <thead><tr><th>Name</th><th>Description</th><th>Default</th><th></th></tr></thead>
781
+ <thead><tr><th>名称</th><th>描述</th><th>默认</th><th></th></tr></thead>
888
782
  <tbody>
889
783
  <tr v-for="g in toolGroups" :key="g.id">
890
784
  <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>
785
+ <td><span v-if="g.isDefault" class="pill on">默认</span></td>
786
+ <td><button class="btn sm" @click="openModal('toolGroup',g)">编辑</button><button class="btn sm danger" @click="deleteItem('toolGroup',g.id)">删除</button></td>
896
787
  </tr>
897
- <tr v-if="!toolGroups.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">No groups</td></tr>
788
+ <tr v-if="!toolGroups.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">暂无工具组</td></tr>
898
789
  </tbody>
899
790
  </table></div>
900
791
  </div>
@@ -903,20 +794,16 @@ createApp({
903
794
  <!-- ========== SESSIONS ========== -->
904
795
  <div v-if="page==='conversations'" class="page-enter">
905
796
  <div class="card">
906
- <div class="card-hd"><h3>Active Sessions</h3></div>
797
+ <div class="card-hd"><h3>活跃会话</h3></div>
907
798
  <div class="tbl-wrap"><table class="tbl">
908
- <thead><tr><th>User ID</th><th>Conversation</th><th>Model</th><th></th></tr></thead>
799
+ <thead><tr><th>用户 ID</th><th>对话 ID</th><th>模型</th><th></th></tr></thead>
909
800
  <tbody>
910
801
  <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>
802
+ <td>{{c.userId}}</td><td><code style="font-size:11px">{{c.conversationId||'-'}}</code></td>
913
803
  <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>
804
+ <td><button class="btn sm" @click="viewConv(c.userId)">查看</button><button class="btn sm danger" @click="clearConv(c.userId)">清除</button></td>
918
805
  </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>
806
+ <tr v-if="!conversations.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">暂无活跃会话</td></tr>
920
807
  </tbody>
921
808
  </table></div>
922
809
  </div>
@@ -925,29 +812,43 @@ createApp({
925
812
  <!-- ========== CONFIG ========== -->
926
813
  <div v-if="page==='config'" class="page-enter">
927
814
  <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>
815
+ <div class="card-hd"><h3>插件配置</h3></div>
816
+ <div v-if="cfgSections.length" class="cfg-grid">
817
+ <div v-for="sec in cfgSections" :key="sec.key" class="cfg-card">
818
+ <div class="cfg-card-head" @click="toggleCfg(sec.key)">
819
+ <span>{{sec.label}}</span>
820
+ <span style="display:flex;align-items:center;gap:8px">
821
+ <span style="font-weight:400;color:var(--c-faint);font-size:11px">{{Object.keys(sec.fields||{}).length}} 项</span>
822
+ <span :class="['arr',expandedCfg[sec.key]&&'open']">▶</span>
823
+ </span>
934
824
  </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>
825
+ <div :class="['cfg-card-body',expandedCfg[sec.key]&&'show']">
826
+ <div v-for="(fv,fk) in sec.fields" :key="fk"
827
+ :class="['cfg-field',editingCfgField&&editingCfgField.sec===sec.key&&editingCfgField.key===fk?'edit-mode':'']">
828
+ <span class="k">{{cfgLabel(fk)}}</span>
938
829
  <span class="v">
939
- <template v-if="typeof fv==='boolean'"><span :class="['pill',fv?'on':'off']">{{fv}}</span></template>
830
+ <template v-if="typeof fv==='boolean'"><span :class="['pill',fv?'on':'off']">{{fv?'true':'false'}}</span></template>
940
831
  <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>
832
+ <template v-else-if="typeof fv==='string'&&fv.length<=60"><code>{{fv||'""'}}</code></template>
833
+ <template v-else-if="typeof fv==='string'"><code :title="fv">{{fLen(fv,40)}}</code></template>
834
+ <template v-else><span style="color:var(--c-faint);font-size:11px">对象</span></template>
944
835
  </span>
836
+ <button v-if="typeof fv!=='object'" class="edit-btn" @click="startEditField(sec.key,fk,fv)" title="编辑">✎</button>
837
+ <div v-if="editingCfgField" class="edit-row">
838
+ <input v-if="typeof fv==='string'" v-model="editingCfgField.val" @keyup.enter="saveEditField(sec.key,fk)"/>
839
+ <select v-else-if="typeof fv==='boolean'" v-model="editingCfgField.val">
840
+ <option value="true">true</option><option value="false">false</option>
841
+ </select>
842
+ <input v-else v-model="editingCfgField.val" type="number" @keyup.enter="saveEditField(sec.key,fk)"/>
843
+ <button class="btn-xs save" @click="saveEditField(sec.key,fk)">保存</button>
844
+ <button class="btn-xs" @click="cancelEditField">取消</button>
845
+ </div>
945
846
  </div>
946
847
  </div>
947
848
  </div>
948
849
  </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>
850
+ <div v-else style="color:var(--c-dim);font-size:13px;padding:20px 0">暂无配置数据</div>
851
+ <div class="cfg-actions"><button class="btn sm" @click="editConfig">编辑完整 JSON</button></div>
951
852
  </div>
952
853
  </div>
953
854
 
@@ -960,144 +861,93 @@ createApp({
960
861
  <!-- CRUD Modal -->
961
862
  <div v-if="modal" class="modal-mask" @click.self="closeModal">
962
863
  <div class="modal">
963
- <h3>{{editing?'Edit':'New'}} {{modal}}</h3>
864
+ <h3>{{editing?'编辑':'新建'}} {{modal}}</h3>
964
865
 
965
866
  <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>
867
+ <div class="f-row"><label>名称</label><input v-model="form.name"/></div>
868
+ <div class="f-row"><label>适配器</label><select v-model="form.adapterType"><option>openai</option><option>gemini</option><option>claude</option></select></div>
869
+ <div class="f-row"><label>模型(逗号分隔)</label><input v-model="form._models" :placeholder="(form.models||[]).join(', ')"/></div>
969
870
  <div class="f-row"><label>Base URL</label><input v-model="form.options.baseUrl"/></div>
970
871
  <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>
872
+ <div class="f-row"><label>优先级</label><input v-model.number="form.priority" type="number"/></div>
873
+ <div class="f-row"><label>权重</label><input v-model.number="form.weight" type="number"/></div>
874
+ <div class="f-row"><label>状态</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
974
875
 
975
- <!-- 模型高级配置 -->
876
+ <!-- 模型配置 -->
976
877
  <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>
878
+ <h4 style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--c-accent)">模型配置</h4>
879
+ <div class="f-row"><label>模型名称</label><input v-model="form.sendMessageOption.model"/></div>
880
+ <div class="f-row"><label>温度 (Temperature)</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1" min="0" max="2"/></div>
881
+ <div class="f-row"><label>最大 Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number" min="1"/></div>
882
+ <div class="f-row"><label>系统提示词</label><textarea v-model="form.sendMessageOption.systemOverride" placeholder="覆盖系统提示词..."></textarea></div>
983
883
 
984
- <!-- Gemini 思考配置 -->
985
884
  <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>
885
+ <h5 style="font-size:13px;font-weight:600;margin-bottom:8px;color:var(--c-purple)">Gemini 思考配置</h5>
987
886
  <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>
887
+ <label>启用推理</label>
888
+ <select v-model="form.sendMessageOption.enableReasoning"><option :value="false">关闭</option><option :value="true">开启</option></select>
993
889
  </div>
994
890
  <div class="f-row" v-if="form.sendMessageOption.enableReasoning">
995
- <label>Thinking Level</label>
891
+ <label>思考等级</label>
996
892
  <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>
893
+ <option value="minimal">极低(低延迟)</option><option value="low">低</option><option value="medium">中等(平衡)</option><option value="high">高(深度推理)</option>
1001
894
  </select>
1002
895
  </div>
1003
- <div class="f-row" v-if="form.sendMessageOption.enableReasoning">
1004
- <label>Reasoning Budget Tokens</label>
1005
- <input v-model.number="form.sendMessageOption.reasoningBudgetTokens" type="number" min="0" placeholder="0 for auto"/>
1006
- </div>
1007
896
  </div>
1008
897
 
1009
- <!-- 工具调用限制 -->
1010
898
  <div style="margin-top:12px;padding:12px;background:var(--c-surface2);border-radius:8px">
1011
- <h5 style="font-size:13px;font-weight:600;margin-bottom:8px;color:var(--c-yellow)">Tool Call Limits</h5>
1012
- <div class="f-row">
1013
- <label>Max Consecutive Calls</label>
1014
- <input v-model.number="form.sendMessageOption.toolCallLimit.maxConsecutiveCalls" type="number" min="1"/>
1015
- </div>
1016
- <div class="f-row">
1017
- <label>Max Consecutive Identical Calls</label>
1018
- <input v-model.number="form.sendMessageOption.toolCallLimit.maxConsecutiveIdenticalCalls" type="number" min="1"/>
1019
- </div>
899
+ <h5 style="font-size:13px;font-weight:600;margin-bottom:8px;color:var(--c-yellow)">工具调用限制</h5>
900
+ <div class="f-row"><label>最大连续调用次数</label><input v-model.number="form.sendMessageOption.toolCallLimit.maxConsecutiveCalls" type="number" min="1"/></div>
901
+ <div class="f-row"><label>最大连续相同调用次数</label><input v-model.number="form.sendMessageOption.toolCallLimit.maxConsecutiveIdenticalCalls" type="number" min="1"/></div>
1020
902
  </div>
1021
903
 
1022
- <!-- 高级配置(JSON 格式) -->
1023
904
  <div style="margin-top:12px">
1024
- <div class="f-row">
1025
- <label>Response Modalities (JSON array)</label>
1026
- <input v-model="form._responseModalities" :placeholder="JSON.stringify(form.sendMessageOption.responseModalities||[])"/>
1027
- </div>
1028
- <div class="f-row">
1029
- <label>Safety Settings (JSON array)</label>
1030
- <textarea v-model="form._safetySettings" class="mono" style="min-height:60px" :placeholder="JSON.stringify(form.sendMessageOption.safetySettings||[],null,2)"></textarea>
1031
- </div>
905
+ <div class="f-row"><label>响应模态 (JSON 数组)</label><input v-model="form._responseModalities" :placeholder="JSON.stringify(form.sendMessageOption.responseModalities||[])"/></div>
906
+ <div class="f-row"><label>安全设置 (JSON 数组)</label><textarea v-model="form._safetySettings" class="mono" style="min-height:60px" :placeholder="JSON.stringify(form.sendMessageOption.safetySettings||[],null,2)"></textarea></div>
1032
907
  </div>
1033
908
  </div>
1034
909
  </template>
1035
910
 
1036
911
  <template v-if="modal==='preset'">
1037
- <div class="f-row"><label>Name</label><input v-model="form.name"/></div>
1038
- <div class="f-row"><label>Prefix</label><input v-model="form.prefix"/></div>
1039
- <div class="f-row"><label>Model</label><input v-model="form.sendMessageOption.model"/></div>
1040
- <div class="f-row"><label>Temperature</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1" min="0" max="2"/></div>
1041
- <div class="f-row"><label>Max Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number" min="1"/></div>
1042
- <div class="f-row"><label>System Prompt</label><textarea v-model="form.sendMessageOption.systemOverride" placeholder="System prompt override..."></textarea></div>
912
+ <div class="f-row"><label>名称</label><input v-model="form.name"/></div>
913
+ <div class="f-row"><label>前缀</label><input v-model="form.prefix"/></div>
914
+ <div class="f-row"><label>模型</label><input v-model="form.sendMessageOption.model"/></div>
915
+ <div class="f-row"><label>温度 (Temperature)</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1" min="0" max="2"/></div>
916
+ <div class="f-row"><label>最大 Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number" min="1"/></div>
917
+ <div class="f-row"><label>系统提示词</label><textarea v-model="form.sendMessageOption.systemOverride" placeholder="覆盖系统提示词..."></textarea></div>
1043
918
 
1044
- <!-- 高级配置 -->
1045
919
  <div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--c-border)">
1046
- <h4 style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--c-accent)">Advanced Configuration</h4>
1047
-
920
+ <h4 style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--c-accent)">高级配置</h4>
1048
921
  <div class="f-row">
1049
- <label>Enable Reasoning</label>
1050
- <select v-model="form.sendMessageOption.enableReasoning">
1051
- <option :value="false">Disabled</option>
1052
- <option :value="true">Enabled</option>
1053
- </select>
922
+ <label>启用推理</label>
923
+ <select v-model="form.sendMessageOption.enableReasoning"><option :value="false">关闭</option><option :value="true">开启</option></select>
1054
924
  </div>
1055
-
1056
925
  <div class="f-row" v-if="form.sendMessageOption.enableReasoning">
1057
- <label>Reasoning Effort</label>
1058
- <select v-model="form.sendMessageOption.reasoningEffort">
1059
- <option value="minimal">Minimal</option>
1060
- <option value="low">Low</option>
1061
- <option value="medium">Medium</option>
1062
- <option value="high">High</option>
1063
- </select>
1064
- </div>
1065
-
1066
- <div class="f-row">
1067
- <label>Disable History Read</label>
1068
- <select v-model="form.sendMessageOption.disableHistoryRead">
1069
- <option :value="false">No</option>
1070
- <option :value="true">Yes</option>
1071
- </select>
1072
- </div>
1073
-
1074
- <div class="f-row">
1075
- <label>Disable History Save</label>
1076
- <select v-model="form.sendMessageOption.disableHistorySave">
1077
- <option :value="false">No</option>
1078
- <option :value="true">Yes</option>
1079
- </select>
926
+ <label>推理强度</label>
927
+ <select v-model="form.sendMessageOption.reasoningEffort"><option value="minimal">极低</option><option value="low">低</option><option value="medium">中</option><option value="high">高</option></select>
1080
928
  </div>
929
+ <div class="f-row"><label>禁用读取历史</label><select v-model="form.sendMessageOption.disableHistoryRead"><option :value="false">否</option><option :value="true">是</option></select></div>
930
+ <div class="f-row"><label>禁用保存历史</label><select v-model="form.sendMessageOption.disableHistorySave"><option :value="false">否</option><option :value="true">是</option></select></div>
1081
931
  </div>
1082
932
  </template>
1083
933
 
1084
934
  <template v-if="modal==='tool'||modal==='processor'||modal==='trigger'">
1085
- <div class="f-row"><label>Name</label><input v-model="form.name"/></div>
1086
- <div class="f-row" v-if="modal==='processor'"><label>Type</label><select v-model="form.type"><option>pre</option><option>post</option></select></div>
1087
- <div class="f-row"><label>Description</label><input v-model="form.description"/></div>
1088
- <div class="f-row"><label>Code</label><textarea v-model="form.code" class="mono" style="min-height:200px"></textarea></div>
935
+ <div class="f-row"><label>名称</label><input v-model="form.name"/></div>
936
+ <div class="f-row" v-if="modal==='processor'"><label>类型</label><select v-model="form.type"><option>pre</option><option>post</option></select></div>
937
+ <div class="f-row"><label>描述</label><input v-model="form.description"/></div>
938
+ <div class="f-row"><label>代码</label><textarea v-model="form.code" class="mono" style="min-height:200px"></textarea></div>
1089
939
  </template>
1090
940
 
1091
941
  <template v-if="modal==='toolGroup'">
1092
- <div class="f-row"><label>Name</label><input v-model="form.name"/></div>
1093
- <div class="f-row"><label>Description</label><input v-model="form.description"/></div>
1094
- <div class="f-row"><label>Tool IDs (comma)</label><input v-model="form._toolIds" :placeholder="(form.toolIds||[]).join(', ')"/></div>
1095
- <div class="f-row"><label>Status</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
942
+ <div class="f-row"><label>名称</label><input v-model="form.name"/></div>
943
+ <div class="f-row"><label>描述</label><input v-model="form.description"/></div>
944
+ <div class="f-row"><label>工具 ID(逗号分隔)</label><input v-model="form._toolIds" :placeholder="(form.toolIds||[]).join(', ')"/></div>
945
+ <div class="f-row"><label>状态</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
1096
946
  </template>
1097
947
 
1098
948
  <div class="modal-btns">
1099
- <button class="btn" @click="closeModal">Cancel</button>
1100
- <button class="btn pri" @click="saveItem">Save</button>
949
+ <button class="btn" @click="closeModal">取消</button>
950
+ <button class="btn pri" @click="saveItem">保存</button>
1101
951
  </div>
1102
952
  </div>
1103
953
  </div>
@@ -1105,11 +955,11 @@ createApp({
1105
955
  <!-- Config JSON Edit Modal -->
1106
956
  <div v-if="editingConfig" class="modal-mask" @click.self="editingConfig=false">
1107
957
  <div class="modal">
1108
- <h3>Edit Configuration</h3>
958
+ <h3>编辑配置</h3>
1109
959
  <div class="f-row"><label>JSON</label><textarea v-model="configForm" class="mono" style="min-height:360px"></textarea></div>
1110
960
  <div class="modal-btns">
1111
- <button class="btn" @click="editingConfig=false">Cancel</button>
1112
- <button class="btn pri" @click="saveConfig">Save</button>
961
+ <button class="btn" @click="editingConfig=false">取消</button>
962
+ <button class="btn pri" @click="saveConfig">保存</button>
1113
963
  </div>
1114
964
  </div>
1115
965
  </div>
@@ -1117,12 +967,12 @@ createApp({
1117
967
  <!-- Session Detail Modal -->
1118
968
  <div v-if="convDetail" class="modal-mask" @click.self="convDetail=null">
1119
969
  <div class="modal">
1120
- <h3>Session: {{convDetail.userId}}</h3>
1121
- <div class="f-row"><label>Conversation ID</label><input :value="convDetail.current?.conversationId||'-'" readonly/></div>
1122
- <div class="f-row"><label>Model</label><input :value="convDetail.current?.model||'-'" readonly/></div>
1123
- <div class="f-row"><label>Preset</label><input :value="convDetail.current?.presetId||'-'" readonly/></div>
1124
- <div class="f-row"><label>Full State</label><textarea readonly :value="JSON.stringify(convDetail,null,2)" class="mono" style="min-height:200px"></textarea></div>
1125
- <div class="modal-btns"><button class="btn" @click="convDetail=null">Close</button></div>
970
+ <h3>会话:{{convDetail.userId}}</h3>
971
+ <div class="f-row"><label>对话 ID</label><input :value="convDetail.current?.conversationId||'-'" readonly/></div>
972
+ <div class="f-row"><label>模型</label><input :value="convDetail.current?.model||'-'" readonly/></div>
973
+ <div class="f-row"><label>预设</label><input :value="convDetail.current?.presetId||'-'" readonly/></div>
974
+ <div class="f-row"><label>完整状态</label><textarea readonly :value="JSON.stringify(convDetail,null,2)" class="mono" style="min-height:200px"></textarea></div>
975
+ <div class="modal-btns"><button class="btn" @click="convDetail=null">关闭</button></div>
1126
976
  </div>
1127
977
  </div>
1128
978
  `