@hina114514/chaite 1.9.9 → 2.0.1
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-DH0o9gIS.mjs → adapters-CKtCN3uS.mjs} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-DLwIgFtm.mjs → src-Cf1BYmcj.mjs} +3 -3
- package/dist/{types-rlvvkPnw.mjs → types-BGIMNqTC.mjs} +1 -1
- package/frontend/build/index.html +524 -661
- package/package.json +1 -1
|
@@ -3,310 +3,265 @@
|
|
|
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
11
|
:root{
|
|
12
|
-
--c-bg:#
|
|
13
|
-
--c-surface:rgba(18,
|
|
14
|
-
--c-surface2:rgba(
|
|
15
|
-
--c-surface3:rgba(
|
|
16
|
-
--c-border:rgba(255,
|
|
17
|
-
--c-border2:rgba(255,
|
|
18
|
-
--c-text:#
|
|
19
|
-
--c-dim:#
|
|
20
|
-
--c-faint:#
|
|
21
|
-
--c-accent:#
|
|
22
|
-
--c-accent2:rgba(
|
|
23
|
-
--c-
|
|
24
|
-
--c-
|
|
25
|
-
--c-
|
|
26
|
-
--c-
|
|
27
|
-
--c-
|
|
28
|
-
--c-
|
|
29
|
-
--
|
|
30
|
-
--
|
|
31
|
-
--
|
|
32
|
-
--
|
|
12
|
+
--c-bg:#0d0b14;
|
|
13
|
+
--c-surface:rgba(24,18,36,0.78);
|
|
14
|
+
--c-surface2:rgba(32,24,48,0.88);
|
|
15
|
+
--c-surface3:rgba(44,36,62,0.68);
|
|
16
|
+
--c-border:rgba(255,183,197,0.08);
|
|
17
|
+
--c-border2:rgba(255,183,197,0.15);
|
|
18
|
+
--c-text:#eeeaf4;
|
|
19
|
+
--c-dim:#a89cb8;
|
|
20
|
+
--c-faint:#6e6480;
|
|
21
|
+
--c-accent:#ff9aa2;
|
|
22
|
+
--c-accent2:rgba(255,154,162,0.14);
|
|
23
|
+
--c-accent-glow:rgba(255,154,162,0.25);
|
|
24
|
+
--c-pink:#ffb7c5;
|
|
25
|
+
--c-pink2:rgba(255,183,197,0.1);
|
|
26
|
+
--c-green:#8fd19e;
|
|
27
|
+
--c-green2:rgba(143,209,158,0.12);
|
|
28
|
+
--c-red:#ff8589;
|
|
29
|
+
--c-red2:rgba(255,133,137,0.12);
|
|
30
|
+
--c-yellow:#ffe0a0;
|
|
31
|
+
--c-purple:#c9b1ff;
|
|
32
|
+
--r:10px;
|
|
33
|
+
--r2:16px;
|
|
34
|
+
--ease:cubic-bezier(.16,1,.3,1);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
body{
|
|
36
38
|
background:var(--c-bg);
|
|
37
39
|
color:var(--c-text);
|
|
38
|
-
font-family: '
|
|
39
|
-
font-size:14px;
|
|
40
|
-
|
|
41
|
-
min-height:100vh;
|
|
42
|
-
overflow-x:hidden;
|
|
40
|
+
font-family:'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;
|
|
41
|
+
font-size:14px;line-height:1.6;min-height:100vh;overflow-x:hidden;
|
|
42
|
+
-webkit-font-smoothing:antialiased;
|
|
43
43
|
}
|
|
44
44
|
body::before{
|
|
45
|
-
content:'';position:fixed;inset:0;z-index:-1;
|
|
45
|
+
content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;
|
|
46
46
|
background:
|
|
47
|
-
radial-gradient(ellipse
|
|
48
|
-
radial-gradient(ellipse
|
|
49
|
-
|
|
47
|
+
radial-gradient(ellipse 70% 50% at 0% 0%,rgba(255,154,162,.1),transparent 70%),
|
|
48
|
+
radial-gradient(ellipse 50% 40% at 100% 20%,rgba(255,183,197,.06),transparent 60%),
|
|
49
|
+
radial-gradient(ellipse 50% 50% at 50% 90%,rgba(201,177,255,.05),transparent 60%);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/* Sakura petals (CSS only) */
|
|
53
|
+
@keyframes fall{0%{transform:translateY(-10vh) rotate(0deg);opacity:1}100%{transform:translateY(110vh) rotate(720deg);opacity:0}}
|
|
54
|
+
@keyframes sway{0%,100%{margin-left:0}50%{margin-left:30px}}
|
|
55
|
+
.petal{position:fixed;top:-20px;z-index:-1;pointer-events:none;font-size:16px;opacity:.3;animation:fall linear infinite,sway 4s ease-in-out infinite}
|
|
56
|
+
.petal:nth-child(1){left:5%;animation-duration:12s,4s;animation-delay:0s;font-size:14px}
|
|
57
|
+
.petal:nth-child(2){left:15%;animation-duration:10s,3.5s;animation-delay:2s;font-size:18px}
|
|
58
|
+
.petal:nth-child(3){left:25%;animation-duration:14s,4.5s;animation-delay:4s;font-size:12px}
|
|
59
|
+
.petal:nth-child(4){left:40%;animation-duration:11s,3.8s;animation-delay:1s;font-size:20px}
|
|
60
|
+
.petal:nth-child(5){left:55%;animation-duration:13s,4.2s;animation-delay:3s;font-size:15px}
|
|
61
|
+
.petal:nth-child(6){left:65%;animation-duration:9s,3.2s;animation-delay:5s;font-size:17px}
|
|
62
|
+
.petal:nth-child(7){left:75%;animation-duration:15s,4.8s;animation-delay:0s;font-size:13px}
|
|
63
|
+
.petal:nth-child(8){left:85%;animation-duration:10.5s,3.6s;animation-delay:3.5s;font-size:16px}
|
|
64
|
+
.petal:nth-child(9){left:92%;animation-duration:12.5s,4s;animation-delay:1.5s;font-size:14px}
|
|
65
|
+
.petal:nth-child(10){left:50%;animation-duration:11.5s,3.9s;animation-delay:6s;font-size:19px}
|
|
66
|
+
|
|
52
67
|
#app{min-height:100vh}
|
|
53
68
|
|
|
54
69
|
/* Login */
|
|
55
|
-
.login-wrap{
|
|
56
|
-
display:flex;align-items:center;justify-content:center;min-height:100vh;
|
|
57
|
-
padding:20px;
|
|
58
|
-
}
|
|
70
|
+
.login-wrap{display:flex;align-items:center;justify-content:center;min-height:100vh;padding:20px}
|
|
59
71
|
.login-card{
|
|
60
|
-
width:100%;max-width:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
backdrop-filter:blur(20px);
|
|
66
|
-
-webkit-backdrop-filter:blur(20px);
|
|
67
|
-
text-align:center;
|
|
68
|
-
animation: fadeUp .6s var(--ease) both;
|
|
72
|
+
width:100%;max-width:420px;background:var(--c-surface);border:1px solid var(--c-border);
|
|
73
|
+
border-radius:20px;padding:52px 40px;
|
|
74
|
+
backdrop-filter:blur(24px) saturate(1.4);
|
|
75
|
+
box-shadow:0 24px 80px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,183,197,.06);
|
|
76
|
+
text-align:center;animation:fadeUp .5s var(--ease) both
|
|
69
77
|
}
|
|
70
78
|
.login-card h1{
|
|
71
|
-
font-size:
|
|
72
|
-
background:linear-gradient(135deg,#
|
|
79
|
+
font-size:32px;font-weight:700;letter-spacing:-.5px;margin-bottom:4px;
|
|
80
|
+
background:linear-gradient(135deg,#ffb7c5,#ff9aa2,#ffb7b2,#c9b1ff);
|
|
81
|
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-size:200% auto;
|
|
82
|
+
animation:logoShine 3s linear infinite
|
|
73
83
|
}
|
|
74
|
-
|
|
75
|
-
.login-card
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
.login-card
|
|
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}
|
|
84
|
+
@keyframes logoShine{0%{background-position:0% center}100%{background-position:200% center}}
|
|
85
|
+
.login-card .sub{color:var(--c-dim);font-size:13px;margin-bottom:32px;letter-spacing:.2px}
|
|
86
|
+
.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}
|
|
87
|
+
.login-card input:focus{border-color:var(--c-accent);box-shadow:0 0 0 3px var(--c-accent-glow)}
|
|
88
|
+
.login-card .err{color:var(--c-red);font-size:13px;margin:10px 0;min-height:20px}
|
|
89
|
+
.login-card .hint{font-size:11px;color:var(--c-faint);margin-top:16px}
|
|
90
|
+
.login-card .hint code{background:var(--c-surface2);padding:2px 8px;border-radius:4px;font-size:11px;color:var(--c-pink)}
|
|
84
91
|
|
|
85
92
|
/* 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
|
-
}
|
|
93
|
+
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 16px;border-radius:10px;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
94
|
.btn:hover{background:var(--c-surface3);border-color:var(--c-border2);transform:translateY(-1px)}
|
|
95
|
-
.btn:active{transform:translateY(0) scale(.
|
|
96
|
-
.btn:disabled{opacity:.
|
|
97
|
-
.btn.pri{
|
|
98
|
-
|
|
99
|
-
|
|
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)}
|
|
95
|
+
.btn:active{transform:translateY(0) scale(.97)}
|
|
96
|
+
.btn:disabled{opacity:.35;pointer-events:none}
|
|
97
|
+
.btn.pri{background:linear-gradient(135deg,#ff9aa2,#ff8589);border-color:transparent;color:#fff;box-shadow:0 2px 14px var(--c-accent-glow)}
|
|
98
|
+
.btn.pri:hover{box-shadow:0 4px 24px rgba(255,154,162,.4);background:linear-gradient(135deg,#ffb7c5,#ff9aa2)}
|
|
99
|
+
.btn.danger{color:var(--c-red);border-color:rgba(255,133,137,.2)}
|
|
103
100
|
.btn.danger:hover{background:var(--c-red2)}
|
|
104
|
-
.btn.sm{padding:
|
|
101
|
+
.btn.sm{padding:5px 12px;font-size:11.5px;border-radius:7px}
|
|
105
102
|
.btn.ghost{background:transparent;border-color:transparent;color:var(--c-dim)}
|
|
106
|
-
.btn.ghost:hover{color:var(--c-text);background:rgba(255,255,255,.
|
|
103
|
+
.btn.ghost:hover{color:var(--c-text);background:rgba(255,255,255,.03)}
|
|
107
104
|
.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}
|
|
105
|
+
.btn .icon-s{margin-right:2px}
|
|
108
106
|
|
|
109
107
|
/* Layout */
|
|
110
108
|
.layout{display:flex;min-height:100vh}
|
|
111
|
-
.sidebar{
|
|
112
|
-
|
|
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}
|
|
109
|
+
.sidebar{width:230px;min-width:230px;background:rgba(16,12,24,0.82);border-right:1px solid var(--c-border);display:flex;flex-direction:column;backdrop-filter:blur(20px);box-shadow:2px 0 40px rgba(0,0,0,.35);transition:transform .35s var(--ease);z-index:50}
|
|
110
|
+
.sidebar-logo{padding:26px 20px 18px;border-bottom:1px solid var(--c-border)}
|
|
120
111
|
.sidebar-logo h2{
|
|
121
112
|
font-size:22px;font-weight:700;letter-spacing:-.5px;
|
|
122
|
-
background:linear-gradient(135deg,#
|
|
113
|
+
background:linear-gradient(135deg,#ffb7c5,#ff9aa2,#ffb7b2);-webkit-background-clip:text;-webkit-text-fill-color:transparent
|
|
123
114
|
}
|
|
124
115
|
.sidebar-logo .ver{font-size:11px;color:var(--c-faint);margin-top:2px}
|
|
125
|
-
|
|
126
|
-
.
|
|
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
|
-
}
|
|
116
|
+
.sidebar-nav{flex:1;padding:10px 10px}
|
|
117
|
+
.nav-item{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:10px;font-size:13.5px;color:var(--c-dim);cursor:pointer;transition:all .15s var(--ease);margin-bottom:2px;position:relative}
|
|
133
118
|
.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
|
|
136
|
-
.nav-item .
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
119
|
+
.nav-item.active{color:var(--c-accent);background:var(--c-accent2);font-weight:500}
|
|
120
|
+
.nav-item.active::after{content:'';position:absolute;left:-10px;top:50%;transform:translateY(-50%);width:3px;height:16px;border-radius:2px;background:var(--c-accent)}
|
|
121
|
+
.nav-item .icon{width:18px;text-align:center;font-size:15px;line-height:1;opacity:.8}
|
|
122
|
+
.nav-item.active .icon{opacity:1}
|
|
123
|
+
.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
124
|
.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:
|
|
143
|
-
.sidebar-footer .dot.ok{background:var(--c-green);box-shadow:0 0
|
|
125
|
+
.sidebar-footer .dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:8px}
|
|
126
|
+
.sidebar-footer .dot.ok{background:var(--c-green);box-shadow:0 0 8px var(--c-green);animation:pulse-dot 2s infinite}
|
|
144
127
|
|
|
145
128
|
.main{flex:1;overflow-x:hidden;display:flex;flex-direction:column}
|
|
146
|
-
.topbar{
|
|
147
|
-
|
|
148
|
-
|
|
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)}
|
|
129
|
+
.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);position:sticky;top:0;z-index:40;background:rgba(13,11,20,.85)}
|
|
130
|
+
.topbar .t{font-size:15px;font-weight:600;color:var(--c-text);letter-spacing:-.3px;display:flex;align-items:center;gap:8px}
|
|
131
|
+
.topbar .t::before{content:'🌸';font-size:13px}
|
|
153
132
|
.topbar .meta{display:flex;align-items:center;gap:10px}
|
|
154
133
|
.topbar .refresh-text{font-size:11px;color:var(--c-faint)}
|
|
155
|
-
|
|
156
|
-
.content{padding:24px 28px 40px;flex:1}
|
|
134
|
+
.content{padding:28px 28px 48px;flex:1}
|
|
157
135
|
|
|
158
136
|
/* Toast */
|
|
159
|
-
.toast{
|
|
160
|
-
|
|
161
|
-
|
|
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}
|
|
137
|
+
.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);box-shadow:0 8px 32px rgba(0,0,0,.4)}
|
|
138
|
+
.toast.ok{background:rgba(143,209,158,.92);color:#0a2816}
|
|
139
|
+
.toast.err{background:rgba(255,133,137,.92);color:#fff}
|
|
167
140
|
|
|
168
|
-
|
|
169
|
-
.
|
|
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)}
|
|
141
|
+
.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}
|
|
142
|
+
.sidebar-overlay{display:none;position:fixed;inset:0;z-index:45;background:rgba(0,0,0,.5);backdrop-filter:blur(4px)}
|
|
174
143
|
|
|
175
144
|
/* Cards */
|
|
176
|
-
.card{
|
|
177
|
-
|
|
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
|
-
}
|
|
145
|
+
.card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r2);padding:24px;margin-bottom:18px;backdrop-filter:blur(20px);box-shadow:0 4px 24px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,183,197,.04);animation:fadeUp .45s var(--ease) both;transition:border-color .3s}
|
|
146
|
+
.card:hover{border-color:var(--c-border2)}
|
|
182
147
|
.card:nth-child(1){animation-delay:0s}
|
|
183
148
|
.card:nth-child(2){animation-delay:.06s}
|
|
184
149
|
.card:nth-child(3){animation-delay:.12s}
|
|
185
150
|
.card:nth-child(4){animation-delay:.18s}
|
|
186
151
|
.card:nth-child(5){animation-delay:.24s}
|
|
187
|
-
.card-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:
|
|
188
|
-
.card-hd h3{font-size:
|
|
152
|
+
.card-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}
|
|
153
|
+
.card-hd h3{font-size:15px;font-weight:600;display:flex;align-items:center;gap:8px;letter-spacing:-.2px}
|
|
189
154
|
.card-hd h3 .dot{width:7px;height:7px;border-radius:50%}
|
|
190
155
|
|
|
191
|
-
/*
|
|
192
|
-
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(
|
|
193
|
-
.stat{
|
|
194
|
-
|
|
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)}
|
|
156
|
+
/* Stats */
|
|
157
|
+
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:14px;margin-bottom:22px}
|
|
158
|
+
.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);box-shadow:0 2px 16px rgba(0,0,0,.2),inset 0 1px 0 rgba(255,183,197,.03);transition:all .25s var(--ease);cursor:default;animation:fadeUp .4s var(--ease) both}
|
|
159
|
+
.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,183,197,.08)}
|
|
200
160
|
.stat:nth-child(1){animation-delay:0s}
|
|
201
161
|
.stat:nth-child(2){animation-delay:.05s}
|
|
202
162
|
.stat:nth-child(3){animation-delay:.1s}
|
|
203
163
|
.stat:nth-child(4){animation-delay:.15s}
|
|
204
164
|
.stat:nth-child(5){animation-delay:.2s}
|
|
205
165
|
.stat:nth-child(6){animation-delay:.25s}
|
|
206
|
-
.stat .num{font-size:
|
|
207
|
-
.stat .lbl{font-size:11px;color:var(--c-dim);margin-top:
|
|
166
|
+
.stat .num{font-size:28px;font-weight:700;color:var(--c-accent);letter-spacing:-1px}
|
|
167
|
+
.stat .lbl{font-size:11px;color:var(--c-dim);margin-top:5px;text-transform:uppercase;letter-spacing:.8px;font-weight:500}
|
|
208
168
|
|
|
209
169
|
/* Table */
|
|
210
|
-
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
170
|
+
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;border-radius:8px}
|
|
211
171
|
.tbl{width:100%;border-collapse:collapse;font-size:13px}
|
|
212
|
-
.tbl th{text-align:left;padding:10px
|
|
213
|
-
.tbl td{padding:10px
|
|
172
|
+
.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}
|
|
173
|
+
.tbl td{padding:10px 14px;border-bottom:1px solid var(--c-border)}
|
|
214
174
|
.tbl tbody tr{transition:background .15s}
|
|
215
|
-
.tbl tbody tr:hover{background:
|
|
175
|
+
.tbl tbody tr:hover{background:rgba(255,183,197,.03)}
|
|
216
176
|
|
|
217
177
|
/* Pills */
|
|
218
|
-
.pill{display:inline-block;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:500}
|
|
178
|
+
.pill{display:inline-block;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:500;letter-spacing:.2px}
|
|
219
179
|
.pill.on{background:var(--c-green2);color:var(--c-green)}
|
|
220
180
|
.pill.off{background:var(--c-red2);color:var(--c-red)}
|
|
221
|
-
.pill.model{background:var(--c-accent2);color:var(--c-
|
|
222
|
-
.pill.tag{background:rgba(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
.cfg-
|
|
227
|
-
.cfg-
|
|
228
|
-
.cfg-head{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
.cfg-
|
|
234
|
-
.cfg-
|
|
235
|
-
.cfg-
|
|
236
|
-
.cfg-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
.cfg-
|
|
241
|
-
.cfg-
|
|
242
|
-
.cfg-
|
|
243
|
-
.cfg-
|
|
244
|
-
|
|
245
|
-
.cfg-
|
|
181
|
+
.pill.model{background:var(--c-accent2);color:var(--c-pink);margin:2px 3px 2px 0}
|
|
182
|
+
.pill.tag{background:rgba(201,177,255,.12);color:var(--c-purple);margin:2px 3px 2px 0}
|
|
183
|
+
|
|
184
|
+
/* Config grid cards */
|
|
185
|
+
.cfg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:14px;margin-top:16px}
|
|
186
|
+
.cfg-card{background:rgba(255,183,197,.02);border:1px solid var(--c-border);border-radius:14px;overflow:hidden;transition:all .25s var(--ease)}
|
|
187
|
+
.cfg-card:hover{border-color:var(--c-border2);background:rgba(255,183,197,.04)}
|
|
188
|
+
.cfg-card-head{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;background:rgba(255,183,197,.03);cursor:pointer;font-size:14px;font-weight:600;user-select:none;gap:8px;border-bottom:1px solid transparent;transition:.2s}
|
|
189
|
+
.cfg-card-head:hover{background:rgba(255,183,197,.06)}
|
|
190
|
+
.cfg-card.expanded .cfg-card-head{border-bottom-color:var(--c-border)}
|
|
191
|
+
.cfg-card-head .arr{font-size:10px;color:var(--c-faint);transition:transform .25s var(--ease);flex-shrink:0}
|
|
192
|
+
.cfg-card-head .arr.open{transform:rotate(90deg)}
|
|
193
|
+
.cfg-card-body{display:none;padding:4px 18px 16px}
|
|
194
|
+
.cfg-card-body.show{display:block;animation:slideDown .2s var(--ease) both}
|
|
195
|
+
.cfg-field{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;margin-bottom:3px;border-radius:8px;background:rgba(255,255,255,.012);gap:8px;transition:all .15s var(--ease)}
|
|
196
|
+
.cfg-field:hover{background:rgba(255,183,197,.04)}
|
|
197
|
+
.cfg-field .k{font-size:11px;color:var(--c-faint);font-weight:500;letter-spacing:.3px;flex-shrink:0;min-width:60px}
|
|
198
|
+
.cfg-field .v{font-size:12.5px;text-align:right;word-break:break-all;min-width:0;flex:1}
|
|
199
|
+
.cfg-field .v code{color:var(--c-pink);font-family:'SF Mono',Fira Code,monospace;font-size:11.5px}
|
|
200
|
+
.cfg-field .v .num{color:var(--c-accent)}
|
|
201
|
+
.cfg-field .edit-btn{flex-shrink:0;width:26px;height:26px;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;opacity:0}
|
|
202
|
+
.cfg-field:hover .edit-btn{opacity:1;border-color:var(--c-border)}
|
|
203
|
+
.cfg-field .edit-btn:hover{color:var(--c-accent);border-color:var(--c-accent);background:var(--c-accent2)}
|
|
204
|
+
|
|
205
|
+
.cfg-field.editing{background:rgba(255,154,162,.06);border:1px solid rgba(255,154,162,.18);border-radius:8px;flex-wrap:wrap;padding:10px 12px}
|
|
206
|
+
.cfg-field.editing .v{display:none}
|
|
207
|
+
.cfg-field.editing .edit-btn{display:none}
|
|
208
|
+
.cfg-field .edit-row{display:none;width:100%;gap:6px}
|
|
209
|
+
.cfg-field.editing .edit-row{display:flex;align-items:center}
|
|
210
|
+
.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}
|
|
211
|
+
.cfg-field .edit-row input:focus,.cfg-field .edit-row select:focus{border-color:var(--c-accent)}
|
|
212
|
+
.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}
|
|
213
|
+
.cfg-field .edit-row .btn-xs.save{background:linear-gradient(135deg,#ff9aa2,#ff8589);border-color:transparent;color:#fff}
|
|
214
|
+
.cfg-field .edit-row .btn-xs:hover{opacity:.85}
|
|
215
|
+
.cfg-actions{display:flex;gap:10px;margin-top:22px;padding-top:18px;border-top:1px solid var(--c-border)}
|
|
246
216
|
|
|
247
217
|
/* Modal */
|
|
248
|
-
.modal-mask{
|
|
249
|
-
|
|
250
|
-
|
|
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}
|
|
218
|
+
.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)}
|
|
219
|
+
.modal{background:var(--c-surface);border:1px solid var(--c-border);border-radius:18px;padding:32px;width:100%;max-width:560px;max-height:85vh;overflow-y:auto;backdrop-filter:blur(28px);box-shadow:0 24px 80px rgba(0,0,0,.5);animation:scaleIn .3s var(--ease) both}
|
|
220
|
+
.modal h3{font-size:18px;font-weight:600;margin-bottom:24px;letter-spacing:-.3px}
|
|
261
221
|
.f-row{margin-bottom:14px}
|
|
262
|
-
.f-row label{display:block;font-size:11px;color:var(--c-dim);margin-bottom:
|
|
263
|
-
.f-row input,.f-row select,.f-row textarea{
|
|
264
|
-
|
|
265
|
-
|
|
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}
|
|
222
|
+
.f-row label{display:block;font-size:11px;color:var(--c-dim);margin-bottom:6px;letter-spacing:.5px;font-weight:500}
|
|
223
|
+
.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;resize:vertical}
|
|
224
|
+
.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)}
|
|
225
|
+
.f-row textarea{min-height:80px}
|
|
270
226
|
.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:
|
|
272
|
-
|
|
273
|
-
/*
|
|
274
|
-
.
|
|
275
|
-
|
|
227
|
+
.modal-btns{display:flex;justify-content:flex-end;gap:8px;margin-top:24px}
|
|
228
|
+
|
|
229
|
+
/* Sub-section style */
|
|
230
|
+
.sub-section{margin-top:16px;padding:16px;background:rgba(255,183,197,.03);border:1px solid var(--c-border);border-radius:12px}
|
|
231
|
+
.sub-section h5{font-size:13px;font-weight:600;margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
232
|
+
.sub-section h5.gemini{color:var(--c-pink)}
|
|
233
|
+
.sub-section h5.gemini::before{content:'✦'}
|
|
234
|
+
.sub-section h5.tool{color:var(--c-yellow)}
|
|
235
|
+
.sub-section h5.tool::before{content:'◇'}
|
|
236
|
+
|
|
237
|
+
/* Animations */
|
|
238
|
+
.page-enter{animation:fadeUp .35s var(--ease) both}
|
|
239
|
+
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
|
276
240
|
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
|
277
241
|
@keyframes scaleIn{from{opacity:0;transform:scale(.96) translateY(8px)}to{opacity:1;transform:none}}
|
|
278
|
-
@keyframes slideRight{from{opacity:0;transform:translateX(
|
|
279
|
-
@keyframes slideDown{from{opacity:0;transform:translateY(-
|
|
242
|
+
@keyframes slideRight{from{opacity:0;transform:translateX(16px)}to{opacity:1;transform:none}}
|
|
243
|
+
@keyframes slideDown{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
|
|
280
244
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
281
|
-
@keyframes
|
|
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%}
|
|
245
|
+
@keyframes pulse-dot{0%,100%{opacity:1}50%{opacity:.35}}
|
|
289
246
|
|
|
290
247
|
.mono{font-family:'SF Mono',Fira Code,monospace}
|
|
291
|
-
code{font-family:'SF Mono',Fira Code,monospace;font-size:12px}
|
|
248
|
+
code{font-family:'SF Mono',Fira Code,monospace;font-size:12px;color:var(--c-pink)}
|
|
292
249
|
|
|
293
250
|
/* Responsive */
|
|
294
251
|
@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
|
-
}
|
|
252
|
+
.sidebar{position:fixed;left:0;top:0;bottom:0;transform:translateX(-100%);z-index:50;width:260px}
|
|
299
253
|
.sidebar.open{transform:translateX(0)}
|
|
300
254
|
.sidebar-overlay.show{display:block}
|
|
301
255
|
.menu-toggle{display:flex}
|
|
302
256
|
.topbar{padding:12px 16px}
|
|
303
|
-
.content{padding:16px}
|
|
304
|
-
.stats{grid-template-columns:repeat(2,1fr);gap:
|
|
305
|
-
.stat{padding:14px
|
|
306
|
-
.stat .num{font-size:
|
|
307
|
-
.card{padding:
|
|
308
|
-
.cfg-
|
|
309
|
-
.modal{max-width:95%;padding:
|
|
257
|
+
.content{padding:16px 16px 32px}
|
|
258
|
+
.stats{grid-template-columns:repeat(2,1fr);gap:10px}
|
|
259
|
+
.stat{padding:16px 14px}
|
|
260
|
+
.stat .num{font-size:24px}
|
|
261
|
+
.card{padding:18px}
|
|
262
|
+
.cfg-grid{grid-template-columns:1fr}
|
|
263
|
+
.modal{max-width:95%;padding:24px}
|
|
264
|
+
.petal{display:none}
|
|
310
265
|
}
|
|
311
266
|
@media(max-width:480px){
|
|
312
267
|
.stats{grid-template-columns:1fr 1fr}
|
|
@@ -316,6 +271,12 @@ code{font-family:'SF Mono',Fira Code,monospace;font-size:12px}
|
|
|
316
271
|
</style>
|
|
317
272
|
</head>
|
|
318
273
|
<body>
|
|
274
|
+
<!-- Sakura petals -->
|
|
275
|
+
<div class="petal">🌸</div><div class="petal">🌸</div><div class="petal">🌸</div>
|
|
276
|
+
<div class="petal">🌸</div><div class="petal">🌸</div><div class="petal">🌸</div>
|
|
277
|
+
<div class="petal">🌸</div><div class="petal">🌸</div><div class="petal">🌸</div>
|
|
278
|
+
<div class="petal">🌸</div>
|
|
279
|
+
|
|
319
280
|
<div id="app"></div>
|
|
320
281
|
<script>
|
|
321
282
|
const{createApp,ref,reactive,computed,onMounted,onUnmounted}=Vue
|
|
@@ -330,21 +291,18 @@ const API=(path,opt={})=>{
|
|
|
330
291
|
|
|
331
292
|
createApp({
|
|
332
293
|
setup(){
|
|
333
|
-
// Auth
|
|
334
294
|
const jwt=ref(localStorage.getItem('chaite_jwt')||'')
|
|
335
295
|
const loggedIn=ref(!!jwt.value)
|
|
336
296
|
const loginInput=ref('')
|
|
337
297
|
const loginLoading=ref(false)
|
|
338
298
|
const loginError=ref('')
|
|
339
299
|
|
|
340
|
-
// UI state
|
|
341
300
|
const page=ref('dashboard')
|
|
342
301
|
const sidebarOpen=ref(false)
|
|
343
302
|
const loading=ref(true)
|
|
344
303
|
const refreshTime=ref('')
|
|
345
304
|
let timer=null
|
|
346
305
|
|
|
347
|
-
// Data
|
|
348
306
|
const health=ref(null)
|
|
349
307
|
const stats=ref(null)
|
|
350
308
|
const channels=ref([])
|
|
@@ -357,22 +315,19 @@ createApp({
|
|
|
357
315
|
const conversations=ref([])
|
|
358
316
|
const convDetail=ref(null)
|
|
359
317
|
|
|
360
|
-
// Modal
|
|
361
318
|
const modal=ref(null)
|
|
362
319
|
const editing=ref(null)
|
|
363
320
|
const form=ref({})
|
|
364
321
|
|
|
365
|
-
// Config edit
|
|
366
322
|
const editingConfig=ref(false)
|
|
367
323
|
const configForm=ref('{}')
|
|
324
|
+
const editingCfgField=ref(null)
|
|
368
325
|
|
|
369
|
-
// Toast
|
|
370
326
|
const toastMsg=ref('')
|
|
371
327
|
const toastType=ref('ok')
|
|
372
328
|
let toastTimer=null
|
|
373
329
|
function toast(msg,type='ok'){toastMsg.value=msg;toastType.value=type;clearTimeout(toastTimer);toastTimer=setTimeout(()=>toastMsg.value='',3000)}
|
|
374
330
|
|
|
375
|
-
// Login
|
|
376
331
|
async function doLogin(){
|
|
377
332
|
loginError.value='';loginLoading.value=true
|
|
378
333
|
try{
|
|
@@ -385,13 +340,12 @@ createApp({
|
|
|
385
340
|
jwt.value=d.data.token;localStorage.setItem('chaite_jwt',d.data.token);loggedIn.value=true
|
|
386
341
|
url.searchParams.delete('token');history.replaceState(null,'',url.pathname+url.search)
|
|
387
342
|
refreshAll()
|
|
388
|
-
}else{loginError.value=d.message||'
|
|
389
|
-
}catch(e){loginError.value='
|
|
343
|
+
}else{loginError.value=d.message||'登录失败'}
|
|
344
|
+
}catch(e){loginError.value='网络错误'}
|
|
390
345
|
finally{loginLoading.value=false}
|
|
391
346
|
}
|
|
392
347
|
function logout(){jwt.value='';localStorage.removeItem('chaite_jwt');loggedIn.value=false;clearInterval(timer)}
|
|
393
348
|
|
|
394
|
-
// Data fetch
|
|
395
349
|
async function refreshAll(){
|
|
396
350
|
loading.value=true
|
|
397
351
|
try{
|
|
@@ -407,74 +361,51 @@ createApp({
|
|
|
407
361
|
API('/api/config').catch(()=>({})),
|
|
408
362
|
API('/api/state/conversations/list').catch(()=>({})),
|
|
409
363
|
])
|
|
410
|
-
health.value=h.data||null
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
presets.value=p.data||[]
|
|
415
|
-
processors.value=pr.data||[]
|
|
416
|
-
triggers.value=tr.data||[]
|
|
417
|
-
toolGroups.value=tg.data||[]
|
|
418
|
-
config.value=cfg.data||{}
|
|
419
|
-
conversations.value=cv.data||[]
|
|
420
|
-
refreshTime.value=new Date().toLocaleTimeString()
|
|
364
|
+
health.value=h.data||null;stats.value=s.data||null;channels.value=c.data||[]
|
|
365
|
+
tools.value=t.data||[];presets.value=p.data||[];processors.value=pr.data||[]
|
|
366
|
+
triggers.value=tr.data||[];toolGroups.value=tg.data||[];config.value=cfg.data||{}
|
|
367
|
+
conversations.value=cv.data||[];refreshTime.value=new Date().toLocaleTimeString()
|
|
421
368
|
}catch(e){console.error(e)}
|
|
422
369
|
finally{loading.value=false}
|
|
423
370
|
}
|
|
424
371
|
|
|
425
372
|
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
|
-
}
|
|
373
|
+
function uptimeStr(s){if(!s)return'-';const h=Math.floor(s/3600),m=Math.floor(s%3600/60),sec=s%60;return h>0?`${h}h ${m}m ${sec}s`:`${m}m ${sec}s`}
|
|
433
374
|
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
375
|
function fLen(s,n){return s&&s.length>n?s.slice(0,n)+'...':s||''}
|
|
435
376
|
|
|
436
|
-
// Channel test
|
|
437
377
|
async function testChannel(id){
|
|
438
378
|
const d=await API('/api/system/test-channel',{method:'POST',body:JSON.stringify({channelId:id})})
|
|
439
|
-
if(d.data?.status==='ok')toast(
|
|
440
|
-
else toast(
|
|
379
|
+
if(d.data?.status==='ok')toast(d.data.channelName+' — '+d.data.latency+'ms')
|
|
380
|
+
else toast(d.data?.error||d.message,'err')
|
|
441
381
|
}
|
|
442
382
|
|
|
443
|
-
// CRUD
|
|
444
383
|
function openModal(type,item=null){
|
|
445
384
|
modal.value=type;editing.value=item
|
|
446
385
|
if(item){
|
|
447
386
|
form.value=JSON.parse(JSON.stringify(item))
|
|
448
|
-
// 为 channel 初始化 JSON 字段
|
|
449
387
|
if(type==='channel'){
|
|
450
|
-
form.value._models
|
|
451
|
-
form.value.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
safetySettings:[],
|
|
465
|
-
toolCallLimit:{maxConsecutiveCalls:8,maxConsecutiveIdenticalCalls:2}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
388
|
+
form.value._models=(form.value.models||[]).join(', ')
|
|
389
|
+
if(!form.value.sendMessageOption)form.value.sendMessageOption={}
|
|
390
|
+
const smo=form.value.sendMessageOption
|
|
391
|
+
smo.model=smo.model||''
|
|
392
|
+
smo.temperature=smo.temperature||0.8
|
|
393
|
+
smo.maxToken=smo.maxToken||4096
|
|
394
|
+
smo.systemOverride=smo.systemOverride||''
|
|
395
|
+
if(smo.enableReasoning===undefined)smo.enableReasoning=false
|
|
396
|
+
smo.reasoningEffort=smo.reasoningEffort||'high'
|
|
397
|
+
if(smo.toolCallLimit===undefined)smo.toolCallLimit={maxConsecutiveCalls:8,maxConsecutiveIdenticalCalls:2}
|
|
398
|
+
if(!smo.responseModalities)smo.responseModalities=[]
|
|
399
|
+
if(!smo.safetySettings)smo.safetySettings=[]
|
|
400
|
+
form.value._responseModalities=JSON.stringify(smo.responseModalities)
|
|
401
|
+
form.value._safetySettings=JSON.stringify(smo.safetySettings,null,2)
|
|
468
402
|
}
|
|
469
|
-
}
|
|
470
|
-
else{
|
|
403
|
+
}else{
|
|
471
404
|
form.value={
|
|
472
405
|
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}},
|
|
474
|
-
tool:{name:'',description:'',code:''},
|
|
475
|
-
|
|
476
|
-
trigger:{name:'',description:'',code:''},
|
|
477
|
-
toolGroup:{name:'',description:'',toolIds:[],status:'enabled',isDefault:false},
|
|
406
|
+
preset:{name:'',prefix:'',sendMessageOption:{model:'',temperature:0.8,maxToken:4096,systemOverride:'',enableReasoning:false,reasoningEffort:'high',disableHistoryRead:false,disableHistorySave:false},groupContext:undefined,disableSystemInstructions:false},
|
|
407
|
+
tool:{name:'',description:'',code:''},processor:{name:'',type:'pre',description:'',code:''},
|
|
408
|
+
trigger:{name:'',description:'',code:''},toolGroup:{name:'',description:'',toolIds:[],status:'enabled',isDefault:false},
|
|
478
409
|
}[type]||{}
|
|
479
410
|
}
|
|
480
411
|
}
|
|
@@ -483,93 +414,147 @@ createApp({
|
|
|
483
414
|
async function saveItem(){
|
|
484
415
|
const apiMap={channel:'/api/channels',preset:'/api/preset',tool:'/api/tools',processor:'/api/processors',trigger:'/api/triggers',toolGroup:'/api/toolGroups'}
|
|
485
416
|
try{
|
|
486
|
-
const type=modal.value
|
|
487
|
-
const
|
|
488
|
-
const url=apiMap[type]+(id?'/'+id:'')
|
|
489
|
-
|
|
490
|
-
// 处理 channel 的 JSON 字段
|
|
417
|
+
const type=modal.value,id=editing.value?.id,url=apiMap[type]+(id?'/'+id:'')
|
|
418
|
+
const fd=JSON.parse(JSON.stringify(form.value))
|
|
491
419
|
if(type==='channel'){
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
|
420
|
+
if(fd._models){fd.models=fd._models.split(',').map(m=>m.trim()).filter(m=>m);delete fd._models}
|
|
421
|
+
if(fd._responseModalities){try{fd.sendMessageOption.responseModalities=JSON.parse(fd._responseModalities)}catch(e){};delete fd._responseModalities}
|
|
422
|
+
if(fd._safetySettings){try{fd.sendMessageOption.safetySettings=JSON.parse(fd._safetySettings)}catch(e){};delete fd._safetySettings}
|
|
423
|
+
}
|
|
424
|
+
if(type==='toolGroup'&&fd._toolIds){
|
|
425
|
+
fd.toolIds=fd._toolIds.split(',').map(m=>m.trim()).filter(m=>m)
|
|
426
|
+
delete fd._toolIds
|
|
521
427
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
else toast(d.message||'Error','err')
|
|
428
|
+
const d=await API(url,{method:id?'PUT':'POST',body:JSON.stringify(fd)})
|
|
429
|
+
if(d.data!==undefined){toast('已保存 ✨');closeModal();refreshAll()}
|
|
430
|
+
else toast(d.message||'错误','err')
|
|
526
431
|
}catch(e){toast(e.message,'err')}
|
|
527
432
|
}
|
|
528
433
|
|
|
529
434
|
async function deleteItem(type,id){
|
|
530
|
-
if(!confirm('
|
|
435
|
+
if(!confirm('确认删除?'))return
|
|
531
436
|
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('
|
|
437
|
+
try{await API(apiMap[type]+'/'+id,{method:'DELETE'});toast('已删除');refreshAll()}
|
|
533
438
|
catch(e){toast(e.message,'err')}
|
|
534
439
|
}
|
|
535
440
|
|
|
536
|
-
// Config
|
|
441
|
+
// ===== Config System =====
|
|
537
442
|
const expandedCfg=ref({})
|
|
538
|
-
const
|
|
443
|
+
const SKIP_KEYS=new Set(['version','_saveOrigin','authKey','cloudBaseUrl','cloudApiKey'])
|
|
444
|
+
|
|
445
|
+
const CFG_LABELS={
|
|
446
|
+
// 配置分组标签
|
|
447
|
+
basic:'基础配置',bym:'伪人模式',llm:'模型与对话',management:'管理控制',
|
|
448
|
+
chaite:'Chaite 面板',memory:'记忆系统',update:'更新相关',
|
|
449
|
+
// 基础配置
|
|
450
|
+
toggleMode:'触发方式',togglePrefix:'触发前缀',debug:'调试模式',commandPrefix:'命令前缀',
|
|
451
|
+
// 伪人模式
|
|
452
|
+
enable:'启用',hit:'必触发词',probability:'概率',defaultPreset:'伪人默认预设',presetPrefix:'预设前缀',
|
|
453
|
+
presetMap:'预设映射',maxTokens:'最大Token',temperature:'温度',sendReasoning:'发送思考',
|
|
454
|
+
// 模型与对话
|
|
455
|
+
defaultModel:'默认对话模型',embeddingModel:'嵌入模型',dimensions:'向量维度',
|
|
456
|
+
defaultChatPresetId:'默认对话预设',enableCustomPreset:'允许切换预设',
|
|
457
|
+
customPresetUserWhiteList:'预设切换用户白名单',customPresetUserBlackList:'预设切换用户黑名单',
|
|
458
|
+
promptBlockWords:'输入屏蔽词',responseBlockWords:'回复屏蔽词',
|
|
459
|
+
blockStrategy:'屏蔽策略',blockWordMask:'屏蔽替换字符',
|
|
460
|
+
enableGroupContext:'群组上下文',groupContextLength:'上下文长度',
|
|
461
|
+
groupContextTemplatePrefix:'上下文模板前缀',groupContextTemplateMessage:'上下文模板消息',
|
|
462
|
+
groupContextTemplateSuffix:'上下文模板后缀',
|
|
463
|
+
// 管理控制
|
|
464
|
+
blackGroups:'群黑名单',whiteGroups:'群白名单',blackUsers:'用户黑名单',
|
|
465
|
+
whiteUsers:'用户白名单',defaultRateLimit:'默认速率限制',
|
|
466
|
+
// Chaite 面板
|
|
467
|
+
dataDir:'数据目录',processorsDirPath:'处理器目录',triggersDir:'触发器目录',
|
|
468
|
+
toolsDirPath:'工具目录',host:'监听地址',port:'监听端口',storage:'存储实现',
|
|
469
|
+
accessToken:'访问令牌',tokenExpiresIn:'令牌有效期(秒)',
|
|
470
|
+
// 记忆系统
|
|
471
|
+
database:'数据库路径',vectorDimensions:'向量维度',
|
|
472
|
+
group:'群组记忆',user:'用户记忆',extensions:'扩展模块',
|
|
473
|
+
// 群组记忆
|
|
474
|
+
enabledGroups:'启用群组',extractionModel:'提取模型',extractionPresetId:'提取预设',
|
|
475
|
+
minMessageCount:'最小消息数',maxMessageWindow:'最大消息窗',
|
|
476
|
+
retrievalMode:'检索模式',hybridPrefer:'混合检索偏好',
|
|
477
|
+
historyPollInterval:'历史轮询间隔',historyBatchSize:'历史批次大小',
|
|
478
|
+
promptHeader:'提示词头部',promptItemTemplate:'提示词条目模板',promptFooter:'提示词尾部',
|
|
479
|
+
extractionSystemPrompt:'提取系统提示',extractionUserPrompt:'提取用户提示',
|
|
480
|
+
vectorMaxDistance:'向量最大距离',textMaxBm25Score:'文本BM25最大分数',
|
|
481
|
+
maxFactsPerInjection:'最大注入事实数',minImportanceForInjection:'最小注入重要度',
|
|
482
|
+
// 用户记忆
|
|
483
|
+
whitelist:'白名单',blacklist:'黑名单',
|
|
484
|
+
maxItemsPerInjection:'最大注入条目',maxRelevantItemsPerQuery:'最大检索条目',
|
|
485
|
+
// 扩展
|
|
486
|
+
simple:'简易分词',libraryPath:'库路径',dictPath:'词典路径',useJieba:'使用结巴分词',
|
|
487
|
+
}
|
|
488
|
+
function cfgLabel(k){return CFG_LABELS[k]||k.charAt(0).toUpperCase()+k.slice(1)}
|
|
539
489
|
|
|
540
490
|
const cfgSections=computed(()=>{
|
|
541
491
|
const secs=[]
|
|
542
492
|
const order=['basic','bym','llm','management','chaite','memory','update']
|
|
543
493
|
for(const key of order){
|
|
544
494
|
const v=config.value[key]
|
|
545
|
-
if(v!==undefined) secs.push({key,label:
|
|
495
|
+
if(v!==undefined) secs.push({key,label:cfgLabel(key),fields:v,isObj:typeof v==='object'&&!Array.isArray(v)})
|
|
546
496
|
}
|
|
547
|
-
// Remaining keys not in order
|
|
548
497
|
for(const[k,v]of Object.entries(config.value)){
|
|
549
|
-
if(order.includes(k)||
|
|
550
|
-
secs.push({key:k,label:k,fields:
|
|
498
|
+
if(order.includes(k)||SKIP_KEYS.has(k))continue
|
|
499
|
+
secs.push({key:k,label:cfgLabel(k),fields:v,isObj:typeof v==='object'&&!Array.isArray(v)})
|
|
551
500
|
}
|
|
552
501
|
return secs
|
|
553
502
|
})
|
|
554
503
|
function toggleCfg(k){expandedCfg.value[k]=!expandedCfg.value[k]}
|
|
504
|
+
|
|
505
|
+
function startEditField(secKey,fieldKey,currentVal){
|
|
506
|
+
const isArr=Array.isArray(currentVal)
|
|
507
|
+
const raw=isArr?JSON.stringify(currentVal):(typeof currentVal==='string'?currentVal:JSON.stringify(currentVal))
|
|
508
|
+
editingCfgField.value={sec:secKey,key:fieldKey,val:raw,type:typeof currentVal==='boolean'?'bool':typeof currentVal==='number'?'num':isArr?'arr':'str'}
|
|
509
|
+
}
|
|
510
|
+
function cancelEditField(){editingCfgField.value=null}
|
|
511
|
+
// 深层设置对象值,支持 'a.b.c' 路径
|
|
512
|
+
function deepSet(obj,path,val){
|
|
513
|
+
const keys=path.split('.')
|
|
514
|
+
let cur=obj
|
|
515
|
+
for(let i=0;i<keys.length-1;i++){
|
|
516
|
+
if(!cur[keys[i]])cur[keys[i]]={}
|
|
517
|
+
cur=cur[keys[i]]
|
|
518
|
+
}
|
|
519
|
+
cur[keys[keys.length-1]]=val
|
|
520
|
+
}
|
|
521
|
+
async function saveEditField(secKey,fieldKey){
|
|
522
|
+
if(!editingCfgField.value)return
|
|
523
|
+
let newVal=editingCfgField.value.val
|
|
524
|
+
const type=editingCfgField.value.type
|
|
525
|
+
if(type==='bool')newVal=newVal==='true'||newVal===true
|
|
526
|
+
else if(type==='num')newVal=Number(newVal)
|
|
527
|
+
else if(type==='arr'){try{newVal=JSON.parse(newVal)}catch(e){toast('数组格式错误,请使用 JSON 数组格式','err');return}}
|
|
528
|
+
try{
|
|
529
|
+
const cfgCopy=JSON.parse(JSON.stringify(config.value))
|
|
530
|
+
if(fieldKey.includes('.')){
|
|
531
|
+
deepSet(cfgCopy[secKey],fieldKey,newVal)
|
|
532
|
+
}else{
|
|
533
|
+
cfgCopy[secKey][fieldKey]=newVal
|
|
534
|
+
}
|
|
535
|
+
const d=await API('/api/config',{method:'POST',body:JSON.stringify(cfgCopy)})
|
|
536
|
+
if(d.data!==undefined){toast('配置已保存 ✨');cancelEditField();refreshAll()}
|
|
537
|
+
else toast(d.message||'错误','err')
|
|
538
|
+
}catch(e){toast('保存失败: '+e.message,'err')}
|
|
539
|
+
}
|
|
540
|
+
|
|
555
541
|
function editConfig(){configForm.value=JSON.stringify(config.value,null,2);editingConfig.value=true}
|
|
556
542
|
async function saveConfig(){
|
|
557
543
|
try{
|
|
558
544
|
const obj=JSON.parse(configForm.value)
|
|
559
545
|
const d=await API('/api/config',{method:'POST',body:JSON.stringify(obj)})
|
|
560
|
-
if(d.data!==undefined){toast('
|
|
561
|
-
else toast(d.message||'
|
|
562
|
-
}catch(e){toast('
|
|
546
|
+
if(d.data!==undefined){toast('配置已保存 ✨');editingConfig.value=false;refreshAll()}
|
|
547
|
+
else toast(d.message||'错误','err')
|
|
548
|
+
}catch(e){toast('无效 JSON: '+e.message,'err')}
|
|
563
549
|
}
|
|
564
550
|
|
|
565
|
-
// Conversations
|
|
566
551
|
async function viewConv(uid){
|
|
567
552
|
try{const d=await API('/api/state/'+encodeURIComponent(uid));convDetail.value=d.data||null}
|
|
568
553
|
catch(e){toast(e.message,'err')}
|
|
569
554
|
}
|
|
570
555
|
async function clearConv(uid){
|
|
571
|
-
if(!confirm('
|
|
572
|
-
try{await API('/api/state/'+encodeURIComponent(uid),{method:'DELETE'});toast('
|
|
556
|
+
if(!confirm('清除用户 '+uid+' 的状态?'))return
|
|
557
|
+
try{await API('/api/state/'+encodeURIComponent(uid),{method:'DELETE'});toast('已清除');refreshAll()}
|
|
573
558
|
catch(e){toast(e.message,'err')}
|
|
574
559
|
}
|
|
575
560
|
|
|
@@ -583,44 +568,52 @@ createApp({
|
|
|
583
568
|
})
|
|
584
569
|
onUnmounted(()=>clearInterval(timer))
|
|
585
570
|
|
|
571
|
+
// Helper: flatten object fields for display, handling nested objects
|
|
572
|
+
function flattenFields(obj,prefix=''){
|
|
573
|
+
if(!obj||typeof obj!=='object')return{[prefix]:obj}
|
|
574
|
+
if(Array.isArray(obj))return{[prefix]:obj}
|
|
575
|
+
const result={}
|
|
576
|
+
for(const[k,v]of Object.entries(obj)){
|
|
577
|
+
if(v&&typeof v==='object'&&!Array.isArray(v)){
|
|
578
|
+
const nested=flattenFields(v,k+'.')
|
|
579
|
+
Object.assign(result,nested)
|
|
580
|
+
}else{result[prefix+k]=v}
|
|
581
|
+
}
|
|
582
|
+
return result
|
|
583
|
+
}
|
|
584
|
+
|
|
586
585
|
return{
|
|
587
586
|
jwt,loggedIn,loginInput,loginLoading,loginError,doLogin,logout,
|
|
588
587
|
page,sidebarOpen,navigate,loading,refreshTime,isHealthy,
|
|
589
588
|
health,stats,channels,tools,presets,processors,triggers,toolGroups,config,conversations,convDetail,
|
|
590
|
-
channelsOk,
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
viewConv,clearConv,
|
|
594
|
-
toastMsg,toastType,uptimeStr,fmtNum,fLen,refreshAll,
|
|
589
|
+
channelsOk,modal,editing,form,openModal,closeModal,saveItem,deleteItem,testChannel,
|
|
590
|
+
expandedCfg,cfgSections,toggleCfg,editConfig,editingConfig,configForm,saveConfig,
|
|
591
|
+
editingCfgField,startEditField,cancelEditField,saveEditField,cfgLabel,flattenFields,
|
|
592
|
+
viewConv,clearConv,toastMsg,toastType,uptimeStr,fmtNum,fLen,refreshAll,
|
|
595
593
|
}
|
|
596
594
|
},
|
|
597
595
|
template:`
|
|
598
|
-
<!-- Toast -->
|
|
599
596
|
<div v-if="toastMsg" :class="['toast',toastType]">{{toastMsg}}</div>
|
|
600
597
|
|
|
601
598
|
<!-- Login -->
|
|
602
599
|
<div v-if="!loggedIn" class="login-wrap">
|
|
603
600
|
<div class="login-card">
|
|
604
601
|
<h1>Chaite</h1>
|
|
605
|
-
<p class="sub"
|
|
602
|
+
<p class="sub">✦ 管理控制台 ✦</p>
|
|
606
603
|
<div v-if="!loginLoading">
|
|
607
|
-
<input v-model="loginInput" type="password" placeholder="
|
|
604
|
+
<input v-model="loginInput" type="password" placeholder="输入访问令牌..."
|
|
608
605
|
@keyup.enter="doLogin" autofocus/>
|
|
609
606
|
<div class="err">{{loginError}}</div>
|
|
610
|
-
<button class="btn pri" @click="doLogin" style="width:100%;padding:10px;margin-top:4px"
|
|
611
|
-
<p class="hint"
|
|
607
|
+
<button class="btn pri" @click="doLogin" style="width:100%;padding:10px;margin-top:4px">登录</button>
|
|
608
|
+
<p class="hint">或在 URL 中携带 <code>?token=xxx</code> 访问</p>
|
|
612
609
|
</div>
|
|
613
610
|
<div v-else style="padding:20px"><div class="spin" style="display:inline-block"></div></div>
|
|
614
611
|
</div>
|
|
615
612
|
</div>
|
|
616
613
|
|
|
617
|
-
<!-- Main
|
|
614
|
+
<!-- Main -->
|
|
618
615
|
<div v-else class="layout">
|
|
619
|
-
|
|
620
|
-
<!-- Sidebar overlay (mobile) -->
|
|
621
616
|
<div :class="['sidebar-overlay',sidebarOpen&&'show']" @click="sidebarOpen=false"></div>
|
|
622
|
-
|
|
623
|
-
<!-- Sidebar -->
|
|
624
617
|
<aside :class="['sidebar',sidebarOpen&&'open']">
|
|
625
618
|
<div class="sidebar-logo">
|
|
626
619
|
<h2>Chaite</h2>
|
|
@@ -628,60 +621,57 @@ createApp({
|
|
|
628
621
|
</div>
|
|
629
622
|
<nav class="sidebar-nav">
|
|
630
623
|
<div :class="['nav-item',page==='dashboard'&&'active']" @click="navigate('dashboard')">
|
|
631
|
-
<span class="icon">◈</span
|
|
632
|
-
<span class="badge-mini" v-if="health">{{health.channels?.total||0}}
|
|
624
|
+
<span class="icon">◈</span>概览
|
|
625
|
+
<span class="badge-mini" v-if="health">{{health.channels?.total||0}}渠道</span>
|
|
633
626
|
</div>
|
|
634
627
|
<div :class="['nav-item',page==='channels'&&'active']" @click="navigate('channels')">
|
|
635
|
-
<span class="icon">▤</span
|
|
628
|
+
<span class="icon">▤</span>渠道
|
|
636
629
|
<span class="badge-mini" v-if="channelsOk">{{channelsOk}}</span>
|
|
637
630
|
</div>
|
|
638
631
|
<div :class="['nav-item',page==='tools'&&'active']" @click="navigate('tools')">
|
|
639
|
-
<span class="icon">⚙</span
|
|
632
|
+
<span class="icon">⚙</span>工具
|
|
640
633
|
<span class="badge-mini" v-if="tools.length">{{tools.length}}</span>
|
|
641
634
|
</div>
|
|
642
635
|
<div :class="['nav-item',page==='presets'&&'active']" @click="navigate('presets')">
|
|
643
|
-
<span class="icon">✎</span
|
|
636
|
+
<span class="icon">✎</span>预设
|
|
644
637
|
</div>
|
|
645
638
|
<div :class="['nav-item',page==='processors'&&'active']" @click="navigate('processors')">
|
|
646
|
-
<span class="icon">⇄</span
|
|
639
|
+
<span class="icon">⇄</span>处理器
|
|
647
640
|
</div>
|
|
648
641
|
<div :class="['nav-item',page==='triggers'&&'active']" @click="navigate('triggers')">
|
|
649
|
-
<span class="icon">⚡</span
|
|
642
|
+
<span class="icon">⚡</span>触发器
|
|
650
643
|
</div>
|
|
651
644
|
<div :class="['nav-item',page==='groups'&&'active']" @click="navigate('groups')">
|
|
652
|
-
<span class="icon">☰</span
|
|
645
|
+
<span class="icon">☰</span>工具组
|
|
653
646
|
</div>
|
|
654
647
|
<div style="margin:8px 0;border-top:1px solid var(--c-border)"></div>
|
|
655
648
|
<div :class="['nav-item',page==='conversations'&&'active']" @click="navigate('conversations')">
|
|
656
|
-
<span class="icon">◎</span
|
|
649
|
+
<span class="icon">◎</span>会话
|
|
657
650
|
<span class="badge-mini" v-if="conversations.length">{{conversations.length}}</span>
|
|
658
651
|
</div>
|
|
659
652
|
<div :class="['nav-item',page==='config'&&'active']" @click="navigate('config')">
|
|
660
|
-
<span class="icon">⚙</span
|
|
653
|
+
<span class="icon">⚙</span>配置
|
|
661
654
|
</div>
|
|
662
655
|
</nav>
|
|
663
656
|
<div class="sidebar-footer">
|
|
664
657
|
<span :class="['dot',isHealthy?'ok':'']"></span>
|
|
665
|
-
{{isHealthy?'
|
|
666
|
-
<br><span style="font-size:10px"
|
|
658
|
+
{{isHealthy?'在线':'离线'}}
|
|
659
|
+
<br><span style="font-size:10px">端口 {{health?.system?.platform||'-'}}</span>
|
|
667
660
|
</div>
|
|
668
661
|
</aside>
|
|
669
662
|
|
|
670
|
-
<!-- Main -->
|
|
671
663
|
<div class="main">
|
|
672
|
-
<!-- Top bar -->
|
|
673
664
|
<header class="topbar">
|
|
674
665
|
<div style="display:flex;align-items:center;gap:12px">
|
|
675
666
|
<button class="menu-toggle" @click="sidebarOpen=!sidebarOpen">☰</button>
|
|
676
667
|
<span class="t">
|
|
677
|
-
{{page==='dashboard'?'
|
|
668
|
+
{{page==='dashboard'?'概览':page==='conversations'?'会话':page==='groups'?'工具组':page.charAt(0).toUpperCase()+page.slice(1)}}
|
|
678
669
|
</span>
|
|
679
670
|
</div>
|
|
680
671
|
<div class="meta">
|
|
681
672
|
<span class="refresh-text" v-if="refreshTime">↻ {{refreshTime}}</span>
|
|
682
673
|
<button class="btn ghost" @click="refreshAll" :disabled="loading">
|
|
683
|
-
<span v-if="loading" class="spin"></span>
|
|
684
|
-
<span v-else>↻</span>
|
|
674
|
+
<span v-if="loading" class="spin"></span><span v-else>↻</span>
|
|
685
675
|
</button>
|
|
686
676
|
<button class="btn ghost" @click="logout">⏻</button>
|
|
687
677
|
</div>
|
|
@@ -689,411 +679,287 @@ createApp({
|
|
|
689
679
|
|
|
690
680
|
<div class="content" :key="page">
|
|
691
681
|
|
|
692
|
-
<!--
|
|
682
|
+
<!-- ===== DASHBOARD ===== -->
|
|
693
683
|
<div v-if="page==='dashboard'" class="page-enter">
|
|
694
684
|
<div class="stats" v-if="health">
|
|
695
|
-
<div class="stat"><div class="num">{{uptimeStr(health.uptime)}}</div><div class="lbl"
|
|
696
|
-
<div class="stat"><div class="num">{{health.channels?.total||0}}</div><div class="lbl"
|
|
697
|
-
<div class="stat"><div class="num">{{health.models?.count||0}}</div><div class="lbl"
|
|
698
|
-
<div class="stat"><div class="num">{{health.tools?.count||0}}</div><div class="lbl"
|
|
699
|
-
<div class="stat"><div class="num">{{health.system?.processMemory||0}}M</div><div class="lbl"
|
|
700
|
-
<div class="stat"><div class="num">{{fmtNum(stats?.summary?.totalCalls)}}</div><div class="lbl"
|
|
685
|
+
<div class="stat"><div class="num">{{uptimeStr(health.uptime)}}</div><div class="lbl">运行时长</div></div>
|
|
686
|
+
<div class="stat"><div class="num">{{health.channels?.total||0}}</div><div class="lbl">渠道</div></div>
|
|
687
|
+
<div class="stat"><div class="num">{{health.models?.count||0}}</div><div class="lbl">模型</div></div>
|
|
688
|
+
<div class="stat"><div class="num">{{health.tools?.count||0}}</div><div class="lbl">工具</div></div>
|
|
689
|
+
<div class="stat"><div class="num">{{health.system?.processMemory||0}}M</div><div class="lbl">内存</div></div>
|
|
690
|
+
<div class="stat"><div class="num">{{fmtNum(stats?.summary?.totalCalls)}}</div><div class="lbl">总调用</div></div>
|
|
701
691
|
</div>
|
|
702
692
|
|
|
703
|
-
<div v-if="!loading&&!health" class="card" style="text-align:center;color:var(--c-dim);padding:48px"
|
|
693
|
+
<div v-if="!loading&&!health" class="card" style="text-align:center;color:var(--c-dim);padding:48px">无数据 — 服务器是否运行?</div>
|
|
704
694
|
|
|
705
695
|
<div class="card" v-if="health">
|
|
706
|
-
<div class="card-hd"><h3><span class="dot" style="background:var(--c-green)"></span
|
|
696
|
+
<div class="card-hd"><h3><span class="dot" style="background:var(--c-green)"></span>系统信息</h3></div>
|
|
707
697
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;font-size:13px">
|
|
708
|
-
<div><span style="color:var(--c-dim)"
|
|
698
|
+
<div><span style="color:var(--c-dim)">版本</span><br>{{health.version}}</div>
|
|
709
699
|
<div><span style="color:var(--c-dim)">Node</span><br>{{health.system?.nodeVersion}}</div>
|
|
710
|
-
<div><span style="color:var(--c-dim)"
|
|
711
|
-
<div><span style="color:var(--c-dim)">
|
|
712
|
-
<div><span style="color:var(--c-dim)"
|
|
713
|
-
<div><span style="color:var(--c-dim)"
|
|
700
|
+
<div><span style="color:var(--c-dim)">平台</span><br>{{health.system?.platform}} {{health.system?.arch}}</div>
|
|
701
|
+
<div><span style="color:var(--c-dim)">CPU</span><br>{{health.system?.cpus}}</div>
|
|
702
|
+
<div><span style="color:var(--c-dim)">堆内存</span><br>{{health.system?.heapUsed}}M / {{health.system?.processMemory}}M</div>
|
|
703
|
+
<div><span style="color:var(--c-dim)">系统内存</span><br>{{health.system?.freeMemory}}M 可用</div>
|
|
714
704
|
</div>
|
|
715
705
|
</div>
|
|
716
706
|
|
|
717
707
|
<div class="card" v-if="health?.models?.list?.length">
|
|
718
|
-
<div class="card-hd"><h3
|
|
708
|
+
<div class="card-hd"><h3>模型列表</h3></div>
|
|
719
709
|
<div><span v-for="m in health.models.list" class="pill model">{{m}}</span></div>
|
|
720
710
|
</div>
|
|
721
711
|
|
|
722
712
|
<div class="card" v-if="stats?.perModel&&Object.keys(stats.perModel).length">
|
|
723
|
-
<div class="card-hd"><h3
|
|
713
|
+
<div class="card-hd"><h3>模型使用统计</h3></div>
|
|
724
714
|
<div class="tbl-wrap"><table class="tbl">
|
|
725
|
-
<thead><tr><th
|
|
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>
|
|
715
|
+
<thead><tr><th>模型</th><th>调用次数</th><th>Token</th></tr></thead>
|
|
716
|
+
<tbody><tr v-for="(v,k) in stats.perModel" :key="k"><td><span class="pill model">{{k}}</span></td><td>{{fmtNum(v.calls)}}</td><td>{{fmtNum(v.tokens)}}</td></tr></tbody>
|
|
732
717
|
</table></div>
|
|
733
718
|
</div>
|
|
734
719
|
|
|
735
720
|
<div class="card" v-if="stats?.perChannel&&Object.keys(stats.perChannel).length">
|
|
736
|
-
<div class="card-hd"><h3
|
|
721
|
+
<div class="card-hd"><h3>渠道使用统计</h3></div>
|
|
737
722
|
<div class="tbl-wrap"><table class="tbl">
|
|
738
|
-
<thead><tr><th
|
|
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>
|
|
723
|
+
<thead><tr><th>渠道</th><th>调用次数</th><th>Token</th><th>模型</th></tr></thead>
|
|
724
|
+
<tbody><tr v-for="(v,k) in stats.perChannel" :key="k"><td>{{k}}</td><td>{{fmtNum(v.calls)}}</td><td>{{fmtNum(v.tokens)}}</td><td><span v-for="m in (v.models||[])" class="pill model">{{m}}</span></td></tr></tbody>
|
|
745
725
|
</table></div>
|
|
746
726
|
</div>
|
|
747
727
|
|
|
748
728
|
<div class="card" v-if="conversations.length">
|
|
749
|
-
<div class="card-hd"><h3>{{conversations.length}}
|
|
729
|
+
<div class="card-hd"><h3>{{conversations.length}} 个活跃会话</h3></div>
|
|
750
730
|
<span v-for="c in conversations" class="pill tag">{{c.userId}}</span>
|
|
751
731
|
</div>
|
|
752
732
|
</div>
|
|
753
733
|
|
|
754
|
-
<!--
|
|
734
|
+
<!-- ===== CHANNELS ===== -->
|
|
755
735
|
<div v-if="page==='channels'" class="page-enter">
|
|
756
736
|
<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>
|
|
737
|
+
<div class="card-hd"><h3>渠道</h3><button class="btn pri sm" @click="openModal('channel')">+ 新建</button></div>
|
|
761
738
|
<div class="tbl-wrap"><table class="tbl">
|
|
762
|
-
<thead><tr><th
|
|
739
|
+
<thead><tr><th>状态</th><th>名称</th><th>适配器</th><th>模型</th><th>优先级</th><th>调用次数</th><th></th></tr></thead>
|
|
763
740
|
<tbody>
|
|
764
741
|
<tr v-for="ch in channels" :key="ch.id">
|
|
765
|
-
<td><span :class="['pill',ch.status==='enabled'?'on':'off']">{{ch.status}}</span></td>
|
|
742
|
+
<td><span :class="['pill',ch.status==='enabled'?'on':'off']">{{ch.status==='enabled'?'启用':'禁用'}}</span></td>
|
|
766
743
|
<td>{{ch.name}}</td><td>{{ch.adapterType}}</td>
|
|
767
744
|
<td><span v-for="m in (ch.models||[])" class="pill model">{{m}}</span></td>
|
|
768
745
|
<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>
|
|
746
|
+
<td><button class="btn sm" @click="testChannel(ch.id)">测试</button><button class="btn sm" @click="openModal('channel',ch)">编辑</button><button class="btn sm danger" @click="deleteItem('channel',ch.id)">删除</button></td>
|
|
774
747
|
</tr>
|
|
775
|
-
<tr v-if="!channels.length"><td colspan="7" style="color:var(--c-dim);text-align:center;padding:32px"
|
|
748
|
+
<tr v-if="!channels.length"><td colspan="7" style="color:var(--c-dim);text-align:center;padding:32px">暂无渠道</td></tr>
|
|
776
749
|
</tbody>
|
|
777
750
|
</table></div>
|
|
778
751
|
</div>
|
|
779
752
|
</div>
|
|
780
753
|
|
|
781
|
-
<!--
|
|
754
|
+
<!-- ===== TOOLS ===== -->
|
|
782
755
|
<div v-if="page==='tools'" class="page-enter">
|
|
783
756
|
<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>
|
|
757
|
+
<div class="card-hd"><h3>工具</h3><button class="btn pri sm" @click="openModal('tool')">+ 新建</button></div>
|
|
788
758
|
<div class="tbl-wrap"><table class="tbl">
|
|
789
|
-
<thead><tr><th
|
|
790
|
-
<tbody>
|
|
791
|
-
|
|
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>
|
|
759
|
+
<thead><tr><th>名称</th><th>描述</th><th></th></tr></thead>
|
|
760
|
+
<tbody><tr v-for="t in tools" :key="t.id"><td>{{t.name||t.id}}</td><td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{t.description}}</td><td><button class="btn sm" @click="openModal('tool',t)">编辑</button><button class="btn sm danger" @click="deleteItem('tool',t.id)">删除</button></td></tr>
|
|
761
|
+
<tr v-if="!tools.length"><td colspan="3" style="color:var(--c-dim);text-align:center;padding:32px">暂无工具</td></tr></tbody>
|
|
801
762
|
</table></div>
|
|
802
763
|
</div>
|
|
803
764
|
</div>
|
|
804
765
|
|
|
805
|
-
<!--
|
|
766
|
+
<!-- ===== PRESETS ===== -->
|
|
806
767
|
<div v-if="page==='presets'" class="page-enter">
|
|
807
768
|
<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>
|
|
769
|
+
<div class="card-hd"><h3>预设</h3><button class="btn pri sm" @click="openModal('preset')">+ 新建</button></div>
|
|
812
770
|
<div class="tbl-wrap"><table class="tbl">
|
|
813
|
-
<thead><tr><th
|
|
814
|
-
<tbody>
|
|
815
|
-
<
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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>
|
|
771
|
+
<thead><tr><th>名称</th><th>预设 ID</th><th>模型</th><th>温度</th><th>前缀</th><th>群聊</th><th></th></tr></thead>
|
|
772
|
+
<tbody><tr v-for="p in presets" :key="p.id">
|
|
773
|
+
<td>{{p.name}}</td><td><code>{{p.id}}</code></td>
|
|
774
|
+
<td><span class="pill model">{{p.sendMessageOption?.model||'-'}}</span></td><td>{{p.sendMessageOption?.temperature}}</td>
|
|
775
|
+
<td><code v-if="p.prefix" style="font-size:11px" :title="p.prefix">{{fLen(p.prefix,12)}}</code><span v-else style="color:var(--c-faint)">-</span></td>
|
|
776
|
+
<td><span class="pill" :class="p.groupContext==='disabled'?'off':(p.groupContext?'on':'')">{{p.groupContext==='disabled'?'禁用':p.groupContext==='enabled'?'启用':p.groupContext==='use_system'?'系统':'默认'}}</span></td>
|
|
777
|
+
<td><button class="btn sm" @click="openModal('preset',p)">编辑</button><button class="btn sm danger" @click="deleteItem('preset',p.id)">删除</button></td>
|
|
778
|
+
</tr>
|
|
779
|
+
<tr v-if="!presets.length"><td colspan="5" style="color:var(--c-dim);text-align:center;padding:32px">暂无预设</td></tr></tbody>
|
|
827
780
|
</table></div>
|
|
828
781
|
</div>
|
|
829
782
|
</div>
|
|
830
783
|
|
|
831
|
-
<!--
|
|
784
|
+
<!-- ===== PROCESSORS ===== -->
|
|
832
785
|
<div v-if="page==='processors'" class="page-enter">
|
|
833
786
|
<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>
|
|
787
|
+
<div class="card-hd"><h3>处理器</h3><button class="btn pri sm" @click="openModal('processor')">+ 新建</button></div>
|
|
838
788
|
<div class="tbl-wrap"><table class="tbl">
|
|
839
|
-
<thead><tr><th
|
|
840
|
-
<tbody>
|
|
841
|
-
|
|
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>
|
|
789
|
+
<thead><tr><th>名称</th><th>类型</th><th>描述</th><th></th></tr></thead>
|
|
790
|
+
<tbody><tr v-for="p in processors" :key="p.id"><td>{{p.name}}</td><td><span class="pill" :style="{background:p.type==='pre'?'rgba(201,177,255,.12)':'rgba(255,224,160,.12)',color:p.type==='pre'?'var(--c-purple)':'var(--c-yellow)'}">{{p.type==='pre'?'前置':'后置'}}</span></td><td>{{p.description}}</td><td><button class="btn sm" @click="openModal('processor',p)">编辑</button><button class="btn sm danger" @click="deleteItem('processor',p.id)">删除</button></td></tr>
|
|
791
|
+
<tr v-if="!processors.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">暂无处理器</td></tr></tbody>
|
|
852
792
|
</table></div>
|
|
853
793
|
</div>
|
|
854
794
|
</div>
|
|
855
795
|
|
|
856
|
-
<!--
|
|
796
|
+
<!-- ===== TRIGGERS ===== -->
|
|
857
797
|
<div v-if="page==='triggers'" class="page-enter">
|
|
858
798
|
<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>
|
|
799
|
+
<div class="card-hd"><h3>触发器</h3><button class="btn pri sm" @click="openModal('trigger')">+ 新建</button></div>
|
|
863
800
|
<div class="tbl-wrap"><table class="tbl">
|
|
864
|
-
<thead><tr><th
|
|
865
|
-
<tbody>
|
|
866
|
-
|
|
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>
|
|
801
|
+
<thead><tr><th>名称</th><th>描述</th><th></th></tr></thead>
|
|
802
|
+
<tbody><tr v-for="t in triggers" :key="t.id"><td>{{t.name||t.id}}</td><td>{{t.description}}</td><td><button class="btn sm" @click="openModal('trigger',t)">编辑</button><button class="btn sm danger" @click="deleteItem('trigger',t.id)">删除</button></td></tr>
|
|
803
|
+
<tr v-if="!triggers.length"><td colspan="3" style="color:var(--c-dim);text-align:center;padding:32px">暂无触发器</td></tr></tbody>
|
|
875
804
|
</table></div>
|
|
876
805
|
</div>
|
|
877
806
|
</div>
|
|
878
807
|
|
|
879
|
-
<!--
|
|
808
|
+
<!-- ===== GROUPS ===== -->
|
|
880
809
|
<div v-if="page==='groups'" class="page-enter">
|
|
881
810
|
<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>
|
|
811
|
+
<div class="card-hd"><h3>工具组</h3><button class="btn pri sm" @click="openModal('toolGroup')">+ 新建</button></div>
|
|
886
812
|
<div class="tbl-wrap"><table class="tbl">
|
|
887
|
-
<thead><tr><th
|
|
888
|
-
<tbody>
|
|
889
|
-
|
|
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>
|
|
813
|
+
<thead><tr><th>名称</th><th>描述</th><th>默认</th><th></th></tr></thead>
|
|
814
|
+
<tbody><tr v-for="g in toolGroups" :key="g.id"><td>{{g.name}}</td><td>{{g.description}}</td><td><span v-if="g.isDefault" class="pill on">默认</span></td><td><button class="btn sm" @click="openModal('toolGroup',g)">编辑</button><button class="btn sm danger" @click="deleteItem('toolGroup',g.id)">删除</button></td></tr>
|
|
815
|
+
<tr v-if="!toolGroups.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">暂无工具组</td></tr></tbody>
|
|
899
816
|
</table></div>
|
|
900
817
|
</div>
|
|
901
818
|
</div>
|
|
902
819
|
|
|
903
|
-
<!--
|
|
820
|
+
<!-- ===== SESSIONS ===== -->
|
|
904
821
|
<div v-if="page==='conversations'" class="page-enter">
|
|
905
822
|
<div class="card">
|
|
906
|
-
<div class="card-hd"><h3
|
|
823
|
+
<div class="card-hd"><h3>活跃会话</h3></div>
|
|
907
824
|
<div class="tbl-wrap"><table class="tbl">
|
|
908
|
-
<thead><tr><th
|
|
909
|
-
<tbody>
|
|
910
|
-
|
|
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>
|
|
825
|
+
<thead><tr><th>用户 ID</th><th>对话 ID</th><th>模型</th><th></th></tr></thead>
|
|
826
|
+
<tbody><tr v-for="c in conversations" :key="c.userId"><td>{{c.userId}}</td><td><code style="font-size:11px">{{c.conversationId||'-'}}</code></td><td><span v-if="c.currentModel" class="pill model">{{c.currentModel}}</span><span v-else style="color:var(--c-dim)">-</span></td><td><button class="btn sm" @click="viewConv(c.userId)">查看</button><button class="btn sm danger" @click="clearConv(c.userId)">清除</button></td></tr>
|
|
827
|
+
<tr v-if="!conversations.length"><td colspan="4" style="color:var(--c-dim);text-align:center;padding:32px">暂无活跃会话</td></tr></tbody>
|
|
921
828
|
</table></div>
|
|
922
829
|
</div>
|
|
923
830
|
</div>
|
|
924
831
|
|
|
925
|
-
<!--
|
|
832
|
+
<!-- ===== CONFIG (redesigned) ===== -->
|
|
926
833
|
<div v-if="page==='config'" class="page-enter">
|
|
927
834
|
<div class="card">
|
|
928
|
-
<div class="card-hd"
|
|
929
|
-
|
|
930
|
-
<
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
835
|
+
<div class="card-hd">
|
|
836
|
+
<h3>插件配置</h3>
|
|
837
|
+
<button class="btn sm" @click="editConfig">编辑完整 JSON</button>
|
|
838
|
+
</div>
|
|
839
|
+
<div v-if="cfgSections.length" class="cfg-grid">
|
|
840
|
+
<div v-for="sec in cfgSections" :key="sec.key"
|
|
841
|
+
:class="['cfg-card',expandedCfg[sec.key]&&'expanded']">
|
|
842
|
+
<div class="cfg-card-head" @click="toggleCfg(sec.key)">
|
|
843
|
+
<span>{{sec.label}}</span>
|
|
844
|
+
<span style="display:flex;align-items:center;gap:8px">
|
|
845
|
+
<span style="font-weight:400;color:var(--c-faint);font-size:11px">{{Object.keys(sec.fields||{}).length}} 项</span>
|
|
846
|
+
<span :class="['arr',expandedCfg[sec.key]&&'open']">▶</span>
|
|
847
|
+
</span>
|
|
934
848
|
</div>
|
|
935
|
-
<div
|
|
936
|
-
<
|
|
937
|
-
<
|
|
938
|
-
|
|
939
|
-
<
|
|
940
|
-
<
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
849
|
+
<div :class="['cfg-card-body',expandedCfg[sec.key]&&'show']">
|
|
850
|
+
<template v-if="sec.isObj">
|
|
851
|
+
<div v-for="(fv,fk) in flattenFields(sec.fields)" :key="fk"
|
|
852
|
+
:class="['cfg-field',editingCfgField&&editingCfgField.sec===sec.key&&editingCfgField.key===fk?'editing':'']">
|
|
853
|
+
<span class="k">{{cfgLabel(fk)}}</span>
|
|
854
|
+
<span class="v">
|
|
855
|
+
<template v-if="typeof fv==='boolean'"><span :class="['pill',fv?'on':'off']">{{fv?'是':'否'}}</span></template>
|
|
856
|
+
<template v-else-if="typeof fv==='number'"><span class="num">{{fv}}</span></template>
|
|
857
|
+
<template v-else-if="Array.isArray(fv)"><code style="font-size:10px">[{{fv.length}}项]</code></template>
|
|
858
|
+
<template v-else-if="typeof fv==='string'&&fv.length<=50"><code>{{fv||'""'}}</code></template>
|
|
859
|
+
<template v-else-if="typeof fv==='string'"><code :title="fv">{{fLen(fv,30)}}</code></template>
|
|
860
|
+
<template v-else><span style="color:var(--c-faint);font-size:11px">对象</span></template>
|
|
861
|
+
</span>
|
|
862
|
+
<button v-if="typeof fv!=='object'||Array.isArray(fv)" class="edit-btn" @click="startEditField(sec.key,fk,fv)" title="编辑">✎</button>
|
|
863
|
+
<div v-if="editingCfgField" class="edit-row">
|
|
864
|
+
<input v-if="Array.isArray(fv)||(typeof fv==='string'&&fv.length>50)" v-model="editingCfgField.val" @keyup.enter="saveEditField(sec.key,fk)"/>
|
|
865
|
+
<input v-else-if="typeof fv==='string'" v-model="editingCfgField.val" @keyup.enter="saveEditField(sec.key,fk)"/>
|
|
866
|
+
<select v-else-if="typeof fv==='boolean'" v-model="editingCfgField.val"><option value="true">是</option><option value="false">否</option></select>
|
|
867
|
+
<input v-else v-model="editingCfgField.val" type="number" @keyup.enter="saveEditField(sec.key,fk)"/>
|
|
868
|
+
<button class="btn-xs save" @click="saveEditField(sec.key,fk)">保存</button>
|
|
869
|
+
<button class="btn-xs" @click="cancelEditField">取消</button>
|
|
870
|
+
</div>
|
|
871
|
+
</div>
|
|
872
|
+
</template>
|
|
873
|
+
<div v-else style="color:var(--c-dim);font-size:13px;padding:8px 0">{{JSON.stringify(sec.fields)}}</div>
|
|
946
874
|
</div>
|
|
947
875
|
</div>
|
|
948
876
|
</div>
|
|
949
|
-
<div v-else style="color:var(--c-dim);font-size:13px;padding:20px 0"
|
|
950
|
-
<div class="cfg-json-btn"><button class="btn" @click="editConfig">Edit JSON</button></div>
|
|
877
|
+
<div v-else style="color:var(--c-dim);font-size:13px;padding:20px 0">暂无配置数据</div>
|
|
951
878
|
</div>
|
|
952
879
|
</div>
|
|
953
880
|
|
|
954
|
-
</div
|
|
955
|
-
</div
|
|
956
|
-
</div
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
</div>
|
|
957
884
|
|
|
958
885
|
<!-- ===== MODALS ===== -->
|
|
959
|
-
|
|
960
|
-
<!-- CRUD Modal -->
|
|
961
886
|
<div v-if="modal" class="modal-mask" @click.self="closeModal">
|
|
962
887
|
<div class="modal">
|
|
963
|
-
<h3>{{editing?'
|
|
888
|
+
<h3>{{editing?'编辑':'新建'}} {{modal}}</h3>
|
|
964
889
|
|
|
890
|
+
<!-- Channel -->
|
|
965
891
|
<template v-if="modal==='channel'">
|
|
966
|
-
<div class="f-row"><label
|
|
967
|
-
<div class="f-row"><label
|
|
968
|
-
<div class="f-row"><label
|
|
892
|
+
<div class="f-row"><label>名称</label><input v-model="form.name"/></div>
|
|
893
|
+
<div class="f-row"><label>适配器</label><select v-model="form.adapterType"><option>openai</option><option>gemini</option><option>claude</option></select></div>
|
|
894
|
+
<div class="f-row"><label>模型(逗号分隔)</label><input v-model="form._models" :placeholder="(form.models||[]).join(', ')"/></div>
|
|
969
895
|
<div class="f-row"><label>Base URL</label><input v-model="form.options.baseUrl"/></div>
|
|
970
896
|
<div class="f-row"><label>API Key</label><input v-model="form.options.apiKey" type="password"/></div>
|
|
971
|
-
<div class="f-row"><label
|
|
972
|
-
<div class="f-row"><label
|
|
973
|
-
<div class="f-row"><label
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
<
|
|
978
|
-
|
|
979
|
-
<div class="f-row"><label
|
|
980
|
-
<div class="f-row"><label
|
|
981
|
-
|
|
982
|
-
<div
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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>
|
|
897
|
+
<div class="f-row"><label>优先级</label><input v-model.number="form.priority" type="number"/></div>
|
|
898
|
+
<div class="f-row"><label>权重</label><input v-model.number="form.weight" type="number"/></div>
|
|
899
|
+
<div class="f-row"><label>状态</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
|
|
900
|
+
|
|
901
|
+
<div class="sub-section">
|
|
902
|
+
<h5 style="font-size:13px;font-weight:600;margin-bottom:10px;color:var(--c-accent)">✦ 模型配置</h5>
|
|
903
|
+
<div class="f-row"><label>模型名称</label><input v-model="form.sendMessageOption.model"/></div>
|
|
904
|
+
<div class="f-row"><label>温度</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1" min="0" max="2"/></div>
|
|
905
|
+
<div class="f-row"><label>最大 Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number" min="1"/></div>
|
|
906
|
+
<div class="f-row"><label>系统提示词</label><textarea v-model="form.sendMessageOption.systemOverride" placeholder="覆盖系统提示词..."></textarea></div>
|
|
907
|
+
|
|
908
|
+
<div v-if="form.adapterType==='gemini'" class="sub-section">
|
|
909
|
+
<h5 class="gemini">Gemini 思考配置</h5>
|
|
910
|
+
<div class="f-row"><label>启用推理</label><select v-model="form.sendMessageOption.enableReasoning"><option :value="false">关闭</option><option :value="true">开启</option></select></div>
|
|
911
|
+
<div class="f-row" v-if="form.sendMessageOption.enableReasoning"><label>思考等级</label><select v-model="form.sendMessageOption.reasoningEffort"><option value="minimal">极低</option><option value="low">低</option><option value="medium">中</option><option value="high">高</option></select></div>
|
|
1003
912
|
</div>
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
<
|
|
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>
|
|
913
|
+
|
|
914
|
+
<div class="sub-section">
|
|
915
|
+
<h5 class="tool">工具调用限制</h5>
|
|
916
|
+
<div class="f-row"><label>最大连续调用</label><input v-model.number="form.sendMessageOption.toolCallLimit.maxConsecutiveCalls" type="number" min="1"/></div>
|
|
917
|
+
<div class="f-row"><label>最大连续相同调用</label><input v-model.number="form.sendMessageOption.toolCallLimit.maxConsecutiveIdenticalCalls" type="number" min="1"/></div>
|
|
1028
918
|
</div>
|
|
919
|
+
|
|
920
|
+
<div class="f-row"><label>响应模态 (JSON)</label><input v-model="form._responseModalities" :placeholder="JSON.stringify(form.sendMessageOption.responseModalities||[])"/></div>
|
|
921
|
+
<div class="f-row"><label>安全设置 (JSON)</label><textarea v-model="form._safetySettings" class="mono" style="min-height:50px" :placeholder="JSON.stringify(form.sendMessageOption.safetySettings||[],null,2)"></textarea></div>
|
|
1029
922
|
</div>
|
|
1030
923
|
</template>
|
|
1031
924
|
|
|
925
|
+
<!-- Preset -->
|
|
1032
926
|
<template v-if="modal==='preset'">
|
|
1033
|
-
<div class="f-row"><label
|
|
1034
|
-
<div class="f-row"><label
|
|
1035
|
-
<div class="f-row"><label
|
|
1036
|
-
<div class="f-row"><label
|
|
1037
|
-
<div class="f-row"><label
|
|
1038
|
-
<div class="f-row"><label
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
<
|
|
1043
|
-
|
|
1044
|
-
<div class="f-row">
|
|
1045
|
-
|
|
1046
|
-
|
|
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>
|
|
927
|
+
<div class="f-row"><label>名称</label><input v-model="form.name"/></div>
|
|
928
|
+
<div class="f-row"><label>前缀</label><input v-model="form.prefix"/></div>
|
|
929
|
+
<div class="f-row"><label>模型</label><input v-model="form.sendMessageOption.model"/></div>
|
|
930
|
+
<div class="f-row"><label>温度</label><input v-model.number="form.sendMessageOption.temperature" type="number" step="0.1" min="0" max="2"/></div>
|
|
931
|
+
<div class="f-row"><label>最大 Token</label><input v-model.number="form.sendMessageOption.maxToken" type="number" min="1"/></div>
|
|
932
|
+
<div class="f-row"><label>系统提示词</label><textarea v-model="form.sendMessageOption.systemOverride" placeholder="覆盖系统提示词..."></textarea></div>
|
|
933
|
+
<div class="sub-section">
|
|
934
|
+
<h5 style="font-size:13px;font-weight:600;margin-bottom:10px;color:var(--c-accent)">✦ 高级配置</h5>
|
|
935
|
+
<div class="f-row"><label>启用推理</label><select v-model="form.sendMessageOption.enableReasoning"><option :value="false">关闭</option><option :value="true">开启</option></select></div>
|
|
936
|
+
<div class="f-row" v-if="form.sendMessageOption.enableReasoning"><label>推理强度</label><select v-model="form.sendMessageOption.reasoningEffort"><option value="minimal">极低</option><option value="low">低</option><option value="medium">中</option><option value="high">高</option></select></div>
|
|
937
|
+
<div class="f-row"><label>禁用读历史</label><select v-model="form.sendMessageOption.disableHistoryRead"><option :value="false">否</option><option :value="true">是</option></select></div>
|
|
938
|
+
<div class="f-row"><label>禁用写历史</label><select v-model="form.sendMessageOption.disableHistorySave"><option :value="false">否</option><option :value="true">是</option></select></div>
|
|
939
|
+
<div class="f-row"><label>群聊上下文</label><select v-model="form.groupContext"><option :value="undefined">默认</option><option value="enabled">启用</option><option value="disabled">禁用</option><option value="use_system">使用系统预设</option></select></div>
|
|
940
|
+
<div class="f-row"><label>禁用系统提示词</label><select v-model="form.disableSystemInstructions"><option :value="false">否</option><option :value="true">是</option></select></div>
|
|
1077
941
|
</div>
|
|
1078
942
|
</template>
|
|
1079
943
|
|
|
944
|
+
<!-- Tool / Processor / Trigger -->
|
|
1080
945
|
<template v-if="modal==='tool'||modal==='processor'||modal==='trigger'">
|
|
1081
|
-
<div class="f-row"><label
|
|
1082
|
-
<div class="f-row" v-if="modal==='processor'"><label
|
|
1083
|
-
<div class="f-row"><label
|
|
1084
|
-
<div class="f-row"><label
|
|
946
|
+
<div class="f-row"><label>名称</label><input v-model="form.name"/></div>
|
|
947
|
+
<div class="f-row" v-if="modal==='processor'"><label>类型</label><select v-model="form.type"><option>pre</option><option>post</option></select></div>
|
|
948
|
+
<div class="f-row"><label>描述</label><input v-model="form.description"/></div>
|
|
949
|
+
<div class="f-row"><label>代码</label><textarea v-model="form.code" class="mono" style="min-height:200px"></textarea></div>
|
|
1085
950
|
</template>
|
|
1086
951
|
|
|
952
|
+
<!-- ToolGroup -->
|
|
1087
953
|
<template v-if="modal==='toolGroup'">
|
|
1088
|
-
<div class="f-row"><label
|
|
1089
|
-
<div class="f-row"><label
|
|
1090
|
-
<div class="f-row"><label
|
|
1091
|
-
<div class="f-row"><label
|
|
954
|
+
<div class="f-row"><label>名称</label><input v-model="form.name"/></div>
|
|
955
|
+
<div class="f-row"><label>描述</label><input v-model="form.description"/></div>
|
|
956
|
+
<div class="f-row"><label>工具 ID(逗号分隔)</label><input v-model="form._toolIds" :placeholder="(form.toolIds||[]).join(', ')"/></div>
|
|
957
|
+
<div class="f-row"><label>状态</label><select v-model="form.status"><option>enabled</option><option>disabled</option></select></div>
|
|
1092
958
|
</template>
|
|
1093
959
|
|
|
1094
960
|
<div class="modal-btns">
|
|
1095
|
-
<button class="btn" @click="closeModal"
|
|
1096
|
-
<button class="btn pri" @click="saveItem"
|
|
961
|
+
<button class="btn" @click="closeModal">取消</button>
|
|
962
|
+
<button class="btn pri" @click="saveItem">保存</button>
|
|
1097
963
|
</div>
|
|
1098
964
|
</div>
|
|
1099
965
|
</div>
|
|
@@ -1101,24 +967,21 @@ createApp({
|
|
|
1101
967
|
<!-- Config JSON Edit Modal -->
|
|
1102
968
|
<div v-if="editingConfig" class="modal-mask" @click.self="editingConfig=false">
|
|
1103
969
|
<div class="modal">
|
|
1104
|
-
<h3
|
|
1105
|
-
<div class="f-row"><
|
|
1106
|
-
<div class="modal-btns">
|
|
1107
|
-
<button class="btn" @click="editingConfig=false">Cancel</button>
|
|
1108
|
-
<button class="btn pri" @click="saveConfig">Save</button>
|
|
1109
|
-
</div>
|
|
970
|
+
<h3>编辑配置 JSON</h3>
|
|
971
|
+
<div class="f-row"><textarea v-model="configForm" class="mono" style="min-height:400px"></textarea></div>
|
|
972
|
+
<div class="modal-btns"><button class="btn" @click="editingConfig=false">取消</button><button class="btn pri" @click="saveConfig">保存</button></div>
|
|
1110
973
|
</div>
|
|
1111
974
|
</div>
|
|
1112
975
|
|
|
1113
976
|
<!-- Session Detail Modal -->
|
|
1114
977
|
<div v-if="convDetail" class="modal-mask" @click.self="convDetail=null">
|
|
1115
978
|
<div class="modal">
|
|
1116
|
-
<h3
|
|
1117
|
-
<div class="f-row"><label
|
|
1118
|
-
<div class="f-row"><label
|
|
1119
|
-
<div class="f-row"><label
|
|
1120
|
-
<div class="f-row"><label
|
|
1121
|
-
<div class="modal-btns"><button class="btn" @click="convDetail=null"
|
|
979
|
+
<h3>会话详情 · {{convDetail.userId}}</h3>
|
|
980
|
+
<div class="f-row"><label>对话 ID</label><input :value="convDetail.current?.conversationId||'-'" readonly/></div>
|
|
981
|
+
<div class="f-row"><label>模型</label><input :value="convDetail.current?.model||'-'" readonly/></div>
|
|
982
|
+
<div class="f-row"><label>预设</label><input :value="convDetail.current?.presetId||'-'" readonly/></div>
|
|
983
|
+
<div class="f-row"><label>完整状态</label><textarea readonly :value="JSON.stringify(convDetail,null,2)" class="mono" style="min-height:200px"></textarea></div>
|
|
984
|
+
<div class="modal-btns"><button class="btn" @click="convDetail=null">关闭</button></div>
|
|
1122
985
|
</div>
|
|
1123
986
|
</div>
|
|
1124
987
|
`
|