agent-purge 0.2.1 → 0.2.2
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/README.md +121 -76
- package/bin/agent-purge.mjs +731 -730
- package/lib/personas.mjs +207 -180
- package/package.json +48 -48
- package/web/server.mjs +375 -331
- package/web/ui.html +1131 -1103
package/web/ui.html
CHANGED
|
@@ -1,1103 +1,1131 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<meta name="color-scheme" content="light" />
|
|
7
|
-
<title>agent-purge · 提示词控制台</title>
|
|
8
|
-
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%232f63ee'/%3E%3Cpath d='M8 24 16 8l8 16' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M11.5 19h9' stroke='white' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E" />
|
|
9
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Host+Grotesk:wght@500;600;700&family=Instrument+Sans:wght@400;500;600&display=swap" />
|
|
12
|
-
<style>
|
|
13
|
-
/* ── Design system: Software(浅色 / 电光蓝 / Grotesk + Instrument) ── */
|
|
14
|
-
:root{
|
|
15
|
-
/* 权威令牌 */
|
|
16
|
-
--ground:#fcfcfd;
|
|
17
|
-
--ink:#16191f;
|
|
18
|
-
--ink-2:#454c58;
|
|
19
|
-
--ground-muted:#f3f4f6;
|
|
20
|
-
--rule:#dde0e6;
|
|
21
|
-
--accent:#074ce0;
|
|
22
|
-
--field:#16191f;
|
|
23
|
-
--on-field:#fcfcfd;
|
|
24
|
-
--on-field-2:#d0d4dd;
|
|
25
|
-
--panel:#ffffff;
|
|
26
|
-
--on-accent:#ffffff;
|
|
27
|
-
--r-panel:12px;
|
|
28
|
-
--hair:1px;
|
|
29
|
-
--motion:160ms;
|
|
30
|
-
--ease:ease-out;
|
|
31
|
-
--gap:24px;
|
|
32
|
-
|
|
33
|
-
/* 派生 */
|
|
34
|
-
--accent-soft:rgba(7,76,224,.08);
|
|
35
|
-
--accent-line:rgba(7,76,224,.32);
|
|
36
|
-
--on-field-soft:rgba(252,252,253,.10);
|
|
37
|
-
--danger:#b42318;
|
|
38
|
-
--danger-soft:rgba(180,35,24,.06);
|
|
39
|
-
--shadow:0 1px 2px rgba(22,25,31,.04),0 12px 24px -18px rgba(22,25,31,.35);
|
|
40
|
-
|
|
41
|
-
--font-display:"Host Grotesk","Instrument Sans","Segoe UI Variable Display","Segoe UI",system-ui,sans-serif;
|
|
42
|
-
--font-ui:"Instrument Sans","Segoe UI Variable Text","Segoe UI",system-ui,-apple-system,"Microsoft YaHei",sans-serif;
|
|
43
|
-
--font-mono:"Cascadia Code","JetBrains Mono",ui-monospace,SFMono-Regular,Consolas,monospace;
|
|
44
|
-
--t:var(--motion) var(--ease);
|
|
45
|
-
}
|
|
46
|
-
*{box-sizing:border-box}
|
|
47
|
-
html{color-scheme:light}
|
|
48
|
-
body{margin:0;background:var(--ground);color:var(--ink);min-height:100vh;display:flex;flex-direction:column;
|
|
49
|
-
font:16px/1.6 var(--font-ui);-webkit-font-smoothing:antialiased;overflow-x:clip}
|
|
50
|
-
button{font:inherit;color:inherit;cursor:pointer;background:none;border:none}
|
|
51
|
-
input,select,textarea{font:15px/1.5 var(--font-ui);color:var(--ink);background:var(--panel);
|
|
52
|
-
border:var(--hair) solid var(--rule);border-radius:8px;padding:9px 11px;outline:none;width:100%;
|
|
53
|
-
transition:border-color var(--t),box-shadow var(--t)}
|
|
54
|
-
input::placeholder,textarea::placeholder{color:var(--ink-2);opacity:.72}
|
|
55
|
-
input:hover,select:hover,textarea:hover{border-color:#c3c8d2}
|
|
56
|
-
select{cursor:pointer;appearance:none;padding-right:32px;
|
|
57
|
-
background-image:linear-gradient(45deg,transparent 50%,var(--ink-2) 50%),linear-gradient(135deg,var(--ink-2) 50%,transparent 50%);
|
|
58
|
-
background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px,5px 5px;background-repeat:no-repeat}
|
|
59
|
-
input:focus-visible,select:focus-visible,textarea:focus-visible,button:focus-visible,[tabindex]:focus-visible{
|
|
60
|
-
outline:2px solid var(--accent);outline-offset:2px;border-color:var(--accent);box-shadow:none}
|
|
61
|
-
code,pre,kbd{font-family:var(--font-mono);font-size:.92em}
|
|
62
|
-
::selection{background:var(--accent-soft)}
|
|
63
|
-
:focus:not(:focus-visible){outline:none}
|
|
64
|
-
|
|
65
|
-
/* ── 顶栏 ─────────────────────────────────────────────────────── */
|
|
66
|
-
header.top{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:16px;
|
|
67
|
-
min-height:64px;padding:12px 24px;border-bottom:var(--hair) solid var(--rule);
|
|
68
|
-
background:rgba(252,252,253,.92);backdrop-filter:blur(14px)}
|
|
69
|
-
.brand{display:flex;align-items:center;gap:11px;padding-right:20px;border-right:var(--hair) solid var(--rule)}
|
|
70
|
-
.brand .mark{width:32px;height:32px;border-radius:9px;display:grid;place-items:center;flex:0 0 auto;
|
|
71
|
-
background:var(--accent);color:var(--on-accent)}
|
|
72
|
-
.brand .txt{display:flex;flex-direction:column;line-height:1.25}
|
|
73
|
-
.brand b{font-family:var(--font-display);font-size:15px;font-weight:700;letter-spacing:-.01em}
|
|
74
|
-
.brand s{text-decoration:none;font-size:12px;color:var(--ink-2)}
|
|
75
|
-
.spacer{flex:1}
|
|
76
|
-
.facts{display:flex;gap:8px;flex-wrap:wrap;min-width:0}
|
|
77
|
-
.fact{display:flex;flex-direction:column;gap:1px;padding:6px 12px;border-radius:8px;
|
|
78
|
-
border:var(--hair) solid var(--rule);background:var(--panel);min-width:0;max-width:190px}
|
|
79
|
-
.fact s{text-decoration:none;font-size:11px;color:var(--ink-2);letter-spacing:.04em;text-transform:uppercase}
|
|
80
|
-
.fact em{font-style:normal;font-family:var(--font-mono);font-size:13px;color:var(--ink);
|
|
81
|
-
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
82
|
-
.fact.warn{border-color:var(--accent-line);background:var(--accent-soft)}
|
|
83
|
-
.fact.warn em{color:var(--accent)}
|
|
84
|
-
.fact.off{opacity:.72}
|
|
85
|
-
.fact.off em{color:var(--ink-2)}
|
|
86
|
-
.power{display:flex;align-items:center;gap:12px;padding:8px 12px 8px 14px;border-radius:10px;
|
|
87
|
-
border:var(--hair) solid var(--rule);background:var(--panel);transition:border-color var(--t),background var(--t)}
|
|
88
|
-
.power:hover{border-color:#c3c8d2}
|
|
89
|
-
.power .txt{display:flex;flex-direction:column;line-height:1.3;text-align:right}
|
|
90
|
-
.power .txt b{font-size:13px;font-weight:600;display:flex;align-items:center;gap:6px;justify-content:flex-end}
|
|
91
|
-
.power .txt s{text-decoration:none;font-size:11px;color:var(--ink-2)}
|
|
92
|
-
.power .live{width:6px;height:6px;border-radius:50%;background:var(--ink-2);transition:background var(--t)}
|
|
93
|
-
.switch{position:relative;width:46px;height:26px;border-radius:99px;flex:0 0 auto;
|
|
94
|
-
background:#dfe2e8;border:var(--hair) solid var(--rule);transition:background var(--t),border-color var(--t)}
|
|
95
|
-
.switch::after{content:"";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;
|
|
96
|
-
background:var(--panel);box-shadow:0 1px 2px rgba(22,25,31,.28);transition:left var(--t),background var(--t)}
|
|
97
|
-
.switch:active::after{width:22px}
|
|
98
|
-
.power.on{border-color:var(--accent-line);background:var(--accent-soft)}
|
|
99
|
-
.power.on .switch{background:var(--accent);border-color:var(--accent)}
|
|
100
|
-
.power.on .switch::after{left:23px;background:var(--panel)}
|
|
101
|
-
.power.on .live{background:var(--accent)}
|
|
102
|
-
.power.on .txt b{color:var(--accent)}
|
|
103
|
-
|
|
104
|
-
/* ── 骨架 ─────────────────────────────────────────────────────── */
|
|
105
|
-
.shell{display:grid;grid-template-columns:236px minmax(0,1fr);flex:1 0 auto;width:100%;
|
|
106
|
-
align-items:start;max-width:1340px;margin:0 auto;min-width:0}
|
|
107
|
-
nav.rail{position:sticky;top:64px;display:flex;flex-direction:column;gap:2px;padding:24px 18px 48px;min-width:0}
|
|
108
|
-
nav.rail .lab{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2);padding:0 12px 10px}
|
|
109
|
-
nav.rail button{position:relative;display:flex;align-items:center;gap:11px;padding:10px 12px;
|
|
110
|
-
border-radius:8px;color:var(--ink-2);font-size:15px;text-align:left;
|
|
111
|
-
transition:background var(--t),color var(--t)}
|
|
112
|
-
nav.rail button svg{width:18px;height:18px;flex:0 0 auto;opacity:.8}
|
|
113
|
-
nav.rail button:hover{background:var(--ground-muted);color:var(--ink)}
|
|
114
|
-
nav.rail button.cur{background:var(--accent);color:var(--on-accent);font-weight:600}
|
|
115
|
-
nav.rail button.cur svg{color:var(--on-accent);opacity:1}
|
|
116
|
-
nav.rail button.cur::before{display:none}
|
|
117
|
-
nav.rail button kbd{margin-left:auto;font-size:11px;color:var(--ink-2);border:var(--hair) solid var(--rule);
|
|
118
|
-
border-radius:5px;padding:0 5px;line-height:16px;background:var(--panel)}
|
|
119
|
-
nav.rail button.cur kbd{color:var(--on-accent);border-color:rgba(255,255,255,.45);background:transparent}
|
|
120
|
-
main{padding:26px 32px 96px;min-width:0}
|
|
121
|
-
section{display:none}
|
|
122
|
-
section.cur{display:block}
|
|
123
|
-
|
|
124
|
-
/* ── 标题 ─────────────────────────────────────────────────────── */
|
|
125
|
-
h2{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:32px 0 14px;
|
|
126
|
-
font-family:var(--font-display);font-size:20px;font-weight:700;letter-spacing:-.015em;line-height:1.25}
|
|
127
|
-
h2:first-child{margin-top:0}
|
|
128
|
-
h2 .n{font-family:var(--font-mono);font-size:11px;color:var(--accent);background:var(--accent-soft);
|
|
129
|
-
border:var(--hair) solid var(--accent-line);border-radius:5px;padding:1px 6px;line-height:16px;
|
|
130
|
-
font-weight:500;transform:translateY(-2px)}
|
|
131
|
-
h2>span:last-child{font-size:14px;font-weight:400;color:var(--ink-2);letter-spacing:0;
|
|
132
|
-
font-family:var(--font-ui)}
|
|
133
|
-
h2 span code{background:var(--ground-muted);border:var(--hair) solid var(--rule);border-radius:5px;
|
|
134
|
-
padding:1px 5px;font-size:12px}
|
|
135
|
-
h3{margin:0 0 10px;font-family:var(--font-display);font-size:16px;font-weight:600;letter-spacing:-.01em}
|
|
136
|
-
|
|
137
|
-
/* ── 卡片 ─────────────────────────────────────────────────────── */
|
|
138
|
-
.card{background:var(--panel);border:var(--hair) solid var(--rule);border-radius:var(--r-panel);
|
|
139
|
-
padding:20px 22px;margin-bottom:16px;box-shadow:var(--shadow)}
|
|
140
|
-
.card>.lead{color:var(--ink-2);font-size:15px;margin:-2px 0 16px}
|
|
141
|
-
.card-dark{background:var(--field);color:var(--on-field);border-color:var(--field)}
|
|
142
|
-
.card-dark .lead{color:var(--on-field-2)}
|
|
143
|
-
.card-dark h3{color:var(--on-field)}
|
|
144
|
-
.row{display:flex;gap:16px;align-items:flex-end;flex-wrap:wrap}
|
|
145
|
-
.field{display:flex;flex-direction:column;gap:6px;min-width:200px;flex:1}
|
|
146
|
-
.field>label{font-size:13px;color:var(--ink-2);font-weight:500}
|
|
147
|
-
.field>small{font-size:13px;color:var(--ink-2);line-height:1.5}
|
|
148
|
-
.tiny{font-size:13px;color:var(--ink-2)}
|
|
149
|
-
.mono{font-family:var(--font-mono);font-size:13px}
|
|
150
|
-
|
|
151
|
-
.btn{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;min-height:40px;border-radius:8px;
|
|
152
|
-
border:var(--hair) solid var(--rule);background:var(--panel);color:var(--ink);font-size:15px;
|
|
153
|
-
font-weight:500;white-space:nowrap;transition:transform var(--t),background var(--t),border-color var(--t),color var(--t),box-shadow var(--t)}
|
|
154
|
-
.btn:hover{border-color:var(--accent);color:var(--accent);box-shadow:0 2px 8px -4px rgba(7,76,224,.4);transform:translateY(-1px)}
|
|
155
|
-
.btn:active{transform:none;box-shadow:none}
|
|
156
|
-
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent);font-weight:600}
|
|
157
|
-
.btn.primary:hover{background:#0539ad;border-color:#0539ad;color:var(--on-accent)}
|
|
158
|
-
.btn:disabled{cursor:not-allowed;opacity:.5}
|
|
159
|
-
.btn.primary:disabled{background:var(--ground-muted);border-color:var(--rule);color:var(--ink-2);opacity:1}
|
|
160
|
-
.btn.danger{color:var(--danger);border-color:rgba(180,35,24,.34);background:var(--panel)}
|
|
161
|
-
.btn.danger:hover{background:var(--danger-soft);border-color:var(--danger);color:var(--danger);box-shadow:none}
|
|
162
|
-
.btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}
|
|
163
|
-
/* 深底卡里的按钮 */
|
|
164
|
-
.card-dark .btn{border-color:rgba(255,255,255,.28);background:transparent;color:var(--on-field)}
|
|
165
|
-
.card-dark .btn:hover{border-color:var(--on-field);color:var(--on-field);background:var(--on-field-soft);box-shadow:none}
|
|
166
|
-
.card-dark .btn.primary{background:var(--on-field);border-color:var(--on-field);color:var(--field)}
|
|
167
|
-
.card-dark .btn.primary:hover{background:var(--on-field-2);border-color:var(--on-field-2);color:var(--field)}
|
|
168
|
-
.card-dark .btn.danger{border-color:rgba(255,255,255,.28);color:#ffb4ab;background:transparent}
|
|
169
|
-
.card-dark .btn.danger:hover{background:rgba(255,180,171,.12);border-color:#ffb4ab}
|
|
170
|
-
/* 忙态 */
|
|
171
|
-
.btn.is-busy,.switch.is-busy,.ch.is-busy{position:relative;pointer-events:none;opacity:.66}
|
|
172
|
-
.btn.is-busy::after,.ch.is-busy::after{content:"";position:absolute;right:10px;top:50%;
|
|
173
|
-
width:11px;height:11px;margin-top:-6px;border-radius:50%;border:1.5px solid currentColor;
|
|
174
|
-
border-right-color:transparent;animation:ap-spin .7s linear infinite}
|
|
175
|
-
@keyframes ap-spin{to{transform:rotate(360deg)}}
|
|
176
|
-
|
|
177
|
-
/* ── 总览:现在会发生什么 ─────────────────────────────────────── */
|
|
178
|
-
.hero{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--gap);align-items:start;margin-bottom:16px}
|
|
179
|
-
.hero .card{margin-bottom:0;height:100%}
|
|
180
|
-
.now{display:grid;min-width:0}
|
|
181
|
-
.nowline{display:grid;grid-template-columns:78px minmax(0,1fr);gap:16px;padding:14px 0;border-top:var(--hair) solid var(--rule)}
|
|
182
|
-
.nowline:first-child{border-top:none;padding-top:0}
|
|
183
|
-
.nowline>s{text-decoration:none;font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-2);padding-top:4px}
|
|
184
|
-
.nowline .big{font-family:var(--font-display);font-size:18px;font-weight:700;letter-spacing:-.015em;
|
|
185
|
-
display:flex;align-items:center;gap:9px;flex-wrap:wrap;line-height:1.3}
|
|
186
|
-
.nowline>*{min-width:0}
|
|
187
|
-
.nowline .sub{font-size:15px;color:var(--ink-2);margin-top:4px;min-width:0}
|
|
188
|
-
.pinline{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:9px}
|
|
189
|
-
.cmdbox{display:flex;align-items:center;gap:10px;margin-top:10px;background:var(--ground-muted);min-width:0;
|
|
190
|
-
border:var(--hair) solid var(--rule);border-radius:8px;padding:8px 10px 8px 13px}
|
|
191
|
-
.cmdbox code{flex:1;min-width:0;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;
|
|
192
|
-
color:var(--ink);font-size:13px;line-height:1.55;max-height:4.8em}
|
|
193
|
-
.cmdbox .btn{min-height:32px;padding:5px 12px;font-size:13px}
|
|
194
|
-
|
|
195
|
-
/* 执行器选择(原「执行器」页合并进来,只展示选中的那一个) */
|
|
196
|
-
.hpick{display:flex;gap:7px;flex-wrap:wrap;margin:0 0 12px}
|
|
197
|
-
.hchip{display:inline-flex;align-items:center;gap:7px;padding:7px 12px;border-radius:99px;
|
|
198
|
-
border:var(--hair) solid var(--rule);background:var(--panel);color:var(--ink);font-size:13px;
|
|
199
|
-
font-family:var(--font-mono);cursor:pointer;transition:border-color var(--t),background var(--t),color var(--t)}
|
|
200
|
-
.hchip:hover{border-color:var(--accent);color:var(--accent)}
|
|
201
|
-
.hchip .pdot{width:7px;height:7px;border-radius:50%;background:#c3c8d2;flex:0 0 auto;transition:background var(--t)}
|
|
202
|
-
.hchip.on .pdot{background:var(--accent)}
|
|
203
|
-
.hchip.miss{color:var(--ink-2);opacity:.62;border-style:dashed}
|
|
204
|
-
.hchip.sel{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
|
|
205
|
-
.hchip.sel .pdot{background:var(--on-accent)}
|
|
206
|
-
.selbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 10px}
|
|
207
|
-
.selbar .grow{flex:1 1 auto;min-width:0;font-size:13px;color:var(--ink-2)}
|
|
208
|
-
#hsel>.hc{box-shadow:var(--shadow)}
|
|
209
|
-
.hdead{margin-top:18px;border-top:var(--hair) solid var(--rule);padding-top:4px}
|
|
210
|
-
.hdead[hidden]{display:none}
|
|
211
|
-
.hdead>summary{cursor:pointer;padding:11px 0;font-size:14px;color:var(--ink-2);display:flex;gap:10px;
|
|
212
|
-
align-items:baseline;flex-wrap:wrap;list-style-position:inside}
|
|
213
|
-
.hdead>summary:hover{color:var(--ink)}
|
|
214
|
-
.hdead>summary::marker{color:var(--ink-2)}
|
|
215
|
-
.hdead .hgrid{margin-top:10px}
|
|
216
|
-
|
|
217
|
-
/* ── 模式选择 ─────────────────────────────────────────────────── */
|
|
218
|
-
.modes{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;align-items:stretch}
|
|
219
|
-
.mode{position:relative;display:flex;flex-direction:column;gap:2px;min-height:132px;height:100%;padding:13px 14px;
|
|
220
|
-
border:var(--hair) solid var(--rule);border-radius:10px;background:var(--panel);cursor:pointer;text-align:left;
|
|
221
|
-
transition:border-color var(--t),background var(--t),box-shadow var(--t)}
|
|
222
|
-
.mode:hover{border-color:var(--accent);background:var(--accent-soft)}
|
|
223
|
-
.mode b{display:flex;align-items:center;gap:6px;font-size:15px;font-weight:600}
|
|
224
|
-
.mode i{display:block;font-style:normal;font-family:var(--font-mono);font-size:11px;color:var(--ink-2);margin-top:2px}
|
|
225
|
-
.mode small{display:block;font-size:13px;color:var(--ink-2);margin-top:auto;padding-top:8px;line-height:1.5}
|
|
226
|
-
.mode.on{border-color:var(--accent);background:var(--accent);color:var(--on-accent);
|
|
227
|
-
box-shadow:0 12px 24px -18px rgba(7,76,224,.95)}
|
|
228
|
-
.mode.on b{color:var(--on-accent)}
|
|
229
|
-
.mode.on i,.mode.on small{color:rgba(255,255,255,.84)}
|
|
230
|
-
.mode.on::after{content:"✓";position:absolute;top:10px;right:11px;font-size:12px;font-weight:700;
|
|
231
|
-
color:var(--accent);background:var(--on-accent);border-radius:50%;width:18px;height:18px;
|
|
232
|
-
display:grid;place-items:center;line-height:1}
|
|
233
|
-
.mode.cur{border-color:var(--ink)}
|
|
234
|
-
.card-dark .mode{background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.18);color:var(--on-field)}
|
|
235
|
-
.card-dark .mode small,.card-dark .mode i{color:var(--on-field-2)}
|
|
236
|
-
.card-dark .mode:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.4)}
|
|
237
|
-
.card-dark .mode.on{background:rgba(252,252,253,.14);border-color:rgba(252,252,253,.6);color:var(--on-field)}
|
|
238
|
-
.card-dark .mode.on b{color:var(--on-field)}
|
|
239
|
-
.card-dark .mode.on small,.card-dark .mode.on i{color:var(--on-field-2)}
|
|
240
|
-
.card-dark .mode.on::after{background:var(--on-field);color:var(--field)}
|
|
241
|
-
|
|
242
|
-
/* ── 通道 ─────────────────────────────────────────────────────── */
|
|
243
|
-
.chans{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:10px;min-width:0}
|
|
244
|
-
.chan{display:flex;gap:11px;align-items:flex-start;padding:13px 14px;border:var(--hair) solid var(--rule);
|
|
245
|
-
border-radius:10px;background:var(--panel);text-align:left;min-width:0;overflow:hidden;
|
|
246
|
-
transition:border-color var(--t),background var(--t)}
|
|
247
|
-
.chan:hover{border-color:var(--accent);background:var(--accent-soft)}
|
|
248
|
-
.chan .dot{width:9px;height:9px;border-radius:50%;background:#c3c8d2;margin-top:6px;flex:0 0 auto;
|
|
249
|
-
transition:background var(--t)}
|
|
250
|
-
.chan > span:last-of-type{min-width:0;flex:1 1 auto}
|
|
251
|
-
.chan b{display:flex;align-items:center;gap:7px;font-size:14px;font-weight:600}
|
|
252
|
-
.chan b u{text-decoration:none;font-family:var(--font-mono);font-size:11px;color:var(--ink-2);
|
|
253
|
-
border:var(--hair) solid var(--rule);border-radius:5px;padding:0 5px;line-height:16px}
|
|
254
|
-
.chan b i.lay{font-style:normal;font-family:var(--font-mono);font-size:10px;letter-spacing:.07em;
|
|
255
|
-
color:var(--ink-2);border:var(--hair) solid var(--rule);border-radius:4px;padding:0 4px;line-height:15px}
|
|
256
|
-
.chan b i.lay.warn{color:#8a5a00;border-color:#e6cfa4;background:#fff7e8}
|
|
257
|
-
.chan small{display:block;font-size:12px;color:var(--ink-2);margin-top:3px;line-height:1.45;
|
|
258
|
-
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
259
|
-
.chan small.path{display:block;-webkit-line-clamp:none;
|
|
260
|
-
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left}
|
|
261
|
-
.chan.on{border-color:var(--accent);background:var(--accent-soft)}
|
|
262
|
-
.chan.on .dot{background:var(--accent)}
|
|
263
|
-
.chan.on b u{color:var(--accent);border-color:var(--accent-line)}
|
|
264
|
-
.chan.dis{opacity:.62;cursor:not-allowed}
|
|
265
|
-
/* 结构性做不到(不是“没开”):虚线 + 不跟手,和“关了”区分开 */
|
|
266
|
-
.chan.na{border-style:dashed;opacity:.55;cursor:not-allowed;background:transparent}
|
|
267
|
-
.chan.na:hover{border-color:var(--line);background:transparent}
|
|
268
|
-
.card-dark .chan{background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.18);color:var(--on-field)}
|
|
269
|
-
.card-dark .chan small{-webkit-line-clamp:3}
|
|
270
|
-
.card-dark .chan small,.card-dark .chan b u{color:var(--on-field-2)}
|
|
271
|
-
.card-dark .chan b u{border-color:rgba(255,255,255,.24)}
|
|
272
|
-
.card-dark .chan:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.42)}
|
|
273
|
-
.card-dark .chan .dot{background:rgba(255,255,255,.4)}
|
|
274
|
-
.card-dark .chan.on{background:rgba(252,252,253,.13);border-color:rgba(252,252,253,.6);color:var(--on-field)}
|
|
275
|
-
.card-dark .chan.on small{color:var(--on-field-2)}
|
|
276
|
-
.card-dark .chan.on .dot{background:#7ea6ff}
|
|
277
|
-
.card-dark .chan.on b u{border-color:rgba(126,166,255,.5);color:#a9c3ff}
|
|
278
|
-
.card-dark .tiny{color:var(--on-field-2)}
|
|
279
|
-
|
|
280
|
-
/* ── 执行器卡 ─────────────────────────────────────────────────── */
|
|
281
|
-
.hgrid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));align-items:start}
|
|
282
|
-
.hc{background:var(--panel);border:var(--hair) solid var(--rule);border-radius:var(--r-panel);
|
|
283
|
-
padding:18px 20px;transition:border-color var(--t),box-shadow var(--t);min-width:0}
|
|
284
|
-
.hc:hover{border-color:#c3c8d2;box-shadow:var(--shadow)}
|
|
285
|
-
.hc.dead{background:var(--ground-muted)}
|
|
286
|
-
.hhead{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
|
|
287
|
-
.hname{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600;font-family:var(--font-display);
|
|
288
|
-
letter-spacing:-.01em}
|
|
289
|
-
.hname .st{width:8px;height:8px;border-radius:50%;background:#c3c8d2;flex:0 0 auto}
|
|
290
|
-
.hname .st.on{background:var(--accent)}
|
|
291
|
-
.badge{font-size:12px;padding:2px 9px;border-radius:99px;border:var(--hair) solid var(--rule);
|
|
292
|
-
color:var(--ink-2);white-space:nowrap;display:inline-flex;align-items:center;gap:5px;background:var(--panel)}
|
|
293
|
-
.badge.ok{border-color:var(--accent-line);color:var(--accent);background:var(--accent-soft)}
|
|
294
|
-
.badge.warn{border-color:var(--rule);color:var(--ink-2);background:var(--ground-muted)}
|
|
295
|
-
.badge.bad{border-color:rgba(180,35,24,.4);color:var(--danger);background:var(--danger-soft)}
|
|
296
|
-
.badge.acc{border-color:var(--accent-line);color:var(--accent);background:var(--accent-soft)}
|
|
297
|
-
.hpath{flex:0 0 100%;margin:0;font-family:var(--font-mono);font-size:11px;color:var(--ink-2);
|
|
298
|
-
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right}
|
|
299
|
-
.rec{display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:13px;color:var(--ink);background:var(--accent-soft);border-radius:8px;padding:7px 11px;margin:10px 0 12px}
|
|
300
|
-
.rec b{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--accent);font-weight:600}
|
|
301
|
-
.rec .sep{color:var(--ink-3)}
|
|
302
|
-
.hmeta{display:flex;gap:20px;flex-wrap:wrap;margin:12px 0 14px;font-size:13px;color:var(--ink-2)}
|
|
303
|
-
.hmeta k{color:var(--ink-2);margin-right:5px;font-weight:500;font-size:11px;letter-spacing:.04em;text-transform:uppercase}
|
|
304
|
-
.hmeta .hint{display:inline-flex;align-items:center;justify-content:center;width:13px;height:13px;border-radius:50%;
|
|
305
|
-
border:var(--hair) solid var(--rule);color:var(--ink-2);font-size:9px;font-style:normal;margin-left:5px;
|
|
306
|
-
cursor:help;vertical-align:1px;background:var(--panel)}
|
|
307
|
-
.hnote{font-size:13px;color:var(--ink);background:var(--ground-muted);border-left:2px solid var(--accent);
|
|
308
|
-
padding:9px 12px;border-radius:0 8px 8px 0;margin:0 0 12px;line-height:1.55}
|
|
309
|
-
.hwhy{font-size:13px;color:var(--ink-2);background:var(--ground-muted);border:var(--hair) solid var(--rule);
|
|
310
|
-
border-radius:8px;padding:10px 12px;margin-bottom:12px;line-height:1.6}
|
|
311
|
-
|
|
312
|
-
/* ── 表格 ─────────────────────────────────────────────────────── */
|
|
313
|
-
.tblwrap{overflow-x:auto;margin:-4px 0;min-width:0}
|
|
314
|
-
.tbl{width:100%;min-width:900px;border-collapse:collapse;font-size:13px}
|
|
315
|
-
.tbl th{text-align:left;font-weight:500;color:var(--ink-2);font-size:11px;letter-spacing:.04em;
|
|
316
|
-
text-transform:uppercase;padding:0 12px 9px;border-bottom:var(--hair) solid var(--rule);white-space:nowrap}
|
|
317
|
-
.tbl td{padding:10px 12px;border-bottom:var(--hair) solid var(--rule);vertical-align:middle;white-space:nowrap}
|
|
318
|
-
.tbl tbody tr:hover td{background:var(--ground-muted)}
|
|
319
|
-
.tbl tr:last-child td{border-bottom:none}
|
|
320
|
-
.tbl .m{font-family:var(--font-mono);font-weight:500;color:var(--ink)}
|
|
321
|
-
.yes{color:var(--accent);font-weight:600}
|
|
322
|
-
.no{color:var(--ink-2)}
|
|
323
|
-
.na{color:var(--ink-2);opacity:.6}
|
|
324
|
-
.warncell{color:#7a5200;background:#fdf6e3;font-weight:500}
|
|
325
|
-
.tbl .g{display:inline-block;width:12px;font-size:12px;margin-right:2px}
|
|
326
|
-
.tbl .v{display:inline-flex;align-items:baseline;gap:1px;justify-content:flex-end}
|
|
327
|
-
|
|
328
|
-
/* 芯片 */
|
|
329
|
-
.chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
330
|
-
.chip{font-family:var(--font-mono);font-size:12px;color:var(--accent);background:var(--accent-soft);
|
|
331
|
-
border:var(--hair) solid var(--accent-line);border-radius:6px;padding:1px 7px;line-height:18px;white-space:nowrap}
|
|
332
|
-
|
|
333
|
-
/* ── 人设 ─────────────────────────────────────────────────────── */
|
|
334
|
-
.split{display:grid;grid-template-columns:300px minmax(0,1fr);gap:16px;align-items:start}
|
|
335
|
-
.plist{display:flex;flex-direction:column;gap:8px;max-height:70vh;overflow:auto;padding:2px 4px 2px 2px}
|
|
336
|
-
.pitem{padding:12px 14px;border:var(--hair) solid var(--rule);border-radius:10px;background:var(--panel);
|
|
337
|
-
text-align:left;transition:border-color var(--t),background var(--t)}
|
|
338
|
-
.pitem:hover{border-color:var(--accent);background:var(--accent-soft)}
|
|
339
|
-
.pitem.cur{border-color:var(--accent);background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent)}
|
|
340
|
-
.pitem b{display:flex;align-items:center;gap:7px;font-size:14px;font-weight:600}
|
|
341
|
-
.pitem .tag{font-size:10px;padding:1px 7px;border-radius:99px;background:var(--accent);color:var(--on-accent);
|
|
342
|
-
border:var(--hair) solid var(--accent);font-weight:600}
|
|
343
|
-
.pitem small{display:block;color:var(--ink-2);font-size:11px;margin-top:4px;font-family:var(--font-mono)}
|
|
344
|
-
.pitem p{margin:6px 0 0;font-size:12px;color:var(--ink-2);display:-webkit-box;-webkit-line-clamp:2;
|
|
345
|
-
-webkit-box-orient:vertical;overflow:hidden}
|
|
346
|
-
textarea{resize:vertical;min-height:340px;font-family:var(--font-mono);font-size:13px;line-height:1.7;tab-size:2}
|
|
347
|
-
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
|
348
|
-
|
|
349
|
-
/* ── 页脚(第二处深色块) ─────────────────────────────────────── */
|
|
350
|
-
.foot{position:relative;z-index:1;background:var(--field);color:var(--on-field);
|
|
351
|
-
padding:28px 32px;margin-top:8px}
|
|
352
|
-
.foot .fin{max-width:1040px;margin:0 auto;display:flex;gap:var(--gap);align-items:baseline;flex-wrap:wrap}
|
|
353
|
-
.foot b{font-family:var(--font-display);font-size:16px;font-weight:700;letter-spacing:-.01em}
|
|
354
|
-
.foot p{margin:0;font-size:13px;color:var(--on-field-2);line-height:1.6;max-width:62ch}
|
|
355
|
-
.foot .end{margin-left:auto;font-family:var(--font-mono);font-size:12px;color:var(--on-field-2)}
|
|
356
|
-
|
|
357
|
-
/* ── toast ────────────────────────────────────────────────────── */
|
|
358
|
-
#toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);display:flex;flex-direction:column;
|
|
359
|
-
gap:8px;align-items:center;pointer-events:none;z-index:200}
|
|
360
|
-
.toast{padding:11px 18px;border-radius:8px;background:var(--field);border:var(--hair) solid var(--field);
|
|
361
|
-
font-size:14px;color:var(--on-field);box-shadow:0 18px 40px -18px rgba(22,25,31,.7);opacity:0;
|
|
362
|
-
transform:translateY(10px);transition:opacity 200ms var(--ease),transform 200ms var(--ease)}
|
|
363
|
-
.toast.in{opacity:1;transform:none}
|
|
364
|
-
.toast.err{border-color:var(--danger);background:#3d1414;color:#ffdad6}
|
|
365
|
-
.empty{padding:28px;text-align:center;color:var(--ink-2);font-size:14px}
|
|
366
|
-
|
|
367
|
-
/* ── 响应式(桌面优先;≤1050 收紧,≤760 堆叠) ───────────────── */
|
|
368
|
-
@media (max-width:1050px){
|
|
369
|
-
.hgrid{grid-template-columns:minmax(0,1fr)}
|
|
370
|
-
.hero{gap:16px}
|
|
371
|
-
.split{grid-template-columns:260px minmax(0,1fr)}
|
|
372
|
-
main{padding:22px 22px 80px}
|
|
373
|
-
}
|
|
374
|
-
@media (max-width:900px){
|
|
375
|
-
.shell{grid-template-columns:minmax(0,1fr)}
|
|
376
|
-
nav.rail{position:sticky;top:64px;z-index:40;flex-direction:row;overflow-x:auto;min-width:0;max-width:100%;gap:4px;
|
|
377
|
-
padding:10px 18px;background:rgba(252,252,253,.94);backdrop-filter:blur(12px);
|
|
378
|
-
border-bottom:var(--hair) solid var(--rule);scrollbar-width:none}
|
|
379
|
-
nav.rail .lab,nav.rail button kbd{display:none}
|
|
380
|
-
nav.rail button{white-space:nowrap}
|
|
381
|
-
main{padding:20px 18px 72px}
|
|
382
|
-
.facts{display:none}
|
|
383
|
-
}
|
|
384
|
-
@media (max-width:760px){
|
|
385
|
-
.split{grid-template-columns:minmax(0,1fr)}
|
|
386
|
-
.plist{max-height:none;flex-direction:row;overflow-x:auto;max-width:100%;padding-bottom:6px}
|
|
387
|
-
.pitem{min-width:230px}
|
|
388
|
-
.grid2{grid-template-columns:1fr}
|
|
389
|
-
.nowline{grid-template-columns:minmax(0,1fr);gap:4px}
|
|
390
|
-
.nowline>s{padding-top:0}
|
|
391
|
-
.hpath{max-width:100%;margin-left:0}
|
|
392
|
-
header.top{flex-wrap:wrap;gap:10px}
|
|
393
|
-
.brand{padding-right:0;border-right:none}
|
|
394
|
-
.cmdbox code{white-space:normal;word-break:break-all}
|
|
395
|
-
.foot{padding:24px 18px}
|
|
396
|
-
.foot .end{margin-left:0}
|
|
397
|
-
}
|
|
398
|
-
@media (prefers-reduced-motion:reduce){
|
|
399
|
-
*,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important;
|
|
400
|
-
transform:none!important}
|
|
401
|
-
}
|
|
402
|
-
.pirow{display:flex;align-items:center;justify-content:space-between;gap:18px;border:1px solid var(--line);border-radius:12px;padding:11px 14px;margin:12px 0 4px;background:var(--field)}
|
|
403
|
-
.pirow b{display:block;font-size:13px;font-weight:600}
|
|
404
|
-
.pirow .tiny{display:block;margin-top:3px;line-height:1.5}
|
|
405
|
-
.pirow .switch{background:var(--line);border:1px solid var(--line)}
|
|
406
|
-
.pirow.on .switch{background:var(--accent);border-color:var(--accent)}
|
|
407
|
-
.pirow.on .switch::after{left:23px;background:#fff}</style>
|
|
408
|
-
</head>
|
|
409
|
-
<body>
|
|
410
|
-
|
|
411
|
-
<header class="top">
|
|
412
|
-
<div class="brand">
|
|
413
|
-
<span class="mark" aria-hidden="true">
|
|
414
|
-
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"
|
|
415
|
-
stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v9"/><path d="M7.4 6.6a7.5 7.5 0 1 0 9.2 0"/></svg>
|
|
416
|
-
</span>
|
|
417
|
-
<span class="txt"><b>agent-purge</b><s>提示词注入控制台</s></span>
|
|
418
|
-
</div>
|
|
419
|
-
<div class="facts" id="facts"></div>
|
|
420
|
-
<div class="spacer"></div>
|
|
421
|
-
<div class="power" id="power">
|
|
422
|
-
<div class="txt"><b><span class="live"></span>总电源</b><s id="powerSub">注入已开</s></div>
|
|
423
|
-
<button class="switch" id="powerBtn" role="switch" aria-checked="true"
|
|
424
|
-
aria-label="总电源:关闭后所有入口都不注入"
|
|
425
|
-
title="关 = 所有入口都不注入,原样启动"></button>
|
|
426
|
-
</div>
|
|
427
|
-
</header>
|
|
428
|
-
|
|
429
|
-
<div class="shell">
|
|
430
|
-
<nav class="rail" aria-label="主导航">
|
|
431
|
-
<div class="lab">控制台</div>
|
|
432
|
-
<button data-v="overview" class="cur" type="button">
|
|
433
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7.5" height="9" rx="1.6"/><rect x="13.5" y="3" width="7.5" height="5.5" rx="1.6"/><rect x="13.5" y="12" width="7.5" height="9" rx="1.6"/><rect x="3" y="15.5" width="7.5" height="5.5" rx="1.6"/></svg>
|
|
434
|
-
总览 <kbd>1</kbd>
|
|
435
|
-
</button>
|
|
436
|
-
<button data-v="personas" type="button">
|
|
437
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8.5" r="3.6"/><path d="M4.6 20a7.4 7.4 0 0 1 14.8 0"/></svg>
|
|
438
|
-
人设 <kbd>2</kbd>
|
|
439
|
-
</button>
|
|
440
|
-
<button data-v="check" type="button">
|
|
441
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l7.5 3.2v6.1c0 4.6-3.1 7.7-7.5 9.2-4.4-1.5-7.5-4.6-7.5-9.2V6.2z"/><path d="m8.8 12.2 2.2 2.2 4.2-4.4"/></svg>
|
|
442
|
-
自检 <kbd>3</kbd>
|
|
443
|
-
</button>
|
|
444
|
-
</nav>
|
|
445
|
-
|
|
446
|
-
<main>
|
|
447
|
-
<!-- ═══ 1 总览 ═══ -->
|
|
448
|
-
<section id="v-overview" class="cur">
|
|
449
|
-
<h2><span class="n">01</span>现在会发生什么 <span>当前全部真相,不用翻别处</span></h2>
|
|
450
|
-
<div class="hero">
|
|
451
|
-
<div class="card">
|
|
452
|
-
<div class="now" id="nowBox"></div>
|
|
453
|
-
</div>
|
|
454
|
-
</div>
|
|
455
|
-
|
|
456
|
-
<h2><span class="n">02</span>默认启动设置 <span>裸敲 <code>agent-purge</code> 或点“启动”时用这套</span></h2>
|
|
457
|
-
<div class="card">
|
|
458
|
-
<div class="row" style="margin-bottom:16px">
|
|
459
|
-
<div class="field" style="max-width:340px">
|
|
460
|
-
<label for="dPersona">默认人设</label>
|
|
461
|
-
<select id="dPersona"></select>
|
|
462
|
-
<small>选中即写入,供所有通道共用;默认执行器在下方选中的卡片上改。</small>
|
|
463
|
-
</div>
|
|
464
|
-
</div>
|
|
465
|
-
|
|
466
|
-
<label class="tiny" style="display:block;margin-bottom:8px;font-size:11.5px">注入模式 —— 决定“人设怎么进模型”</label>
|
|
467
|
-
<div class="modes" id="modeBox"></div>
|
|
468
|
-
|
|
469
|
-
<div class="row" style="margin-top:16px">
|
|
470
|
-
<button class="btn primary" id="saveModeBtn" disabled>保存设置</button>
|
|
471
|
-
<span class="tiny" id="dirtyHint"></span>
|
|
472
|
-
</div>
|
|
473
|
-
|
|
474
|
-
<div class="cmdbox">
|
|
475
|
-
<code id="cmdPreview">—</code>
|
|
476
|
-
<button class="btn" id="copyCmd">复制</button>
|
|
477
|
-
</div>
|
|
478
|
-
<div class="tiny" style="margin-top:8px" id="modeHint"></div>
|
|
479
|
-
</div>
|
|
480
|
-
|
|
481
|
-
<h2><span class="n">03</span>执行器细节 <span>只显示选中的那一个:注入层、实测、三个通道;点名字换一家</span></h2>
|
|
482
|
-
<div class="hpick" id="hpick"></div>
|
|
483
|
-
<div class="selbar" id="selbar"></div>
|
|
484
|
-
<div id="hsel"></div>
|
|
485
|
-
<details class="hdead" id="hdeadWrap" hidden>
|
|
486
|
-
<summary>注入不进去的那几个 <span class="tiny">读不进去是有原因的,不是没做</span></summary>
|
|
487
|
-
<div class="hgrid" id="hdeadBox"></div>
|
|
488
|
-
</details>
|
|
489
|
-
</section>
|
|
490
|
-
|
|
491
|
-
<!-- ═══ 3 人设 ═══ -->
|
|
492
|
-
<section id="v-personas">
|
|
493
|
-
<h2><span class="n">04</span>人设库 <span><code>personas/<id>.md</code>;派生文件 <code>active.md</code> 自动生成,别手改</span></h2>
|
|
494
|
-
<div class="split">
|
|
495
|
-
<div>
|
|
496
|
-
<div class="plist" id="plist"></div>
|
|
497
|
-
<button class="btn" id="pNew" style="width:100%;margin-top:9px">+ 新建空白人设</button>
|
|
498
|
-
</div>
|
|
499
|
-
<div class="card" style="margin:0">
|
|
500
|
-
<div class="row" style="margin-bottom:12px">
|
|
501
|
-
<div class="field"><label for="pId">id(文件名)</label><input id="pId" placeholder="xiaomajiang" /></div>
|
|
502
|
-
<div class="field"><label for="pName">名字</label><input id="pName" placeholder="示例:项目助手" /></div>
|
|
503
|
-
</div>
|
|
504
|
-
<div class="row" style="margin-bottom:12px">
|
|
505
|
-
<div class="field"><label for="pDesc">说明</label><input id="pDesc" placeholder="一句话:这个身份是干嘛的" /></div>
|
|
506
|
-
<div class="field" style="max-width:250px"><label for="pModel">建议模型</label><input id="pModel" placeholder="留空 = 不限制" /></div>
|
|
507
|
-
</div>
|
|
508
|
-
<div class="field" style="margin-bottom:12px">
|
|
509
|
-
<label for="pBody">正文 <span class="tiny" id="pChars">0 字</span></label>
|
|
510
|
-
<textarea id="pBody" placeholder="这段文字会被塞进系统提示词层(sys)或用户消息层(user)……"></textarea>
|
|
511
|
-
<small>写的是“身份”而不是“任务”。带拒绝倾向的模型看到它就开始直接干活,这就是注入的全部内容。</small>
|
|
512
|
-
</div>
|
|
513
|
-
<div class="row">
|
|
514
|
-
<button class="btn primary" id="pSave">保存</button>
|
|
515
|
-
<button class="btn" id="pUse">设为当前</button>
|
|
516
|
-
<div class="spacer" style="flex:1"></div>
|
|
517
|
-
<button class="btn danger" id="pDel">删除</button>
|
|
518
|
-
</div>
|
|
519
|
-
</div>
|
|
520
|
-
</div>
|
|
521
|
-
</section>
|
|
522
|
-
|
|
523
|
-
<!-- ═══ 4 自检 ═══ -->
|
|
524
|
-
<section id="v-check">
|
|
525
|
-
<h2><span class="n">05</span>本机真值 <span>state 说装了 ≠ 盘上有文件,这里两个都看</span></h2>
|
|
526
|
-
<div class="card" style="padding:16px 8px 10px">
|
|
527
|
-
<div class="tblwrap"><table class="tbl" id="tbl"></table></div>
|
|
528
|
-
</div>
|
|
529
|
-
<div class="card">
|
|
530
|
-
<h3>对账</h3>
|
|
531
|
-
<div class="tiny" id="recon" style="font-size:12px;color:var(--ink-2)">—</div>
|
|
532
|
-
<div class="row" style="margin-top:14px">
|
|
533
|
-
<button class="btn" id="reload">重新读盘</button>
|
|
534
|
-
<button class="btn" id="withVersions">连版本号一起查(慢一点)</button>
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
537
|
-
</section>
|
|
538
|
-
</main>
|
|
539
|
-
</div>
|
|
540
|
-
|
|
541
|
-
<footer class="foot">
|
|
542
|
-
<div class="fin">
|
|
543
|
-
<b>agent-purge</b>
|
|
544
|
-
<p>人设写得对,模型就没有拒绝的理由。所有注入都落在本机文件里,关掉总电源即全部撤回。</p>
|
|
545
|
-
<span class="end">127.0.0.1 · 本地控制台</span>
|
|
546
|
-
</div>
|
|
547
|
-
</footer>
|
|
548
|
-
|
|
549
|
-
<div id="toast"></div>
|
|
550
|
-
|
|
551
|
-
<script>
|
|
552
|
-
const $ = (s, r = document) => r.querySelector(s);
|
|
553
|
-
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
554
|
-
|
|
555
|
-
const MODE_INFO = {
|
|
556
|
-
strict: { name: "只留人设", desc: "宿主原有的系统提示整段丢掉,只剩人设。" },
|
|
557
|
-
replace: { name: "整段替换", desc: "默认。用带层的替换把宿主提示换成“人设 + 运行时约束”。" },
|
|
558
|
-
append: { name: "追加", desc: "宿主提示照留,只在末尾续一段人设(最温和)。" },
|
|
559
|
-
off: { name: "不注入", desc: "原样启动,等于没打开过这个工具。" },
|
|
560
|
-
};
|
|
561
|
-
const ARM_INFO = {
|
|
562
|
-
shim: { name: "接管裸命令", desc: (h) => h?.id === "pi"
|
|
563
|
-
? "PATH 最前放个同名壳:敲 pi 就注入。想要“裸启动也带人设”,开这个。"
|
|
564
|
-
: `往 PATH 前置一个同名小壳:直接敲 ${h?.id || "<harness>"} 也是接管版。` },
|
|
565
|
-
slash: { name: "会话内热切", desc: (h) => h?.id === "pi"
|
|
566
|
-
? "写 pi 扩展,只挂 /purge:不敲 /purge 就原样跑,动手才改写。"
|
|
567
|
-
: "写 harness 自己支持的扩展/命令,会话里 /purge 切换。" },
|
|
568
|
-
install: { name: "配置文件常驻", desc: () => "写进 harness 自己的指令文件:连别人启动、GUI 里点开都生效。" },
|
|
569
|
-
};
|
|
570
|
-
const armDesc = (key, h) => ARM_INFO[key].desc(h);
|
|
571
|
-
/* 热切:caps.hot 是唯一裁决(ok + kind)。kind=command 会话内 /purge;kind=state 改设置下一轮生效 */
|
|
572
|
-
const hotOf = (h) => h?.caps?.hot ?? { ok: false, kind: null, why: null, how: null };
|
|
573
|
-
const hotBadge = (h) => {
|
|
574
|
-
const x = hotOf(h);
|
|
575
|
-
if (!x.ok) return null;
|
|
576
|
-
return x.kind === "command" ? { text: "会话内热切", tip: x.how } : { text: "热切", tip: x.how };
|
|
577
|
-
};
|
|
578
|
-
const CLASS_INFO = {
|
|
579
|
-
"native-extension": "原生扩展",
|
|
580
|
-
"plugin": "插件",
|
|
581
|
-
"launch-arg": "启动参数",
|
|
582
|
-
"launch-arg + binary-patch": "启动参数 + 补丁",
|
|
583
|
-
"file-swap": "文件替换",
|
|
584
|
-
"binary-patch": "二进制补丁",
|
|
585
|
-
"config-or-hook": "配置 / 钩子",
|
|
586
|
-
"unsupported": "做不到",
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
let S = null, A = null, view = "overview", selPersona = null, draftMode = null, selHarness = null;
|
|
590
|
-
let lastPop = null, pDirty = false;
|
|
591
|
-
let withVersions = new URLSearchParams(location.search).get("versions") === "1";
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
/*
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}).
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
//
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
$("#
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
const b = e.target.closest("[data-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
});
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
});
|
|
1079
|
-
|
|
1080
|
-
$("#
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
});
|
|
1089
|
-
|
|
1090
|
-
$("#
|
|
1091
|
-
$("#
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light" />
|
|
7
|
+
<title>agent-purge · 提示词控制台</title>
|
|
8
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%232f63ee'/%3E%3Cpath d='M8 24 16 8l8 16' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M11.5 19h9' stroke='white' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E" />
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Host+Grotesk:wght@500;600;700&family=Instrument+Sans:wght@400;500;600&display=swap" />
|
|
12
|
+
<style>
|
|
13
|
+
/* ── Design system: Software(浅色 / 电光蓝 / Grotesk + Instrument) ── */
|
|
14
|
+
:root{
|
|
15
|
+
/* 权威令牌 */
|
|
16
|
+
--ground:#fcfcfd;
|
|
17
|
+
--ink:#16191f;
|
|
18
|
+
--ink-2:#454c58;
|
|
19
|
+
--ground-muted:#f3f4f6;
|
|
20
|
+
--rule:#dde0e6;
|
|
21
|
+
--accent:#074ce0;
|
|
22
|
+
--field:#16191f;
|
|
23
|
+
--on-field:#fcfcfd;
|
|
24
|
+
--on-field-2:#d0d4dd;
|
|
25
|
+
--panel:#ffffff;
|
|
26
|
+
--on-accent:#ffffff;
|
|
27
|
+
--r-panel:12px;
|
|
28
|
+
--hair:1px;
|
|
29
|
+
--motion:160ms;
|
|
30
|
+
--ease:ease-out;
|
|
31
|
+
--gap:24px;
|
|
32
|
+
|
|
33
|
+
/* 派生 */
|
|
34
|
+
--accent-soft:rgba(7,76,224,.08);
|
|
35
|
+
--accent-line:rgba(7,76,224,.32);
|
|
36
|
+
--on-field-soft:rgba(252,252,253,.10);
|
|
37
|
+
--danger:#b42318;
|
|
38
|
+
--danger-soft:rgba(180,35,24,.06);
|
|
39
|
+
--shadow:0 1px 2px rgba(22,25,31,.04),0 12px 24px -18px rgba(22,25,31,.35);
|
|
40
|
+
|
|
41
|
+
--font-display:"Host Grotesk","Instrument Sans","Segoe UI Variable Display","Segoe UI",system-ui,sans-serif;
|
|
42
|
+
--font-ui:"Instrument Sans","Segoe UI Variable Text","Segoe UI",system-ui,-apple-system,"Microsoft YaHei",sans-serif;
|
|
43
|
+
--font-mono:"Cascadia Code","JetBrains Mono",ui-monospace,SFMono-Regular,Consolas,monospace;
|
|
44
|
+
--t:var(--motion) var(--ease);
|
|
45
|
+
}
|
|
46
|
+
*{box-sizing:border-box}
|
|
47
|
+
html{color-scheme:light}
|
|
48
|
+
body{margin:0;background:var(--ground);color:var(--ink);min-height:100vh;display:flex;flex-direction:column;
|
|
49
|
+
font:16px/1.6 var(--font-ui);-webkit-font-smoothing:antialiased;overflow-x:clip}
|
|
50
|
+
button{font:inherit;color:inherit;cursor:pointer;background:none;border:none}
|
|
51
|
+
input,select,textarea{font:15px/1.5 var(--font-ui);color:var(--ink);background:var(--panel);
|
|
52
|
+
border:var(--hair) solid var(--rule);border-radius:8px;padding:9px 11px;outline:none;width:100%;
|
|
53
|
+
transition:border-color var(--t),box-shadow var(--t)}
|
|
54
|
+
input::placeholder,textarea::placeholder{color:var(--ink-2);opacity:.72}
|
|
55
|
+
input:hover,select:hover,textarea:hover{border-color:#c3c8d2}
|
|
56
|
+
select{cursor:pointer;appearance:none;padding-right:32px;
|
|
57
|
+
background-image:linear-gradient(45deg,transparent 50%,var(--ink-2) 50%),linear-gradient(135deg,var(--ink-2) 50%,transparent 50%);
|
|
58
|
+
background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px,5px 5px;background-repeat:no-repeat}
|
|
59
|
+
input:focus-visible,select:focus-visible,textarea:focus-visible,button:focus-visible,[tabindex]:focus-visible{
|
|
60
|
+
outline:2px solid var(--accent);outline-offset:2px;border-color:var(--accent);box-shadow:none}
|
|
61
|
+
code,pre,kbd{font-family:var(--font-mono);font-size:.92em}
|
|
62
|
+
::selection{background:var(--accent-soft)}
|
|
63
|
+
:focus:not(:focus-visible){outline:none}
|
|
64
|
+
|
|
65
|
+
/* ── 顶栏 ─────────────────────────────────────────────────────── */
|
|
66
|
+
header.top{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:16px;
|
|
67
|
+
min-height:64px;padding:12px 24px;border-bottom:var(--hair) solid var(--rule);
|
|
68
|
+
background:rgba(252,252,253,.92);backdrop-filter:blur(14px)}
|
|
69
|
+
.brand{display:flex;align-items:center;gap:11px;padding-right:20px;border-right:var(--hair) solid var(--rule)}
|
|
70
|
+
.brand .mark{width:32px;height:32px;border-radius:9px;display:grid;place-items:center;flex:0 0 auto;
|
|
71
|
+
background:var(--accent);color:var(--on-accent)}
|
|
72
|
+
.brand .txt{display:flex;flex-direction:column;line-height:1.25}
|
|
73
|
+
.brand b{font-family:var(--font-display);font-size:15px;font-weight:700;letter-spacing:-.01em}
|
|
74
|
+
.brand s{text-decoration:none;font-size:12px;color:var(--ink-2)}
|
|
75
|
+
.spacer{flex:1}
|
|
76
|
+
.facts{display:flex;gap:8px;flex-wrap:wrap;min-width:0}
|
|
77
|
+
.fact{display:flex;flex-direction:column;gap:1px;padding:6px 12px;border-radius:8px;
|
|
78
|
+
border:var(--hair) solid var(--rule);background:var(--panel);min-width:0;max-width:190px}
|
|
79
|
+
.fact s{text-decoration:none;font-size:11px;color:var(--ink-2);letter-spacing:.04em;text-transform:uppercase}
|
|
80
|
+
.fact em{font-style:normal;font-family:var(--font-mono);font-size:13px;color:var(--ink);
|
|
81
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
82
|
+
.fact.warn{border-color:var(--accent-line);background:var(--accent-soft)}
|
|
83
|
+
.fact.warn em{color:var(--accent)}
|
|
84
|
+
.fact.off{opacity:.72}
|
|
85
|
+
.fact.off em{color:var(--ink-2)}
|
|
86
|
+
.power{display:flex;align-items:center;gap:12px;padding:8px 12px 8px 14px;border-radius:10px;
|
|
87
|
+
border:var(--hair) solid var(--rule);background:var(--panel);transition:border-color var(--t),background var(--t)}
|
|
88
|
+
.power:hover{border-color:#c3c8d2}
|
|
89
|
+
.power .txt{display:flex;flex-direction:column;line-height:1.3;text-align:right}
|
|
90
|
+
.power .txt b{font-size:13px;font-weight:600;display:flex;align-items:center;gap:6px;justify-content:flex-end}
|
|
91
|
+
.power .txt s{text-decoration:none;font-size:11px;color:var(--ink-2)}
|
|
92
|
+
.power .live{width:6px;height:6px;border-radius:50%;background:var(--ink-2);transition:background var(--t)}
|
|
93
|
+
.switch{position:relative;width:46px;height:26px;border-radius:99px;flex:0 0 auto;
|
|
94
|
+
background:#dfe2e8;border:var(--hair) solid var(--rule);transition:background var(--t),border-color var(--t)}
|
|
95
|
+
.switch::after{content:"";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;
|
|
96
|
+
background:var(--panel);box-shadow:0 1px 2px rgba(22,25,31,.28);transition:left var(--t),background var(--t)}
|
|
97
|
+
.switch:active::after{width:22px}
|
|
98
|
+
.power.on{border-color:var(--accent-line);background:var(--accent-soft)}
|
|
99
|
+
.power.on .switch{background:var(--accent);border-color:var(--accent)}
|
|
100
|
+
.power.on .switch::after{left:23px;background:var(--panel)}
|
|
101
|
+
.power.on .live{background:var(--accent)}
|
|
102
|
+
.power.on .txt b{color:var(--accent)}
|
|
103
|
+
|
|
104
|
+
/* ── 骨架 ─────────────────────────────────────────────────────── */
|
|
105
|
+
.shell{display:grid;grid-template-columns:236px minmax(0,1fr);flex:1 0 auto;width:100%;
|
|
106
|
+
align-items:start;max-width:1340px;margin:0 auto;min-width:0}
|
|
107
|
+
nav.rail{position:sticky;top:64px;display:flex;flex-direction:column;gap:2px;padding:24px 18px 48px;min-width:0}
|
|
108
|
+
nav.rail .lab{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2);padding:0 12px 10px}
|
|
109
|
+
nav.rail button{position:relative;display:flex;align-items:center;gap:11px;padding:10px 12px;
|
|
110
|
+
border-radius:8px;color:var(--ink-2);font-size:15px;text-align:left;
|
|
111
|
+
transition:background var(--t),color var(--t)}
|
|
112
|
+
nav.rail button svg{width:18px;height:18px;flex:0 0 auto;opacity:.8}
|
|
113
|
+
nav.rail button:hover{background:var(--ground-muted);color:var(--ink)}
|
|
114
|
+
nav.rail button.cur{background:var(--accent);color:var(--on-accent);font-weight:600}
|
|
115
|
+
nav.rail button.cur svg{color:var(--on-accent);opacity:1}
|
|
116
|
+
nav.rail button.cur::before{display:none}
|
|
117
|
+
nav.rail button kbd{margin-left:auto;font-size:11px;color:var(--ink-2);border:var(--hair) solid var(--rule);
|
|
118
|
+
border-radius:5px;padding:0 5px;line-height:16px;background:var(--panel)}
|
|
119
|
+
nav.rail button.cur kbd{color:var(--on-accent);border-color:rgba(255,255,255,.45);background:transparent}
|
|
120
|
+
main{padding:26px 32px 96px;min-width:0}
|
|
121
|
+
section{display:none}
|
|
122
|
+
section.cur{display:block}
|
|
123
|
+
|
|
124
|
+
/* ── 标题 ─────────────────────────────────────────────────────── */
|
|
125
|
+
h2{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:32px 0 14px;
|
|
126
|
+
font-family:var(--font-display);font-size:20px;font-weight:700;letter-spacing:-.015em;line-height:1.25}
|
|
127
|
+
h2:first-child{margin-top:0}
|
|
128
|
+
h2 .n{font-family:var(--font-mono);font-size:11px;color:var(--accent);background:var(--accent-soft);
|
|
129
|
+
border:var(--hair) solid var(--accent-line);border-radius:5px;padding:1px 6px;line-height:16px;
|
|
130
|
+
font-weight:500;transform:translateY(-2px)}
|
|
131
|
+
h2>span:last-child{font-size:14px;font-weight:400;color:var(--ink-2);letter-spacing:0;
|
|
132
|
+
font-family:var(--font-ui)}
|
|
133
|
+
h2 span code{background:var(--ground-muted);border:var(--hair) solid var(--rule);border-radius:5px;
|
|
134
|
+
padding:1px 5px;font-size:12px}
|
|
135
|
+
h3{margin:0 0 10px;font-family:var(--font-display);font-size:16px;font-weight:600;letter-spacing:-.01em}
|
|
136
|
+
|
|
137
|
+
/* ── 卡片 ─────────────────────────────────────────────────────── */
|
|
138
|
+
.card{background:var(--panel);border:var(--hair) solid var(--rule);border-radius:var(--r-panel);
|
|
139
|
+
padding:20px 22px;margin-bottom:16px;box-shadow:var(--shadow)}
|
|
140
|
+
.card>.lead{color:var(--ink-2);font-size:15px;margin:-2px 0 16px}
|
|
141
|
+
.card-dark{background:var(--field);color:var(--on-field);border-color:var(--field)}
|
|
142
|
+
.card-dark .lead{color:var(--on-field-2)}
|
|
143
|
+
.card-dark h3{color:var(--on-field)}
|
|
144
|
+
.row{display:flex;gap:16px;align-items:flex-end;flex-wrap:wrap}
|
|
145
|
+
.field{display:flex;flex-direction:column;gap:6px;min-width:200px;flex:1}
|
|
146
|
+
.field>label{font-size:13px;color:var(--ink-2);font-weight:500}
|
|
147
|
+
.field>small{font-size:13px;color:var(--ink-2);line-height:1.5}
|
|
148
|
+
.tiny{font-size:13px;color:var(--ink-2)}
|
|
149
|
+
.mono{font-family:var(--font-mono);font-size:13px}
|
|
150
|
+
|
|
151
|
+
.btn{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;min-height:40px;border-radius:8px;
|
|
152
|
+
border:var(--hair) solid var(--rule);background:var(--panel);color:var(--ink);font-size:15px;
|
|
153
|
+
font-weight:500;white-space:nowrap;transition:transform var(--t),background var(--t),border-color var(--t),color var(--t),box-shadow var(--t)}
|
|
154
|
+
.btn:hover{border-color:var(--accent);color:var(--accent);box-shadow:0 2px 8px -4px rgba(7,76,224,.4);transform:translateY(-1px)}
|
|
155
|
+
.btn:active{transform:none;box-shadow:none}
|
|
156
|
+
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent);font-weight:600}
|
|
157
|
+
.btn.primary:hover{background:#0539ad;border-color:#0539ad;color:var(--on-accent)}
|
|
158
|
+
.btn:disabled{cursor:not-allowed;opacity:.5}
|
|
159
|
+
.btn.primary:disabled{background:var(--ground-muted);border-color:var(--rule);color:var(--ink-2);opacity:1}
|
|
160
|
+
.btn.danger{color:var(--danger);border-color:rgba(180,35,24,.34);background:var(--panel)}
|
|
161
|
+
.btn.danger:hover{background:var(--danger-soft);border-color:var(--danger);color:var(--danger);box-shadow:none}
|
|
162
|
+
.btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}
|
|
163
|
+
/* 深底卡里的按钮 */
|
|
164
|
+
.card-dark .btn{border-color:rgba(255,255,255,.28);background:transparent;color:var(--on-field)}
|
|
165
|
+
.card-dark .btn:hover{border-color:var(--on-field);color:var(--on-field);background:var(--on-field-soft);box-shadow:none}
|
|
166
|
+
.card-dark .btn.primary{background:var(--on-field);border-color:var(--on-field);color:var(--field)}
|
|
167
|
+
.card-dark .btn.primary:hover{background:var(--on-field-2);border-color:var(--on-field-2);color:var(--field)}
|
|
168
|
+
.card-dark .btn.danger{border-color:rgba(255,255,255,.28);color:#ffb4ab;background:transparent}
|
|
169
|
+
.card-dark .btn.danger:hover{background:rgba(255,180,171,.12);border-color:#ffb4ab}
|
|
170
|
+
/* 忙态 */
|
|
171
|
+
.btn.is-busy,.switch.is-busy,.ch.is-busy{position:relative;pointer-events:none;opacity:.66}
|
|
172
|
+
.btn.is-busy::after,.ch.is-busy::after{content:"";position:absolute;right:10px;top:50%;
|
|
173
|
+
width:11px;height:11px;margin-top:-6px;border-radius:50%;border:1.5px solid currentColor;
|
|
174
|
+
border-right-color:transparent;animation:ap-spin .7s linear infinite}
|
|
175
|
+
@keyframes ap-spin{to{transform:rotate(360deg)}}
|
|
176
|
+
|
|
177
|
+
/* ── 总览:现在会发生什么 ─────────────────────────────────────── */
|
|
178
|
+
.hero{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--gap);align-items:start;margin-bottom:16px}
|
|
179
|
+
.hero .card{margin-bottom:0;height:100%}
|
|
180
|
+
.now{display:grid;min-width:0}
|
|
181
|
+
.nowline{display:grid;grid-template-columns:78px minmax(0,1fr);gap:16px;padding:14px 0;border-top:var(--hair) solid var(--rule)}
|
|
182
|
+
.nowline:first-child{border-top:none;padding-top:0}
|
|
183
|
+
.nowline>s{text-decoration:none;font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-2);padding-top:4px}
|
|
184
|
+
.nowline .big{font-family:var(--font-display);font-size:18px;font-weight:700;letter-spacing:-.015em;
|
|
185
|
+
display:flex;align-items:center;gap:9px;flex-wrap:wrap;line-height:1.3}
|
|
186
|
+
.nowline>*{min-width:0}
|
|
187
|
+
.nowline .sub{font-size:15px;color:var(--ink-2);margin-top:4px;min-width:0}
|
|
188
|
+
.pinline{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:9px}
|
|
189
|
+
.cmdbox{display:flex;align-items:center;gap:10px;margin-top:10px;background:var(--ground-muted);min-width:0;
|
|
190
|
+
border:var(--hair) solid var(--rule);border-radius:8px;padding:8px 10px 8px 13px}
|
|
191
|
+
.cmdbox code{flex:1;min-width:0;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;
|
|
192
|
+
color:var(--ink);font-size:13px;line-height:1.55;max-height:4.8em}
|
|
193
|
+
.cmdbox .btn{min-height:32px;padding:5px 12px;font-size:13px}
|
|
194
|
+
|
|
195
|
+
/* 执行器选择(原「执行器」页合并进来,只展示选中的那一个) */
|
|
196
|
+
.hpick{display:flex;gap:7px;flex-wrap:wrap;margin:0 0 12px}
|
|
197
|
+
.hchip{display:inline-flex;align-items:center;gap:7px;padding:7px 12px;border-radius:99px;
|
|
198
|
+
border:var(--hair) solid var(--rule);background:var(--panel);color:var(--ink);font-size:13px;
|
|
199
|
+
font-family:var(--font-mono);cursor:pointer;transition:border-color var(--t),background var(--t),color var(--t)}
|
|
200
|
+
.hchip:hover{border-color:var(--accent);color:var(--accent)}
|
|
201
|
+
.hchip .pdot{width:7px;height:7px;border-radius:50%;background:#c3c8d2;flex:0 0 auto;transition:background var(--t)}
|
|
202
|
+
.hchip.on .pdot{background:var(--accent)}
|
|
203
|
+
.hchip.miss{color:var(--ink-2);opacity:.62;border-style:dashed}
|
|
204
|
+
.hchip.sel{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
|
|
205
|
+
.hchip.sel .pdot{background:var(--on-accent)}
|
|
206
|
+
.selbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 10px}
|
|
207
|
+
.selbar .grow{flex:1 1 auto;min-width:0;font-size:13px;color:var(--ink-2)}
|
|
208
|
+
#hsel>.hc{box-shadow:var(--shadow)}
|
|
209
|
+
.hdead{margin-top:18px;border-top:var(--hair) solid var(--rule);padding-top:4px}
|
|
210
|
+
.hdead[hidden]{display:none}
|
|
211
|
+
.hdead>summary{cursor:pointer;padding:11px 0;font-size:14px;color:var(--ink-2);display:flex;gap:10px;
|
|
212
|
+
align-items:baseline;flex-wrap:wrap;list-style-position:inside}
|
|
213
|
+
.hdead>summary:hover{color:var(--ink)}
|
|
214
|
+
.hdead>summary::marker{color:var(--ink-2)}
|
|
215
|
+
.hdead .hgrid{margin-top:10px}
|
|
216
|
+
|
|
217
|
+
/* ── 模式选择 ─────────────────────────────────────────────────── */
|
|
218
|
+
.modes{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;align-items:stretch}
|
|
219
|
+
.mode{position:relative;display:flex;flex-direction:column;gap:2px;min-height:132px;height:100%;padding:13px 14px;
|
|
220
|
+
border:var(--hair) solid var(--rule);border-radius:10px;background:var(--panel);cursor:pointer;text-align:left;
|
|
221
|
+
transition:border-color var(--t),background var(--t),box-shadow var(--t)}
|
|
222
|
+
.mode:hover{border-color:var(--accent);background:var(--accent-soft)}
|
|
223
|
+
.mode b{display:flex;align-items:center;gap:6px;font-size:15px;font-weight:600}
|
|
224
|
+
.mode i{display:block;font-style:normal;font-family:var(--font-mono);font-size:11px;color:var(--ink-2);margin-top:2px}
|
|
225
|
+
.mode small{display:block;font-size:13px;color:var(--ink-2);margin-top:auto;padding-top:8px;line-height:1.5}
|
|
226
|
+
.mode.on{border-color:var(--accent);background:var(--accent);color:var(--on-accent);
|
|
227
|
+
box-shadow:0 12px 24px -18px rgba(7,76,224,.95)}
|
|
228
|
+
.mode.on b{color:var(--on-accent)}
|
|
229
|
+
.mode.on i,.mode.on small{color:rgba(255,255,255,.84)}
|
|
230
|
+
.mode.on::after{content:"✓";position:absolute;top:10px;right:11px;font-size:12px;font-weight:700;
|
|
231
|
+
color:var(--accent);background:var(--on-accent);border-radius:50%;width:18px;height:18px;
|
|
232
|
+
display:grid;place-items:center;line-height:1}
|
|
233
|
+
.mode.cur{border-color:var(--ink)}
|
|
234
|
+
.card-dark .mode{background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.18);color:var(--on-field)}
|
|
235
|
+
.card-dark .mode small,.card-dark .mode i{color:var(--on-field-2)}
|
|
236
|
+
.card-dark .mode:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.4)}
|
|
237
|
+
.card-dark .mode.on{background:rgba(252,252,253,.14);border-color:rgba(252,252,253,.6);color:var(--on-field)}
|
|
238
|
+
.card-dark .mode.on b{color:var(--on-field)}
|
|
239
|
+
.card-dark .mode.on small,.card-dark .mode.on i{color:var(--on-field-2)}
|
|
240
|
+
.card-dark .mode.on::after{background:var(--on-field);color:var(--field)}
|
|
241
|
+
|
|
242
|
+
/* ── 通道 ─────────────────────────────────────────────────────── */
|
|
243
|
+
.chans{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:10px;min-width:0}
|
|
244
|
+
.chan{display:flex;gap:11px;align-items:flex-start;padding:13px 14px;border:var(--hair) solid var(--rule);
|
|
245
|
+
border-radius:10px;background:var(--panel);text-align:left;min-width:0;overflow:hidden;
|
|
246
|
+
transition:border-color var(--t),background var(--t)}
|
|
247
|
+
.chan:hover{border-color:var(--accent);background:var(--accent-soft)}
|
|
248
|
+
.chan .dot{width:9px;height:9px;border-radius:50%;background:#c3c8d2;margin-top:6px;flex:0 0 auto;
|
|
249
|
+
transition:background var(--t)}
|
|
250
|
+
.chan > span:last-of-type{min-width:0;flex:1 1 auto}
|
|
251
|
+
.chan b{display:flex;align-items:center;gap:7px;font-size:14px;font-weight:600}
|
|
252
|
+
.chan b u{text-decoration:none;font-family:var(--font-mono);font-size:11px;color:var(--ink-2);
|
|
253
|
+
border:var(--hair) solid var(--rule);border-radius:5px;padding:0 5px;line-height:16px}
|
|
254
|
+
.chan b i.lay{font-style:normal;font-family:var(--font-mono);font-size:10px;letter-spacing:.07em;
|
|
255
|
+
color:var(--ink-2);border:var(--hair) solid var(--rule);border-radius:4px;padding:0 4px;line-height:15px}
|
|
256
|
+
.chan b i.lay.warn{color:#8a5a00;border-color:#e6cfa4;background:#fff7e8}
|
|
257
|
+
.chan small{display:block;font-size:12px;color:var(--ink-2);margin-top:3px;line-height:1.45;
|
|
258
|
+
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
259
|
+
.chan small.path{display:block;-webkit-line-clamp:none;
|
|
260
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left}
|
|
261
|
+
.chan.on{border-color:var(--accent);background:var(--accent-soft)}
|
|
262
|
+
.chan.on .dot{background:var(--accent)}
|
|
263
|
+
.chan.on b u{color:var(--accent);border-color:var(--accent-line)}
|
|
264
|
+
.chan.dis{opacity:.62;cursor:not-allowed}
|
|
265
|
+
/* 结构性做不到(不是“没开”):虚线 + 不跟手,和“关了”区分开 */
|
|
266
|
+
.chan.na{border-style:dashed;opacity:.55;cursor:not-allowed;background:transparent}
|
|
267
|
+
.chan.na:hover{border-color:var(--line);background:transparent}
|
|
268
|
+
.card-dark .chan{background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.18);color:var(--on-field)}
|
|
269
|
+
.card-dark .chan small{-webkit-line-clamp:3}
|
|
270
|
+
.card-dark .chan small,.card-dark .chan b u{color:var(--on-field-2)}
|
|
271
|
+
.card-dark .chan b u{border-color:rgba(255,255,255,.24)}
|
|
272
|
+
.card-dark .chan:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.42)}
|
|
273
|
+
.card-dark .chan .dot{background:rgba(255,255,255,.4)}
|
|
274
|
+
.card-dark .chan.on{background:rgba(252,252,253,.13);border-color:rgba(252,252,253,.6);color:var(--on-field)}
|
|
275
|
+
.card-dark .chan.on small{color:var(--on-field-2)}
|
|
276
|
+
.card-dark .chan.on .dot{background:#7ea6ff}
|
|
277
|
+
.card-dark .chan.on b u{border-color:rgba(126,166,255,.5);color:#a9c3ff}
|
|
278
|
+
.card-dark .tiny{color:var(--on-field-2)}
|
|
279
|
+
|
|
280
|
+
/* ── 执行器卡 ─────────────────────────────────────────────────── */
|
|
281
|
+
.hgrid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));align-items:start}
|
|
282
|
+
.hc{background:var(--panel);border:var(--hair) solid var(--rule);border-radius:var(--r-panel);
|
|
283
|
+
padding:18px 20px;transition:border-color var(--t),box-shadow var(--t);min-width:0}
|
|
284
|
+
.hc:hover{border-color:#c3c8d2;box-shadow:var(--shadow)}
|
|
285
|
+
.hc.dead{background:var(--ground-muted)}
|
|
286
|
+
.hhead{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
|
|
287
|
+
.hname{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600;font-family:var(--font-display);
|
|
288
|
+
letter-spacing:-.01em}
|
|
289
|
+
.hname .st{width:8px;height:8px;border-radius:50%;background:#c3c8d2;flex:0 0 auto}
|
|
290
|
+
.hname .st.on{background:var(--accent)}
|
|
291
|
+
.badge{font-size:12px;padding:2px 9px;border-radius:99px;border:var(--hair) solid var(--rule);
|
|
292
|
+
color:var(--ink-2);white-space:nowrap;display:inline-flex;align-items:center;gap:5px;background:var(--panel)}
|
|
293
|
+
.badge.ok{border-color:var(--accent-line);color:var(--accent);background:var(--accent-soft)}
|
|
294
|
+
.badge.warn{border-color:var(--rule);color:var(--ink-2);background:var(--ground-muted)}
|
|
295
|
+
.badge.bad{border-color:rgba(180,35,24,.4);color:var(--danger);background:var(--danger-soft)}
|
|
296
|
+
.badge.acc{border-color:var(--accent-line);color:var(--accent);background:var(--accent-soft)}
|
|
297
|
+
.hpath{flex:0 0 100%;margin:0;font-family:var(--font-mono);font-size:11px;color:var(--ink-2);
|
|
298
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right}
|
|
299
|
+
.rec{display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:13px;color:var(--ink);background:var(--accent-soft);border-radius:8px;padding:7px 11px;margin:10px 0 12px}
|
|
300
|
+
.rec b{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--accent);font-weight:600}
|
|
301
|
+
.rec .sep{color:var(--ink-3)}
|
|
302
|
+
.hmeta{display:flex;gap:20px;flex-wrap:wrap;margin:12px 0 14px;font-size:13px;color:var(--ink-2)}
|
|
303
|
+
.hmeta k{color:var(--ink-2);margin-right:5px;font-weight:500;font-size:11px;letter-spacing:.04em;text-transform:uppercase}
|
|
304
|
+
.hmeta .hint{display:inline-flex;align-items:center;justify-content:center;width:13px;height:13px;border-radius:50%;
|
|
305
|
+
border:var(--hair) solid var(--rule);color:var(--ink-2);font-size:9px;font-style:normal;margin-left:5px;
|
|
306
|
+
cursor:help;vertical-align:1px;background:var(--panel)}
|
|
307
|
+
.hnote{font-size:13px;color:var(--ink);background:var(--ground-muted);border-left:2px solid var(--accent);
|
|
308
|
+
padding:9px 12px;border-radius:0 8px 8px 0;margin:0 0 12px;line-height:1.55}
|
|
309
|
+
.hwhy{font-size:13px;color:var(--ink-2);background:var(--ground-muted);border:var(--hair) solid var(--rule);
|
|
310
|
+
border-radius:8px;padding:10px 12px;margin-bottom:12px;line-height:1.6}
|
|
311
|
+
|
|
312
|
+
/* ── 表格 ─────────────────────────────────────────────────────── */
|
|
313
|
+
.tblwrap{overflow-x:auto;margin:-4px 0;min-width:0}
|
|
314
|
+
.tbl{width:100%;min-width:900px;border-collapse:collapse;font-size:13px}
|
|
315
|
+
.tbl th{text-align:left;font-weight:500;color:var(--ink-2);font-size:11px;letter-spacing:.04em;
|
|
316
|
+
text-transform:uppercase;padding:0 12px 9px;border-bottom:var(--hair) solid var(--rule);white-space:nowrap}
|
|
317
|
+
.tbl td{padding:10px 12px;border-bottom:var(--hair) solid var(--rule);vertical-align:middle;white-space:nowrap}
|
|
318
|
+
.tbl tbody tr:hover td{background:var(--ground-muted)}
|
|
319
|
+
.tbl tr:last-child td{border-bottom:none}
|
|
320
|
+
.tbl .m{font-family:var(--font-mono);font-weight:500;color:var(--ink)}
|
|
321
|
+
.yes{color:var(--accent);font-weight:600}
|
|
322
|
+
.no{color:var(--ink-2)}
|
|
323
|
+
.na{color:var(--ink-2);opacity:.6}
|
|
324
|
+
.warncell{color:#7a5200;background:#fdf6e3;font-weight:500}
|
|
325
|
+
.tbl .g{display:inline-block;width:12px;font-size:12px;margin-right:2px}
|
|
326
|
+
.tbl .v{display:inline-flex;align-items:baseline;gap:1px;justify-content:flex-end}
|
|
327
|
+
|
|
328
|
+
/* 芯片 */
|
|
329
|
+
.chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
330
|
+
.chip{font-family:var(--font-mono);font-size:12px;color:var(--accent);background:var(--accent-soft);
|
|
331
|
+
border:var(--hair) solid var(--accent-line);border-radius:6px;padding:1px 7px;line-height:18px;white-space:nowrap}
|
|
332
|
+
|
|
333
|
+
/* ── 人设 ─────────────────────────────────────────────────────── */
|
|
334
|
+
.split{display:grid;grid-template-columns:300px minmax(0,1fr);gap:16px;align-items:start}
|
|
335
|
+
.plist{display:flex;flex-direction:column;gap:8px;max-height:70vh;overflow:auto;padding:2px 4px 2px 2px}
|
|
336
|
+
.pitem{padding:12px 14px;border:var(--hair) solid var(--rule);border-radius:10px;background:var(--panel);
|
|
337
|
+
text-align:left;transition:border-color var(--t),background var(--t)}
|
|
338
|
+
.pitem:hover{border-color:var(--accent);background:var(--accent-soft)}
|
|
339
|
+
.pitem.cur{border-color:var(--accent);background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent)}
|
|
340
|
+
.pitem b{display:flex;align-items:center;gap:7px;font-size:14px;font-weight:600}
|
|
341
|
+
.pitem .tag{font-size:10px;padding:1px 7px;border-radius:99px;background:var(--accent);color:var(--on-accent);
|
|
342
|
+
border:var(--hair) solid var(--accent);font-weight:600}
|
|
343
|
+
.pitem small{display:block;color:var(--ink-2);font-size:11px;margin-top:4px;font-family:var(--font-mono)}
|
|
344
|
+
.pitem p{margin:6px 0 0;font-size:12px;color:var(--ink-2);display:-webkit-box;-webkit-line-clamp:2;
|
|
345
|
+
-webkit-box-orient:vertical;overflow:hidden}
|
|
346
|
+
textarea{resize:vertical;min-height:340px;font-family:var(--font-mono);font-size:13px;line-height:1.7;tab-size:2}
|
|
347
|
+
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
|
348
|
+
|
|
349
|
+
/* ── 页脚(第二处深色块) ─────────────────────────────────────── */
|
|
350
|
+
.foot{position:relative;z-index:1;background:var(--field);color:var(--on-field);
|
|
351
|
+
padding:28px 32px;margin-top:8px}
|
|
352
|
+
.foot .fin{max-width:1040px;margin:0 auto;display:flex;gap:var(--gap);align-items:baseline;flex-wrap:wrap}
|
|
353
|
+
.foot b{font-family:var(--font-display);font-size:16px;font-weight:700;letter-spacing:-.01em}
|
|
354
|
+
.foot p{margin:0;font-size:13px;color:var(--on-field-2);line-height:1.6;max-width:62ch}
|
|
355
|
+
.foot .end{margin-left:auto;font-family:var(--font-mono);font-size:12px;color:var(--on-field-2)}
|
|
356
|
+
|
|
357
|
+
/* ── toast ────────────────────────────────────────────────────── */
|
|
358
|
+
#toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);display:flex;flex-direction:column;
|
|
359
|
+
gap:8px;align-items:center;pointer-events:none;z-index:200}
|
|
360
|
+
.toast{padding:11px 18px;border-radius:8px;background:var(--field);border:var(--hair) solid var(--field);
|
|
361
|
+
font-size:14px;color:var(--on-field);box-shadow:0 18px 40px -18px rgba(22,25,31,.7);opacity:0;
|
|
362
|
+
transform:translateY(10px);transition:opacity 200ms var(--ease),transform 200ms var(--ease)}
|
|
363
|
+
.toast.in{opacity:1;transform:none}
|
|
364
|
+
.toast.err{border-color:var(--danger);background:#3d1414;color:#ffdad6}
|
|
365
|
+
.empty{padding:28px;text-align:center;color:var(--ink-2);font-size:14px}
|
|
366
|
+
|
|
367
|
+
/* ── 响应式(桌面优先;≤1050 收紧,≤760 堆叠) ───────────────── */
|
|
368
|
+
@media (max-width:1050px){
|
|
369
|
+
.hgrid{grid-template-columns:minmax(0,1fr)}
|
|
370
|
+
.hero{gap:16px}
|
|
371
|
+
.split{grid-template-columns:260px minmax(0,1fr)}
|
|
372
|
+
main{padding:22px 22px 80px}
|
|
373
|
+
}
|
|
374
|
+
@media (max-width:900px){
|
|
375
|
+
.shell{grid-template-columns:minmax(0,1fr)}
|
|
376
|
+
nav.rail{position:sticky;top:64px;z-index:40;flex-direction:row;overflow-x:auto;min-width:0;max-width:100%;gap:4px;
|
|
377
|
+
padding:10px 18px;background:rgba(252,252,253,.94);backdrop-filter:blur(12px);
|
|
378
|
+
border-bottom:var(--hair) solid var(--rule);scrollbar-width:none}
|
|
379
|
+
nav.rail .lab,nav.rail button kbd{display:none}
|
|
380
|
+
nav.rail button{white-space:nowrap}
|
|
381
|
+
main{padding:20px 18px 72px}
|
|
382
|
+
.facts{display:none}
|
|
383
|
+
}
|
|
384
|
+
@media (max-width:760px){
|
|
385
|
+
.split{grid-template-columns:minmax(0,1fr)}
|
|
386
|
+
.plist{max-height:none;flex-direction:row;overflow-x:auto;max-width:100%;padding-bottom:6px}
|
|
387
|
+
.pitem{min-width:230px}
|
|
388
|
+
.grid2{grid-template-columns:1fr}
|
|
389
|
+
.nowline{grid-template-columns:minmax(0,1fr);gap:4px}
|
|
390
|
+
.nowline>s{padding-top:0}
|
|
391
|
+
.hpath{max-width:100%;margin-left:0}
|
|
392
|
+
header.top{flex-wrap:wrap;gap:10px}
|
|
393
|
+
.brand{padding-right:0;border-right:none}
|
|
394
|
+
.cmdbox code{white-space:normal;word-break:break-all}
|
|
395
|
+
.foot{padding:24px 18px}
|
|
396
|
+
.foot .end{margin-left:0}
|
|
397
|
+
}
|
|
398
|
+
@media (prefers-reduced-motion:reduce){
|
|
399
|
+
*,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important;
|
|
400
|
+
transform:none!important}
|
|
401
|
+
}
|
|
402
|
+
.pirow{display:flex;align-items:center;justify-content:space-between;gap:18px;border:1px solid var(--line);border-radius:12px;padding:11px 14px;margin:12px 0 4px;background:var(--field)}
|
|
403
|
+
.pirow b{display:block;font-size:13px;font-weight:600}
|
|
404
|
+
.pirow .tiny{display:block;margin-top:3px;line-height:1.5}
|
|
405
|
+
.pirow .switch{background:var(--line);border:1px solid var(--line)}
|
|
406
|
+
.pirow.on .switch{background:var(--accent);border-color:var(--accent)}
|
|
407
|
+
.pirow.on .switch::after{left:23px;background:#fff}</style>
|
|
408
|
+
</head>
|
|
409
|
+
<body>
|
|
410
|
+
|
|
411
|
+
<header class="top">
|
|
412
|
+
<div class="brand">
|
|
413
|
+
<span class="mark" aria-hidden="true">
|
|
414
|
+
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"
|
|
415
|
+
stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v9"/><path d="M7.4 6.6a7.5 7.5 0 1 0 9.2 0"/></svg>
|
|
416
|
+
</span>
|
|
417
|
+
<span class="txt"><b>agent-purge</b><s>提示词注入控制台</s></span>
|
|
418
|
+
</div>
|
|
419
|
+
<div class="facts" id="facts"></div>
|
|
420
|
+
<div class="spacer"></div>
|
|
421
|
+
<div class="power" id="power">
|
|
422
|
+
<div class="txt"><b><span class="live"></span>总电源</b><s id="powerSub">注入已开</s></div>
|
|
423
|
+
<button class="switch" id="powerBtn" role="switch" aria-checked="true"
|
|
424
|
+
aria-label="总电源:关闭后所有入口都不注入"
|
|
425
|
+
title="关 = 所有入口都不注入,原样启动"></button>
|
|
426
|
+
</div>
|
|
427
|
+
</header>
|
|
428
|
+
|
|
429
|
+
<div class="shell">
|
|
430
|
+
<nav class="rail" aria-label="主导航">
|
|
431
|
+
<div class="lab">控制台</div>
|
|
432
|
+
<button data-v="overview" class="cur" type="button">
|
|
433
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7.5" height="9" rx="1.6"/><rect x="13.5" y="3" width="7.5" height="5.5" rx="1.6"/><rect x="13.5" y="12" width="7.5" height="9" rx="1.6"/><rect x="3" y="15.5" width="7.5" height="5.5" rx="1.6"/></svg>
|
|
434
|
+
总览 <kbd>1</kbd>
|
|
435
|
+
</button>
|
|
436
|
+
<button data-v="personas" type="button">
|
|
437
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8.5" r="3.6"/><path d="M4.6 20a7.4 7.4 0 0 1 14.8 0"/></svg>
|
|
438
|
+
人设 <kbd>2</kbd>
|
|
439
|
+
</button>
|
|
440
|
+
<button data-v="check" type="button">
|
|
441
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l7.5 3.2v6.1c0 4.6-3.1 7.7-7.5 9.2-4.4-1.5-7.5-4.6-7.5-9.2V6.2z"/><path d="m8.8 12.2 2.2 2.2 4.2-4.4"/></svg>
|
|
442
|
+
自检 <kbd>3</kbd>
|
|
443
|
+
</button>
|
|
444
|
+
</nav>
|
|
445
|
+
|
|
446
|
+
<main>
|
|
447
|
+
<!-- ═══ 1 总览 ═══ -->
|
|
448
|
+
<section id="v-overview" class="cur">
|
|
449
|
+
<h2><span class="n">01</span>现在会发生什么 <span>当前全部真相,不用翻别处</span></h2>
|
|
450
|
+
<div class="hero">
|
|
451
|
+
<div class="card">
|
|
452
|
+
<div class="now" id="nowBox"></div>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
456
|
+
<h2><span class="n">02</span>默认启动设置 <span>裸敲 <code>agent-purge</code> 或点“启动”时用这套</span></h2>
|
|
457
|
+
<div class="card">
|
|
458
|
+
<div class="row" style="margin-bottom:16px">
|
|
459
|
+
<div class="field" style="max-width:340px">
|
|
460
|
+
<label for="dPersona">默认人设</label>
|
|
461
|
+
<select id="dPersona"></select>
|
|
462
|
+
<small>选中即写入,供所有通道共用;默认执行器在下方选中的卡片上改。</small>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<label class="tiny" style="display:block;margin-bottom:8px;font-size:11.5px">注入模式 —— 决定“人设怎么进模型”</label>
|
|
467
|
+
<div class="modes" id="modeBox"></div>
|
|
468
|
+
|
|
469
|
+
<div class="row" style="margin-top:16px">
|
|
470
|
+
<button class="btn primary" id="saveModeBtn" disabled>保存设置</button>
|
|
471
|
+
<span class="tiny" id="dirtyHint"></span>
|
|
472
|
+
</div>
|
|
473
|
+
|
|
474
|
+
<div class="cmdbox">
|
|
475
|
+
<code id="cmdPreview">—</code>
|
|
476
|
+
<button class="btn" id="copyCmd">复制</button>
|
|
477
|
+
</div>
|
|
478
|
+
<div class="tiny" style="margin-top:8px" id="modeHint"></div>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<h2><span class="n">03</span>执行器细节 <span>只显示选中的那一个:注入层、实测、三个通道;点名字换一家</span></h2>
|
|
482
|
+
<div class="hpick" id="hpick"></div>
|
|
483
|
+
<div class="selbar" id="selbar"></div>
|
|
484
|
+
<div id="hsel"></div>
|
|
485
|
+
<details class="hdead" id="hdeadWrap" hidden>
|
|
486
|
+
<summary>注入不进去的那几个 <span class="tiny">读不进去是有原因的,不是没做</span></summary>
|
|
487
|
+
<div class="hgrid" id="hdeadBox"></div>
|
|
488
|
+
</details>
|
|
489
|
+
</section>
|
|
490
|
+
|
|
491
|
+
<!-- ═══ 3 人设 ═══ -->
|
|
492
|
+
<section id="v-personas">
|
|
493
|
+
<h2><span class="n">04</span>人设库 <span><code>personas/<id>.md</code>;派生文件 <code>active.md</code> 自动生成,别手改</span></h2>
|
|
494
|
+
<div class="split">
|
|
495
|
+
<div>
|
|
496
|
+
<div class="plist" id="plist"></div>
|
|
497
|
+
<button class="btn" id="pNew" style="width:100%;margin-top:9px">+ 新建空白人设</button>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="card" style="margin:0">
|
|
500
|
+
<div class="row" style="margin-bottom:12px">
|
|
501
|
+
<div class="field"><label for="pId">id(文件名)</label><input id="pId" placeholder="xiaomajiang" /></div>
|
|
502
|
+
<div class="field"><label for="pName">名字</label><input id="pName" placeholder="示例:项目助手" /></div>
|
|
503
|
+
</div>
|
|
504
|
+
<div class="row" style="margin-bottom:12px">
|
|
505
|
+
<div class="field"><label for="pDesc">说明</label><input id="pDesc" placeholder="一句话:这个身份是干嘛的" /></div>
|
|
506
|
+
<div class="field" style="max-width:250px"><label for="pModel">建议模型</label><input id="pModel" placeholder="留空 = 不限制" /></div>
|
|
507
|
+
</div>
|
|
508
|
+
<div class="field" style="margin-bottom:12px">
|
|
509
|
+
<label for="pBody">正文 <span class="tiny" id="pChars">0 字</span></label>
|
|
510
|
+
<textarea id="pBody" placeholder="这段文字会被塞进系统提示词层(sys)或用户消息层(user)……"></textarea>
|
|
511
|
+
<small>写的是“身份”而不是“任务”。带拒绝倾向的模型看到它就开始直接干活,这就是注入的全部内容。</small>
|
|
512
|
+
</div>
|
|
513
|
+
<div class="row">
|
|
514
|
+
<button class="btn primary" id="pSave">保存</button>
|
|
515
|
+
<button class="btn" id="pUse">设为当前</button>
|
|
516
|
+
<div class="spacer" style="flex:1"></div>
|
|
517
|
+
<button class="btn danger" id="pDel">删除</button>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
</section>
|
|
522
|
+
|
|
523
|
+
<!-- ═══ 4 自检 ═══ -->
|
|
524
|
+
<section id="v-check">
|
|
525
|
+
<h2><span class="n">05</span>本机真值 <span>state 说装了 ≠ 盘上有文件,这里两个都看</span></h2>
|
|
526
|
+
<div class="card" style="padding:16px 8px 10px">
|
|
527
|
+
<div class="tblwrap"><table class="tbl" id="tbl"></table></div>
|
|
528
|
+
</div>
|
|
529
|
+
<div class="card">
|
|
530
|
+
<h3>对账</h3>
|
|
531
|
+
<div class="tiny" id="recon" style="font-size:12px;color:var(--ink-2)">—</div>
|
|
532
|
+
<div class="row" style="margin-top:14px">
|
|
533
|
+
<button class="btn" id="reload">重新读盘</button>
|
|
534
|
+
<button class="btn" id="withVersions">连版本号一起查(慢一点)</button>
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
</section>
|
|
538
|
+
</main>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<footer class="foot">
|
|
542
|
+
<div class="fin">
|
|
543
|
+
<b>agent-purge</b>
|
|
544
|
+
<p>人设写得对,模型就没有拒绝的理由。所有注入都落在本机文件里,关掉总电源即全部撤回。</p>
|
|
545
|
+
<span class="end">127.0.0.1 · 本地控制台</span>
|
|
546
|
+
</div>
|
|
547
|
+
</footer>
|
|
548
|
+
|
|
549
|
+
<div id="toast"></div>
|
|
550
|
+
|
|
551
|
+
<script>
|
|
552
|
+
const $ = (s, r = document) => r.querySelector(s);
|
|
553
|
+
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
554
|
+
|
|
555
|
+
const MODE_INFO = {
|
|
556
|
+
strict: { name: "只留人设", desc: "宿主原有的系统提示整段丢掉,只剩人设。" },
|
|
557
|
+
replace: { name: "整段替换", desc: "默认。用带层的替换把宿主提示换成“人设 + 运行时约束”。" },
|
|
558
|
+
append: { name: "追加", desc: "宿主提示照留,只在末尾续一段人设(最温和)。" },
|
|
559
|
+
off: { name: "不注入", desc: "原样启动,等于没打开过这个工具。" },
|
|
560
|
+
};
|
|
561
|
+
const ARM_INFO = {
|
|
562
|
+
shim: { name: "接管裸命令", desc: (h) => h?.id === "pi"
|
|
563
|
+
? "PATH 最前放个同名壳:敲 pi 就注入。想要“裸启动也带人设”,开这个。"
|
|
564
|
+
: `往 PATH 前置一个同名小壳:直接敲 ${h?.id || "<harness>"} 也是接管版。` },
|
|
565
|
+
slash: { name: "会话内热切", desc: (h) => h?.id === "pi"
|
|
566
|
+
? "写 pi 扩展,只挂 /purge:不敲 /purge 就原样跑,动手才改写。"
|
|
567
|
+
: "写 harness 自己支持的扩展/命令,会话里 /purge 切换。" },
|
|
568
|
+
install: { name: "配置文件常驻", desc: () => "写进 harness 自己的指令文件:连别人启动、GUI 里点开都生效。" },
|
|
569
|
+
};
|
|
570
|
+
const armDesc = (key, h) => ARM_INFO[key].desc(h);
|
|
571
|
+
/* 热切:caps.hot 是唯一裁决(ok + kind)。kind=command 会话内 /purge;kind=state 改设置下一轮生效 */
|
|
572
|
+
const hotOf = (h) => h?.caps?.hot ?? { ok: false, kind: null, why: null, how: null };
|
|
573
|
+
const hotBadge = (h) => {
|
|
574
|
+
const x = hotOf(h);
|
|
575
|
+
if (!x.ok) return null;
|
|
576
|
+
return x.kind === "command" ? { text: "会话内热切", tip: x.how } : { text: "热切", tip: x.how };
|
|
577
|
+
};
|
|
578
|
+
const CLASS_INFO = {
|
|
579
|
+
"native-extension": "原生扩展",
|
|
580
|
+
"plugin": "插件",
|
|
581
|
+
"launch-arg": "启动参数",
|
|
582
|
+
"launch-arg + binary-patch": "启动参数 + 补丁",
|
|
583
|
+
"file-swap": "文件替换",
|
|
584
|
+
"binary-patch": "二进制补丁",
|
|
585
|
+
"config-or-hook": "配置 / 钩子",
|
|
586
|
+
"unsupported": "做不到",
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
let S = null, A = null, view = "overview", selPersona = null, draftMode = null, selHarness = null;
|
|
590
|
+
let lastPop = null, pDirty = false;
|
|
591
|
+
let withVersions = new URLSearchParams(location.search).get("versions") === "1";
|
|
592
|
+
|
|
593
|
+
/* 说明字段两套键名:README/模板写 description,旧 UI 写 desc。两套都认 */
|
|
594
|
+
const descOf = (p) => p?.meta?.description || p?.meta?.desc || "";
|
|
595
|
+
/* 前端值统一转成 input 可编辑的一行字:数组用「, 」拼(model: ["a","b"] → "a, b") */
|
|
596
|
+
const fieldText = (v) => (Array.isArray(v) ? v.join(", ") : v == null ? "" : String(v));
|
|
597
|
+
|
|
598
|
+
async function api(path, opts) {
|
|
599
|
+
const r = await fetch(path, opts);
|
|
600
|
+
const txt = await r.text();
|
|
601
|
+
let j; try { j = JSON.parse(txt); } catch { throw new Error(txt.slice(0, 200)); }
|
|
602
|
+
if (!r.ok || j.error) throw new Error(j.error || r.status);
|
|
603
|
+
return j;
|
|
604
|
+
}
|
|
605
|
+
function toast(msg, err) {
|
|
606
|
+
const el = document.createElement("div");
|
|
607
|
+
el.className = "toast" + (err ? " err" : "");
|
|
608
|
+
el.textContent = msg;
|
|
609
|
+
$("#toast").appendChild(el);
|
|
610
|
+
requestAnimationFrame(() => el.classList.add("in"));
|
|
611
|
+
setTimeout(() => { el.classList.remove("in"); setTimeout(() => el.remove(), 250); }, err ? 4200 : 2200);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* ── 数据 ─────────────────────────────────────────── */
|
|
615
|
+
async function load() {
|
|
616
|
+
const [s, a] = await Promise.all([
|
|
617
|
+
api("/api/state?versions=" + (withVersions ? "1" : "0")),
|
|
618
|
+
api("/api/armor"),
|
|
619
|
+
]);
|
|
620
|
+
S = s; A = a;
|
|
621
|
+
/* 旧实例/坏盘可能回 null:这里是页面的唯一入口,先兜底再渲染,
|
|
622
|
+
否则 renderHeader 里 S.defaults.mode 一抛,整页就停在静态 HTML 上了 */
|
|
623
|
+
if (!S || typeof S !== "object") throw new Error("state 接口返回异常");
|
|
624
|
+
if (!Array.isArray(S.harnesses)) S.harnesses = [];
|
|
625
|
+
if (!Array.isArray(S.personas)) S.personas = [];
|
|
626
|
+
const dflt = S.defaults && typeof S.defaults === "object" ? S.defaults : {};
|
|
627
|
+
const fallbackH = S.harnesses.find((h) => h.found && h.supported && h.inject) || S.harnesses.find((h) => h.supported) || S.harnesses[0];
|
|
628
|
+
S.defaults = {
|
|
629
|
+
harness: dflt.harness ?? fallbackH?.id ?? null,
|
|
630
|
+
mode: (S.modes || []).includes(dflt.mode) ? dflt.mode : "replace",
|
|
631
|
+
};
|
|
632
|
+
if (!selPersona) selPersona = S.active || (S.personas[0] && S.personas[0].id) || null;
|
|
633
|
+
if (draftMode !== null && !(S.modes || []).includes(draftMode)) draftMode = null;
|
|
634
|
+
if (draftMode === null) draftMode = S.defaults.mode;
|
|
635
|
+
render();
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/* ── 渲染:顶栏 ───────────────────────────────────── */
|
|
639
|
+
function renderHeader() {
|
|
640
|
+
const mode = S.defaults.mode;
|
|
641
|
+
const h = S.harnesses.find((x) => x.id === S.defaults.harness);
|
|
642
|
+
const armedCount = S.harnesses.filter((x) => (x.armed || []).length).length;
|
|
643
|
+
const persona = S.personas.find((p) => p.id === S.persona);
|
|
644
|
+
const diverge = S.active && S.persona && S.active !== S.persona;
|
|
645
|
+
// defaultsSet=false:state.json 里还没存过默认,这里显示的只是建议值(见 load() 里的兜底)
|
|
646
|
+
const unset = S.defaultsSet === false;
|
|
647
|
+
const unsetTag = unset ? `<span class="tiny"> · 未保存</span>` : "";
|
|
648
|
+
$("#facts").innerHTML = `
|
|
649
|
+
<div class="fact"><s>默认执行器</s><em>${esc(h ? h.id : S.defaults.harness || "未设")}${unsetTag}</em></div>
|
|
650
|
+
<div class="fact ${mode === "off" ? "off" : ""}"><s>注入模式</s><em>${esc(MODE_INFO[mode]?.name || mode)}</em></div>
|
|
651
|
+
<div class="fact ${diverge ? "warn" : ""}"><s>人设</s><em>${esc(persona ? persona.id : S.persona || "未设")}</em></div>
|
|
652
|
+
<div class="fact ${mode === "off" ? "off" : ""}"><s>已武装</s><em>${mode === "off" ? "全部停用" : armedCount + " 个执行器"}</em></div>`;
|
|
653
|
+
// 未设默认时总电源不可点:applyArmAll 没有 default.harness 就写不回落点,点了也没反应
|
|
654
|
+
$("#power").classList.toggle("on", mode !== "off" && !unset);
|
|
655
|
+
const pb = $("#powerBtn");
|
|
656
|
+
pb.setAttribute("aria-checked", String(mode !== "off" && !unset));
|
|
657
|
+
pb.disabled = unset;
|
|
658
|
+
$("#powerSub").textContent = unset ? "未设置 · 先在 01 保存" : mode === "off" ? "已关 · 原样启动" : "注入已开";
|
|
659
|
+
pb.title = unset
|
|
660
|
+
? "还没保存过默认执行器:在 01 里选好模式点“保存设置”,总开关才有落点"
|
|
661
|
+
: mode === "off"
|
|
662
|
+
? "当前关:任何入口(agent-purge / 裸命令 / --ask)都不注入。点一下恢复"
|
|
663
|
+
: "当前开:点一下关掉全部注入(保留各通道原状,开回来自动恢复)";
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/* ── 渲染:总览 ───────────────────────────────────── */
|
|
667
|
+
function layerText(h) {
|
|
668
|
+
if (!h) return "—";
|
|
669
|
+
return (h.layer || "—").toUpperCase();
|
|
670
|
+
}
|
|
671
|
+
/* C:\Users\xxx → ~ :通道路径太长,缩成家目录开头 */
|
|
672
|
+
function shortPath(p) {
|
|
673
|
+
return String(p || "").replace(/^[A-Za-z]:\\Users\\[^\\]+/i, "~").replace(/^\/home\/[^/]+|^\/Users\/[^/]+/, "~");
|
|
674
|
+
}
|
|
675
|
+
/* 尾巴优先:路径太长时砍开头保结尾(不能靠 direction:rtl——会把 . 和 \ 排反) */
|
|
676
|
+
function tailPath(p, max = 46) {
|
|
677
|
+
const s = shortPath(p);
|
|
678
|
+
if (!s) return "";
|
|
679
|
+
return s.length <= max ? s : "…" + s.slice(s.length - (max - 1));
|
|
680
|
+
}
|
|
681
|
+
function renderOverview() {
|
|
682
|
+
const mode = S.defaults.mode;
|
|
683
|
+
const hid = S.defaults.harness;
|
|
684
|
+
const h = S.harnesses.find((x) => x.id === hid) || S.harnesses[0];
|
|
685
|
+
const persona = S.personas.find((p) => p.id === (selPersona || S.persona)) || S.personas[0];
|
|
686
|
+
const hot = S.harnesses.filter((h) => (h.armed || []).length).map((h) => h.id + "(" + h.armed.join("/") + (h.id === "pi" && S.piAuto ? "+裸启动" : "") + ")");
|
|
687
|
+
|
|
688
|
+
const why = mode === "off"
|
|
689
|
+
? "总电源关着 —— 下面写的都只是配置,实际一条都不会注入。"
|
|
690
|
+
: h
|
|
691
|
+
? (() => {
|
|
692
|
+
const chans = h.armed || [];
|
|
693
|
+
if (!chans.length) {
|
|
694
|
+
const c = pickChannel(h);
|
|
695
|
+
return c
|
|
696
|
+
? "没武装任何入口:现在裸启动是原样的,要用就武装 " + c[1] + "。"
|
|
697
|
+
: h.id + " 没有任何可用的注入通道,实际是原样启动(03 里写了为什么)。";
|
|
698
|
+
}
|
|
699
|
+
const say = (c) => c === "shim"
|
|
700
|
+
? (h.id === "pi" ? "敲 pi 会注入(PATH 刚加的前置壳)" : "敲 " + h.id + " 会注入(PATH 前置壳)")
|
|
701
|
+
: c === "slash"
|
|
702
|
+
? (h.id === "pi" ? (S.piAuto ? "裸敲 pi 也注入(扩展常驻)" : "裸敲 pi 不注入,只有会话里 /purge 才改写") : "会话里 /purge 展开成用户消息")
|
|
703
|
+
: "改了它的配置文件,连别人启动的也生效";
|
|
704
|
+
return chans.map(say).join(";") + "。";
|
|
705
|
+
})()
|
|
706
|
+
: "还没选执行器。";
|
|
707
|
+
|
|
708
|
+
$("#nowBox").innerHTML = `
|
|
709
|
+
<div class="nowline"><s>身份</s><div>
|
|
710
|
+
<div class="big">${esc(persona ? (persona.meta?.name || persona.id) : "未选人设")}</div>
|
|
711
|
+
<div class="sub">${esc(persona ? (descOf(persona) || persona.id) : "去“人设”页挑一个")} · ${persona ? persona.chars : 0} 字</div>
|
|
712
|
+
</div></div>
|
|
713
|
+
<div class="nowline"><s>怎么进</s><div>
|
|
714
|
+
<div class="big">${esc(MODE_INFO[mode]?.name || mode)} <span class="tiny">· ${esc(mode)}</span></div>
|
|
715
|
+
<div class="sub">${esc(MODE_INFO[mode]?.desc || "")}</div>
|
|
716
|
+
<div class="rec" style="margin:8px 0 0"><b>推荐</b><code class="mono">${esc(mode)}</code> ${esc(MODE_INFO[mode]?.name || "")}${mode === "off" ? "" : (() => { const c = pickChannel(h); return c ? `<span class="sep">·</span>${esc(c[1])}` : `<span class="sep">·</span>没有可用通道,原样启动`; })()}</div>
|
|
717
|
+
</div></div>
|
|
718
|
+
<div class="nowline"><s>灌进哪</s><div>
|
|
719
|
+
<div class="big">${esc(h ? h.id : "—")} <span class="badge acc">${esc(CLASS_INFO[h?.class] || h?.class || "")}</span></div>
|
|
720
|
+
<div class="sub">${mode === "off" ? "关着,不注入" : "注入层:" + layerText(h) + " · " + (hotOf(h).ok ? (hotOf(h).kind === "command" ? "支持会话内 /purge" : "改设置下一轮生效(不用重启)") : "只能启动时生效")}</div>
|
|
721
|
+
</div></div>
|
|
722
|
+
<div class="nowline"><s>哪些入口</s><div>
|
|
723
|
+
<div class="sub chips">${hot.length ? hot.map((x) => `<span class="chip">${esc(x)}</span>`).join("") : "还没有任何执行器被武装"}</div>
|
|
724
|
+
<div class="cmdbox"><code>${esc(previewCmd(h, mode, persona))}</code><button class="btn" data-copy="${esc(previewCmd(h, mode, persona))}">复制</button></div>
|
|
725
|
+
</div></div>
|
|
726
|
+
<div class="nowline"><s>一句话</s><div class="sub" style="color:var(--ink-2)">${esc(why)}</div></div>`;
|
|
727
|
+
|
|
728
|
+
// 人设下拉
|
|
729
|
+
$("#dPersona").innerHTML = S.personas.map((p) =>
|
|
730
|
+
`<option value="${p.id}" ${p.id === S.persona ? "selected" : ""}>${esc(p.meta?.name || p.id)}(${p.chars} 字)</option>`).join("");
|
|
731
|
+
|
|
732
|
+
// 模式
|
|
733
|
+
$("#modeBox").innerHTML = S.modes.map((m) => `
|
|
734
|
+
<div class="mode ${m === draftMode ? "on" : ""} ${m === S.defaults.mode ? "cur" : ""}" data-mode="${m}">
|
|
735
|
+
<b>${MODE_INFO[m].name}</b><i>${m}${m === S.defaults.mode ? " · 当前" : ""}</i>
|
|
736
|
+
<small>${MODE_INFO[m].desc}</small>
|
|
737
|
+
</div>`).join("");
|
|
738
|
+
$("#modeHint").textContent = S.defaultsSet === false
|
|
739
|
+
? "还没设过默认(上面显示的是建议值):选一个模式 + “保存设置”,裸敲 agent-purge 才真的有落点。"
|
|
740
|
+
: draftMode !== S.defaults.mode
|
|
741
|
+
? "改过还没保存(点上面的“保存设置”才会生效)"
|
|
742
|
+
: mode === "off" ? "现在是 off:保存别的模式并武装之后才会真正注入。" : "";
|
|
743
|
+
$("#saveModeBtn").disabled = draftMode === S.defaults.mode && S.defaultsSet !== false;
|
|
744
|
+
$("#dirtyHint").textContent = S.defaultsSet === false
|
|
745
|
+
? "默认还没保存"
|
|
746
|
+
: draftMode === S.defaults.mode
|
|
747
|
+
? (mode === "off" ? "当前是 off(原样启动)" : "已保存")
|
|
748
|
+
: "未保存:" + (MODE_INFO[draftMode]?.name || draftMode);
|
|
749
|
+
$("#cmdPreview").textContent = previewCmd(h, draftMode, persona);
|
|
750
|
+
$("#copyCmd").dataset.copy = previewCmd(h, draftMode, persona);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function previewCmd(h, mode, persona) {
|
|
754
|
+
if (!h) return "agent-purge";
|
|
755
|
+
const p = persona ? persona.id : "ACTIVE";
|
|
756
|
+
return `agent-purge ${h.id} --persona ${p} --mode ${mode}`;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/* ── 渲染:执行器 ─────────────────────────────────── */
|
|
760
|
+
function harnessCard(h) {
|
|
761
|
+
const row = A.rows.find((r) => r.id === h.id);
|
|
762
|
+
const cls = CLASS_INFO[h.class] || h.class;
|
|
763
|
+
const pct = row ? (row.persona?.sys ?? 0) : null;
|
|
764
|
+
const lyr = (h.layer || "sys").toUpperCase();
|
|
765
|
+
const verdict = pct == null ? null : `${lyr} ${pct >= 100 ? "通过" : pct > 0 ? "部分通过" : "未通过"}`;
|
|
766
|
+
const vcls = pct == null ? "" : pct >= 100 ? "ok" : pct > 0 ? "warn" : "bad";
|
|
767
|
+
const armBadges = (h.armed || []).length
|
|
768
|
+
? h.armed.map((a) => `<span class="badge ok">${ARM_INFO[a]?.name || a}</span>`).join("")
|
|
769
|
+
: `<span class="badge">未武装</span>`;
|
|
770
|
+
const CHAN_ORDER = ["shim", "slash", "install"];
|
|
771
|
+
const isPi = h.id === "pi";
|
|
772
|
+
// 只摆真能用的:能点但没反应的按钮 = 误导,理由改用一行字写在卡片下方
|
|
773
|
+
// pi 例外:它的“裸命令接管”是扩展常驻(piAuto),不是 PATH 壳,所以照样给按钮
|
|
774
|
+
const hiddenChans = CHAN_ORDER.filter((c) => !(isPi && c === "shim") && h.caps?.[c] && !h.caps[c].ok);
|
|
775
|
+
const hiddenText = hiddenChans.map((c) => {
|
|
776
|
+
const cap = h.caps[c];
|
|
777
|
+
if (cap.hiddenLabel) return `${cap.hiddenLabel}(压不住系统提示词)`;
|
|
778
|
+
// 从来就没有 /purge 的,不提:提了只是噪音
|
|
779
|
+
if (c === "slash") return null;
|
|
780
|
+
return `${ARM_INFO[c].name}:${String(cap.why || "").replace(/\s+/g, " ").split(/[;。]/)[0]}`;
|
|
781
|
+
}).filter(Boolean);
|
|
782
|
+
const chans = CHAN_ORDER.filter((c) => (isPi && c === "shim") || !h.caps?.[c] || h.caps[c].ok).map((c) => {
|
|
783
|
+
const on = isPi && c === "shim" ? Boolean(S.piAuto) : h.armed?.includes(c);
|
|
784
|
+
// pi 的裸命令接管=扩展常驻:点它直接切 piAuto,而不是装 PATH 壳
|
|
785
|
+
if (isPi && c === "shim") {
|
|
786
|
+
return `<button class="chan ${on ? "on" : ""}" data-piauto="${on ? "off" : "on"}" type="button" title="扩展常驻,不改 PATH:开关写在 state.json 的 piAuto">
|
|
787
|
+
<span class="dot"></span><span><b>裸命令接管${on ? " · 开" : ""}</b>
|
|
788
|
+
<small>${on ? "扩展常驻:敲 pi 就注入(每次启动)" : "关着:只有 /purge 或 agent-purge pi 才注入"}</small></span></button>`;
|
|
789
|
+
}
|
|
790
|
+
// 能力由后端(lib/caps.mjs)给;UI 不再自己猜一遍,否则两处判断迟早分叉
|
|
791
|
+
const cap = h.caps?.[c] ?? null;
|
|
792
|
+
const avail = cap ? cap.ok : c === "install" ? !!h.persistable : c === "slash" ? !!h.slash : true;
|
|
793
|
+
const enabled = h.found && h.supported && avail;
|
|
794
|
+
const path = c === "install" ? h.persistFile : c === "slash" ? h.slash?.file : (S.shimDir ? S.shimDir + "\\" + h.id + ".cmd" : null);
|
|
795
|
+
const why = !h.found ? "本机没装"
|
|
796
|
+
: cap && !cap.ok ? cap.why
|
|
797
|
+
: c === "install" && !h.persistable ? "它没有可写的配置文件"
|
|
798
|
+
: c === "slash" && !h.slash ? "它没有 /purge 通道" : "";
|
|
799
|
+
const na = Boolean(h.found && cap && !cap.ok);
|
|
800
|
+
// 常驻通道要额外说清楚两件事:写进哪一层(SYS/USER)、实测过还是只有文档
|
|
801
|
+
const lay = c === "install" && cap?.layer ? ` <i class="lay">${cap.layer.toUpperCase()}</i>` : "";
|
|
802
|
+
const unverified = c === "install" && cap?.ok && cap.verified === false ? `<i class="lay warn">未实测</i>` : "";
|
|
803
|
+
const smallText = why || (c === "install" && cap?.note ? cap.note : (path ? tailPath(path) : armDesc(c, h)));
|
|
804
|
+
const smallCls = why || (c === "install" && cap?.note) ? "" : "path";
|
|
805
|
+
return `<button class="chan ${on ? "on" : ""} ${enabled ? "" : "dis"} ${na ? "na" : ""}" ${enabled ? `data-chan="${c}" data-h="${h.id}"` : "disabled"}>
|
|
806
|
+
<span class="dot"></span><span><b>${ARM_INFO[c].name}${lay}${unverified}${on ? " · 开" : ""}${na ? " · 不可用" : ""}</b>
|
|
807
|
+
<small class="${smallCls}" ${path && !why ? `title="${esc(path)}"` : ""}>${esc(smallText)}</small></span></button>`;
|
|
808
|
+
}).join("");
|
|
809
|
+
|
|
810
|
+
const notes = []
|
|
811
|
+
// 卡片上的徽章已经写了“SYS 通过/部分通过”,同义的行就不再重复
|
|
812
|
+
.concat((row?.warnings || []).filter((w) => !/^(sys|user) (通过|部分通过|未通过)$/.test(w)))
|
|
813
|
+
// 只存在于 user 层的落点:不再给按钮,只留一行实话,免得以为“忘了做”
|
|
814
|
+
.concat(hiddenText.length ? [`不提供:${hiddenText.join(";")}`] : [])
|
|
815
|
+
.concat(h.residue ? ["关掉后仍残留:" + h.residue] : [])
|
|
816
|
+
.concat(h.reason ? [h.reason] : []);
|
|
817
|
+
|
|
818
|
+
const recMode = recommendMode(h);
|
|
819
|
+
const recChan = pickChannel(h);
|
|
820
|
+
|
|
821
|
+
const noChannel = h.caps ? !(h.caps.shim.ok || h.caps.install.ok || h.caps.slash.ok) : false;
|
|
822
|
+
|
|
823
|
+
return `<div class="hc ${h.found ? "" : "dead"}">
|
|
824
|
+
<div class="hhead">
|
|
825
|
+
<span class="hname"><span class="st ${h.armed && h.armed.length ? "on" : ""}"></span>${esc(h.id)}</span>
|
|
826
|
+
<span class="badge acc">${esc(cls)}</span>
|
|
827
|
+
${verdict ? `<span class="badge ${vcls}">${verdict}</span>` : ""}
|
|
828
|
+
${hotBadge(h) ? `<span class="badge" title="${esc(hotBadge(h).tip || "")}">${esc(hotBadge(h).text)}</span>` : ""}
|
|
829
|
+
${h.oneshot ? `<span class="badge">支持 --ask</span>` : `<span class="badge warn">无 --ask</span>`}
|
|
830
|
+
${noChannel ? `<span class="badge bad">只能原样启动</span>` : ""}
|
|
831
|
+
${h.found ? "" : `<span class="badge bad">本机未安装</span>`}
|
|
832
|
+
<span class="hpath" title="${esc(h.bin || "")}">${esc(h.bin ? tailPath(h.bin, 58) : "—")}</span>
|
|
833
|
+
</div>
|
|
834
|
+
${recMode !== "—" ? `<div class="rec"><b>推荐</b><code class="mono">${esc(recMode)}</code> ${esc(MODE_INFO[recMode]?.name || "")}${recChan ? `<span class="sep">·</span>${esc(recChan[1])}` : ""}</div>` : ""}
|
|
835
|
+
<div class="hmeta">
|
|
836
|
+
<span><k>注入层</k>${h.layer ? h.layer.toUpperCase() : "—"}<i class="hint" title="层不是可选项:每家只有它自己暴露的那一个写入点。sys = 系统提示 / 配置 / 插件 splice;user = 只落在用户消息里">?</i></span>
|
|
837
|
+
<span><k>版本</k>${esc(h.version || "—")}${h.version ? "" : `<i class="hint" title="默认不跑各家的 --version(会 spawn 九个进程)。想看就开“自检”页的“连版本号一起查”">?</i>`}</span>
|
|
838
|
+
<span><k>home</k>${esc(h.home || "—")}${h.homeExists === false ? "(不存在)" : ""}</span>
|
|
839
|
+
</div>
|
|
840
|
+
${notes.length ? `<div class="hnote">${notes.map(esc).join("<br>")}</div>` : ""}
|
|
841
|
+
<div class="chans">${chans}</div>
|
|
842
|
+
</div>`;
|
|
843
|
+
}
|
|
844
|
+
function recommendMode(h) {
|
|
845
|
+
const row = A.rows.find((r) => r.id === h.id);
|
|
846
|
+
return row?.mode || "—";
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/* 推荐哪条通道:只认 caps 的裁决(本机真能不能用),不猜 h.found / h.slash */
|
|
850
|
+
function pickChannel(h) {
|
|
851
|
+
if (!h) return null;
|
|
852
|
+
const c = h.caps;
|
|
853
|
+
if (c) {
|
|
854
|
+
if (c.hot?.ok && c.hot.kind === "state" && c.install?.ok && !c.slash?.ok) return ["install", "配置文件常驻(装了就能热切)"];
|
|
855
|
+
if (c.slash?.ok) return ["slash", c.hot?.ok ? "会话内热切(/purge)" : "会话内命令"];
|
|
856
|
+
if (c.shim?.ok) return ["shim", "裸命令接管(敲 " + h.id + ")"];
|
|
857
|
+
if (c.install?.ok) return ["install", "配置文件常驻"];
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
return h.slash ? ["slash", "会话内热切(/purge)"]
|
|
861
|
+
: h.found ? ["shim", "裸命令接管(敲 " + h.id + ")"]
|
|
862
|
+
: h.persistable ? ["install", "配置文件常驻"] : null;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function renderHarness() {
|
|
866
|
+
const list = S.harnesses.filter((h) => h.supported && h.inject);
|
|
867
|
+
const dead = S.harnesses.filter((h) => !(h.supported && h.inject));
|
|
868
|
+
if (!selHarness || !S.harnesses.some((x) => x.id === selHarness)) {
|
|
869
|
+
selHarness = (S.defaults.harness && S.harnesses.some((x) => x.id === S.defaults.harness) ? S.defaults.harness : null)
|
|
870
|
+
|| list[0]?.id || dead[0]?.id || null;
|
|
871
|
+
}
|
|
872
|
+
$("#hpick").innerHTML = list.map((h) => {
|
|
873
|
+
const on = (h.armed || []).length > 0;
|
|
874
|
+
return `<button class="hchip ${h.found ? "" : "miss"} ${on ? "on" : ""} ${h.id === selHarness ? "sel" : ""}"
|
|
875
|
+
data-hpick="${h.id}" title="${esc(h.bin || "")}"><span class="pdot"></span>${esc(h.id)}</button>`;
|
|
876
|
+
}).join("");
|
|
877
|
+
const cur = S.harnesses.find((h) => h.id === selHarness);
|
|
878
|
+
const isDefault = !!cur && cur.id === S.defaults.harness;
|
|
879
|
+
$("#selbar").innerHTML = cur
|
|
880
|
+
? `<span class="grow">${isDefault
|
|
881
|
+
? `<span class="badge ok">默认执行器</span> 裸敲 <code class="mono">agent-purge</code> 就用它`
|
|
882
|
+
: `当前默认是 <code class="mono">${esc(S.defaults.harness || "未设")}</code>`}</span>
|
|
883
|
+
<button class="btn" id="setDefaultH" ${isDefault ? "disabled" : ""}>设为默认执行器</button>`
|
|
884
|
+
: "";
|
|
885
|
+
$("#hsel").innerHTML = cur ? harnessCard(cur) : `<div class="empty">没有可选的执行器</div>`;
|
|
886
|
+
$("#hdeadBox").innerHTML = dead.map(harnessCard).join("");
|
|
887
|
+
$("#hdeadWrap").hidden = dead.length === 0;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/* ── 渲染:人设 ───────────────────────────────────── */
|
|
891
|
+
function renderPersonas() {
|
|
892
|
+
$("#plist").innerHTML = S.personas.map((p) => `
|
|
893
|
+
<button class="pitem ${p.id === selPersona ? "cur" : ""}" data-p="${p.id}">
|
|
894
|
+
<b>${esc(p.meta?.name || p.id)}${p.id === S.persona ? '<span class="tag">当前</span>' : ""}</b>
|
|
895
|
+
<small>${esc(p.id)} · ${p.chars} 字</small>
|
|
896
|
+
<p>${esc(descOf(p) || p.body.slice(0, 90))}</p>
|
|
897
|
+
</button>`).join("") || `<div class="empty">还没有人设,点下面新建</div>`;
|
|
898
|
+
const p = S.personas.find((x) => x.id === selPersona);
|
|
899
|
+
if (!p) return;
|
|
900
|
+
// 正在编辑的草稿不能被 8 秒轮询刷掉:只在“换了选中项”且没改过时才回填
|
|
901
|
+
if (pDirty || lastPop === p.id) return;
|
|
902
|
+
lastPop = p.id; pDirty = false;
|
|
903
|
+
$("#pId").value = p.id;
|
|
904
|
+
$("#pName").value = p.meta?.name || "";
|
|
905
|
+
$("#pDesc").value = descOf(p);
|
|
906
|
+
$("#pModel").value = fieldText(p.meta?.model);
|
|
907
|
+
$("#pBody").value = p.body || "";
|
|
908
|
+
$("#pChars").textContent = (p.body || "").length + " 字";
|
|
909
|
+
// 已有 id 的 persona 不能直接改文件名(否则会写出第二份),要换 id 请点“新建”
|
|
910
|
+
$("#pId").disabled = true;
|
|
911
|
+
$("#pId").title = "要换 id 请点下面「新建空白人设」";
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/* ── 渲染:自检 ───────────────────────────────────── */
|
|
915
|
+
function renderCheck() {
|
|
916
|
+
const head = `<tr><th>执行器</th><th>类别</th><th>装</th><th>版本</th><th>实测</th><th>接管</th><th>热切</th><th>常驻</th><th>state</th></tr>`;
|
|
917
|
+
$("#tbl").innerHTML = head + S.harnesses.map((h) => {
|
|
918
|
+
const cell = (b, t, l) => b === null || b === undefined ? `<td class="na" data-l="${l}">—</td>` : b ? `<td class="yes" data-l="${l}"><span class="v"><span class="g">✓</span>${t}</span></td>` : `<td class="no" data-l="${l}"><span class="v"><span class="g">✕</span>${t}</span></td>`;
|
|
919
|
+
const row = A.rows.find((r) => r.id === h.id);
|
|
920
|
+
const pct = row ? row.persona?.sys : null;
|
|
921
|
+
const pctCell = pct == null
|
|
922
|
+
? `<td class="na" data-l="实测注入">未测</td>`
|
|
923
|
+
: `<td class="${pct >= 100 ? "yes" : pct > 0 ? "warncell" : "no"}" data-l="实测注入"><span class="v"><span class="g">${pct >= 100 ? "✓" : pct > 0 ? "~" : "✕"}</span>${pct}%</span></td>`;
|
|
924
|
+
const arms = (h.armed || []).map((a) => ARM_INFO[a]?.name || a);
|
|
925
|
+
return `<tr>
|
|
926
|
+
<td class="m" data-l="执行器">${esc(h.id)}</td>
|
|
927
|
+
<td class="tiny" data-l="类别">${esc(CLASS_INFO[h.class] || h.class)}</td>
|
|
928
|
+
${cell(h.found, h.found ? "装好" : "没装", "本机装了")}
|
|
929
|
+
<td class="tiny m" data-l="版本">${esc(h.version || "—")}</td>
|
|
930
|
+
${pctCell}
|
|
931
|
+
${(() => {
|
|
932
|
+
const ok = h.caps?.shim?.ok;
|
|
933
|
+
if (!ok) return `<td class="na" data-l="裸命令接管">—</td>`;
|
|
934
|
+
return h.shim ? `<td class="yes" data-l="裸命令接管"><span class="v"><span class="g">✓</span>已接管</span></td>` : `<td class="warncell" data-l="裸命令接管"><span class="v"><span class="g">·</span>可用</span></td>`;
|
|
935
|
+
})()}
|
|
936
|
+
${(() => {
|
|
937
|
+
const hot = h.caps?.hot;
|
|
938
|
+
if (!hot?.ok) return `<td class="na" data-l="会话内热切">—</td>`;
|
|
939
|
+
const on = (h.armed || []).includes(hot.needs);
|
|
940
|
+
const label = hot.kind === "command" ? "会话内热切" : "热切(改设置即生效)";
|
|
941
|
+
return on
|
|
942
|
+
? `<td class="yes" data-l="${label}"><span class="v"><span class="g">✓</span>已开</span></td>`
|
|
943
|
+
: `<td class="warncell" data-l="${label}"><span class="v"><span class="g">·</span>可用</span></td>`;
|
|
944
|
+
})()}
|
|
945
|
+
${(() => {
|
|
946
|
+
// 常驻不再写“无”:能装就是一条可用通道,没装只是没拧开(和 CLI status 同口径)
|
|
947
|
+
const cap = h.caps?.install;
|
|
948
|
+
if (!cap || !cap.ok) return `<td class="na" data-l="配置常驻">—</td>`;
|
|
949
|
+
if (h.installed) return `<td class="yes" data-l="配置常驻"><span class="v"><span class="g">✓</span>已写</span></td>`;
|
|
950
|
+
return `<td class="warncell" data-l="配置常驻"><span class="v"><span class="g">·</span>可用${cap.verified === false ? "(未实测)" : ""}</span></td>`;
|
|
951
|
+
})()}
|
|
952
|
+
<td class="tiny" data-l="state 记录">${arms.length ? esc(arms.join("、")) : "—"}</td>
|
|
953
|
+
</tr>`;
|
|
954
|
+
}).join("");
|
|
955
|
+
const r = S.reconciled || {};
|
|
956
|
+
const lines = Object.entries(r).map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
957
|
+
$("#recon").innerHTML = lines.length ? lines.map(esc).join("<br>") : "state 与磁盘一致,没发现残留。";
|
|
958
|
+
$("#withVersions").textContent = withVersions ? "已连版本号(点这里用缓存)" : "连版本号一起查(慢一点)";
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/* ── 主渲染 ───────────────────────────────────────── */
|
|
962
|
+
function render() {
|
|
963
|
+
renderHeader(); renderOverview(); renderHarness(); renderPersonas(); renderCheck();
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/* ── 事件 ─────────────────────────────────────────── */
|
|
967
|
+
/* ── 视图切换(带 #hash,可直链、可后退)─────────────── */
|
|
968
|
+
function setView(v, sync) {
|
|
969
|
+
if (v === "targets") v = "overview"; // 旧链接兼容:执行器已合并进总览
|
|
970
|
+
view = v;
|
|
971
|
+
document.querySelectorAll("nav.rail button").forEach((x) => x.classList.toggle("cur", x.dataset.v === v));
|
|
972
|
+
document.querySelectorAll("main section").forEach((s) => s.classList.toggle("cur", s.id === "v-" + v));
|
|
973
|
+
if (sync !== false && location.hash.slice(1) !== v) history.replaceState(null, "", "#" + v);
|
|
974
|
+
}
|
|
975
|
+
window.addEventListener("hashchange", () => {
|
|
976
|
+
let v = location.hash.slice(1) || "overview";
|
|
977
|
+
if (v === "targets") v = "overview";
|
|
978
|
+
if (v !== view) setView(v, false);
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
$("nav.rail").addEventListener("click", (e) => {
|
|
982
|
+
const b = e.target.closest("button[data-v]"); if (!b) return;
|
|
983
|
+
setView(b.dataset.v);
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
async function withBusy(el, fn) {
|
|
987
|
+
if (!el || el.dataset.busy) return undefined;
|
|
988
|
+
el.dataset.busy = "1"; el.classList.add("is-busy");
|
|
989
|
+
try { return await fn(); }
|
|
990
|
+
finally { delete el.dataset.busy; el.classList.remove("is-busy"); }
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
$("#powerBtn").addEventListener("click", () => withBusy($("#powerBtn"), async () => {
|
|
994
|
+
const on = S.defaults.mode !== "off";
|
|
995
|
+
try {
|
|
996
|
+
const r = await api("/api/arm-all", {
|
|
997
|
+
method: "POST", headers: { "content-type": "application/json" },
|
|
998
|
+
body: JSON.stringify({ action: on ? "off" : "on" }),
|
|
999
|
+
});
|
|
1000
|
+
toast(on ? "已关闭全部注入" : "已恢复注入(模式 " + r.mode + ")");
|
|
1001
|
+
draftMode = null; await load();
|
|
1002
|
+
} catch (e) { toast("失败:" + e.message, true); }
|
|
1003
|
+
}));
|
|
1004
|
+
|
|
1005
|
+
$("#modeBox").addEventListener("click", (e) => {
|
|
1006
|
+
const m = e.target.closest("[data-mode]"); if (!m) return;
|
|
1007
|
+
draftMode = m.dataset.mode; renderOverview();
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
$("#dPersona").addEventListener("change", async (e) => {
|
|
1011
|
+
await api("/api/use", { method: "POST", headers: { "content-type": "application/json" },
|
|
1012
|
+
body: JSON.stringify({ id: e.target.value }) });
|
|
1013
|
+
selPersona = e.target.value; lastPop = null; await load(); toast("人设 → " + e.target.value);
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
$("#saveModeBtn").addEventListener("click", async () => {
|
|
1017
|
+
if (draftMode === S.defaults.mode) return;
|
|
1018
|
+
try {
|
|
1019
|
+
await api("/api/defaults", { method: "POST", headers: { "content-type": "application/json" },
|
|
1020
|
+
body: JSON.stringify({ harness: S.defaults.harness, mode: draftMode }) });
|
|
1021
|
+
toast("默认模式 → " + MODE_INFO[draftMode].name);
|
|
1022
|
+
draftMode = null; await load();
|
|
1023
|
+
} catch (e) { toast("失败:" + e.message, true); }
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
$("#hpick").addEventListener("click", (e) => {
|
|
1027
|
+
const b = e.target.closest("[data-hpick]"); if (!b) return;
|
|
1028
|
+
selHarness = b.dataset.hpick; renderHarness();
|
|
1029
|
+
});
|
|
1030
|
+
$("#hsel").addEventListener("click", async (e) => {
|
|
1031
|
+
const b = e.target.closest("[data-piauto]"); if (!b) return;
|
|
1032
|
+
await withBusy(b, async () => {
|
|
1033
|
+
try {
|
|
1034
|
+
await api("/api/pi-auto", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ on: b.dataset.piauto === "on" }) });
|
|
1035
|
+
toast("裸启动自动注入 → " + (b.dataset.piauto === "on" ? "开" : "关"));
|
|
1036
|
+
await load();
|
|
1037
|
+
} catch (err) { toast("失败:" + err.message, true); }
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
$("#hsel").addEventListener("click", onChan);
|
|
1041
|
+
$("#hdeadBox").addEventListener("click", onChan);
|
|
1042
|
+
$("#selbar").addEventListener("click", async (e) => {
|
|
1043
|
+
const b = e.target.closest("#setDefaultH"); if (!b) return;
|
|
1044
|
+
await withBusy(b, async () => {
|
|
1045
|
+
try {
|
|
1046
|
+
await api("/api/defaults", { method: "POST", headers: { "content-type": "application/json" },
|
|
1047
|
+
body: JSON.stringify({ harness: selHarness, mode: S.defaults.mode }) });
|
|
1048
|
+
toast("默认执行器 → " + selHarness);
|
|
1049
|
+
await load();
|
|
1050
|
+
} catch (err) { toast("失败:" + err.message, true); }
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
async function onChan(e) {
|
|
1054
|
+
const b = e.target.closest("[data-chan]"); if (!b) return;
|
|
1055
|
+
const { chan, h } = b.dataset;
|
|
1056
|
+
const on = S.harnesses.find((x) => x.id === h)?.armed?.includes(chan);
|
|
1057
|
+
try {
|
|
1058
|
+
await api("/api/arm", { method: "POST", headers: { "content-type": "application/json" },
|
|
1059
|
+
body: JSON.stringify({ harness: h, channel: chan, action: on ? "uninstall" : "install" }) });
|
|
1060
|
+
toast(`${h} · ${ARM_INFO[chan].name} ${on ? "已撤" : "已装"}`);
|
|
1061
|
+
await load();
|
|
1062
|
+
} catch (err) { toast("失败:" + err.message, true); }
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
document.addEventListener("click", async (e) => {
|
|
1066
|
+
const c = e.target.closest("[data-copy]");
|
|
1067
|
+
if (c) { try { await navigator.clipboard.writeText(c.dataset.copy); toast("已复制"); } catch { toast("复制失败", true); } }
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
$("#plist").addEventListener("click", (e) => {
|
|
1071
|
+
const b = e.target.closest("[data-p]"); if (!b) return;
|
|
1072
|
+
selPersona = b.dataset.p; pDirty = false; lastPop = null; renderPersonas();
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
$("#pBody").addEventListener("input", (e) => {
|
|
1076
|
+
pDirty = true; $("#pChars").textContent = e.target.value.length + " 字";
|
|
1077
|
+
});
|
|
1078
|
+
["#pId", "#pName", "#pDesc", "#pModel"].forEach((s) => $(s).addEventListener("input", () => { pDirty = true; }));
|
|
1079
|
+
|
|
1080
|
+
$("#pSave").addEventListener("click", async () => {
|
|
1081
|
+
const id = $("#pId").value.trim();
|
|
1082
|
+
if (!id) return toast("id 不能为空", true);
|
|
1083
|
+
try {
|
|
1084
|
+
await api("/api/persona", { method: "POST", headers: { "content-type": "application/json" },
|
|
1085
|
+
body: JSON.stringify({ id, meta: { name: $("#pName").value, description: $("#pDesc").value, model: $("#pModel").value }, body: $("#pBody").value }) });
|
|
1086
|
+
toast("已保存 " + id); selPersona = id; pDirty = false; lastPop = null; await load();
|
|
1087
|
+
} catch (e) { toast("失败:" + e.message, true); }
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
$("#pUse").addEventListener("click", async () => {
|
|
1091
|
+
const id = $("#pId").value.trim() || selPersona;
|
|
1092
|
+
try {
|
|
1093
|
+
await api("/api/use", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id }) });
|
|
1094
|
+
toast("当前人设 → " + id); selPersona = id; await load();
|
|
1095
|
+
} catch (e) { toast("失败:" + e.message, true); }
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
$("#pDel").addEventListener("click", async () => {
|
|
1099
|
+
const id = $("#pId").value.trim() || selPersona;
|
|
1100
|
+
if (!id) return;
|
|
1101
|
+
try {
|
|
1102
|
+
await api("/api/persona?id=" + encodeURIComponent(id), { method: "DELETE" });
|
|
1103
|
+
toast("已删除 " + id); selPersona = null; await load();
|
|
1104
|
+
} catch (e) { toast("失败:" + e.message, true); }
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
$("#pNew").addEventListener("click", () => {
|
|
1108
|
+
selPersona = null; pDirty = true; lastPop = null;
|
|
1109
|
+
$("#pId").disabled = false;
|
|
1110
|
+
$("#pId").title = "";
|
|
1111
|
+
$("#pId").value = ""; $("#pName").value = ""; $("#pDesc").value = ""; $("#pModel").value = "";
|
|
1112
|
+
$("#pBody").value = "你是这个项目的编码助手。\n\n"; $("#pChars").textContent = $("#pBody").value.length + " 字";
|
|
1113
|
+
document.querySelectorAll(".pitem").forEach((x) => x.classList.remove("cur"));
|
|
1114
|
+
$("#pId").focus();
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
$("#reload").addEventListener("click", async () => { await load(); toast("已重新读盘"); });
|
|
1118
|
+
$("#withVersions").addEventListener("click", async () => {
|
|
1119
|
+
withVersions = !withVersions;
|
|
1120
|
+
const u = new URL(location.href); withVersions ? u.searchParams.set("versions", "1") : u.searchParams.delete("versions");
|
|
1121
|
+
history.replaceState(null, "", u);
|
|
1122
|
+
await load();
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
load().then(() => setView(location.hash.slice(1) || "overview", false)).catch((e) => { console.error("[agent-purge] 首次加载失败", e); toast("加载失败:" + e.message, true); });
|
|
1126
|
+
// 轮询不静默吞错:页面渲染失败以前只表现为“永远空白”,DevTools 里都看不到是谁在抛
|
|
1127
|
+
setInterval(() => load().catch((e) => console.error("[agent-purge] 轮询失败", e)), 8000);
|
|
1128
|
+
</script>
|
|
1129
|
+
|
|
1130
|
+
</body>
|
|
1131
|
+
</html>
|