dsh-immune-system 0.9.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -16
- package/dsh-immune-system/client/client.js +375 -159
- package/dsh-immune-system/dist/core/distiller.js +78 -0
- package/dsh-immune-system/dist/core/pairing.js +178 -0
- package/dsh-immune-system/dist/core/prompts.js +88 -2
- package/dsh-immune-system/dist/core/similarity.js +51 -0
- package/dsh-immune-system/dist/core/store/antibodies.js +37 -8
- package/dsh-immune-system/dist/core/store/episodes.js +19 -5
- package/dsh-immune-system/dist/core/store/index.js +58 -35
- package/dsh-immune-system/dist/core/store/jobs.js +17 -0
- package/dsh-immune-system/dist/core/store/open.js +1 -0
- package/dsh-immune-system/dist/core/store/rerank.js +57 -0
- package/dsh-immune-system/dist/core/store/schema.js +9 -0
- package/dsh-immune-system/dist/core/store/turn-state.js +52 -0
- package/dsh-immune-system/dist/core/ui/page.js +481 -132
- package/dsh-immune-system/dist/core/ui/query.js +12 -2
- package/dsh-immune-system/dist/core/ui/server.js +2 -2
- package/dsh-immune-system/dist/dsh/src/dsh-adapter.js +66 -17
- package/dsh-immune-system/dist/dsh/src/index.js +143 -9
- package/dsh-immune-system/dist/dsh/src/ui-route.js +7 -3
- package/dsh-immune-system/dist/zcode/src/doctor.js +51 -3
- package/dsh-immune-system/dist/zcode/src/drain.js +73 -20
- package/dsh-immune-system/dist/zcode/src/hooks/post-tool-use-failure.js +3 -1
- package/dsh-immune-system/dist/zcode/src/hooks/post-tool-use.js +4 -2
- package/dsh-immune-system/dist/zcode/src/hooks/session-start.js +6 -0
- package/dsh-immune-system/dist/zcode/src/hooks/user-prompt-submit.js +3 -0
- package/dsh-immune-system/package.json +7 -7
- package/package.json +4 -3
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* Written without JSX for the same reason — `React.createElement` is enough for
|
|
10
10
|
* one tab, and it keeps "build" out of the client entirely.
|
|
11
11
|
*
|
|
12
|
+
* Styling: one injected <style> block (scoped under `.imm`) instead of dozens of
|
|
13
|
+
* inline style objects, so hover states, the responsive card layout and the
|
|
14
|
+
* light/dark tokens are expressible at all. The block is static text this file
|
|
15
|
+
* owns, so there is no interpolation and nothing external.
|
|
16
|
+
*
|
|
12
17
|
* Data comes from the host half's route (`/plugins/dsh-immune-system/api`),
|
|
13
18
|
* registered through `ctx.inject(['webServer'], …)`. Nothing here talks to
|
|
14
19
|
* SQLite directly; the browser never sees a database path or a file handle.
|
|
@@ -20,8 +25,106 @@ window.__ModuleLoader__.load({
|
|
|
20
25
|
var exports = module.exports;
|
|
21
26
|
var React = require('react');
|
|
22
27
|
var h = React.createElement;
|
|
28
|
+
var ReactDOM = null;
|
|
29
|
+
try {
|
|
30
|
+
ReactDOM = require('react-dom');
|
|
31
|
+
} catch (error) {
|
|
32
|
+
ReactDOM = null;
|
|
33
|
+
}
|
|
23
34
|
|
|
24
35
|
var API = '/plugins/dsh-immune-system/api';
|
|
36
|
+
var STYLE_ID = 'dsh-immune-system-style';
|
|
37
|
+
|
|
38
|
+
var CSS = [
|
|
39
|
+
'.imm{--i-line:var(--dsh-border,#2c3240);--i-line-soft:var(--dsh-border-subtle,#242a36);',
|
|
40
|
+
'--i-panel:var(--dsh-bg-secondary,#141821);--i-panel2:var(--dsh-bg-tertiary,#1b2231);',
|
|
41
|
+
'--i-text:var(--dsh-text-primary,inherit);--i-muted:var(--dsh-text-secondary,#8b94a3);',
|
|
42
|
+
'--i-faint:var(--dsh-text-tertiary,#6b7789);--i-accent:var(--dsh-accent,#4f9cf9);',
|
|
43
|
+
'--i-ok:#37c98a;--i-warn:#e3a53f;--i-bad:#e56565;',
|
|
44
|
+
'display:flex;flex-direction:column;gap:12px;padding:2px;font-size:13px;color:var(--i-text)}',
|
|
45
|
+
'.imm .row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}',
|
|
46
|
+
'.imm .kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:10px}',
|
|
47
|
+
'.imm .kpi{position:relative;background:var(--i-panel);border:1px solid var(--i-line);',
|
|
48
|
+
'border-radius:11px;padding:10px 13px;overflow:hidden}',
|
|
49
|
+
'.imm .kpi::after{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--i-line)}',
|
|
50
|
+
'.imm .kpi.ok::after{background:var(--i-ok)}.imm .kpi.warn::after{background:var(--i-warn)}',
|
|
51
|
+
'.imm .kpi.accent::after{background:var(--i-accent)}',
|
|
52
|
+
'.imm .kpi .v{font-size:20px;font-weight:650;letter-spacing:-.2px;font-variant-numeric:tabular-nums}',
|
|
53
|
+
'.imm .kpi .k{color:var(--i-muted);font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;margin-top:1px}',
|
|
54
|
+
'.imm .search{position:relative;flex:1 1 260px;min-width:180px;display:flex;align-items:center}',
|
|
55
|
+
'.imm .search input{width:100%;padding:8px 32px 8px 30px;font-size:13px;color:inherit;',
|
|
56
|
+
'background:var(--i-panel);border:1px solid var(--i-line);border-radius:8px;transition:border-color .15s,box-shadow .15s}',
|
|
57
|
+
'.imm .search input:focus{outline:none;border-color:var(--i-accent);box-shadow:0 0 0 3px rgba(79,156,249,.15)}',
|
|
58
|
+
'.imm .search .glass{position:absolute;left:10px;width:12px;height:12px;border:2px solid var(--i-faint);border-radius:50%;pointer-events:none}',
|
|
59
|
+
'.imm .search .glass::after{content:"";position:absolute;right:-4px;bottom:-3px;width:6px;height:2px;background:var(--i-faint);transform:rotate(45deg);border-radius:2px}',
|
|
60
|
+
'.imm .search .x{position:absolute;right:5px;width:22px;height:22px;border:0;border-radius:6px;background:transparent;',
|
|
61
|
+
'color:var(--i-faint);cursor:pointer;font-size:15px;line-height:1;display:none}',
|
|
62
|
+
'.imm .search .x.on{display:block}.imm .search .x:hover{background:var(--i-panel2);color:var(--i-text)}',
|
|
63
|
+
'.imm .seg{display:inline-flex;background:var(--i-panel);border:1px solid var(--i-line);border-radius:999px;padding:2px}',
|
|
64
|
+
'.imm .seg button{border:0;background:transparent;color:var(--i-muted);cursor:pointer;padding:6px 12px;',
|
|
65
|
+
'border-radius:999px;font-size:12.5px;font-variant-numeric:tabular-nums;transition:background .15s,color .15s}',
|
|
66
|
+
'.imm .seg button.on{background:var(--i-panel2);color:var(--i-text);box-shadow:inset 0 0 0 1px var(--i-line)}',
|
|
67
|
+
'.imm .seg button b{font-weight:600;margin-left:5px;color:var(--i-faint)}.imm .seg button.on b{color:var(--i-accent)}',
|
|
68
|
+
'.imm select{appearance:none;padding:7px 26px 7px 10px;border-radius:8px;border:1px solid var(--i-line);',
|
|
69
|
+
'background:var(--i-panel);color:inherit;font-size:12.5px;cursor:pointer}',
|
|
70
|
+
'.imm .panel{background:var(--i-panel);border:1px solid var(--i-line);border-radius:11px;overflow:hidden}',
|
|
71
|
+
'.imm .meta-row{display:flex;justify-content:space-between;gap:10px;padding:8px 13px;border-bottom:1px solid var(--i-line-soft);',
|
|
72
|
+
'color:var(--i-faint);font-size:11.5px}',
|
|
73
|
+
'.imm table{width:100%;border-collapse:collapse}',
|
|
74
|
+
'.imm thead th{position:sticky;top:0;z-index:1;text-align:left;padding:8px 13px;background:var(--i-panel2);',
|
|
75
|
+
'color:var(--i-muted);font-weight:550;font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;',
|
|
76
|
+
'border-bottom:1px solid var(--i-line);white-space:nowrap}',
|
|
77
|
+
'.imm tbody td{padding:10px 13px;border-bottom:1px solid var(--i-line-soft);vertical-align:top}',
|
|
78
|
+
'.imm tbody tr:last-child td{border-bottom:0}.imm tbody tr{transition:background .12s}',
|
|
79
|
+
'.imm tbody tr:hover{background:var(--i-panel2)}.imm tr.retired td{opacity:.62}',
|
|
80
|
+
'.imm td.id{color:var(--i-faint);width:44px;font-variant-numeric:tabular-nums}',
|
|
81
|
+
'.imm td.trigger{font-family:ui-monospace,Consolas,monospace;font-size:12px;max-width:280px;word-break:break-word}',
|
|
82
|
+
'.imm td.txt{max-width:270px;word-break:break-word;color:var(--i-muted)}',
|
|
83
|
+
'.imm td.txt.do{color:var(--i-text)}',
|
|
84
|
+
'.imm .counts{font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--i-faint)}',
|
|
85
|
+
'.imm .counts b{color:var(--i-ok);font-weight:600}.imm .counts .f{color:var(--i-bad);font-weight:600}',
|
|
86
|
+
'.imm .pill{display:inline-flex;align-items:center;gap:6px;padding:2px 9px;border-radius:999px;font-size:11.5px;white-space:nowrap}',
|
|
87
|
+
'.imm .pill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}',
|
|
88
|
+
'.imm .pill.active{color:var(--i-ok);background:rgba(55,201,138,.13)}',
|
|
89
|
+
'.imm .pill.retired{color:var(--i-muted);background:rgba(139,148,163,.13)}',
|
|
90
|
+
'.imm .acts{display:flex;gap:6px;flex-wrap:nowrap}',
|
|
91
|
+
'.imm td.ops{white-space:nowrap}',
|
|
92
|
+
'.imm button.act{border:1px solid var(--i-line);background:transparent;color:var(--i-muted);border-radius:7px;',
|
|
93
|
+
'padding:4px 9px;cursor:pointer;font-size:12px;transition:all .12s}',
|
|
94
|
+
'.imm button.act:hover{color:var(--i-text);border-color:var(--i-muted);background:var(--i-panel2)}',
|
|
95
|
+
'.imm button.act.danger:hover{color:var(--i-bad);border-color:var(--i-bad);background:rgba(229,101,101,.1)}',
|
|
96
|
+
'.imm button.act:disabled{opacity:.45;cursor:progress}',
|
|
97
|
+
'.imm .empty{padding:38px 18px 42px;text-align:center}',
|
|
98
|
+
'.imm .empty .glyph{width:42px;height:42px;margin:0 auto 10px;border-radius:11px;border:1.5px dashed var(--i-line);position:relative}',
|
|
99
|
+
'.imm .empty .glyph::before{content:"";position:absolute;left:12px;right:12px;top:19px;height:2px;background:var(--i-line);border-radius:2px}',
|
|
100
|
+
'.imm .empty .glyph::after{content:"";position:absolute;top:12px;bottom:12px;left:19px;width:2px;background:var(--i-line);border-radius:2px}',
|
|
101
|
+
'.imm .empty .t{font-weight:600}.imm .empty .hd{color:var(--i-muted);margin-top:4px;font-size:12px}',
|
|
102
|
+
'.imm .skel .r{height:36px;border-bottom:1px solid var(--i-line-soft);display:flex;align-items:center;gap:12px;padding:0 13px}',
|
|
103
|
+
'.imm .skel .b{height:9px;border-radius:5px;background:linear-gradient(90deg,var(--i-line-soft),var(--i-line),var(--i-line-soft));',
|
|
104
|
+
'background-size:200% 100%;animation:imm-shimmer 1.2s linear infinite}',
|
|
105
|
+
'.imm .skel .b:nth-child(1){width:30px}.imm .skel .b:nth-child(2){width:26%}',
|
|
106
|
+
'.imm .skel .b:nth-child(3){width:22%}.imm .skel .b:nth-child(4){width:16%}',
|
|
107
|
+
'@keyframes imm-shimmer{from{background-position:200% 0}to{background-position:-200% 0}}',
|
|
108
|
+
'@media (prefers-reduced-motion:reduce){.imm .skel .b{animation:none}}',
|
|
109
|
+
'.imm .note{font-size:11.5px;color:var(--i-faint)}',
|
|
110
|
+
'.imm .err{color:var(--i-bad)}',
|
|
111
|
+
'@media (max-width:720px){.imm thead{display:none}.imm table,.imm tbody,.imm tr,.imm td{display:block;width:100%}',
|
|
112
|
+
'.imm tbody tr{border-bottom:1px solid var(--i-line);padding:8px 2px}',
|
|
113
|
+
'.imm tbody td{border:0;padding:3px 11px;display:flex;gap:10px}',
|
|
114
|
+
'.imm tbody td::before{content:attr(data-label);flex:0 0 76px;color:var(--i-faint);font-size:10.5px;',
|
|
115
|
+
'text-transform:uppercase;letter-spacing:.05em;padding-top:2px}',
|
|
116
|
+
'.imm td.id{width:auto}.imm td.trigger,.imm td.txt{max-width:none}}',
|
|
117
|
+
// Defensive layout: the settings modal is an unknown host, so nothing here
|
|
118
|
+
// may depend on inherited line-height, block-ness or table display.
|
|
119
|
+
'.imm{line-height:1.45;font-family:inherit}',
|
|
120
|
+
'.imm .kpi .v,.imm .kpi .k{display:block}',
|
|
121
|
+
'.imm table{display:table;table-layout:auto}',
|
|
122
|
+
'.imm thead{display:table-header-group}.imm tbody{display:table-row-group}',
|
|
123
|
+
'.imm tr{display:table-row}.imm th,.imm td{display:table-cell}',
|
|
124
|
+
'.imm select{color:var(--i-text)}',
|
|
125
|
+
'.imm select option{color:var(--i-text);background:var(--i-panel)}',
|
|
126
|
+
'.imm .shield{display:block;width:100%;min-width:0}',
|
|
127
|
+
].join('');
|
|
25
128
|
|
|
26
129
|
function request(path, init) {
|
|
27
130
|
return fetch(API + path, Object.assign({ credentials: 'same-origin' }, init)).then(
|
|
@@ -34,76 +137,25 @@ window.__ModuleLoader__.load({
|
|
|
34
137
|
);
|
|
35
138
|
}
|
|
36
139
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} catch (e) {
|
|
41
|
-
return '';
|
|
42
|
-
}
|
|
140
|
+
function formatBudget(ms) {
|
|
141
|
+
if (typeof ms !== 'number' || !isFinite(ms)) return '—';
|
|
142
|
+
return ms >= 1000 ? (ms / 1000) + ' s' : Math.round(ms) + ' ms';
|
|
43
143
|
}
|
|
44
144
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
padding: '7px 10px',
|
|
54
|
-
borderRadius: '8px',
|
|
55
|
-
border: '1px solid var(--dsh-border, #2c3240)',
|
|
56
|
-
background: 'var(--dsh-bg-secondary, #141821)',
|
|
57
|
-
color: 'inherit',
|
|
58
|
-
fontSize: '13px',
|
|
59
|
-
},
|
|
60
|
-
tab: {
|
|
61
|
-
padding: '5px 12px',
|
|
62
|
-
borderRadius: '999px',
|
|
63
|
-
border: '1px solid var(--dsh-border, #2c3240)',
|
|
64
|
-
background: 'transparent',
|
|
65
|
-
color: 'inherit',
|
|
66
|
-
cursor: 'pointer',
|
|
67
|
-
fontSize: '12px',
|
|
68
|
-
},
|
|
69
|
-
tabOn: {
|
|
70
|
-
borderColor: 'var(--dsh-accent, #4f9cf9)',
|
|
71
|
-
color: 'var(--dsh-accent, #4f9cf9)',
|
|
72
|
-
},
|
|
73
|
-
table: { width: '100%', borderCollapse: 'collapse' },
|
|
74
|
-
th: {
|
|
75
|
-
textAlign: 'left',
|
|
76
|
-
padding: '6px 8px',
|
|
77
|
-
fontSize: '11px',
|
|
78
|
-
textTransform: 'uppercase',
|
|
79
|
-
letterSpacing: '.04em',
|
|
80
|
-
color: 'var(--dsh-text-secondary, #8b94a3)',
|
|
81
|
-
borderBottom: '1px solid var(--dsh-border, #2c3240)',
|
|
82
|
-
},
|
|
83
|
-
td: {
|
|
84
|
-
padding: '8px',
|
|
85
|
-
borderBottom: '1px solid var(--dsh-border, #242a36)',
|
|
86
|
-
verticalAlign: 'top',
|
|
87
|
-
},
|
|
88
|
-
mono: { fontFamily: 'ui-monospace, Consolas, monospace', fontSize: '12px' },
|
|
89
|
-
btn: {
|
|
90
|
-
padding: '3px 8px',
|
|
91
|
-
marginRight: '6px',
|
|
92
|
-
borderRadius: '6px',
|
|
93
|
-
border: '1px solid var(--dsh-border, #2c3240)',
|
|
94
|
-
background: 'transparent',
|
|
95
|
-
color: 'inherit',
|
|
96
|
-
cursor: 'pointer',
|
|
97
|
-
fontSize: '12px',
|
|
98
|
-
},
|
|
99
|
-
muted: { color: 'var(--dsh-text-secondary, #8b94a3)' },
|
|
100
|
-
note: { fontSize: '12px', color: 'var(--dsh-text-secondary, #8b94a3)' },
|
|
101
|
-
};
|
|
145
|
+
function kpi(value, label, tone) {
|
|
146
|
+
return h(
|
|
147
|
+
'div',
|
|
148
|
+
{ key: label, className: 'kpi' + (tone ? ' ' + tone : '') },
|
|
149
|
+
h('div', { className: 'v' }, String(value)),
|
|
150
|
+
h('div', { className: 'k' }, label),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
102
153
|
|
|
103
|
-
function
|
|
104
|
-
var state = React.useState({ loading: true, error: '', stats: null, rows: [] });
|
|
154
|
+
function ImmunePanel() {
|
|
155
|
+
var state = React.useState({ loading: true, error: '', stats: null, rows: [], budgetMs: null });
|
|
105
156
|
var view = React.useState('project');
|
|
106
157
|
var query = React.useState('');
|
|
158
|
+
var order = React.useState('recent');
|
|
107
159
|
|
|
108
160
|
var data = state[0];
|
|
109
161
|
var setData = state[1];
|
|
@@ -111,6 +163,8 @@ window.__ModuleLoader__.load({
|
|
|
111
163
|
var setTab = view[1];
|
|
112
164
|
var q = query[0];
|
|
113
165
|
var setQ = query[1];
|
|
166
|
+
var sort = order[0];
|
|
167
|
+
var setSort = order[1];
|
|
114
168
|
|
|
115
169
|
var load = React.useCallback(
|
|
116
170
|
function (nextTab, nextQuery) {
|
|
@@ -123,10 +177,22 @@ window.__ModuleLoader__.load({
|
|
|
123
177
|
});
|
|
124
178
|
request(path)
|
|
125
179
|
.then(function (body) {
|
|
126
|
-
setData({
|
|
180
|
+
setData({
|
|
181
|
+
loading: false,
|
|
182
|
+
error: '',
|
|
183
|
+
stats: body.stats,
|
|
184
|
+
rows: body.antibodies,
|
|
185
|
+
budgetMs: body.budgetMs,
|
|
186
|
+
});
|
|
127
187
|
})
|
|
128
188
|
.catch(function (error) {
|
|
129
|
-
setData({
|
|
189
|
+
setData({
|
|
190
|
+
loading: false,
|
|
191
|
+
error: String(error.message || error),
|
|
192
|
+
stats: null,
|
|
193
|
+
rows: [],
|
|
194
|
+
budgetMs: null,
|
|
195
|
+
});
|
|
130
196
|
});
|
|
131
197
|
},
|
|
132
198
|
[setData],
|
|
@@ -166,120 +232,267 @@ window.__ModuleLoader__.load({
|
|
|
166
232
|
}
|
|
167
233
|
|
|
168
234
|
var stats = data.stats;
|
|
169
|
-
|
|
235
|
+
|
|
236
|
+
var kpis = h(
|
|
170
237
|
'div',
|
|
171
|
-
{
|
|
238
|
+
{ className: 'kpis' },
|
|
172
239
|
stats
|
|
173
240
|
? [
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
241
|
+
kpi(stats.active, '生效 / active', 'ok'),
|
|
242
|
+
kpi(stats.retired, '已退役 / retired', 'warn'),
|
|
243
|
+
kpi(stats.hits, '注入 / injections', 'accent'),
|
|
244
|
+
kpi(stats.shared, '共享 / shared', ''),
|
|
245
|
+
kpi(formatBudget(data.budgetMs), '蒸馏预算 / budget', ''),
|
|
178
246
|
]
|
|
179
|
-
: [h('
|
|
247
|
+
: [h('div', { key: 'l', className: 'note' }, data.loading ? '加载中… / loading…' : '')],
|
|
180
248
|
);
|
|
181
249
|
|
|
182
|
-
var
|
|
183
|
-
return
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
250
|
+
var sorted = data.rows.slice().sort(function (a, b) {
|
|
251
|
+
if (sort === 'hits') return b.hit_count - a.hit_count || a.id - b.id;
|
|
252
|
+
if (sort === 'fails') return b.fail_count - a.fail_count || a.id - b.id;
|
|
253
|
+
return String(b.updated_at).localeCompare(String(a.updated_at)) || b.id - a.id;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
var body;
|
|
257
|
+
if (data.error) {
|
|
258
|
+
body = h(
|
|
259
|
+
'div',
|
|
260
|
+
{ className: 'empty' },
|
|
261
|
+
h('div', { className: 'glyph' }),
|
|
262
|
+
h('div', { className: 't' }, '加载失败 / load failed'),
|
|
263
|
+
h('div', { className: 'hd err' }, data.error),
|
|
264
|
+
);
|
|
265
|
+
} else if (data.loading && data.rows.length === 0) {
|
|
266
|
+
body = h(
|
|
267
|
+
'div',
|
|
268
|
+
{ className: 'skel' },
|
|
269
|
+
[0, 1, 2, 3].map(function (n) {
|
|
270
|
+
return h(
|
|
271
|
+
'div',
|
|
272
|
+
{ className: 'r', key: n },
|
|
273
|
+
h('div', { className: 'b' }),
|
|
274
|
+
h('div', { className: 'b' }),
|
|
275
|
+
h('div', { className: 'b' }),
|
|
276
|
+
h('div', { className: 'b' }),
|
|
277
|
+
);
|
|
278
|
+
}),
|
|
279
|
+
);
|
|
280
|
+
} else if (sorted.length === 0) {
|
|
281
|
+
body = h(
|
|
282
|
+
'div',
|
|
283
|
+
{ className: 'empty' },
|
|
284
|
+
h('div', { className: 'glyph' }),
|
|
285
|
+
h(
|
|
286
|
+
'div',
|
|
287
|
+
{ className: 't' },
|
|
288
|
+
tab === 'shared' ? '共享库为空 / shared library is empty' : '还没有抗体 / no antibodies yet',
|
|
289
|
+
),
|
|
192
290
|
h(
|
|
193
|
-
'
|
|
194
|
-
{
|
|
195
|
-
tab === '
|
|
196
|
-
?
|
|
197
|
-
|
|
198
|
-
'button',
|
|
199
|
-
{
|
|
200
|
-
key: 't',
|
|
201
|
-
style: S.btn,
|
|
202
|
-
onClick: function () {
|
|
203
|
-
act(a.status === 'active' ? 'retire' : 'restore', a.id);
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
a.status === 'active' ? '退役 / retire' : '恢复 / restore',
|
|
207
|
-
),
|
|
208
|
-
h(
|
|
209
|
-
'button',
|
|
210
|
-
{ key: 'd', style: S.btn, onClick: function () { act('delete', a.id); } },
|
|
211
|
-
'删除 / delete',
|
|
212
|
-
),
|
|
213
|
-
]
|
|
214
|
-
: null,
|
|
291
|
+
'div',
|
|
292
|
+
{ className: 'hd' },
|
|
293
|
+
tab === 'shared'
|
|
294
|
+
? '泛型坑在多个项目反复命中后才会晋升到这里。'
|
|
295
|
+
: '工具先失败、随后用另一种方式成功之后才会产生。',
|
|
215
296
|
),
|
|
216
297
|
);
|
|
217
|
-
}
|
|
298
|
+
} else {
|
|
299
|
+
body = h(
|
|
300
|
+
'table',
|
|
301
|
+
null,
|
|
302
|
+
h(
|
|
303
|
+
'thead',
|
|
304
|
+
null,
|
|
305
|
+
h(
|
|
306
|
+
'tr',
|
|
307
|
+
null,
|
|
308
|
+
['#', '触发 / trigger', '忌 / avoid', '宜 / do', 'hits / fails', '状态 / status', ''].map(function (label) {
|
|
309
|
+
return h('th', { key: label }, label);
|
|
310
|
+
}),
|
|
311
|
+
),
|
|
312
|
+
),
|
|
313
|
+
h(
|
|
314
|
+
'tbody',
|
|
315
|
+
null,
|
|
316
|
+
sorted.map(function (a) {
|
|
317
|
+
return h(
|
|
318
|
+
'tr',
|
|
319
|
+
{ key: a.id, className: a.status === 'retired' ? 'retired' : '' },
|
|
320
|
+
h('td', { className: 'id', 'data-label': '#' }, String(a.id)),
|
|
321
|
+
h('td', { className: 'trigger', 'data-label': '触发' }, a.trigger_pattern),
|
|
322
|
+
h('td', { className: 'txt', 'data-label': '忌' }, a.symptom),
|
|
323
|
+
h('td', { className: 'txt do', 'data-label': '宜' }, a.prescription),
|
|
324
|
+
h(
|
|
325
|
+
'td',
|
|
326
|
+
{ className: 'counts', 'data-label': 'hits/fails' },
|
|
327
|
+
h('b', null, String(a.hit_count)),
|
|
328
|
+
' / ',
|
|
329
|
+
h('span', { className: 'f' }, String(a.fail_count)),
|
|
330
|
+
),
|
|
331
|
+
h(
|
|
332
|
+
'td',
|
|
333
|
+
{ 'data-label': '状态' },
|
|
334
|
+
h('span', { className: 'pill ' + a.status }, a.status === 'active' ? '生效 / active' : '已退役 / retired'),
|
|
335
|
+
),
|
|
336
|
+
h(
|
|
337
|
+
'td',
|
|
338
|
+
{ className: 'ops', 'data-label': '' },
|
|
339
|
+
tab === 'project'
|
|
340
|
+
? h(
|
|
341
|
+
'div',
|
|
342
|
+
{ className: 'acts' },
|
|
343
|
+
h(
|
|
344
|
+
'button',
|
|
345
|
+
{
|
|
346
|
+
className: 'act',
|
|
347
|
+
type: 'button',
|
|
348
|
+
onClick: function () {
|
|
349
|
+
act(a.status === 'active' ? 'retire' : 'restore', a.id);
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
a.status === 'active' ? '退役 / retire' : '恢复 / restore',
|
|
353
|
+
),
|
|
354
|
+
h(
|
|
355
|
+
'button',
|
|
356
|
+
{ className: 'act danger', type: 'button', onClick: function () { act('delete', a.id); } },
|
|
357
|
+
'删除 / delete',
|
|
358
|
+
),
|
|
359
|
+
)
|
|
360
|
+
: null,
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
}),
|
|
364
|
+
),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
218
367
|
|
|
219
368
|
return h(
|
|
220
369
|
'div',
|
|
221
|
-
{
|
|
222
|
-
|
|
370
|
+
{ className: 'imm' },
|
|
371
|
+
h('style', { id: STYLE_ID }, CSS),
|
|
372
|
+
kpis,
|
|
223
373
|
h(
|
|
224
374
|
'div',
|
|
225
|
-
{
|
|
226
|
-
h('input', {
|
|
227
|
-
style: S.input,
|
|
228
|
-
type: 'search',
|
|
229
|
-
placeholder: '搜索触发词、症状或处方… / search…',
|
|
230
|
-
value: q,
|
|
231
|
-
onChange: function (e) {
|
|
232
|
-
onQuery(e.target.value);
|
|
233
|
-
},
|
|
234
|
-
}),
|
|
375
|
+
{ className: 'row' },
|
|
235
376
|
h(
|
|
236
|
-
'
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
377
|
+
'div',
|
|
378
|
+
{ className: 'search' },
|
|
379
|
+
h('span', { className: 'glass', 'aria-hidden': 'true' }),
|
|
380
|
+
h('input', {
|
|
381
|
+
type: 'search',
|
|
382
|
+
placeholder: '搜索触发词、症状或处方… / search…',
|
|
383
|
+
value: q,
|
|
384
|
+
'aria-label': 'search antibodies',
|
|
385
|
+
onChange: function (e) {
|
|
386
|
+
onQuery(e.target.value);
|
|
241
387
|
},
|
|
242
|
-
},
|
|
243
|
-
|
|
388
|
+
}),
|
|
389
|
+
h(
|
|
390
|
+
'button',
|
|
391
|
+
{
|
|
392
|
+
className: q ? 'x on' : 'x',
|
|
393
|
+
type: 'button',
|
|
394
|
+
title: '清除 / clear',
|
|
395
|
+
onClick: function () {
|
|
396
|
+
onQuery('');
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
'×',
|
|
400
|
+
),
|
|
244
401
|
),
|
|
245
402
|
h(
|
|
246
|
-
'
|
|
247
|
-
{
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
403
|
+
'div',
|
|
404
|
+
{ className: 'seg', role: 'tablist' },
|
|
405
|
+
h(
|
|
406
|
+
'button',
|
|
407
|
+
{
|
|
408
|
+
className: tab === 'project' ? 'on' : '',
|
|
409
|
+
type: 'button',
|
|
410
|
+
role: 'tab',
|
|
411
|
+
'aria-selected': tab === 'project' ? 'true' : 'false',
|
|
412
|
+
onClick: function () { setTab('project'); },
|
|
413
|
+
},
|
|
414
|
+
'项目 / project',
|
|
415
|
+
h('b', null, stats ? String(stats.active + stats.retired) : ''),
|
|
416
|
+
),
|
|
417
|
+
h(
|
|
418
|
+
'button',
|
|
419
|
+
{
|
|
420
|
+
className: tab === 'shared' ? 'on' : '',
|
|
421
|
+
type: 'button',
|
|
422
|
+
role: 'tab',
|
|
423
|
+
'aria-selected': tab === 'shared' ? 'true' : 'false',
|
|
424
|
+
onClick: function () { setTab('shared'); },
|
|
251
425
|
},
|
|
426
|
+
'共享 / shared',
|
|
427
|
+
h('b', null, stats ? String(stats.shared) : ''),
|
|
428
|
+
),
|
|
429
|
+
),
|
|
430
|
+
h(
|
|
431
|
+
'select',
|
|
432
|
+
{
|
|
433
|
+
value: sort,
|
|
434
|
+
'aria-label': 'sort antibodies',
|
|
435
|
+
onChange: function (e) { setSort(e.target.value); },
|
|
252
436
|
},
|
|
253
|
-
'
|
|
437
|
+
h('option', { value: 'recent' }, '最近更新 / recent'),
|
|
438
|
+
h('option', { value: 'hits' }, '命中最多 / most hits'),
|
|
439
|
+
h('option', { value: 'fails' }, '失效最多 / most fails'),
|
|
254
440
|
),
|
|
255
441
|
),
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
442
|
+
h(
|
|
443
|
+
'div',
|
|
444
|
+
{ className: 'panel' },
|
|
445
|
+
h(
|
|
446
|
+
'div',
|
|
447
|
+
{ className: 'meta-row' },
|
|
448
|
+
h('span', null, sorted.length + ' 条结果 / results'),
|
|
449
|
+
h('span', null, stats ? '蒸馏预算 / budget ' + formatBudget(data.budgetMs) : ''),
|
|
450
|
+
),
|
|
451
|
+
body,
|
|
452
|
+
),
|
|
453
|
+
h('div', { className: 'note' }, '浏览不计入命中数——只有真正注入给模型才算一次命中。 / browsing is not injection.'),
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Render the panel inside a shadow root.
|
|
459
|
+
*
|
|
460
|
+
* The settings modal is an unknown host: its global stylesheet can (and in
|
|
461
|
+
* practice does) reshape bare `table`/`td`/`div` inside a plugin tab, which
|
|
462
|
+
* collapsed our rows and hid the sort control. A shadow root seals both
|
|
463
|
+
* directions — the host CSS cannot reach in, and our tokens cannot leak out.
|
|
464
|
+
* Only inherited properties (font, colour, line-height) cross the boundary,
|
|
465
|
+
* so `.imm` still pins those explicitly.
|
|
466
|
+
*
|
|
467
|
+
* If the browser has no shadow DOM, or react-dom is unavailable for portals,
|
|
468
|
+
* fall back to plain rendering; the defensive CSS in the style block keeps
|
|
469
|
+
* that readable.
|
|
470
|
+
*/
|
|
471
|
+
function ImmuneTab() {
|
|
472
|
+
var host = React.useRef(null);
|
|
473
|
+
var target = React.useState(null);
|
|
474
|
+
var mount = target[0];
|
|
475
|
+
var setMount = target[1];
|
|
476
|
+
|
|
477
|
+
React.useEffect(function () {
|
|
478
|
+
var node = host.current;
|
|
479
|
+
if (!node || !ReactDOM || typeof node.attachShadow !== 'function') return undefined;
|
|
480
|
+
var shadow = node.shadowRoot || node.attachShadow({ mode: 'open' });
|
|
481
|
+
var slot = shadow.querySelector('div[data-immune-root]');
|
|
482
|
+
if (!slot) {
|
|
483
|
+
slot = document.createElement('div');
|
|
484
|
+
slot.setAttribute('data-immune-root', '1');
|
|
485
|
+
shadow.appendChild(slot);
|
|
486
|
+
}
|
|
487
|
+
setMount(slot);
|
|
488
|
+
return undefined;
|
|
489
|
+
}, []);
|
|
490
|
+
|
|
491
|
+
var ready = mount !== null && ReactDOM !== null && typeof ReactDOM.createPortal === 'function';
|
|
492
|
+
return h(
|
|
493
|
+
'div',
|
|
494
|
+
{ ref: host, className: 'shield', style: { display: 'block', width: '100%', minWidth: 0 } },
|
|
495
|
+
ready ? ReactDOM.createPortal(h(ImmunePanel, null), mount) : null,
|
|
283
496
|
);
|
|
284
497
|
}
|
|
285
498
|
|
|
@@ -302,6 +515,9 @@ window.__ModuleLoader__.load({
|
|
|
302
515
|
exports.name = 'dsh-immune-system';
|
|
303
516
|
exports.inject = ['slots'];
|
|
304
517
|
exports.apply = apply;
|
|
518
|
+
// Exposed for the offline render harness (scripts/render-tab.mjs); the host
|
|
519
|
+
// only uses `apply`. Harmless if a future host ignores it.
|
|
520
|
+
exports.__tab = ImmuneTab;
|
|
305
521
|
return module.exports;
|
|
306
522
|
},
|
|
307
523
|
});
|