079project 6.0.0 → 8.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/.cache/38/9a0e6a4756f17b0edebad6a7be1eed.json +1 -0
- package/079project_frontend/README.md +70 -0
- package/079project_frontend/package-lock.json +17310 -0
- package/079project_frontend/package.json +40 -0
- package/079project_frontend/public/favicon.ico +0 -0
- package/079project_frontend/public/index.html +43 -0
- package/079project_frontend/public/logo192.png +0 -0
- package/079project_frontend/public/logo512.png +0 -0
- package/079project_frontend/public/manifest.json +25 -0
- package/079project_frontend/public/robots.txt +3 -0
- package/079project_frontend/src/App.css +515 -0
- package/079project_frontend/src/App.js +286 -0
- package/079project_frontend/src/App.test.js +8 -0
- package/079project_frontend/src/api/client.js +103 -0
- package/079project_frontend/src/components/AuthGate.js +153 -0
- package/079project_frontend/src/components/ConfigPanel.js +643 -0
- package/079project_frontend/src/index.css +21 -0
- package/079project_frontend/src/index.js +17 -0
- package/079project_frontend/src/logo.svg +1 -0
- package/079project_frontend/src/reportWebVitals.js +13 -0
- package/079project_frontend/src/setupTests.js +5 -0
- package/README.en.md +234 -0
- package/README.md +0 -0
- package/auth_frontend_server.cjs +312 -0
- package/main.cjs +2259 -83
- package/memeMergeWorker.cjs +256 -0
- package/package.json +28 -15
- package/robots/wikitext-something.txt +1 -39254
- package/tools_install.js +136 -0
- package/model_RNN.py +0 -209
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { api } from '../api/client';
|
|
3
|
+
|
|
4
|
+
const asNum = (v, fallback) => {
|
|
5
|
+
const n = Number(v);
|
|
6
|
+
return Number.isFinite(n) ? n : fallback;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function FieldRow({ label, hint, children }) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="cfg-row">
|
|
12
|
+
<div className="cfg-label">
|
|
13
|
+
<div className="cfg-label-title">{label}</div>
|
|
14
|
+
{hint ? <div className="cfg-label-hint">{hint}</div> : null}
|
|
15
|
+
</div>
|
|
16
|
+
<div className="cfg-control">{children}</div>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function ConfigPanel({ onError }) {
|
|
22
|
+
const [loading, setLoading] = useState(false);
|
|
23
|
+
const [features, setFeatures] = useState(null);
|
|
24
|
+
const [patchBusy, setPatchBusy] = useState(false);
|
|
25
|
+
const [studyStatus, setStudyStatus] = useState(null);
|
|
26
|
+
const [systemConfig, setSystemConfig] = useState(null);
|
|
27
|
+
const [groupsInfo, setGroupsInfo] = useState(null);
|
|
28
|
+
|
|
29
|
+
const [maliciousThreshold, setMaliciousThreshold] = useState('');
|
|
30
|
+
const [rlEvery, setRlEvery] = useState('');
|
|
31
|
+
const [advEvery, setAdvEvery] = useState('');
|
|
32
|
+
|
|
33
|
+
const [testsDir, setTestsDir] = useState('');
|
|
34
|
+
const [testsFiles, setTestsFiles] = useState([]);
|
|
35
|
+
const [newTestName, setNewTestName] = useState('');
|
|
36
|
+
const [newTestContent, setNewTestContent] = useState('');
|
|
37
|
+
|
|
38
|
+
const [robotsFiles, setRobotsFiles] = useState([]);
|
|
39
|
+
const [robotsSelected, setRobotsSelected] = useState([]);
|
|
40
|
+
const [robotsLimit, setRobotsLimit] = useState('10');
|
|
41
|
+
const [robotsShuffle, setRobotsShuffle] = useState(true);
|
|
42
|
+
const [robotsEnqueueStudy, setRobotsEnqueueStudy] = useState(true);
|
|
43
|
+
|
|
44
|
+
const [searchConfig, setSearchConfig] = useState(null);
|
|
45
|
+
const [searchAddUrl, setSearchAddUrl] = useState('');
|
|
46
|
+
const [searchTestQuery, setSearchTestQuery] = useState('');
|
|
47
|
+
const [searchTestResult, setSearchTestResult] = useState(null);
|
|
48
|
+
|
|
49
|
+
const [graphGroupId, setGraphGroupId] = useState('');
|
|
50
|
+
const [graphSeedsText, setGraphSeedsText] = useState('');
|
|
51
|
+
const [graphRadius, setGraphRadius] = useState('2');
|
|
52
|
+
const [graphExportBusy, setGraphExportBusy] = useState(false);
|
|
53
|
+
const [graphExportResult, setGraphExportResult] = useState(null);
|
|
54
|
+
|
|
55
|
+
const featureSummary = useMemo(() => {
|
|
56
|
+
if (!features) return null;
|
|
57
|
+
return {
|
|
58
|
+
memebarrier: features.memebarrier,
|
|
59
|
+
rl: features.rl,
|
|
60
|
+
adv: features.adv,
|
|
61
|
+
thresholds: features.dialogThresholds
|
|
62
|
+
};
|
|
63
|
+
}, [features]);
|
|
64
|
+
|
|
65
|
+
const refreshAll = async () => {
|
|
66
|
+
setLoading(true);
|
|
67
|
+
try {
|
|
68
|
+
const [f, t, r, s, sc, cfg, groups] = await Promise.all([
|
|
69
|
+
api.runtimeFeatures(),
|
|
70
|
+
api.testsList(),
|
|
71
|
+
api.robotsList(),
|
|
72
|
+
api.studyStatus(),
|
|
73
|
+
api.searchConfig(),
|
|
74
|
+
api.systemConfig().catch(() => null),
|
|
75
|
+
api.groups().catch(() => null)
|
|
76
|
+
]);
|
|
77
|
+
setFeatures(f?.features || null);
|
|
78
|
+
setMaliciousThreshold(String(f?.features?.memebarrier?.threshold ?? ''));
|
|
79
|
+
setRlEvery(String(f?.features?.dialogThresholds?.rlEvery ?? ''));
|
|
80
|
+
setAdvEvery(String(f?.features?.dialogThresholds?.advEvery ?? ''));
|
|
81
|
+
|
|
82
|
+
setTestsDir(t?.directory || '');
|
|
83
|
+
setTestsFiles(Array.isArray(t?.files) ? t.files : []);
|
|
84
|
+
|
|
85
|
+
setRobotsFiles(Array.isArray(r?.files) ? r.files : []);
|
|
86
|
+
setStudyStatus(s || null);
|
|
87
|
+
setSearchConfig(sc?.config || null);
|
|
88
|
+
|
|
89
|
+
setSystemConfig(cfg?.config || null);
|
|
90
|
+
setGroupsInfo(groups || null);
|
|
91
|
+
|
|
92
|
+
const firstGroup = (groups?.groups && groups.groups[0]?.gid) || (cfg?.config?.groupIds && cfg.config.groupIds[0]) || '';
|
|
93
|
+
if (!graphGroupId && firstGroup) setGraphGroupId(firstGroup);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
onError?.(e.message);
|
|
96
|
+
} finally {
|
|
97
|
+
setLoading(false);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const parseSeeds = (text) => {
|
|
102
|
+
const raw = String(text || '')
|
|
103
|
+
.split(/\r?\n|,|;/g)
|
|
104
|
+
.map((s) => s.trim())
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
return raw.length ? raw : undefined;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const exportGraphForGroup = async () => {
|
|
110
|
+
if (!graphGroupId) {
|
|
111
|
+
onError?.('groupId required');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
setGraphExportBusy(true);
|
|
115
|
+
try {
|
|
116
|
+
const seeds = parseSeeds(graphSeedsText);
|
|
117
|
+
const radius = asNum(graphRadius, 2);
|
|
118
|
+
const r = await api.exportGraphGroup(graphGroupId, seeds, radius);
|
|
119
|
+
setGraphExportResult(r || null);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
onError?.(e.message);
|
|
122
|
+
} finally {
|
|
123
|
+
setGraphExportBusy(false);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const downloadGraphTxt = () => {
|
|
128
|
+
const content = graphExportResult?.content;
|
|
129
|
+
if (!content) return;
|
|
130
|
+
const nameFromApi = graphExportResult?.file;
|
|
131
|
+
const safeGroup = String(graphGroupId || 'group').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
132
|
+
const filename = nameFromApi ? String(nameFromApi).replace(/\.(json|txt)$/i, '') + '.txt' : `graph_${safeGroup}.txt`;
|
|
133
|
+
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
|
134
|
+
const url = URL.createObjectURL(blob);
|
|
135
|
+
const a = document.createElement('a');
|
|
136
|
+
a.href = url;
|
|
137
|
+
a.download = filename;
|
|
138
|
+
document.body.appendChild(a);
|
|
139
|
+
a.click();
|
|
140
|
+
a.remove();
|
|
141
|
+
URL.revokeObjectURL(url);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const updateSearchConfig = async (patch) => {
|
|
145
|
+
try {
|
|
146
|
+
const current = searchConfig || { enabled: true, active: '', endpoints: [] };
|
|
147
|
+
const next = { ...current, ...(patch || {}) };
|
|
148
|
+
const r = await api.setSearchConfig(next);
|
|
149
|
+
setSearchConfig(r?.config || null);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
onError?.(e.message);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
refreshAll();
|
|
157
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
158
|
+
}, []);
|
|
159
|
+
|
|
160
|
+
const applyPatch = async (patch) => {
|
|
161
|
+
setPatchBusy(true);
|
|
162
|
+
try {
|
|
163
|
+
const r = await api.runtimePatch(patch);
|
|
164
|
+
setFeatures(r?.features || null);
|
|
165
|
+
setMaliciousThreshold(String(r?.features?.memebarrier?.threshold ?? ''));
|
|
166
|
+
setRlEvery(String(r?.features?.dialogThresholds?.rlEvery ?? ''));
|
|
167
|
+
setAdvEvery(String(r?.features?.dialogThresholds?.advEvery ?? ''));
|
|
168
|
+
if (Array.isArray(r?.result?.warnings) && r.result.warnings.length) {
|
|
169
|
+
onError?.(r.result.warnings.join(' | '));
|
|
170
|
+
}
|
|
171
|
+
} catch (e) {
|
|
172
|
+
onError?.(e.message);
|
|
173
|
+
} finally {
|
|
174
|
+
setPatchBusy(false);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const saveThresholds = async () => {
|
|
179
|
+
await applyPatch({ rlEvery: asNum(rlEvery, undefined), advEvery: asNum(advEvery, undefined) });
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const saveBarrierThreshold = async () => {
|
|
183
|
+
await applyPatch({ maliciousThreshold: asNum(maliciousThreshold, undefined) });
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const createTestCase = async () => {
|
|
187
|
+
if (!newTestName.trim()) {
|
|
188
|
+
onError?.('name required');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
await api.testsCase(newTestName.trim(), newTestContent);
|
|
193
|
+
setNewTestName('');
|
|
194
|
+
setNewTestContent('');
|
|
195
|
+
const t = await api.testsList();
|
|
196
|
+
setTestsDir(t?.directory || '');
|
|
197
|
+
setTestsFiles(Array.isArray(t?.files) ? t.files : []);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
onError?.(e.message);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const refreshTests = async () => {
|
|
204
|
+
try {
|
|
205
|
+
await api.testsRefresh();
|
|
206
|
+
} catch (e) {
|
|
207
|
+
onError?.(e.message);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const retrainRobots = async () => {
|
|
212
|
+
try {
|
|
213
|
+
await api.robotsRetrain({
|
|
214
|
+
files: robotsSelected.length ? robotsSelected : undefined,
|
|
215
|
+
limit: asNum(robotsLimit, undefined),
|
|
216
|
+
shuffle: Boolean(robotsShuffle),
|
|
217
|
+
enqueueStudy: Boolean(robotsEnqueueStudy)
|
|
218
|
+
});
|
|
219
|
+
} catch (e) {
|
|
220
|
+
onError?.(e.message);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div className="cfg">
|
|
226
|
+
<div className="cfg-head">
|
|
227
|
+
<div>
|
|
228
|
+
<div className="cfg-title">Config</div>
|
|
229
|
+
<div className="cfg-sub">运行时开关 / tests 刷新 / robots 重训</div>
|
|
230
|
+
</div>
|
|
231
|
+
<div className="cfg-actions">
|
|
232
|
+
<button className="btn btn-ghost" disabled={loading} onClick={refreshAll}>
|
|
233
|
+
刷新
|
|
234
|
+
</button>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<div className="cfg-grid">
|
|
239
|
+
<section className="card">
|
|
240
|
+
<div className="card-title">工作组 / 图导出</div>
|
|
241
|
+
<div className="muted">组数/组大小为启动配置(修改需重启)</div>
|
|
242
|
+
|
|
243
|
+
<div className="cfg-kv" style={{ marginTop: 10 }}>
|
|
244
|
+
<div className="muted">groupCount / groupSize</div>
|
|
245
|
+
<div className="mono">{String(systemConfig?.groupCount ?? '-')}/{String(systemConfig?.groupSize ?? '-')}</div>
|
|
246
|
+
</div>
|
|
247
|
+
<div className="cfg-kv">
|
|
248
|
+
<div className="muted">groupIds</div>
|
|
249
|
+
<div className="mono">{Array.isArray(systemConfig?.groupIds) ? systemConfig.groupIds.join(', ') : '-'}</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div className="hr" />
|
|
253
|
+
|
|
254
|
+
<div className="card-subtitle">按组导出 graph JSON</div>
|
|
255
|
+
<FieldRow label="Group" hint="选择一个工作组导出其 graph">
|
|
256
|
+
<select className="input" value={graphGroupId} onChange={(e) => setGraphGroupId(e.target.value)}>
|
|
257
|
+
{(groupsInfo?.groups || []).map((g) => (
|
|
258
|
+
<option key={g.gid} value={g.gid}>
|
|
259
|
+
{g.gid}
|
|
260
|
+
</option>
|
|
261
|
+
))}
|
|
262
|
+
</select>
|
|
263
|
+
</FieldRow>
|
|
264
|
+
<FieldRow label="Radius" hint="图窗口半径">
|
|
265
|
+
<input className="input" value={graphRadius} onChange={(e) => setGraphRadius(e.target.value)} placeholder="2" />
|
|
266
|
+
</FieldRow>
|
|
267
|
+
<FieldRow label="Seeds(可选)" hint="逗号/分号/换行分隔;留空表示导出默认窗口">
|
|
268
|
+
<textarea className="textarea" value={graphSeedsText} onChange={(e) => setGraphSeedsText(e.target.value)} placeholder="seedA, seedB\nseedC" />
|
|
269
|
+
</FieldRow>
|
|
270
|
+
<div className="cfg-inline">
|
|
271
|
+
<button className="btn" disabled={graphExportBusy || !graphGroupId} onClick={exportGraphForGroup}>
|
|
272
|
+
{graphExportBusy ? '导出中…' : '导出'}
|
|
273
|
+
</button>
|
|
274
|
+
<button
|
|
275
|
+
className="btn btn-ghost"
|
|
276
|
+
disabled={!graphExportResult?.content}
|
|
277
|
+
onClick={() => {
|
|
278
|
+
if (!graphExportResult?.content) return;
|
|
279
|
+
navigator.clipboard?.writeText(graphExportResult.content).catch(() => {});
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
复制 JSON
|
|
283
|
+
</button>
|
|
284
|
+
<button className="btn btn-ghost" disabled={!graphExportResult?.content} onClick={downloadGraphTxt}>
|
|
285
|
+
下载 .txt
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
288
|
+
{graphExportResult?.content ? (
|
|
289
|
+
<pre className="mono" style={{ whiteSpace: 'pre-wrap', marginTop: 10, maxHeight: 260, overflow: 'auto' }}>
|
|
290
|
+
{graphExportResult.content}
|
|
291
|
+
</pre>
|
|
292
|
+
) : null}
|
|
293
|
+
</section>
|
|
294
|
+
|
|
295
|
+
<section className="card">
|
|
296
|
+
<div className="card-title">运行时开关</div>
|
|
297
|
+
|
|
298
|
+
<FieldRow label="MemeBarrier" hint="启用/停用隔离扫描">
|
|
299
|
+
<div className="cfg-inline">
|
|
300
|
+
<button className="btn" disabled={patchBusy} onClick={() => applyPatch({ memebarrierEnabled: true })}>
|
|
301
|
+
启用
|
|
302
|
+
</button>
|
|
303
|
+
<button className="btn btn-ghost" disabled={patchBusy} onClick={() => applyPatch({ memebarrierEnabled: false })}>
|
|
304
|
+
停用
|
|
305
|
+
</button>
|
|
306
|
+
<span className="pill">{featureSummary?.memebarrier?.enabled ? 'running' : 'stopped'}</span>
|
|
307
|
+
</div>
|
|
308
|
+
</FieldRow>
|
|
309
|
+
|
|
310
|
+
<FieldRow label="Barrier 阈值" hint="maliciousThreshold">
|
|
311
|
+
<div className="cfg-inline">
|
|
312
|
+
<input className="input" value={maliciousThreshold} onChange={(e) => setMaliciousThreshold(e.target.value)} placeholder="e.g. 0.82" />
|
|
313
|
+
<button className="btn" disabled={patchBusy} onClick={saveBarrierThreshold}>
|
|
314
|
+
保存
|
|
315
|
+
</button>
|
|
316
|
+
</div>
|
|
317
|
+
</FieldRow>
|
|
318
|
+
|
|
319
|
+
<FieldRow label="RL" hint="强化学习端点与对话触发">
|
|
320
|
+
<div className="cfg-inline">
|
|
321
|
+
<button className="btn" disabled={patchBusy} onClick={() => applyPatch({ rlEnabled: true })}>
|
|
322
|
+
启用
|
|
323
|
+
</button>
|
|
324
|
+
<button className="btn btn-ghost" disabled={patchBusy} onClick={() => applyPatch({ rlEnabled: false })}>
|
|
325
|
+
停用
|
|
326
|
+
</button>
|
|
327
|
+
<span className="pill">{featureSummary?.rl?.enabled ? 'enabled' : 'disabled'}</span>
|
|
328
|
+
</div>
|
|
329
|
+
</FieldRow>
|
|
330
|
+
|
|
331
|
+
<FieldRow label="ADV" hint="对抗学习端点与对话触发">
|
|
332
|
+
<div className="cfg-inline">
|
|
333
|
+
<button className="btn" disabled={patchBusy} onClick={() => applyPatch({ advEnabled: true })}>
|
|
334
|
+
启用
|
|
335
|
+
</button>
|
|
336
|
+
<button className="btn btn-ghost" disabled={patchBusy} onClick={() => applyPatch({ advEnabled: false })}>
|
|
337
|
+
停用
|
|
338
|
+
</button>
|
|
339
|
+
<span className="pill">{featureSummary?.adv?.enabled ? 'enabled' : 'disabled'}</span>
|
|
340
|
+
</div>
|
|
341
|
+
</FieldRow>
|
|
342
|
+
|
|
343
|
+
<FieldRow label="对话触发学习" hint="开启后才会按阈值触发 RL/ADV">
|
|
344
|
+
<div className="cfg-inline">
|
|
345
|
+
<button
|
|
346
|
+
className="btn"
|
|
347
|
+
disabled={patchBusy}
|
|
348
|
+
onClick={() => applyPatch({ dialogLearningEnabled: true })}
|
|
349
|
+
>
|
|
350
|
+
启用
|
|
351
|
+
</button>
|
|
352
|
+
<button
|
|
353
|
+
className="btn btn-ghost"
|
|
354
|
+
disabled={patchBusy}
|
|
355
|
+
onClick={() => applyPatch({ dialogLearningEnabled: false })}
|
|
356
|
+
>
|
|
357
|
+
停用
|
|
358
|
+
</button>
|
|
359
|
+
<span className="pill">{features?.dialogLearning?.enabled ? 'enabled' : 'disabled'}</span>
|
|
360
|
+
<button
|
|
361
|
+
className="btn btn-ghost"
|
|
362
|
+
disabled={patchBusy}
|
|
363
|
+
onClick={async () => {
|
|
364
|
+
try {
|
|
365
|
+
await api.dialogReset();
|
|
366
|
+
const f = await api.runtimeFeatures();
|
|
367
|
+
setFeatures(f?.features || null);
|
|
368
|
+
} catch (e) {
|
|
369
|
+
onError?.(e.message);
|
|
370
|
+
}
|
|
371
|
+
}}
|
|
372
|
+
>
|
|
373
|
+
重置计数
|
|
374
|
+
</button>
|
|
375
|
+
</div>
|
|
376
|
+
</FieldRow>
|
|
377
|
+
|
|
378
|
+
<div className="hr" />
|
|
379
|
+
|
|
380
|
+
<FieldRow label="对话触发阈值" hint="每 N 次对话触发学习">
|
|
381
|
+
<div className="cfg-inline">
|
|
382
|
+
<input className="input" value={rlEvery} onChange={(e) => setRlEvery(e.target.value)} placeholder="rlEvery" />
|
|
383
|
+
<input className="input" value={advEvery} onChange={(e) => setAdvEvery(e.target.value)} placeholder="advEvery" />
|
|
384
|
+
<button className="btn" disabled={patchBusy} onClick={saveThresholds}>
|
|
385
|
+
保存
|
|
386
|
+
</button>
|
|
387
|
+
</div>
|
|
388
|
+
</FieldRow>
|
|
389
|
+
|
|
390
|
+
<div className="cfg-kv">
|
|
391
|
+
<div className="muted">对话计数</div>
|
|
392
|
+
<div className="mono">total={features?.dialogCounters?.total ?? 0}, lastRL={features?.dialogCounters?.lastRL ?? 0}, lastADV={features?.dialogCounters?.lastADV ?? 0}</div>
|
|
393
|
+
</div>
|
|
394
|
+
</section>
|
|
395
|
+
|
|
396
|
+
<section className="card">
|
|
397
|
+
<div className="card-title">Study 状态</div>
|
|
398
|
+
<div className="muted">显示 study 队列与 worker 错误(用于排查为何未学习)</div>
|
|
399
|
+
|
|
400
|
+
<div className="cfg-kv" style={{ marginTop: 10 }}>
|
|
401
|
+
<div className="muted">running</div>
|
|
402
|
+
<div className="mono">{String(studyStatus?.running ?? false)}</div>
|
|
403
|
+
</div>
|
|
404
|
+
<div className="cfg-kv">
|
|
405
|
+
<div className="muted">queue</div>
|
|
406
|
+
<div className="mono">{String(studyStatus?.queue ?? 0)}</div>
|
|
407
|
+
</div>
|
|
408
|
+
<div className="cfg-kv">
|
|
409
|
+
<div className="muted">metrics</div>
|
|
410
|
+
<div className="mono">
|
|
411
|
+
enqueued={studyStatus?.metrics?.enqueued ?? 0}, processed={studyStatus?.metrics?.processed ?? 0}, lastError={studyStatus?.metrics?.lastError ?? 'null'}
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<div className="cfg-inline" style={{ marginTop: 10 }}>
|
|
416
|
+
<button
|
|
417
|
+
className="btn"
|
|
418
|
+
onClick={async () => {
|
|
419
|
+
try {
|
|
420
|
+
const s = await api.studyStatus();
|
|
421
|
+
setStudyStatus(s || null);
|
|
422
|
+
} catch (e) {
|
|
423
|
+
onError?.(e.message);
|
|
424
|
+
}
|
|
425
|
+
}}
|
|
426
|
+
>
|
|
427
|
+
刷新 Study 状态
|
|
428
|
+
</button>
|
|
429
|
+
</div>
|
|
430
|
+
</section>
|
|
431
|
+
|
|
432
|
+
<section className="card">
|
|
433
|
+
<div className="card-title">Tests 用例(运行时刷新)</div>
|
|
434
|
+
<div className="muted">目录:{testsDir || '-'}</div>
|
|
435
|
+
|
|
436
|
+
<div className="cfg-inline" style={{ marginTop: 10 }}>
|
|
437
|
+
<button className="btn" onClick={refreshTests}>
|
|
438
|
+
刷新 tests 词表
|
|
439
|
+
</button>
|
|
440
|
+
<button
|
|
441
|
+
className="btn btn-ghost"
|
|
442
|
+
onClick={async () => {
|
|
443
|
+
try {
|
|
444
|
+
const t = await api.testsList();
|
|
445
|
+
setTestsDir(t?.directory || '');
|
|
446
|
+
setTestsFiles(Array.isArray(t?.files) ? t.files : []);
|
|
447
|
+
} catch (e) {
|
|
448
|
+
onError?.(e.message);
|
|
449
|
+
}
|
|
450
|
+
}}
|
|
451
|
+
>
|
|
452
|
+
重新列出
|
|
453
|
+
</button>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
456
|
+
<div className="cfg-list">
|
|
457
|
+
{(testsFiles || []).slice(0, 50).map((f) => (
|
|
458
|
+
<div key={f} className="cfg-list-item">
|
|
459
|
+
<span className="mono">{f}</span>
|
|
460
|
+
</div>
|
|
461
|
+
))}
|
|
462
|
+
{testsFiles.length > 50 ? <div className="muted">仅显示前 50 个</div> : null}
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
<div className="hr" />
|
|
466
|
+
|
|
467
|
+
<div className="card-subtitle">新增/覆盖用例</div>
|
|
468
|
+
<FieldRow label="文件名" hint="自动补 .txt,非法字符会被替换">
|
|
469
|
+
<input className="input" value={newTestName} onChange={(e) => setNewTestName(e.target.value)} placeholder="case_001" />
|
|
470
|
+
</FieldRow>
|
|
471
|
+
<FieldRow label="内容" hint="任意文本;RL 侧按你的实现读取">
|
|
472
|
+
<textarea className="textarea" value={newTestContent} onChange={(e) => setNewTestContent(e.target.value)} placeholder="输入测试文本…" />
|
|
473
|
+
</FieldRow>
|
|
474
|
+
<button className="btn" onClick={createTestCase}>
|
|
475
|
+
写入用例
|
|
476
|
+
</button>
|
|
477
|
+
</section>
|
|
478
|
+
|
|
479
|
+
<section className="card">
|
|
480
|
+
<div className="card-title">Robots 重训(重新 ingest)</div>
|
|
481
|
+
<div className="muted">选择 robots 文件并触发重新训练/重建词表</div>
|
|
482
|
+
|
|
483
|
+
<FieldRow label="limit" hint="本次 ingest 文档数量(可空)">
|
|
484
|
+
<input className="input" value={robotsLimit} onChange={(e) => setRobotsLimit(e.target.value)} placeholder="10" />
|
|
485
|
+
</FieldRow>
|
|
486
|
+
<FieldRow label="shuffle" hint="随机抽样">
|
|
487
|
+
<label className="toggle">
|
|
488
|
+
<input type="checkbox" checked={robotsShuffle} onChange={(e) => setRobotsShuffle(e.target.checked)} />
|
|
489
|
+
<span>启用</span>
|
|
490
|
+
</label>
|
|
491
|
+
</FieldRow>
|
|
492
|
+
<FieldRow label="enqueueStudy" hint="同时推送到 study 队列">
|
|
493
|
+
<label className="toggle">
|
|
494
|
+
<input type="checkbox" checked={robotsEnqueueStudy} onChange={(e) => setRobotsEnqueueStudy(e.target.checked)} />
|
|
495
|
+
<span>启用</span>
|
|
496
|
+
</label>
|
|
497
|
+
</FieldRow>
|
|
498
|
+
|
|
499
|
+
<div className="card-subtitle">选择文件(可多选)</div>
|
|
500
|
+
<div className="cfg-checklist">
|
|
501
|
+
{(robotsFiles || []).slice(0, 80).map((f) => {
|
|
502
|
+
const checked = robotsSelected.includes(f);
|
|
503
|
+
return (
|
|
504
|
+
<label key={f} className="check">
|
|
505
|
+
<input
|
|
506
|
+
type="checkbox"
|
|
507
|
+
checked={checked}
|
|
508
|
+
onChange={(e) => {
|
|
509
|
+
const on = e.target.checked;
|
|
510
|
+
setRobotsSelected((prev) => (on ? Array.from(new Set([...prev, f])) : prev.filter((x) => x !== f)));
|
|
511
|
+
}}
|
|
512
|
+
/>
|
|
513
|
+
<span className="mono">{f}</span>
|
|
514
|
+
</label>
|
|
515
|
+
);
|
|
516
|
+
})}
|
|
517
|
+
{robotsFiles.length > 80 ? <div className="muted">仅显示前 80 个</div> : null}
|
|
518
|
+
</div>
|
|
519
|
+
|
|
520
|
+
<div className="cfg-inline" style={{ marginTop: 10 }}>
|
|
521
|
+
<button className="btn" onClick={retrainRobots}>
|
|
522
|
+
触发重训
|
|
523
|
+
</button>
|
|
524
|
+
<button className="btn btn-ghost" onClick={() => setRobotsSelected([])}>
|
|
525
|
+
清空选择
|
|
526
|
+
</button>
|
|
527
|
+
</div>
|
|
528
|
+
</section>
|
|
529
|
+
|
|
530
|
+
<section className="card">
|
|
531
|
+
<div className="card-title">线上搜索(Online Research)</div>
|
|
532
|
+
<div className="muted">运行时开关 + 搜索网址库(endpoint 列表)</div>
|
|
533
|
+
|
|
534
|
+
<FieldRow label="启用线上搜索" hint="关闭后 /api/corpus/online 将直接走本地 fallback">
|
|
535
|
+
<div className="cfg-inline">
|
|
536
|
+
<button className="btn" onClick={() => updateSearchConfig({ enabled: true })}>
|
|
537
|
+
启用
|
|
538
|
+
</button>
|
|
539
|
+
<button className="btn btn-ghost" onClick={() => updateSearchConfig({ enabled: false })}>
|
|
540
|
+
停用
|
|
541
|
+
</button>
|
|
542
|
+
<span className="pill">{searchConfig?.enabled ? 'enabled' : 'disabled'}</span>
|
|
543
|
+
</div>
|
|
544
|
+
</FieldRow>
|
|
545
|
+
|
|
546
|
+
<FieldRow label="当前 endpoint" hint="用于远程 GET 请求的 base URL">
|
|
547
|
+
<div className="cfg-inline">
|
|
548
|
+
<select
|
|
549
|
+
className="input"
|
|
550
|
+
value={searchConfig?.active || ''}
|
|
551
|
+
onChange={(e) => updateSearchConfig({ active: e.target.value })}
|
|
552
|
+
>
|
|
553
|
+
<option value="">(空)</option>
|
|
554
|
+
{(searchConfig?.endpoints || []).map((u) => (
|
|
555
|
+
<option key={u} value={u}>
|
|
556
|
+
{u}
|
|
557
|
+
</option>
|
|
558
|
+
))}
|
|
559
|
+
</select>
|
|
560
|
+
<button
|
|
561
|
+
className="btn btn-ghost"
|
|
562
|
+
onClick={() => {
|
|
563
|
+
if (!searchConfig?.active) return;
|
|
564
|
+
navigator.clipboard?.writeText(searchConfig.active).catch(() => {});
|
|
565
|
+
}}
|
|
566
|
+
>
|
|
567
|
+
复制
|
|
568
|
+
</button>
|
|
569
|
+
</div>
|
|
570
|
+
</FieldRow>
|
|
571
|
+
|
|
572
|
+
<div className="hr" />
|
|
573
|
+
|
|
574
|
+
<div className="card-subtitle">添加 endpoint</div>
|
|
575
|
+
<div className="cfg-inline">
|
|
576
|
+
<input className="input" value={searchAddUrl} onChange={(e) => setSearchAddUrl(e.target.value)} placeholder="https://example.com/search" />
|
|
577
|
+
<button
|
|
578
|
+
className="btn"
|
|
579
|
+
onClick={async () => {
|
|
580
|
+
const url = searchAddUrl.trim();
|
|
581
|
+
if (!url) return;
|
|
582
|
+
try {
|
|
583
|
+
const r = await api.searchEndpointAdd(url);
|
|
584
|
+
setSearchConfig(r?.config || null);
|
|
585
|
+
setSearchAddUrl('');
|
|
586
|
+
} catch (e) {
|
|
587
|
+
onError?.(e.message);
|
|
588
|
+
}
|
|
589
|
+
}}
|
|
590
|
+
>
|
|
591
|
+
添加
|
|
592
|
+
</button>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<div className="cfg-list">
|
|
596
|
+
{(searchConfig?.endpoints || []).map((u) => (
|
|
597
|
+
<div key={u} className="cfg-list-item" style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center' }}>
|
|
598
|
+
<span className="mono" style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{u}</span>
|
|
599
|
+
<button
|
|
600
|
+
className="btn btn-ghost"
|
|
601
|
+
onClick={async () => {
|
|
602
|
+
try {
|
|
603
|
+
const r = await api.searchEndpointRemove(u);
|
|
604
|
+
setSearchConfig(r?.config || null);
|
|
605
|
+
} catch (e) {
|
|
606
|
+
onError?.(e.message);
|
|
607
|
+
}
|
|
608
|
+
}}
|
|
609
|
+
>
|
|
610
|
+
删除
|
|
611
|
+
</button>
|
|
612
|
+
</div>
|
|
613
|
+
))}
|
|
614
|
+
{!searchConfig?.endpoints?.length ? <div className="muted">暂无 endpoint</div> : null}
|
|
615
|
+
</div>
|
|
616
|
+
|
|
617
|
+
<div className="hr" />
|
|
618
|
+
|
|
619
|
+
<div className="card-subtitle">快速测试</div>
|
|
620
|
+
<div className="cfg-inline">
|
|
621
|
+
<input className="input" value={searchTestQuery} onChange={(e) => setSearchTestQuery(e.target.value)} placeholder="输入 query,然后走 /api/corpus/online" />
|
|
622
|
+
<button
|
|
623
|
+
className="btn"
|
|
624
|
+
onClick={async () => {
|
|
625
|
+
try {
|
|
626
|
+
const q = searchTestQuery.trim();
|
|
627
|
+
if (!q) return;
|
|
628
|
+
const r = await api.requestOnline(q);
|
|
629
|
+
setSearchTestResult(r?.result || r);
|
|
630
|
+
} catch (e) {
|
|
631
|
+
onError?.(e.message);
|
|
632
|
+
}
|
|
633
|
+
}}
|
|
634
|
+
>
|
|
635
|
+
测试
|
|
636
|
+
</button>
|
|
637
|
+
</div>
|
|
638
|
+
{searchTestResult ? <pre className="mono" style={{ whiteSpace: 'pre-wrap', marginTop: 10 }}>{JSON.stringify(searchTestResult, null, 2)}</pre> : null}
|
|
639
|
+
</section>
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
);
|
|
643
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
background: #0b0f19;
|
|
4
|
+
color: #e6e8ee;
|
|
5
|
+
overflow-x: hidden;
|
|
6
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
7
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
8
|
+
sans-serif;
|
|
9
|
+
-webkit-font-smoothing: antialiased;
|
|
10
|
+
-moz-osx-font-smoothing: grayscale;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
html, body, #root {
|
|
14
|
+
height: 100%;
|
|
15
|
+
background: #0b0f19;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
code {
|
|
19
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
20
|
+
monospace;
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import './index.css';
|
|
4
|
+
import App from './App';
|
|
5
|
+
import reportWebVitals from './reportWebVitals';
|
|
6
|
+
|
|
7
|
+
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
8
|
+
root.render(
|
|
9
|
+
<React.StrictMode>
|
|
10
|
+
<App />
|
|
11
|
+
</React.StrictMode>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// If you want to start measuring performance in your app, pass a function
|
|
15
|
+
// to log results (for example: reportWebVitals(console.log))
|
|
16
|
+
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
17
|
+
reportWebVitals();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|