dsh-manage-session 0.5.4 → 0.5.7
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/CHANGELOG.ja.md +22 -0
- package/CHANGELOG.ko.md +22 -0
- package/CHANGELOG.md +22 -0
- package/README.en.md +13 -11
- package/README.md +11 -10
- package/dsh.plugin.json +2 -2
- package/lib/client.js +900 -386
- package/lib/index.d.ts +241 -16
- package/lib/index.js +526 -71
- package/package.json +15 -9
- package/src/client/card.tsx +14 -10
- package/src/client/index.ts +53 -15
- package/src/client/locales.ts +36 -0
- package/src/client/panel.tsx +26 -36
- package/src/client/search/host-api.ts +5 -0
- package/src/client/search/locales.ts +81 -0
- package/src/client/search/locate.ts +330 -0
- package/src/client/search/manage.tsx +6 -2
- package/src/client/search/panel.tsx +154 -29
- package/src/client/session-menu-legacy.ts +249 -0
- package/src/client/session-menu.tsx +96 -0
- package/src/config.ts +17 -0
- package/src/host/history/tombstones.ts +100 -0
- package/src/host/index/drs-source.ts +211 -0
- package/src/host/index/engine.ts +699 -571
- package/src/host/index/rebuild.ts +432 -368
- package/src/host/index/sync.ts +413 -286
- package/src/host/settings-bridge.ts +27 -0
- package/src/index.ts +1499 -1421
- package/src/client/history-panel.tsx +0 -319
package/lib/client.js
CHANGED
|
@@ -12,7 +12,8 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
enabled: true,
|
|
13
13
|
historyFiles: true,
|
|
14
14
|
healthCheck: true,
|
|
15
|
-
search: true
|
|
15
|
+
search: true,
|
|
16
|
+
directDelete: false
|
|
16
17
|
};
|
|
17
18
|
/** host 半身注册的设置命名空间(与 src/index.ts 保持一致)。 */
|
|
18
19
|
const STEWARD_SETTINGS_NAMESPACE = "session-steward";
|
|
@@ -30,6 +31,10 @@ window.__ModuleLoader__.load({
|
|
|
30
31
|
"card.history.desc": "归档浏览与清理(养老院)。关闭后不注册历史接口、不渲染该页签。",
|
|
31
32
|
"card.health": "健康检查",
|
|
32
33
|
"card.health.desc": "体检 → 处方 → 出院。关闭后不注册体检接口、不渲染该页签。",
|
|
34
|
+
"card.directDelete": "直接删除入口",
|
|
35
|
+
"card.directDelete.desc": "开启后搜索面板结果行出现「删除」按钮(两步确认,与管理端同款链路)。默认关闭。",
|
|
36
|
+
"menu.deleteSession": "删除会话",
|
|
37
|
+
"menu.deleteSession.confirm": "确认删除",
|
|
33
38
|
"card.readonly": "当前会话无法修改设置(只读挂载)。",
|
|
34
39
|
"card.unavailable": "设置服务不可用,无法读取配置。",
|
|
35
40
|
"family.title": "起子插件设置",
|
|
@@ -123,6 +128,10 @@ window.__ModuleLoader__.load({
|
|
|
123
128
|
"card.history.desc": "Archive browsing and cleanup (Retirement Home). When off, history routes and the tab are gone.",
|
|
124
129
|
"card.health": "Health check",
|
|
125
130
|
"card.health.desc": "Checkup → Prescription → Discharge. When off, health routes and the tab are gone.",
|
|
131
|
+
"card.directDelete": "Direct delete entry",
|
|
132
|
+
"card.directDelete.desc": "When on, result rows in the search panel get a Delete button (two-step confirm, same pipeline as the manage view). Off by default.",
|
|
133
|
+
"menu.deleteSession": "Delete session",
|
|
134
|
+
"menu.deleteSession.confirm": "Confirm delete",
|
|
126
135
|
"card.readonly": "This session cannot edit settings (read-only mount).",
|
|
127
136
|
"card.unavailable": "Settings service unavailable; configuration cannot be read.",
|
|
128
137
|
"family.title": "Screwdriver plugin settings",
|
|
@@ -210,6 +219,10 @@ window.__ModuleLoader__.load({
|
|
|
210
219
|
"card.history.desc": "アーカイブの閲覧とクリーンアップ(老人ホーム)。オフにすると履歴 API を登録せず、このタブも描画しません。",
|
|
211
220
|
"card.health": "ヘルスチェック",
|
|
212
221
|
"card.health.desc": "健診 → 処方 → 退院。オフにすると健診 API を登録せず、このタブも描画しません。",
|
|
222
|
+
"card.directDelete": "直接削除の入口",
|
|
223
|
+
"card.directDelete.desc": "オンにすると検索パネルの結果行に「削除」ボタンが表示されます(二段階確認、管理画面と同じ経路)。デフォルトはオフ。",
|
|
224
|
+
"menu.deleteSession": "セッションを削除",
|
|
225
|
+
"menu.deleteSession.confirm": "削除を確認",
|
|
213
226
|
"card.readonly": "このセッションでは設定を変更できません(読み取り専用マウント)。",
|
|
214
227
|
"card.unavailable": "設定サービスが利用できず、構成を読み取れません。",
|
|
215
228
|
"family.title": "スクリュードライバー プラグイン設定",
|
|
@@ -296,6 +309,10 @@ window.__ModuleLoader__.load({
|
|
|
296
309
|
"card.history.desc": "아카이브 탐색 및 정리(요양소). 끄면 기록 API를 등록하지 않고 이 탭을 렌더링하지 않습니다.",
|
|
297
310
|
"card.health": "건강 점검",
|
|
298
311
|
"card.health.desc": "진료 → 처방 → 퇴원. 끄면 진료 API를 등록하지 않고 이 탭을 렌더링하지 않습니다.",
|
|
312
|
+
"card.directDelete": "직접 삭제 입구",
|
|
313
|
+
"card.directDelete.desc": "켜면 검색 패널 결과 행에 「삭제」 버튼이 표시됩니다(2단계 확인, 관리 화면과 동일한 경로). 기본은 끄기.",
|
|
314
|
+
"menu.deleteSession": "세션 삭제",
|
|
315
|
+
"menu.deleteSession.confirm": "삭제 확인",
|
|
299
316
|
"card.readonly": "이 세션에서는 설정을 수정할 수 없습니다(읽기 전용 마운트).",
|
|
300
317
|
"card.unavailable": "설정 서비스를 사용할 수 없어 구성을 읽을 수 없습니다.",
|
|
301
318
|
"family.title": "스크루드라이버 플러그인 설정",
|
|
@@ -382,6 +399,10 @@ window.__ModuleLoader__.load({
|
|
|
382
399
|
"card.history.desc": "Consultation et nettoyage des archives (Maison de retraite). Désactivé, aucune API d’historique n’est enregistrée et cet onglet n’est pas rendu.",
|
|
383
400
|
"card.health": "Contrôle de santé",
|
|
384
401
|
"card.health.desc": "Examen → Ordonnance → Sortie. Désactivé, aucune API de contrôle n’est enregistrée et cet onglet n’est pas rendu.",
|
|
402
|
+
"card.directDelete": "Entrée de suppression directe",
|
|
403
|
+
"card.directDelete.desc": "Activé, les lignes de résultat du panneau de recherche affichent un bouton Supprimer (double confirmation, même pipeline que la gestion). Désactivé par défaut.",
|
|
404
|
+
"menu.deleteSession": "Supprimer la session",
|
|
405
|
+
"menu.deleteSession.confirm": "Confirmer la suppression",
|
|
385
406
|
"card.readonly": "Cette session ne peut pas modifier les réglages (montage en lecture seule).",
|
|
386
407
|
"card.unavailable": "Service de réglages indisponible ; configuration illisible.",
|
|
387
408
|
"family.title": "Réglages du plugin Screwdriver",
|
|
@@ -468,6 +489,10 @@ window.__ModuleLoader__.load({
|
|
|
468
489
|
"card.history.desc": "Archivansicht und Bereinigung (Altersheim). Wenn aus, werden keine Verlaufs-Routen registriert und dieser Tab nicht gerendert.",
|
|
469
490
|
"card.health": "Health-Check",
|
|
470
491
|
"card.health.desc": "Untersuchung → Rezept → Entlassung. Wenn aus, werden keine Check-Routen registriert und dieser Tab nicht gerendert.",
|
|
492
|
+
"card.directDelete": "Direkt-Lösch-Eingang",
|
|
493
|
+
"card.directDelete.desc": "Wenn aktiviert, zeigen Ergebniszeilen im Suchpanel eine Löschen-Schaltfläche (Bestätigung in zwei Schritten, gleiche Pipeline wie die Verwaltung). Standardmäßig aus.",
|
|
494
|
+
"menu.deleteSession": "Sitzung löschen",
|
|
495
|
+
"menu.deleteSession.confirm": "Löschen bestätigen",
|
|
471
496
|
"card.readonly": "Diese Sitzung kann Einstellungen nicht ändern (schreibgeschützter Mount).",
|
|
472
497
|
"card.unavailable": "Einstellungsdienst nicht verfügbar; Konfiguration kann nicht gelesen werden.",
|
|
473
498
|
"family.title": "Screwdriver-Plugin-Einstellungen",
|
|
@@ -554,6 +579,10 @@ window.__ModuleLoader__.load({
|
|
|
554
579
|
"card.history.desc": "Consultazione e pulizia degli archivi (Casa di riposo). Se disattivato, non registra API di cronologia e non rende questa scheda.",
|
|
555
580
|
"card.health": "Controllo di salute",
|
|
556
581
|
"card.health.desc": "Visita → Prescrizione → Dimissione. Se disattivato, non registra API di controllo e non rende questa scheda.",
|
|
582
|
+
"card.directDelete": "Ingresso eliminazione diretta",
|
|
583
|
+
"card.directDelete.desc": "Se attivo, le righe dei risultati nel pannello di ricerca mostrano un pulsante Elimina (conferma in due passaggi, stessa pipeline della gestione). Predefinito disattivo.",
|
|
584
|
+
"menu.deleteSession": "Elimina sessione",
|
|
585
|
+
"menu.deleteSession.confirm": "Conferma eliminazione",
|
|
557
586
|
"card.readonly": "Questa sessione non può modificare le impostazioni (montaggio in sola lettura).",
|
|
558
587
|
"card.unavailable": "Servizio di impostazioni non disponibile; impossibile leggere la configurazione.",
|
|
559
588
|
"family.title": "Impostazioni del plugin Screwdriver",
|
|
@@ -640,6 +669,10 @@ window.__ModuleLoader__.load({
|
|
|
640
669
|
"card.history.desc": "Просмотр и очистка архивов (Пансионат). При выключении не регистрируются API истории и эта вкладка не отображается.",
|
|
641
670
|
"card.health": "Проверка здоровья",
|
|
642
671
|
"card.health.desc": "Осмотр → Рецепт → Выписка. При выключении не регистрируются API проверки и эта вкладка не отображается.",
|
|
672
|
+
"card.directDelete": "Точка прямого удаления",
|
|
673
|
+
"card.directDelete.desc": "Если включено, в строках результатов панели поиска появляется кнопка «Удалить» (двухшаговое подтверждение, тот же конвейер, что и в администраторе). По умолчанию выключено.",
|
|
674
|
+
"menu.deleteSession": "Удалить сессию",
|
|
675
|
+
"menu.deleteSession.confirm": "Подтвердить удаление",
|
|
643
676
|
"card.readonly": "Эта сессия не может изменять настройки (монтирование только для чтения).",
|
|
644
677
|
"card.unavailable": "Сервис настроек недоступен; конфигурацию невозможно прочитать.",
|
|
645
678
|
"family.title": "Настройки плагинов Screwdriver",
|
|
@@ -726,6 +759,10 @@ window.__ModuleLoader__.load({
|
|
|
726
759
|
"card.history.desc": "Visualización y limpieza del archivo (Residencia). Al desactivarlo no se registran rutas de historial ni se muestra esa pestaña.",
|
|
727
760
|
"card.health": "Comprobación de salud",
|
|
728
761
|
"card.health.desc": "Revisión → Receta → Alta. Al desactivarlo no se registran rutas de revisión ni se muestra esa pestaña.",
|
|
762
|
+
"card.directDelete": "Entrada de borrado directo",
|
|
763
|
+
"card.directDelete.desc": "Al activarlo, las filas de resultados del panel de búsqueda muestran un botón Eliminar (confirmación en dos pasos, misma ruta que la gestión). Desactivado de forma predeterminada.",
|
|
764
|
+
"menu.deleteSession": "Eliminar sesión",
|
|
765
|
+
"menu.deleteSession.confirm": "Confirmar eliminación",
|
|
729
766
|
"card.readonly": "Esta sesión no puede modificar la configuración (montaje de solo lectura).",
|
|
730
767
|
"card.unavailable": "Servicio de configuración no disponible; no se puede leer la configuración.",
|
|
731
768
|
"family.title": "Ajustes del plugin Screwdriver",
|
|
@@ -915,25 +952,25 @@ window.__ModuleLoader__.load({
|
|
|
915
952
|
}
|
|
916
953
|
})
|
|
917
954
|
}), (0, react.createElement)(Row$1, {
|
|
918
|
-
key: "
|
|
919
|
-
title: translate(t, "card.
|
|
920
|
-
desc: translate(t, "card.
|
|
955
|
+
key: "health",
|
|
956
|
+
title: translate(t, "card.health"),
|
|
957
|
+
desc: translate(t, "card.health.desc"),
|
|
921
958
|
control: (0, react.createElement)(Toggle$1, {
|
|
922
|
-
checked: value.
|
|
959
|
+
checked: value.healthCheck ?? DEFAULT_CONFIG.healthCheck,
|
|
923
960
|
disabled: !writable,
|
|
924
961
|
onChange: (checked) => {
|
|
925
|
-
scope.set("
|
|
962
|
+
scope.set("healthCheck", checked);
|
|
926
963
|
}
|
|
927
964
|
})
|
|
928
965
|
}), (0, react.createElement)(Row$1, {
|
|
929
|
-
key: "
|
|
930
|
-
title: translate(t, "card.
|
|
931
|
-
desc: translate(t, "card.
|
|
966
|
+
key: "directDelete",
|
|
967
|
+
title: translate(t, "card.directDelete"),
|
|
968
|
+
desc: translate(t, "card.directDelete.desc"),
|
|
932
969
|
control: (0, react.createElement)(Toggle$1, {
|
|
933
|
-
checked: value.
|
|
970
|
+
checked: value.directDelete ?? DEFAULT_CONFIG.directDelete,
|
|
934
971
|
disabled: !writable,
|
|
935
972
|
onChange: (checked) => {
|
|
936
|
-
scope.set("
|
|
973
|
+
scope.set("directDelete", checked);
|
|
937
974
|
}
|
|
938
975
|
})
|
|
939
976
|
}));
|
|
@@ -1121,317 +1158,6 @@ window.__ModuleLoader__.load({
|
|
|
1121
1158
|
};
|
|
1122
1159
|
}
|
|
1123
1160
|
//#endregion
|
|
1124
|
-
//#region src/client/history-panel.tsx
|
|
1125
|
-
/**
|
|
1126
|
-
* 养老院(会话历史文件)面板 —— 自 dsh-session-search-toggle `src/client/archive-panel.tsx`
|
|
1127
|
-
* **迁入**并适配到本包的方法名。
|
|
1128
|
-
*
|
|
1129
|
-
* 与迁移前的关键差异(**两个操作刻意分开,不可互换**):
|
|
1130
|
-
*
|
|
1131
|
-
* | 操作 | 路由 | 改什么 | 可逆 |
|
|
1132
|
-
* |---|---|---|---|
|
|
1133
|
-
* | 取消归档状态 | `session-history-prune` | 只改归档数组 | ✅ 重启后会话回到侧边栏 |
|
|
1134
|
-
* | 清理归档文件 | `session-history-purge` | **真删**转录目录 + 投影缓存 + 两处 id | ❌ 不可逆 |
|
|
1135
|
-
*
|
|
1136
|
-
* 「删除」一词在本面板**不存在** —— 旧文案承诺「删除」却只改了个数组,
|
|
1137
|
-
* 是导致「点了确认没反应」那类误判的一部分。
|
|
1138
|
-
* 体积**按行**显示(实测单条可从 0 到 23 MB,报一个总量没有意义),按钮上给所选小计。
|
|
1139
|
-
*/
|
|
1140
|
-
/** 人类可读体积。 */
|
|
1141
|
-
function fmtSize(bytes) {
|
|
1142
|
-
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
1143
|
-
const units = [
|
|
1144
|
-
"B",
|
|
1145
|
-
"KB",
|
|
1146
|
-
"MB",
|
|
1147
|
-
"GB",
|
|
1148
|
-
"TB"
|
|
1149
|
-
];
|
|
1150
|
-
let value = bytes;
|
|
1151
|
-
let unit = 0;
|
|
1152
|
-
while (value >= 1024 && unit < units.length - 1) {
|
|
1153
|
-
value /= 1024;
|
|
1154
|
-
unit++;
|
|
1155
|
-
}
|
|
1156
|
-
return `${unit === 0 || value >= 100 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
1157
|
-
}
|
|
1158
|
-
/** 养老院面板。 */
|
|
1159
|
-
function HistoryPanel({ t, onClose }) {
|
|
1160
|
-
const [items, setItems] = (0, react.useState)(null);
|
|
1161
|
-
const [source, setSource] = (0, react.useState)("");
|
|
1162
|
-
const [degraded, setDegraded] = (0, react.useState)("");
|
|
1163
|
-
const [pendingRestart, setPendingRestart] = (0, react.useState)(0);
|
|
1164
|
-
const [error, setError] = (0, react.useState)(null);
|
|
1165
|
-
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
1166
|
-
const [selected, setSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
1167
|
-
const [busy, setBusy] = (0, react.useState)(null);
|
|
1168
|
-
const [note, setNote] = (0, react.useState)(null);
|
|
1169
|
-
/** 清理失败明细(逐条);空数组表示无失败。 */
|
|
1170
|
-
const [failures, setFailures] = (0, react.useState)([]);
|
|
1171
|
-
const [editing, setEditing] = (0, react.useState)(false);
|
|
1172
|
-
(0, react.useEffect)(() => {
|
|
1173
|
-
let cancelled = false;
|
|
1174
|
-
setError(null);
|
|
1175
|
-
callHostAny$1("session-history-list", {}).then((res) => {
|
|
1176
|
-
if (cancelled) return;
|
|
1177
|
-
if (res.ok === true && Array.isArray(res.items)) {
|
|
1178
|
-
setItems(res.items);
|
|
1179
|
-
setSource(typeof res.source === "string" ? res.source : "");
|
|
1180
|
-
setDegraded(typeof res.degraded === "string" ? res.degraded : "");
|
|
1181
|
-
setPendingRestart(typeof res.pendingRestart === "number" ? res.pendingRestart : 0);
|
|
1182
|
-
} else setError(res.error ?? "读取归档列表失败");
|
|
1183
|
-
});
|
|
1184
|
-
return () => {
|
|
1185
|
-
cancelled = true;
|
|
1186
|
-
};
|
|
1187
|
-
}, [attempt]);
|
|
1188
|
-
(0, react.useEffect)(() => {
|
|
1189
|
-
const onKey = (event) => {
|
|
1190
|
-
if (event.key === "Escape") onClose();
|
|
1191
|
-
};
|
|
1192
|
-
document.addEventListener("keydown", onKey);
|
|
1193
|
-
return () => {
|
|
1194
|
-
document.removeEventListener("keydown", onKey);
|
|
1195
|
-
};
|
|
1196
|
-
}, [onClose]);
|
|
1197
|
-
const toggleRow = (sessionId) => {
|
|
1198
|
-
setSelected((prev) => {
|
|
1199
|
-
const next = new Set(prev);
|
|
1200
|
-
if (next.has(sessionId)) next.delete(sessionId);
|
|
1201
|
-
else next.add(sessionId);
|
|
1202
|
-
return next;
|
|
1203
|
-
});
|
|
1204
|
-
};
|
|
1205
|
-
/** 进出编辑模式;离开时清空选择。 */
|
|
1206
|
-
const toggleEditing = () => {
|
|
1207
|
-
setEditing((prev) => !prev);
|
|
1208
|
-
setSelected(/* @__PURE__ */ new Set());
|
|
1209
|
-
setNote(null);
|
|
1210
|
-
setFailures([]);
|
|
1211
|
-
};
|
|
1212
|
-
const toggleAll = () => {
|
|
1213
|
-
setSelected((prev) => prev.size === (items?.length ?? 0) ? /* @__PURE__ */ new Set() : new Set((items ?? []).map((item) => item.sessionId)));
|
|
1214
|
-
};
|
|
1215
|
-
const selectedRows = (items ?? []).filter((item) => selected.has(item.sessionId));
|
|
1216
|
-
const selectedBytes = selectedRows.reduce((sum, item) => sum + item.bytes + item.cacheBytes, 0);
|
|
1217
|
-
const totalBytes = (items ?? []).reduce((sum, item) => sum + item.bytes + item.cacheBytes, 0);
|
|
1218
|
-
/** 确认弹窗里的逐条摘要(清理时带体积,取消归档时不带)。 */
|
|
1219
|
-
const summarise = (rows, withSize) => {
|
|
1220
|
-
const lines = rows.slice(0, 5).map((row) => {
|
|
1221
|
-
const label = row.title || translate(t, "panel.untitled");
|
|
1222
|
-
const size = fmtSize(row.bytes + row.cacheBytes);
|
|
1223
|
-
return withSize ? `· ${label} — ${size}` : `· ${label}`;
|
|
1224
|
-
});
|
|
1225
|
-
if (rows.length > 5) lines.push(`… 共 ${rows.length} 个`);
|
|
1226
|
-
return lines.join("\n");
|
|
1227
|
-
};
|
|
1228
|
-
const confirm = (message) => typeof window !== "undefined" && typeof window.confirm === "function" ? window.confirm(message) : false;
|
|
1229
|
-
/** 提交一次操作:确认 → 调用 → 刷新列表(列表以存储文件为准,因此立刻可见变化)。 */
|
|
1230
|
-
const run = (kind, method, confirmText) => {
|
|
1231
|
-
const rows = selectedRows;
|
|
1232
|
-
if (rows.length === 0) return;
|
|
1233
|
-
if (!confirm(confirmText)) return;
|
|
1234
|
-
setBusy(kind);
|
|
1235
|
-
setNote(null);
|
|
1236
|
-
setFailures([]);
|
|
1237
|
-
callHostAny$1(method, { sessionIds: rows.map((row) => row.sessionId) }, 6e4).then((res) => {
|
|
1238
|
-
if (res.ok === true) {
|
|
1239
|
-
if (kind === "unarchive") setNote(translate(t, "history.restartHint", {
|
|
1240
|
-
removed: res.removed ?? rows.length,
|
|
1241
|
-
remaining: res.remaining ?? "?"
|
|
1242
|
-
}));
|
|
1243
|
-
else {
|
|
1244
|
-
const freed = fmtSize(res.freedBytes ?? 0);
|
|
1245
|
-
const failed = res.failures?.length ?? 0;
|
|
1246
|
-
setNote(failed === 0 ? translate(t, "history.purgeResult", {
|
|
1247
|
-
purged: res.purged ?? rows.length,
|
|
1248
|
-
size: freed
|
|
1249
|
-
}) : translate(t, "history.purgePartial", {
|
|
1250
|
-
purged: res.purged ?? rows.length,
|
|
1251
|
-
size: freed,
|
|
1252
|
-
failed
|
|
1253
|
-
}));
|
|
1254
|
-
setFailures(res.failures ?? []);
|
|
1255
|
-
}
|
|
1256
|
-
setSelected(/* @__PURE__ */ new Set());
|
|
1257
|
-
setAttempt((n) => n + 1);
|
|
1258
|
-
} else setNote(res.error ?? "操作失败");
|
|
1259
|
-
}).catch((err) => setNote(`操作失败:${String(err instanceof Error ? err.message : err)}`)).finally(() => setBusy(null));
|
|
1260
|
-
};
|
|
1261
|
-
const unarchiveSelected = () => {
|
|
1262
|
-
run("unarchive", "session-history-prune", translate(t, "history.confirmUnarchive", {
|
|
1263
|
-
n: selectedRows.length,
|
|
1264
|
-
summary: summarise(selectedRows, false)
|
|
1265
|
-
}));
|
|
1266
|
-
};
|
|
1267
|
-
const purgeSelected = () => {
|
|
1268
|
-
run("purge", "session-history-purge", translate(t, "history.confirmPurge", {
|
|
1269
|
-
n: selectedRows.length,
|
|
1270
|
-
summary: summarise(selectedRows, true),
|
|
1271
|
-
size: fmtSize(selectedBytes)
|
|
1272
|
-
}));
|
|
1273
|
-
};
|
|
1274
|
-
const children = [];
|
|
1275
|
-
if (error !== null) children.push((0, react.createElement)("div", {
|
|
1276
|
-
key: "err",
|
|
1277
|
-
className: "dss_error"
|
|
1278
|
-
}, [(0, react.createElement)("div", { key: "msg" }, error === "请求超时" ? "读取归档列表超时:Host 可能正忙,稍后重试。" : error), (0, react.createElement)("button", {
|
|
1279
|
-
key: "retry",
|
|
1280
|
-
type: "button",
|
|
1281
|
-
className: "dss_actBtn",
|
|
1282
|
-
style: { marginTop: "6px" },
|
|
1283
|
-
onClick: () => {
|
|
1284
|
-
setAttempt((n) => n + 1);
|
|
1285
|
-
}
|
|
1286
|
-
}, "重试")]));
|
|
1287
|
-
else if (items === null) children.push((0, react.createElement)("div", {
|
|
1288
|
-
key: "loading",
|
|
1289
|
-
className: "dss_status"
|
|
1290
|
-
}, translate(t, "history.loading")));
|
|
1291
|
-
else if (items.length === 0) children.push((0, react.createElement)("div", {
|
|
1292
|
-
key: "empty",
|
|
1293
|
-
className: "dss_empty"
|
|
1294
|
-
}, translate(t, "history.empty")));
|
|
1295
|
-
else {
|
|
1296
|
-
const allSelected = selected.size === items.length;
|
|
1297
|
-
if (editing) children.push((0, react.createElement)("div", {
|
|
1298
|
-
key: "manage",
|
|
1299
|
-
className: "dss_btnRow",
|
|
1300
|
-
style: { padding: "4px 10px 0" }
|
|
1301
|
-
}, [
|
|
1302
|
-
(0, react.createElement)("button", {
|
|
1303
|
-
key: "all",
|
|
1304
|
-
type: "button",
|
|
1305
|
-
className: "dss_actBtn",
|
|
1306
|
-
onClick: toggleAll
|
|
1307
|
-
}, allSelected ? translate(t, "history.unselectAll") : translate(t, "history.selectAll")),
|
|
1308
|
-
(0, react.createElement)("button", {
|
|
1309
|
-
key: "unarchive",
|
|
1310
|
-
type: "button",
|
|
1311
|
-
className: "dss_actBtn",
|
|
1312
|
-
disabled: busy !== null || selected.size === 0,
|
|
1313
|
-
onClick: unarchiveSelected
|
|
1314
|
-
}, busy === "unarchive" ? translate(t, "history.unarchiving") : translate(t, "history.unarchive", { n: selected.size })),
|
|
1315
|
-
(0, react.createElement)("button", {
|
|
1316
|
-
key: "purge",
|
|
1317
|
-
type: "button",
|
|
1318
|
-
className: "dss_actBtn dss_dangerBtn",
|
|
1319
|
-
disabled: busy !== null || selected.size === 0,
|
|
1320
|
-
onClick: purgeSelected
|
|
1321
|
-
}, busy === "purge" ? translate(t, "history.purging") : translate(t, "history.purge", {
|
|
1322
|
-
n: selected.size,
|
|
1323
|
-
size: fmtSize(selectedBytes)
|
|
1324
|
-
}))
|
|
1325
|
-
]));
|
|
1326
|
-
children.push((0, react.createElement)("ul", {
|
|
1327
|
-
key: "list",
|
|
1328
|
-
className: "dss_list",
|
|
1329
|
-
role: "list",
|
|
1330
|
-
"aria-label": translate(t, "history.source.registry")
|
|
1331
|
-
}, items.map((item) => (0, react.createElement)("li", {
|
|
1332
|
-
key: item.sessionId,
|
|
1333
|
-
className: "dss_row"
|
|
1334
|
-
}, [
|
|
1335
|
-
editing && (0, react.createElement)("label", {
|
|
1336
|
-
key: "sel",
|
|
1337
|
-
className: "dss_check"
|
|
1338
|
-
}, [(0, react.createElement)("input", {
|
|
1339
|
-
type: "checkbox",
|
|
1340
|
-
checked: selected.has(item.sessionId),
|
|
1341
|
-
onChange: () => {
|
|
1342
|
-
toggleRow(item.sessionId);
|
|
1343
|
-
}
|
|
1344
|
-
})]),
|
|
1345
|
-
(0, react.createElement)("span", {
|
|
1346
|
-
key: "t",
|
|
1347
|
-
className: "dss_rowTitle"
|
|
1348
|
-
}, [(0, react.createElement)("span", {
|
|
1349
|
-
key: "x",
|
|
1350
|
-
className: "dss_titleText"
|
|
1351
|
-
}, item.title || translate(t, "panel.untitled")), item.updatedAt > 0 && (0, react.createElement)("span", {
|
|
1352
|
-
key: "tag",
|
|
1353
|
-
className: "dss_tag"
|
|
1354
|
-
}, fmtTime$2(item.updatedAt))]),
|
|
1355
|
-
(0, react.createElement)("span", {
|
|
1356
|
-
key: "size",
|
|
1357
|
-
className: "dss_meta dss_size"
|
|
1358
|
-
}, item.bytes + item.cacheBytes > 0 ? translate(t, "history.size", {
|
|
1359
|
-
t: fmtSize(item.bytes),
|
|
1360
|
-
c: fmtSize(item.cacheBytes)
|
|
1361
|
-
}) : translate(t, "history.sizeUnknown")),
|
|
1362
|
-
item.cwd !== "" && (0, react.createElement)("span", {
|
|
1363
|
-
key: "c",
|
|
1364
|
-
className: "dss_meta"
|
|
1365
|
-
}, item.cwd),
|
|
1366
|
-
(0, react.createElement)("span", {
|
|
1367
|
-
key: "id",
|
|
1368
|
-
className: "dss_meta dss_uuid"
|
|
1369
|
-
}, item.sessionId)
|
|
1370
|
-
]))));
|
|
1371
|
-
}
|
|
1372
|
-
return (0, react.createElement)("div", { className: "dss_tabBody" }, [
|
|
1373
|
-
note !== null && (0, react.createElement)("div", {
|
|
1374
|
-
key: "note",
|
|
1375
|
-
className: "dss_status"
|
|
1376
|
-
}, note),
|
|
1377
|
-
failures.length > 0 && (0, react.createElement)("div", {
|
|
1378
|
-
key: "fail",
|
|
1379
|
-
className: "dss_status dss_error"
|
|
1380
|
-
}, [(0, react.createElement)("div", { key: "h" }, translate(t, "history.purgeFailures")), ...failures.slice(0, 10).map((failure, index) => (0, react.createElement)("div", {
|
|
1381
|
-
key: `f${index}`,
|
|
1382
|
-
className: "dss_meta"
|
|
1383
|
-
}, `${failure.sessionId.slice(0, 22)}… — ${failure.reason}`))]),
|
|
1384
|
-
editing && (0, react.createElement)("div", {
|
|
1385
|
-
key: "hint",
|
|
1386
|
-
className: "dss_status"
|
|
1387
|
-
}, translate(t, "history.editingHint")),
|
|
1388
|
-
degraded !== "" && (0, react.createElement)("div", {
|
|
1389
|
-
key: "degraded",
|
|
1390
|
-
className: "dss_status dss_warnText"
|
|
1391
|
-
}, degraded),
|
|
1392
|
-
pendingRestart > 0 && (0, react.createElement)("div", {
|
|
1393
|
-
key: "pending",
|
|
1394
|
-
className: "dss_status dss_warnText"
|
|
1395
|
-
}, translate(t, "history.pendingRestart", { n: pendingRestart })),
|
|
1396
|
-
source !== "" && (0, react.createElement)("div", {
|
|
1397
|
-
key: "source",
|
|
1398
|
-
className: "dss_metaLine"
|
|
1399
|
-
}, translate(t, source === "registry" ? "history.source.registry" : source === "storage-file" ? "history.source.storage-file" : "history.source.none")),
|
|
1400
|
-
items !== null && error === null && (0, react.createElement)("div", {
|
|
1401
|
-
key: "count",
|
|
1402
|
-
className: "dss_metaLine"
|
|
1403
|
-
}, translate(t, "history.count", {
|
|
1404
|
-
n: items.length,
|
|
1405
|
-
size: fmtSize(totalBytes)
|
|
1406
|
-
})),
|
|
1407
|
-
(0, react.createElement)("div", {
|
|
1408
|
-
key: "headBtns",
|
|
1409
|
-
className: "dss_btnRow"
|
|
1410
|
-
}, [(0, react.createElement)("button", {
|
|
1411
|
-
key: "edit",
|
|
1412
|
-
type: "button",
|
|
1413
|
-
className: `dss_actBtn${editing ? " dss_editActive" : ""}`,
|
|
1414
|
-
onClick: toggleEditing
|
|
1415
|
-
}, editing ? translate(t, "history.done") : translate(t, "history.edit"))]),
|
|
1416
|
-
...children
|
|
1417
|
-
]);
|
|
1418
|
-
}
|
|
1419
|
-
/** 时间格式化(与迁移前一致)。 */
|
|
1420
|
-
function fmtTime$2(ms) {
|
|
1421
|
-
if (!ms || typeof ms !== "number") return "";
|
|
1422
|
-
try {
|
|
1423
|
-
const date = new Date(ms);
|
|
1424
|
-
const now = /* @__PURE__ */ new Date();
|
|
1425
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
1426
|
-
const sameDay = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate();
|
|
1427
|
-
const time = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
1428
|
-
if (sameDay) return time;
|
|
1429
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${time}`;
|
|
1430
|
-
} catch {
|
|
1431
|
-
return "";
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
//#endregion
|
|
1435
1161
|
//#region src/client/health-panel.tsx
|
|
1436
1162
|
/**
|
|
1437
1163
|
* 体检面板:体检(五门)→ 处方(命令清单)→ 出院(可逆处置 + before/after 对照)。
|
|
@@ -2090,6 +1816,11 @@ window.__ModuleLoader__.load({
|
|
|
2090
1816
|
"panel.noContent": "没有匹配的内容。",
|
|
2091
1817
|
"panel.contentHint": "输入内容关键词开始搜索。",
|
|
2092
1818
|
"panel.noText": "(无文本)",
|
|
1819
|
+
"panel.delete": "删除",
|
|
1820
|
+
"panel.delete.confirm": "确认删除",
|
|
1821
|
+
"panel.delete.hint": "删除该会话(不可逆,与管理端同款链路)",
|
|
1822
|
+
"panel.delete.done": "已删除会话 {n} 个",
|
|
1823
|
+
"panel.delete.failed": "删除失败:{error}",
|
|
2093
1824
|
"panel.untitled": "(未命名)",
|
|
2094
1825
|
"panel.openSession": "打开会话",
|
|
2095
1826
|
"panel.footer.invoke": "唤出",
|
|
@@ -2103,6 +1834,10 @@ window.__ModuleLoader__.load({
|
|
|
2103
1834
|
"sort.time": "时间",
|
|
2104
1835
|
"sort.relevance.hint": "按匹配强度排序(默认)。",
|
|
2105
1836
|
"sort.time.hint": "按会话最后活动时间倒序,更新的排前面;同一时间再按匹配强度。",
|
|
1837
|
+
"group.merge": "合并",
|
|
1838
|
+
"group.expand": "分开",
|
|
1839
|
+
"group.merge.hint": "每个会话只显示最强的一条命中(默认)。",
|
|
1840
|
+
"group.expand.hint": "同一会话的多条命中分行显示(每行仍是可跳转的独立命中)。",
|
|
2106
1841
|
"type.user/message": "用户",
|
|
2107
1842
|
"type.assistant/message": "回复",
|
|
2108
1843
|
"type.tool/call": "工具调用",
|
|
@@ -2200,6 +1935,11 @@ window.__ModuleLoader__.load({
|
|
|
2200
1935
|
"panel.noContent": "No matching content.",
|
|
2201
1936
|
"panel.contentHint": "Type keywords to search content.",
|
|
2202
1937
|
"panel.noText": "(no text)",
|
|
1938
|
+
"panel.delete": "Delete",
|
|
1939
|
+
"panel.delete.confirm": "Confirm delete",
|
|
1940
|
+
"panel.delete.hint": "Delete this session (irreversible, same pipeline as the manage view)",
|
|
1941
|
+
"panel.delete.done": "Deleted {n} session(s)",
|
|
1942
|
+
"panel.delete.failed": "Delete failed: {error}",
|
|
2203
1943
|
"panel.untitled": "(untitled)",
|
|
2204
1944
|
"panel.openSession": "Open session",
|
|
2205
1945
|
"panel.footer.invoke": "Open",
|
|
@@ -2213,6 +1953,10 @@ window.__ModuleLoader__.load({
|
|
|
2213
1953
|
"sort.time": "Time",
|
|
2214
1954
|
"sort.relevance.hint": "Order by match strength (default).",
|
|
2215
1955
|
"sort.time.hint": "Order by session last-activity, newest first; ties break on match strength.",
|
|
1956
|
+
"group.merge": "Merge",
|
|
1957
|
+
"group.expand": "Expand",
|
|
1958
|
+
"group.merge.hint": "Show only the strongest hit per session (default).",
|
|
1959
|
+
"group.expand.hint": "List every hit of a session as its own row (each row is a jump target).",
|
|
2216
1960
|
"type.user/message": "User",
|
|
2217
1961
|
"type.assistant/message": "Reply",
|
|
2218
1962
|
"type.tool/call": "Tool call",
|
|
@@ -2310,6 +2054,11 @@ window.__ModuleLoader__.load({
|
|
|
2310
2054
|
"panel.noContent": "一致するコンテンツがありません。",
|
|
2311
2055
|
"panel.contentHint": "キーワードを入力してコンテンツを検索。",
|
|
2312
2056
|
"panel.noText": "(テキストなし)",
|
|
2057
|
+
"panel.delete": "削除",
|
|
2058
|
+
"panel.delete.confirm": "削除を確認",
|
|
2059
|
+
"panel.delete.hint": "このセッションを削除(不可逆、管理画面と同じ経路)",
|
|
2060
|
+
"panel.delete.done": "セッション {n} 件を削除しました",
|
|
2061
|
+
"panel.delete.failed": "削除に失敗:{error}",
|
|
2313
2062
|
"panel.untitled": "(無題)",
|
|
2314
2063
|
"panel.openSession": "セッションを開く",
|
|
2315
2064
|
"panel.footer.invoke": "開く",
|
|
@@ -2323,6 +2072,10 @@ window.__ModuleLoader__.load({
|
|
|
2323
2072
|
"sort.time": "時間",
|
|
2324
2073
|
"sort.relevance.hint": "一致強度順に並べ替え(デフォルト)。",
|
|
2325
2074
|
"sort.time.hint": "セッション最終活動日時の降順、同着時は一致強度順。",
|
|
2075
|
+
"group.merge": "まとめる",
|
|
2076
|
+
"group.expand": "展開",
|
|
2077
|
+
"group.merge.hint": "セッションごとに最強の一致1件のみ表示(デフォルト)。",
|
|
2078
|
+
"group.expand.hint": "同一セッションの複数一致を行ごとに展開(各行が個別のジャンプ先)。",
|
|
2326
2079
|
"type.user/message": "ユーザー",
|
|
2327
2080
|
"type.assistant/message": "返信",
|
|
2328
2081
|
"type.tool/call": "ツール呼び出し",
|
|
@@ -2384,6 +2137,11 @@ window.__ModuleLoader__.load({
|
|
|
2384
2137
|
"panel.noContent": "일치하는 콘텐츠가 없습니다.",
|
|
2385
2138
|
"panel.contentHint": "키워드를 입력하여 콘텐츠를 검색하세요.",
|
|
2386
2139
|
"panel.noText": "(텍스트 없음)",
|
|
2140
|
+
"panel.delete": "삭제",
|
|
2141
|
+
"panel.delete.confirm": "삭제 확인",
|
|
2142
|
+
"panel.delete.hint": "이 세션 삭제(되돌릴 수 없음, 관리 화면과 동일한 경로)",
|
|
2143
|
+
"panel.delete.done": "세션 {n}개 삭제됨",
|
|
2144
|
+
"panel.delete.failed": "삭제 실패: {error}",
|
|
2387
2145
|
"panel.untitled": "(제목 없음)",
|
|
2388
2146
|
"panel.openSession": "세션 열기",
|
|
2389
2147
|
"panel.footer.invoke": "열기",
|
|
@@ -2397,6 +2155,10 @@ window.__ModuleLoader__.load({
|
|
|
2397
2155
|
"sort.time": "시간",
|
|
2398
2156
|
"sort.relevance.hint": "일치 강도순 정렬(기본값).",
|
|
2399
2157
|
"sort.time.hint": "세션 마지막 활동 시간 내림차순, 동률 시 일치 강도순.",
|
|
2158
|
+
"group.merge": "병합",
|
|
2159
|
+
"group.expand": "분할",
|
|
2160
|
+
"group.merge.hint": "세션별 최강 일치 1건만 표시(기본값).",
|
|
2161
|
+
"group.expand.hint": "같은 세션의 여러 일치를 각 행으로 표시(각 행이 개별 이동 대상).",
|
|
2400
2162
|
"type.user/message": "사용자",
|
|
2401
2163
|
"type.assistant/message": "답변",
|
|
2402
2164
|
"type.tool/call": "도구 호출",
|
|
@@ -2458,6 +2220,11 @@ window.__ModuleLoader__.load({
|
|
|
2458
2220
|
"panel.noContent": "Aucun contenu correspondant.",
|
|
2459
2221
|
"panel.contentHint": "Saisissez des mots-clés pour rechercher dans le contenu.",
|
|
2460
2222
|
"panel.noText": "(pas de texte)",
|
|
2223
|
+
"panel.delete": "Supprimer",
|
|
2224
|
+
"panel.delete.confirm": "Confirmer la suppression",
|
|
2225
|
+
"panel.delete.hint": "Supprimer cette session (irréversible, même pipeline que la gestion)",
|
|
2226
|
+
"panel.delete.done": "{n} session(s) supprimée(s)",
|
|
2227
|
+
"panel.delete.failed": "Échec de suppression : {error}",
|
|
2461
2228
|
"panel.untitled": "(sans titre)",
|
|
2462
2229
|
"panel.openSession": "Ouvrir la session",
|
|
2463
2230
|
"panel.footer.invoke": "Ouvrir",
|
|
@@ -2471,6 +2238,10 @@ window.__ModuleLoader__.load({
|
|
|
2471
2238
|
"sort.time": "Temps",
|
|
2472
2239
|
"sort.relevance.hint": "Trier par force de correspondance (par défaut).",
|
|
2473
2240
|
"sort.time.hint": "Trier par dernière activité, plus récent d'abord ; à égalité, par force de correspondance.",
|
|
2241
|
+
"group.merge": "Fusionner",
|
|
2242
|
+
"group.expand": "Détailler",
|
|
2243
|
+
"group.merge.hint": "Une seule correspondance la plus forte par session (par défaut).",
|
|
2244
|
+
"group.expand.hint": "Afficher chaque correspondance de la session sur sa propre ligne (chacune est une cible de saut).",
|
|
2474
2245
|
"type.user/message": "Utilisateur",
|
|
2475
2246
|
"type.assistant/message": "Réponse",
|
|
2476
2247
|
"type.tool/call": "Appel d'outil",
|
|
@@ -2532,6 +2303,11 @@ window.__ModuleLoader__.load({
|
|
|
2532
2303
|
"panel.noContent": "Kein passender Inhalt.",
|
|
2533
2304
|
"panel.contentHint": "Geben Sie Stichwörter ein, um Inhalte zu durchsuchen.",
|
|
2534
2305
|
"panel.noText": "(kein Text)",
|
|
2306
|
+
"panel.delete": "Löschen",
|
|
2307
|
+
"panel.delete.confirm": "Löschen bestätigen",
|
|
2308
|
+
"panel.delete.hint": "Diese Sitzung löschen (unwiderruflich, gleiche Pipeline wie die Verwaltung)",
|
|
2309
|
+
"panel.delete.done": "{n} Sitzung(en) gelöscht",
|
|
2310
|
+
"panel.delete.failed": "Löschen fehlgeschlagen: {error}",
|
|
2535
2311
|
"panel.untitled": "(unbenannt)",
|
|
2536
2312
|
"panel.openSession": "Sitzung öffnen",
|
|
2537
2313
|
"panel.footer.invoke": "Öffnen",
|
|
@@ -2545,6 +2321,10 @@ window.__ModuleLoader__.load({
|
|
|
2545
2321
|
"sort.time": "Zeit",
|
|
2546
2322
|
"sort.relevance.hint": "Nach Übereinstimmungsstärke sortieren (Standard).",
|
|
2547
2323
|
"sort.time.hint": "Nach letzter Sitzungsaktivität, neueste zuerst; bei Gleichstand nach Übereinstimmungsstärke.",
|
|
2324
|
+
"group.merge": "Zusammen",
|
|
2325
|
+
"group.expand": "Aufklappen",
|
|
2326
|
+
"group.merge.hint": "Nur der stärkste Treffer je Sitzung (Standard).",
|
|
2327
|
+
"group.expand.hint": "Jeden Treffer einer Sitzung als eigene Zeile anzeigen (jede Zeile ist ein Sprungziel).",
|
|
2548
2328
|
"type.user/message": "Benutzer",
|
|
2549
2329
|
"type.assistant/message": "Antwort",
|
|
2550
2330
|
"type.tool/call": "Werkzeugaufruf",
|
|
@@ -2606,6 +2386,11 @@ window.__ModuleLoader__.load({
|
|
|
2606
2386
|
"panel.noContent": "Nessun contenuto corrispondente.",
|
|
2607
2387
|
"panel.contentHint": "Inserisci parole chiave per cercare nel contenuto.",
|
|
2608
2388
|
"panel.noText": "(nessun testo)",
|
|
2389
|
+
"panel.delete": "Elimina",
|
|
2390
|
+
"panel.delete.confirm": "Conferma eliminazione",
|
|
2391
|
+
"panel.delete.hint": "Elimina questa sessione (irreversibile, stessa pipeline della gestione)",
|
|
2392
|
+
"panel.delete.done": "Eliminate {n} sessione/i",
|
|
2393
|
+
"panel.delete.failed": "Eliminazione non riuscita: {error}",
|
|
2609
2394
|
"panel.untitled": "(senza titolo)",
|
|
2610
2395
|
"panel.openSession": "Apri sessione",
|
|
2611
2396
|
"panel.footer.invoke": "Apri",
|
|
@@ -2619,6 +2404,10 @@ window.__ModuleLoader__.load({
|
|
|
2619
2404
|
"sort.time": "Tempo",
|
|
2620
2405
|
"sort.relevance.hint": "Ordina per forza di corrispondenza (predefinito).",
|
|
2621
2406
|
"sort.time.hint": "Ordina per ultima attività, più recente prima; a parità, per forza di corrispondenza.",
|
|
2407
|
+
"group.merge": "Unisci",
|
|
2408
|
+
"group.expand": "Espandi",
|
|
2409
|
+
"group.merge.hint": "Mostra solo il risultato più forte per sessione (predefinito).",
|
|
2410
|
+
"group.expand.hint": "Mostra ogni corrispondenza della sessione su riga propria (ogni riga è una destinazione).",
|
|
2622
2411
|
"type.user/message": "Utente",
|
|
2623
2412
|
"type.assistant/message": "Risposta",
|
|
2624
2413
|
"type.tool/call": "Chiamata strumento",
|
|
@@ -2680,6 +2469,11 @@ window.__ModuleLoader__.load({
|
|
|
2680
2469
|
"panel.noContent": "Нет подходящего содержимого.",
|
|
2681
2470
|
"panel.contentHint": "Введите ключевые слова для поиска по содержимому.",
|
|
2682
2471
|
"panel.noText": "(без текста)",
|
|
2472
|
+
"panel.delete": "Удалить",
|
|
2473
|
+
"panel.delete.confirm": "Подтвердить удаление",
|
|
2474
|
+
"panel.delete.hint": "Удалить эту сессию (необратимо, тот же конвейер, что и в администраторе)",
|
|
2475
|
+
"panel.delete.done": "Удалено сессий: {n}",
|
|
2476
|
+
"panel.delete.failed": "Ошибка удаления: {error}",
|
|
2683
2477
|
"panel.untitled": "(без названия)",
|
|
2684
2478
|
"panel.openSession": "Открыть сессию",
|
|
2685
2479
|
"panel.footer.invoke": "Открыть",
|
|
@@ -2693,6 +2487,10 @@ window.__ModuleLoader__.load({
|
|
|
2693
2487
|
"sort.time": "Время",
|
|
2694
2488
|
"sort.relevance.hint": "Сортировать по силе совпадения (по умолчанию).",
|
|
2695
2489
|
"sort.time.hint": "Сортировать по последней активности, сначала новые; при равенстве — по силе совпадения.",
|
|
2490
|
+
"group.merge": "Объединить",
|
|
2491
|
+
"group.expand": "Развернуть",
|
|
2492
|
+
"group.merge.hint": "По одному сильнейшему совпадению на сессию (по умолчанию).",
|
|
2493
|
+
"group.expand.hint": "Каждое совпадение сессии — отдельной строкой (каждая строка — цель перехода).",
|
|
2696
2494
|
"type.user/message": "Пользователь",
|
|
2697
2495
|
"type.assistant/message": "Ответ",
|
|
2698
2496
|
"type.tool/call": "Вызов инструмента",
|
|
@@ -2754,6 +2552,11 @@ window.__ModuleLoader__.load({
|
|
|
2754
2552
|
"panel.noContent": "Sin contenido coincidente.",
|
|
2755
2553
|
"panel.contentHint": "Escriba palabras clave para buscar contenido.",
|
|
2756
2554
|
"panel.noText": "(sin texto)",
|
|
2555
|
+
"panel.delete": "Eliminar",
|
|
2556
|
+
"panel.delete.confirm": "Confirmar borrado",
|
|
2557
|
+
"panel.delete.hint": "Eliminar esta sesión (irreversible, misma ruta que la gestión)",
|
|
2558
|
+
"panel.delete.done": "{n} sesión(es) eliminadas",
|
|
2559
|
+
"panel.delete.failed": "Error de borrado: {error}",
|
|
2757
2560
|
"panel.untitled": "(sin título)",
|
|
2758
2561
|
"panel.openSession": "Abrir sesión",
|
|
2759
2562
|
"panel.footer.invoke": "Abrir",
|
|
@@ -2767,6 +2570,10 @@ window.__ModuleLoader__.load({
|
|
|
2767
2570
|
"sort.time": "Tiempo",
|
|
2768
2571
|
"sort.relevance.hint": "Ordenar por fuerza de coincidencia (predeterminado).",
|
|
2769
2572
|
"sort.time.hint": "Ordenar por última actividad, más reciente primero; en caso de empate, por fuerza de coincidencia.",
|
|
2573
|
+
"group.merge": "Unir",
|
|
2574
|
+
"group.expand": "Desplegar",
|
|
2575
|
+
"group.merge.hint": "Solo la coincidencia más fuerte por sesión (predeterminado).",
|
|
2576
|
+
"group.expand.hint": "Mostrar cada coincidencia de la sesión en su propia fila (cada fila es un destino).",
|
|
2770
2577
|
"type.user/message": "Usuario",
|
|
2771
2578
|
"type.assistant/message": "Respuesta",
|
|
2772
2579
|
"type.tool/call": "Llamada de herramienta",
|
|
@@ -2813,6 +2620,8 @@ window.__ModuleLoader__.load({
|
|
|
2813
2620
|
.dsws_batchBar{flex:none;display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 10px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
2814
2621
|
.dsws_batchInfo{flex:1 1 auto;min-width:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
2815
2622
|
.dsws_btnDanger{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
2623
|
+
.dsws_btnDangerSolid{background:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);color:#fff;font-weight:600}
|
|
2624
|
+
.dsws_btnDangerSolid:hover{background:var(--dsw-alias-state-error-primary);filter:brightness(1.08);color:#fff}
|
|
2816
2625
|
.dsws_manageHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;padding:8px 10px 0}
|
|
2817
2626
|
.dsws_chips{display:flex;align-items:center;gap:6px;padding:8px 10px 0;flex:none;flex-wrap:wrap}
|
|
2818
2627
|
.dsws_chip{height:24px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:999px;padding:0 10px;font-size:12px;font-weight:500;line-height:22px;white-space:nowrap}
|
|
@@ -3270,12 +3079,8 @@ window.__ModuleLoader__.load({
|
|
|
3270
3079
|
(0, react.createElement)("button", {
|
|
3271
3080
|
key: "purge",
|
|
3272
3081
|
type: "button",
|
|
3273
|
-
className: `dsws_actBtn dsws_btnDanger`,
|
|
3082
|
+
className: `dsws_actBtn${confirmPurge ? " dsws_btnDangerSolid" : " dsws_btnDanger"}`,
|
|
3274
3083
|
disabled: batch.busy,
|
|
3275
|
-
style: confirmPurge ? {
|
|
3276
|
-
background: "var(--dsw-alias-state-error-primary)",
|
|
3277
|
-
color: "var(--dsw-specific-tip)"
|
|
3278
|
-
} : void 0,
|
|
3279
3084
|
onClick: () => {
|
|
3280
3085
|
runBatch("purge");
|
|
3281
3086
|
}
|
|
@@ -3313,25 +3118,28 @@ window.__ModuleLoader__.load({
|
|
|
3313
3118
|
//#endregion
|
|
3314
3119
|
//#region src/client/panel.tsx
|
|
3315
3120
|
/**
|
|
3316
|
-
*
|
|
3121
|
+
* 会话管家面板壳:一个侧边栏入口,两个页签(管理=主页面 / 体检=备用)。
|
|
3317
3122
|
*
|
|
3318
|
-
*
|
|
3319
|
-
*
|
|
3123
|
+
* 2026-10-20 页面调整(用户定标):「养老院」页签取消——管理台已覆盖其全部
|
|
3124
|
+
* 动作面(archive/unarchive/purge + 归档域筛选),独立页签属重复入口;
|
|
3125
|
+
* 历史文件组件(history-panel.tsx)随摘除,宿主侧 history 路由与配置字段
|
|
3126
|
+
* (historyFiles)保留不动,字典键留用以备回迁。
|
|
3127
|
+
*
|
|
3128
|
+
* 页签可见性由开关决定:`search=false` 时「管理」消失(退到体检),
|
|
3129
|
+
* `healthCheck=false` 时「体检」消失;两者都关时进入面板直接显示空壳提示
|
|
3130
|
+
* (客户端也会隐藏入口)。
|
|
3320
3131
|
*/
|
|
3321
3132
|
/** 页签 id(导出供测试断言)。 */
|
|
3322
|
-
const TAB_HISTORY = "dss-tab-history";
|
|
3323
3133
|
const TAB_HEALTH = "dss-tab-health";
|
|
3324
3134
|
const TAB_MANAGE = "dss-tab-manage";
|
|
3325
3135
|
/** 会话管家对话框。 */
|
|
3326
|
-
function StewardPanel({ t,
|
|
3327
|
-
const [tab, setTab] = (0, react.useState)(
|
|
3136
|
+
function StewardPanel({ t, healthCheck, searchEnabled = false, openSession, onClose }) {
|
|
3137
|
+
const [tab, setTab] = (0, react.useState)(searchEnabled ? "manage" : "health");
|
|
3328
3138
|
(0, react.useEffect)(() => {
|
|
3329
|
-
if (tab === "
|
|
3330
|
-
if (tab === "health" && !healthCheck &&
|
|
3331
|
-
if (tab === "manage" && !searchEnabled && historyFiles) setTab("history");
|
|
3139
|
+
if (tab === "manage" && !searchEnabled && healthCheck) setTab("health");
|
|
3140
|
+
if (tab === "health" && !healthCheck && searchEnabled) setTab("manage");
|
|
3332
3141
|
}, [
|
|
3333
3142
|
tab,
|
|
3334
|
-
historyFiles,
|
|
3335
3143
|
healthCheck,
|
|
3336
3144
|
searchEnabled
|
|
3337
3145
|
]);
|
|
@@ -3345,16 +3153,16 @@ window.__ModuleLoader__.load({
|
|
|
3345
3153
|
};
|
|
3346
3154
|
}, [onClose]);
|
|
3347
3155
|
const tabs = [];
|
|
3348
|
-
if (
|
|
3349
|
-
key:
|
|
3350
|
-
id:
|
|
3156
|
+
if (searchEnabled) tabs.push((0, react.createElement)("button", {
|
|
3157
|
+
key: TAB_MANAGE,
|
|
3158
|
+
id: TAB_MANAGE,
|
|
3351
3159
|
type: "button",
|
|
3352
|
-
className: `dss_tab${tab === "
|
|
3353
|
-
"aria-selected": tab === "
|
|
3160
|
+
className: `dss_tab${tab === "manage" ? " dss_tabActive" : ""}`,
|
|
3161
|
+
"aria-selected": tab === "manage",
|
|
3354
3162
|
onClick: () => {
|
|
3355
|
-
setTab("
|
|
3163
|
+
setTab("manage");
|
|
3356
3164
|
}
|
|
3357
|
-
}, translate(t, "panel.tab.
|
|
3165
|
+
}, translate(t, "panel.tab.manage")));
|
|
3358
3166
|
if (healthCheck) tabs.push((0, react.createElement)("button", {
|
|
3359
3167
|
key: TAB_HEALTH,
|
|
3360
3168
|
id: TAB_HEALTH,
|
|
@@ -3365,28 +3173,15 @@ window.__ModuleLoader__.load({
|
|
|
3365
3173
|
setTab("health");
|
|
3366
3174
|
}
|
|
3367
3175
|
}, translate(t, "panel.tab.health")));
|
|
3368
|
-
|
|
3369
|
-
key: TAB_MANAGE,
|
|
3370
|
-
id: TAB_MANAGE,
|
|
3371
|
-
type: "button",
|
|
3372
|
-
className: `dss_tab${tab === "manage" ? " dss_tabActive" : ""}`,
|
|
3373
|
-
"aria-selected": tab === "manage",
|
|
3374
|
-
onClick: () => {
|
|
3375
|
-
setTab("manage");
|
|
3376
|
-
}
|
|
3377
|
-
}, translate(t, "panel.tab.manage")));
|
|
3378
|
-
const body = tab === "history" && historyFiles ? (0, react.createElement)(HistoryPanel, {
|
|
3379
|
-
t,
|
|
3380
|
-
onClose
|
|
3381
|
-
}) : tab === "health" && healthCheck ? (0, react.createElement)(HealthPanel, {
|
|
3382
|
-
t,
|
|
3383
|
-
onClose
|
|
3384
|
-
}) : tab === "manage" && searchEnabled ? (0, react.createElement)("div", { style: {
|
|
3176
|
+
const body = tab === "manage" && searchEnabled ? (0, react.createElement)("div", { style: {
|
|
3385
3177
|
display: "flex",
|
|
3386
3178
|
flexDirection: "column",
|
|
3387
3179
|
flex: "1 1 auto",
|
|
3388
3180
|
minHeight: 0
|
|
3389
|
-
} }, (0, react.createElement)(ManageConsole, { open: openSession ?? (() => {}) })) : (0, react.createElement)(
|
|
3181
|
+
} }, (0, react.createElement)(ManageConsole, { open: openSession ?? (() => {}) })) : tab === "health" && healthCheck ? (0, react.createElement)(HealthPanel, {
|
|
3182
|
+
t,
|
|
3183
|
+
onClose
|
|
3184
|
+
}) : (0, react.createElement)("div", { className: "dss_empty" }, translate(t, "card.enabled.desc"));
|
|
3390
3185
|
return (0, react_dom.createPortal)((0, react.createElement)("div", { key: "steward-root" }, [(0, react.createElement)("div", {
|
|
3391
3186
|
key: "backdrop",
|
|
3392
3187
|
className: "dss_backdrop",
|
|
@@ -3439,6 +3234,269 @@ window.__ModuleLoader__.load({
|
|
|
3439
3234
|
}, props.label) : null]);
|
|
3440
3235
|
}
|
|
3441
3236
|
//#endregion
|
|
3237
|
+
//#region src/client/search/locate.ts
|
|
3238
|
+
/** DRS fenced 路由(同源相对路径,与 dsh-reference-session-collector DRS_API_PATH 字面一致)。 */
|
|
3239
|
+
const DRS_ROUTE = "/api/dsh-reference-session-collector";
|
|
3240
|
+
/** 定位总超时(打开会话后 DOM 渲染窗口)。 */
|
|
3241
|
+
const LOCATE_TIMEOUT_MS = 8e3;
|
|
3242
|
+
/** 高亮驻留时长后自动摘除。 */
|
|
3243
|
+
const FLASH_MS = 6e3;
|
|
3244
|
+
/** needle 下限——短于此值的行("好的")歧义面太大,不值得定位。 */
|
|
3245
|
+
const MIN_NEEDLE_CHARS = 12;
|
|
3246
|
+
/** needle 上限——取前缀足够唯一,过长整串匹配反而更易被渲染差异打断。 */
|
|
3247
|
+
const MAX_NEEDLE_CHARS = 80;
|
|
3248
|
+
/** 驻留确认窗口:目标行在视口内稳定停留这么久才算跳转成功。 */
|
|
3249
|
+
const STICK_MS = 900;
|
|
3250
|
+
/** 翻页上限:24 页 × 宿主页宽足够任何真机会话;防无限拉页。 */
|
|
3251
|
+
const MAX_OLDER_PAGES = 24;
|
|
3252
|
+
/** 硬封顶:带翻页的定位窗口上限。 */
|
|
3253
|
+
const HARD_DEADLINE_MS = 3e4;
|
|
3254
|
+
/** 巡逻间隔:会话重渲染会换 DOM 节点,每轮重新定位。 */
|
|
3255
|
+
const TICK_MS = 350;
|
|
3256
|
+
/** 定位行在视口内的判定余量(顶部允许略出屏,代码块开头行高度不定)。 */
|
|
3257
|
+
const VISIBILITY_SLACK = 40;
|
|
3258
|
+
/** markdown 结构符与空白——DOM 渲染后不含这些,两侧同抹才能对上。 */
|
|
3259
|
+
const MATCH_NOISE_RE = /[\s`*_~#>|[\]()!,。:;、"'\\{}-]+/gu;
|
|
3260
|
+
/** 归一化比对文本:去空白与 markdown 噪声字符。 */
|
|
3261
|
+
function normalizeForMatch(text) {
|
|
3262
|
+
return text.replace(MATCH_NOISE_RE, "");
|
|
3263
|
+
}
|
|
3264
|
+
/**
|
|
3265
|
+
* 从段行选一条可定位 needle(按纯函数导出,供单测)。
|
|
3266
|
+
* @returns 归一化 needle;null=该行不可定位(被替换原文/无有效文本/过短歧义)。
|
|
3267
|
+
*/
|
|
3268
|
+
function resolveNeedle(row) {
|
|
3269
|
+
if (row === void 0) return null;
|
|
3270
|
+
if (Number(row.on_surface ?? 1) === 0) return null;
|
|
3271
|
+
const sources = Number(row.compressed ?? 0) === 1 ? [
|
|
3272
|
+
row.surface_text,
|
|
3273
|
+
row.search_text,
|
|
3274
|
+
row.text
|
|
3275
|
+
] : [row.search_text, row.text];
|
|
3276
|
+
for (const source of sources) {
|
|
3277
|
+
if (typeof source !== "string" || source === "") continue;
|
|
3278
|
+
const needle = normalizeForMatch(source.split("\n").find((line) => line.trim() !== "") ?? source).slice(0, MAX_NEEDLE_CHARS);
|
|
3279
|
+
if (needle.length >= MIN_NEEDLE_CHARS) return needle;
|
|
3280
|
+
const whole = normalizeForMatch(source).slice(0, MAX_NEEDLE_CHARS);
|
|
3281
|
+
if (whole.length >= MIN_NEEDLE_CHARS) return whole;
|
|
3282
|
+
}
|
|
3283
|
+
return null;
|
|
3284
|
+
}
|
|
3285
|
+
/** 正文候选标签:只取消息渲染会用的块级/内联代码标签——不含 div/span,
|
|
3286
|
+
* 否则搜索面板自己的结果行(dsws_snippet 的 span,文本就含 needle 前缀)会抢先命中。 */
|
|
3287
|
+
const MESSAGE_TAGS = "p, li, pre, blockquote, h1, h2, h3, h4, td, code";
|
|
3288
|
+
/** 阅读线偏移(dsh-tidy-display conversation-scroll.ts READING_LINE_OFFSET_PX 同值)。 */
|
|
3289
|
+
const READING_LINE_OFFSET_PX = 24;
|
|
3290
|
+
/** 有布局才叫可见:未挂载页签(display:none)的 rect 全 0,旧视口判定会误判驻留。 */
|
|
3291
|
+
function hasLayout(el) {
|
|
3292
|
+
return el.offsetHeight > 0 || el.offsetWidth > 0;
|
|
3293
|
+
}
|
|
3294
|
+
/** 祖先滚动盒(tidy-display 官方选择子);缺席时退化窗口滚动。 */
|
|
3295
|
+
function scrollerOf(el) {
|
|
3296
|
+
return el.closest("[data-conversation-scroll]");
|
|
3297
|
+
}
|
|
3298
|
+
/** 在滚动窗口坐标系里把行着陆到顶部偏读线位——**绝不 scrollIntoView**
|
|
3299
|
+
* (dsh-tidy-display 实锤:它会爬祖先 scroller 把 sticky composer 抬离底部,
|
|
3300
|
+
* 随后宿主回贴 = 「还停在底部」);与阅读模式 landTurn 同款 scrollTop 数学。 */
|
|
3301
|
+
function landRow(el, offsetPx = READING_LINE_OFFSET_PX) {
|
|
3302
|
+
const html = el;
|
|
3303
|
+
const port = scrollerOf(html) ?? el.ownerDocument.scrollingElement ?? html;
|
|
3304
|
+
if (port === html) {
|
|
3305
|
+
html.scrollIntoView({
|
|
3306
|
+
block: "start",
|
|
3307
|
+
behavior: "auto"
|
|
3308
|
+
});
|
|
3309
|
+
return;
|
|
3310
|
+
}
|
|
3311
|
+
port.scrollTop += html.getBoundingClientRect().top - port.getBoundingClientRect().top - offsetPx;
|
|
3312
|
+
}
|
|
3313
|
+
/** 行在**其滚动窗口**内是否已就位(驻留判定也用窗口系,不用视口系)。 */
|
|
3314
|
+
function landedInPort(el) {
|
|
3315
|
+
const html = el;
|
|
3316
|
+
if (!hasLayout(html)) return false;
|
|
3317
|
+
const port = scrollerOf(html);
|
|
3318
|
+
if (port === null) {
|
|
3319
|
+
const rect = html.getBoundingClientRect();
|
|
3320
|
+
const vh = html.ownerDocument.defaultView?.innerHeight ?? 0;
|
|
3321
|
+
return rect.top >= -40 && rect.bottom <= vh + VISIBILITY_SLACK;
|
|
3322
|
+
}
|
|
3323
|
+
const flow = html.getBoundingClientRect().top - port.getBoundingClientRect().top;
|
|
3324
|
+
return flow >= -40 && flow <= port.clientHeight + VISIBILITY_SLACK;
|
|
3325
|
+
}
|
|
3326
|
+
/** 阅读模式优先:命中行若落在 [data-reader-turn] 段内,直接返该段(turn 级
|
|
3327
|
+
* 锚点由插件自标,比文本匹配稳);env.turn 给定时还能直接按 turn 号选段。 */
|
|
3328
|
+
function findReaderRow(root, needle, turn) {
|
|
3329
|
+
if (turn !== void 0 && turn > 0) {
|
|
3330
|
+
const direct = root.querySelector(`[data-reader-turn="${String(turn)}"]`);
|
|
3331
|
+
if (direct !== null && hasLayout(direct)) return direct;
|
|
3332
|
+
}
|
|
3333
|
+
for (const el of Array.from(root.querySelectorAll(MESSAGE_TAGS))) {
|
|
3334
|
+
if (!hasLayout(el)) continue;
|
|
3335
|
+
if (el.closest("[class*=\"dsws_\"], [role=\"dialog\"]") !== null) continue;
|
|
3336
|
+
const text = el.textContent;
|
|
3337
|
+
if (text === null || !normalizeForMatch(text).includes(needle)) continue;
|
|
3338
|
+
const section = el.closest("[data-reader-turn]");
|
|
3339
|
+
if (section !== null && hasLayout(section)) return section;
|
|
3340
|
+
return el;
|
|
3341
|
+
}
|
|
3342
|
+
return null;
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* 找正文行:多义时取 DOM 最深者(段落本体而非包裹层);**排除插件自家
|
|
3346
|
+
* 子树**(dsws_ 前缀类与任何 role=dialog 弹层)——面板行/管理台行都是
|
|
3347
|
+
* 目标文本的镜像,绝不允许当作跳转目的地。
|
|
3348
|
+
*/
|
|
3349
|
+
function findMessageElement(root, needle, turn) {
|
|
3350
|
+
const reader = findReaderRow(root, needle, turn);
|
|
3351
|
+
if (reader !== null) return reader;
|
|
3352
|
+
let best = null;
|
|
3353
|
+
let bestDepth = -1;
|
|
3354
|
+
for (const el of Array.from(root.querySelectorAll(MESSAGE_TAGS))) {
|
|
3355
|
+
if (!hasLayout(el)) continue;
|
|
3356
|
+
const text = el.textContent;
|
|
3357
|
+
if (text === null) continue;
|
|
3358
|
+
if (el.closest("[class*=\"dsws_\"], [role=\"dialog\"]") !== null) continue;
|
|
3359
|
+
if (!normalizeForMatch(text).includes(needle)) continue;
|
|
3360
|
+
if (el.closest("[data-reader-turn]") !== null) continue;
|
|
3361
|
+
let depth = 0;
|
|
3362
|
+
for (let n = el.parentElement; n !== null; n = n.parentElement) depth += 1;
|
|
3363
|
+
if (depth > bestDepth) {
|
|
3364
|
+
best = el;
|
|
3365
|
+
bestDepth = depth;
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
return best;
|
|
3369
|
+
}
|
|
3370
|
+
/** 瞬时高亮:类切换(不写 inline 样式——真机教训:prepend 历史后 React 复用
|
|
3371
|
+
* 节点,定时器迟到把旧样式写在不相干元素上)。首次使用时惰性注入样式表。 */
|
|
3372
|
+
const FLASH_CLASS = "dss-located-flash";
|
|
3373
|
+
let flashCssReady = false;
|
|
3374
|
+
function ensureFlashCss(doc) {
|
|
3375
|
+
if (flashCssReady) return;
|
|
3376
|
+
flashCssReady = true;
|
|
3377
|
+
const style = doc.createElement("style");
|
|
3378
|
+
style.textContent = `.${FLASH_CLASS}{outline:2px solid var(--dsw-alias-accent,#4a9eff);outline-offset:2px;transition:outline 120ms ease}`;
|
|
3379
|
+
doc.head.appendChild(style);
|
|
3380
|
+
}
|
|
3381
|
+
function flashElement(el) {
|
|
3382
|
+
if (el.ownerDocument !== null) ensureFlashCss(el.ownerDocument);
|
|
3383
|
+
el.setAttribute("data-dss-located", "1");
|
|
3384
|
+
el.classList.add(FLASH_CLASS);
|
|
3385
|
+
setTimeout(() => {
|
|
3386
|
+
el.classList.remove(FLASH_CLASS);
|
|
3387
|
+
el.removeAttribute("data-dss-located");
|
|
3388
|
+
}, FLASH_MS);
|
|
3389
|
+
}
|
|
3390
|
+
let injectedDeps = {};
|
|
3391
|
+
/** 接线点:client apply 调一次;未接线时定位退化为 v3 纯 DOM 巡逻。 */
|
|
3392
|
+
function configureLocator(deps) {
|
|
3393
|
+
injectedDeps = deps;
|
|
3394
|
+
}
|
|
3395
|
+
/** 目标行是否基本完整落在**其滚动窗口**内由 landedInPort 判定;旧视口函数已废弃。 */
|
|
3396
|
+
/**
|
|
3397
|
+
* 主管线:拉 DRS 段行 → 选 needle → 巡逻宿主 DOM 补滚 → 驻留后高亮。
|
|
3398
|
+
* fire-and-forget 语义:任何一步不满足都 resolve(false),从不抛。
|
|
3399
|
+
*/
|
|
3400
|
+
async function locateSearchHit(hit, env = {}) {
|
|
3401
|
+
const fetchImpl = env.fetchImpl ?? globalThis.fetch;
|
|
3402
|
+
const doc = env.doc ?? (typeof document === "undefined" ? void 0 : document);
|
|
3403
|
+
if (doc === void 0) return false;
|
|
3404
|
+
const win = env.win ?? doc.defaultView ?? (typeof window === "undefined" ? void 0 : window);
|
|
3405
|
+
if (win === void 0) return false;
|
|
3406
|
+
const log = env.log ?? ((message) => {
|
|
3407
|
+
console.debug(`[dsh-manage-session] ${message}`);
|
|
3408
|
+
});
|
|
3409
|
+
try {
|
|
3410
|
+
const response = await fetchImpl(`${DRS_ROUTE}?op=session&session=${encodeURIComponent(hit.sessionId)}`);
|
|
3411
|
+
if (!response.ok) {
|
|
3412
|
+
log(`locate: drs http ${String(response.status)}`);
|
|
3413
|
+
return false;
|
|
3414
|
+
}
|
|
3415
|
+
const body = await response.json();
|
|
3416
|
+
if (body.ok !== true || !Array.isArray(body.rows)) {
|
|
3417
|
+
log("locate: drs envelope shape");
|
|
3418
|
+
return false;
|
|
3419
|
+
}
|
|
3420
|
+
const row = body.rows.find((r) => Number(r.seq) === Number(hit.seq));
|
|
3421
|
+
const needle = resolveNeedle(row);
|
|
3422
|
+
if (needle === null) {
|
|
3423
|
+
log(`locate: skip row seq=${String(hit.seq)} (shadowed/短文本/无行)`);
|
|
3424
|
+
return false;
|
|
3425
|
+
}
|
|
3426
|
+
const targetTurn = typeof row?.turn === "number" ? row.turn : void 0;
|
|
3427
|
+
const startedAt = Date.now();
|
|
3428
|
+
let deadline = startedAt + (env.timeoutMs ?? LOCATE_TIMEOUT_MS);
|
|
3429
|
+
const hardDeadline = startedAt + HARD_DEADLINE_MS;
|
|
3430
|
+
let lastScrollAt = 0;
|
|
3431
|
+
let userTookOver = false;
|
|
3432
|
+
const pullOlder = env.loadOlder ?? injectedDeps.loadOlder;
|
|
3433
|
+
let pagesPulled = 0;
|
|
3434
|
+
let pullInFlight = false;
|
|
3435
|
+
let lastPullAt = 0;
|
|
3436
|
+
const USER_EVENTS = [
|
|
3437
|
+
"wheel",
|
|
3438
|
+
"touchstart",
|
|
3439
|
+
"keydown",
|
|
3440
|
+
"pointerdown"
|
|
3441
|
+
];
|
|
3442
|
+
const onUser = () => {
|
|
3443
|
+
userTookOver = true;
|
|
3444
|
+
};
|
|
3445
|
+
return await new Promise((resolve) => {
|
|
3446
|
+
const finish = (ok, why) => {
|
|
3447
|
+
clearInterval(ticker);
|
|
3448
|
+
for (const ev of USER_EVENTS) win.removeEventListener(ev, onUser);
|
|
3449
|
+
log(`${why}: seq=${String(hit.seq)} ${hit.sessionId.slice(0, 12)} +${String(Date.now() - startedAt)}ms`);
|
|
3450
|
+
resolve(ok);
|
|
3451
|
+
};
|
|
3452
|
+
for (const ev of USER_EVENTS) win.addEventListener(ev, onUser, {
|
|
3453
|
+
passive: true,
|
|
3454
|
+
once: true
|
|
3455
|
+
});
|
|
3456
|
+
const ticker = setInterval(() => {
|
|
3457
|
+
if (userTookOver) {
|
|
3458
|
+
finish(false, "yield user");
|
|
3459
|
+
return;
|
|
3460
|
+
}
|
|
3461
|
+
if (Date.now() >= deadline) {
|
|
3462
|
+
finish(false, `locate timeout (pages=${String(pagesPulled)})`);
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
const el = findMessageElement(doc.body ?? doc, needle, targetTurn);
|
|
3466
|
+
if (el === null) {
|
|
3467
|
+
if (pullOlder !== void 0 && !pullInFlight && pagesPulled < MAX_OLDER_PAGES && deadline < hardDeadline && Date.now() - lastPullAt >= TICK_MS * 2) {
|
|
3468
|
+
pullInFlight = true;
|
|
3469
|
+
lastPullAt = Date.now();
|
|
3470
|
+
Promise.resolve().then(() => pullOlder()).then((pulled) => {
|
|
3471
|
+
if (pulled) {
|
|
3472
|
+
pagesPulled += 1;
|
|
3473
|
+
deadline = Math.min(hardDeadline, Date.now() + 2e3 + (env.timeoutMs ?? LOCATE_TIMEOUT_MS) / 2);
|
|
3474
|
+
} else deadline = Math.min(deadline, Date.now());
|
|
3475
|
+
}).catch(() => {}).finally(() => {
|
|
3476
|
+
pullInFlight = false;
|
|
3477
|
+
});
|
|
3478
|
+
}
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
if (landedInPort(el)) {
|
|
3482
|
+
if (lastScrollAt === 0 || Date.now() - lastScrollAt >= STICK_MS) {
|
|
3483
|
+
flashElement(el);
|
|
3484
|
+
finish(true, "located");
|
|
3485
|
+
}
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3488
|
+
if (Date.now() - lastScrollAt >= TICK_MS) {
|
|
3489
|
+
landRow(el);
|
|
3490
|
+
lastScrollAt = Date.now();
|
|
3491
|
+
}
|
|
3492
|
+
}, TICK_MS);
|
|
3493
|
+
});
|
|
3494
|
+
} catch (err) {
|
|
3495
|
+
log(`locate error(silent): ${String(err instanceof Error ? err.message : err)}`);
|
|
3496
|
+
return false;
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
//#endregion
|
|
3442
3500
|
//#region src/client/search/platform.ts
|
|
3443
3501
|
/**
|
|
3444
3502
|
* Resolve the running platform string, three tiers deep.
|
|
@@ -3568,6 +3626,14 @@ window.__ModuleLoader__.load({
|
|
|
3568
3626
|
id: "time",
|
|
3569
3627
|
labelKey: "sort.time"
|
|
3570
3628
|
}];
|
|
3629
|
+
/** 命中粒度 chips(仅内容模式)。 */
|
|
3630
|
+
const GROUP_CHIPS = [{
|
|
3631
|
+
id: "merge",
|
|
3632
|
+
labelKey: "group.merge"
|
|
3633
|
+
}, {
|
|
3634
|
+
id: "expand",
|
|
3635
|
+
labelKey: "group.expand"
|
|
3636
|
+
}];
|
|
3571
3637
|
/**
|
|
3572
3638
|
* The identity of one content-search request, derived from every input that
|
|
3573
3639
|
* changes the result set.
|
|
@@ -3585,8 +3651,8 @@ window.__ModuleLoader__.load({
|
|
|
3585
3651
|
* @param archivedFilter - the retrieval-domain chip (all/active/archived).
|
|
3586
3652
|
* @returns an opaque key, stable for equal inputs.
|
|
3587
3653
|
*/
|
|
3588
|
-
function contentRequestKey(normalized, contentType, sortBy, archivedFilter, dateRange) {
|
|
3589
|
-
return `${normalized}\u0000${contentType}\u0000${sortBy}\u0000${archivedFilter}\u0000${dateRange.from}\u0000${dateRange.to}`;
|
|
3654
|
+
function contentRequestKey(normalized, contentType, sortBy, archivedFilter, dateRange, groupMode) {
|
|
3655
|
+
return `${normalized}\u0000${contentType}\u0000${sortBy}\u0000${archivedFilter}\u0000${dateRange.from}\u0000${dateRange.to}\u0000${groupMode}`;
|
|
3590
3656
|
}
|
|
3591
3657
|
/**
|
|
3592
3658
|
* Persisted ordering preference. Unlike `lastPanelMode` this one survives a
|
|
@@ -3607,6 +3673,20 @@ window.__ModuleLoader__.load({
|
|
|
3607
3673
|
window.localStorage.setItem(SORT_STORE_KEY, next);
|
|
3608
3674
|
} catch {}
|
|
3609
3675
|
}
|
|
3676
|
+
/** 命中粒度持久化(与排序同款:耐久偏好,不是会话情绪)。 */
|
|
3677
|
+
const GROUP_STORE_KEY = "dsh-search-index.groupMode";
|
|
3678
|
+
function readStoredGroup() {
|
|
3679
|
+
try {
|
|
3680
|
+
return window.localStorage.getItem(GROUP_STORE_KEY) === "expand" ? "expand" : "merge";
|
|
3681
|
+
} catch {
|
|
3682
|
+
return "merge";
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
function writeStoredGroup(next) {
|
|
3686
|
+
try {
|
|
3687
|
+
window.localStorage.setItem(GROUP_STORE_KEY, next);
|
|
3688
|
+
} catch {}
|
|
3689
|
+
}
|
|
3610
3690
|
/**
|
|
3611
3691
|
* Local re-sort so the page honours the chosen ordering even against an older
|
|
3612
3692
|
* host half that ignores `sortBy` and therefore omits `updatedAt` entirely —
|
|
@@ -3665,6 +3745,12 @@ window.__ModuleLoader__.load({
|
|
|
3665
3745
|
.dsws_list{flex:1 1 auto;min-height:0;overflow-y:auto;margin:8px 0 0;padding:0 6px 8px;list-style:none}
|
|
3666
3746
|
.dsws_row{box-sizing:border-box;border-radius:8px;width:100%;padding:7px 8px;cursor:pointer;text-align:left;border:none;background:transparent;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
3667
3747
|
.dsws_row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
3748
|
+
.dsws_rowLine{display:flex;align-items:stretch;gap:4px}
|
|
3749
|
+
.dsws_rowLine>.dsws_row{flex:1 1 auto;width:auto;min-width:0}
|
|
3750
|
+
.dsws_delBtn{flex:none;align-self:center;height:24px;box-sizing:border-box;border:1px solid var(--dsw-alias-state-error-primary);background:transparent;color:var(--dsw-alias-state-error-primary);border-radius:999px;padding:0 10px;font-size:12px;line-height:22px;cursor:pointer;white-space:nowrap}
|
|
3751
|
+
.dsws_delBtn:hover{background:var(--dsw-alias-state-error-primary);color:#fff}
|
|
3752
|
+
.dsws_delBtnArm{background:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);color:#fff;font-weight:600}
|
|
3753
|
+
.dsws_delBtn:disabled{opacity:.5;cursor:default}
|
|
3668
3754
|
.dsws_rowTitle{display:flex;align-items:center;gap:8px;min-width:0}
|
|
3669
3755
|
.dsws_titleText{flex:auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;line-height:18px}
|
|
3670
3756
|
.dsws_tag{flex:none;color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
@@ -3748,11 +3834,21 @@ window.__ModuleLoader__.load({
|
|
|
3748
3834
|
const [archivedFilter, setArchivedFilter] = (0, react.useState)("all");
|
|
3749
3835
|
const [dateFrom, setDateFrom] = (0, react.useState)("");
|
|
3750
3836
|
const [dateTo, setDateTo] = (0, react.useState)("");
|
|
3837
|
+
const [locateStatus, setLocateStatus] = (0, react.useState)({});
|
|
3838
|
+
const [directDelete, setDirectDelete] = (0, react.useState)(false);
|
|
3839
|
+
const [armedDelete, setArmedDelete] = (0, react.useState)(null);
|
|
3840
|
+
const [deleteBusy, setDeleteBusy] = (0, react.useState)(false);
|
|
3841
|
+
const [deleteNote, setDeleteNote] = (0, react.useState)(null);
|
|
3751
3842
|
const [sortBy, setSortByState] = (0, react.useState)(readStoredSort);
|
|
3752
3843
|
const setSortBy = (next) => {
|
|
3753
3844
|
writeStoredSort(next);
|
|
3754
3845
|
setSortByState(next);
|
|
3755
3846
|
};
|
|
3847
|
+
const [groupMode, setGroupModeState] = (0, react.useState)(readStoredGroup);
|
|
3848
|
+
const setGroupMode = (next) => {
|
|
3849
|
+
writeStoredGroup(next);
|
|
3850
|
+
setGroupModeState(next);
|
|
3851
|
+
};
|
|
3756
3852
|
const [sessions, setSessions] = (0, react.useState)(null);
|
|
3757
3853
|
const [sessionsError, setSessionsError] = (0, react.useState)(null);
|
|
3758
3854
|
const [content, setContent] = (0, react.useState)({
|
|
@@ -3789,6 +3885,7 @@ window.__ModuleLoader__.load({
|
|
|
3789
3885
|
}
|
|
3790
3886
|
const status = res;
|
|
3791
3887
|
const rebuilding = status.rebuild?.state === "building" || status.rebuild?.state === "swapping";
|
|
3888
|
+
setDirectDelete(status.directDelete === true);
|
|
3792
3889
|
setSearchStatus({
|
|
3793
3890
|
available: status.available ?? false,
|
|
3794
3891
|
reason: status.reason,
|
|
@@ -3813,6 +3910,46 @@ window.__ModuleLoader__.load({
|
|
|
3813
3910
|
}));
|
|
3814
3911
|
callHostAny("index-rebuild", {});
|
|
3815
3912
|
};
|
|
3913
|
+
/**
|
|
3914
|
+
* 真删一个会话(功能①):走管理端同款 session-history-purge 链路(内存
|
|
3915
|
+
* 全痕删除优先,宿主成功后落墓碑台账);成功只把该会话从两侧结果面
|
|
3916
|
+
* 摸掉——不刷语料、不关面板(失败要能原地重试)。
|
|
3917
|
+
*/
|
|
3918
|
+
const purgeSession = (sessionId) => {
|
|
3919
|
+
setArmedDelete(null);
|
|
3920
|
+
setDeleteBusy(true);
|
|
3921
|
+
setDeleteNote(null);
|
|
3922
|
+
callSteward("session-history-purge", { sessionIds: [sessionId] }).then((res) => {
|
|
3923
|
+
if (res.ok) {
|
|
3924
|
+
setSessions((prev) => prev === null ? prev : prev.filter((it) => it.sessionId !== sessionId));
|
|
3925
|
+
setContent((prev) => ({
|
|
3926
|
+
...prev,
|
|
3927
|
+
items: prev.items.filter((it) => it.sessionId !== sessionId)
|
|
3928
|
+
}));
|
|
3929
|
+
setDeleteNote({
|
|
3930
|
+
text: translate$1(t, "panel.delete.done", { n: res.purged ?? 1 }),
|
|
3931
|
+
failed: false
|
|
3932
|
+
});
|
|
3933
|
+
} else setDeleteNote({
|
|
3934
|
+
text: translate$1(t, "panel.delete.failed", { error: res.error ?? "?" }),
|
|
3935
|
+
failed: true
|
|
3936
|
+
});
|
|
3937
|
+
}).finally(() => {
|
|
3938
|
+
setDeleteBusy(false);
|
|
3939
|
+
});
|
|
3940
|
+
};
|
|
3941
|
+
/** 行内删除按钮(仅 directDelete 开启时参与渲染):两步确认,第一击上弦。 */
|
|
3942
|
+
const deleteBtn = (sessionId) => (0, react.createElement)("button", {
|
|
3943
|
+
key: "del",
|
|
3944
|
+
type: "button",
|
|
3945
|
+
className: `dsws_delBtn${armedDelete === sessionId ? " dsws_delBtnArm" : ""}`,
|
|
3946
|
+
title: translate$1(t, "panel.delete.hint"),
|
|
3947
|
+
disabled: deleteBusy,
|
|
3948
|
+
onClick: () => {
|
|
3949
|
+
if (armedDelete === sessionId) purgeSession(sessionId);
|
|
3950
|
+
else setArmedDelete(sessionId);
|
|
3951
|
+
}
|
|
3952
|
+
}, armedDelete === sessionId ? translate$1(t, "panel.delete.confirm") : translate$1(t, "panel.delete"));
|
|
3816
3953
|
(0, react.useEffect)(() => {
|
|
3817
3954
|
if (sessions !== null) return;
|
|
3818
3955
|
let cancelled = false;
|
|
@@ -3841,7 +3978,7 @@ window.__ModuleLoader__.load({
|
|
|
3841
3978
|
const requestKey = contentRequestKey(normalized, requestType, sortBy, archivedFilter, {
|
|
3842
3979
|
from: dateFrom,
|
|
3843
3980
|
to: dateTo
|
|
3844
|
-
});
|
|
3981
|
+
}, groupMode);
|
|
3845
3982
|
setContent((prev) => ({
|
|
3846
3983
|
query: requestKey,
|
|
3847
3984
|
status: "loading",
|
|
@@ -3854,6 +3991,7 @@ window.__ModuleLoader__.load({
|
|
|
3854
3991
|
types: requestType === "all" ? void 0 : [requestType],
|
|
3855
3992
|
sortBy,
|
|
3856
3993
|
archived: archivedFilter,
|
|
3994
|
+
expand: groupMode === "expand" || void 0,
|
|
3857
3995
|
from: dateFrom === "" ? void 0 : dateFrom,
|
|
3858
3996
|
to: dateTo === "" ? void 0 : dateTo
|
|
3859
3997
|
}).then((res) => {
|
|
@@ -3877,7 +4015,8 @@ window.__ModuleLoader__.load({
|
|
|
3877
4015
|
sortBy,
|
|
3878
4016
|
archivedFilter,
|
|
3879
4017
|
dateFrom,
|
|
3880
|
-
dateTo
|
|
4018
|
+
dateTo,
|
|
4019
|
+
groupMode
|
|
3881
4020
|
]);
|
|
3882
4021
|
(0, react.useEffect)(() => {
|
|
3883
4022
|
inputRef.current?.focus();
|
|
@@ -3920,6 +4059,10 @@ window.__ModuleLoader__.load({
|
|
|
3920
4059
|
dateRangeMs
|
|
3921
4060
|
]);
|
|
3922
4061
|
const children = [];
|
|
4062
|
+
if (deleteNote !== null) children.push((0, react.createElement)("div", {
|
|
4063
|
+
key: "delnote",
|
|
4064
|
+
className: deleteNote.failed ? "dsws_error" : "dsws_status"
|
|
4065
|
+
}, deleteNote.text));
|
|
3923
4066
|
if (sessionsError !== null) children.push((0, react.createElement)("div", {
|
|
3924
4067
|
key: "err",
|
|
3925
4068
|
className: "dsws_error"
|
|
@@ -3927,7 +4070,7 @@ window.__ModuleLoader__.load({
|
|
|
3927
4070
|
const activeRequestKey = contentRequestKey(normalized, contentType, sortBy, archivedFilter, {
|
|
3928
4071
|
from: dateFrom,
|
|
3929
4072
|
to: dateTo
|
|
3930
|
-
});
|
|
4073
|
+
}, groupMode);
|
|
3931
4074
|
const activeContent = content.query === activeRequestKey ? content : {
|
|
3932
4075
|
query: activeRequestKey,
|
|
3933
4076
|
status: "loading",
|
|
@@ -3951,10 +4094,12 @@ window.__ModuleLoader__.load({
|
|
|
3951
4094
|
}, titleRows.slice(0, 200).map((item) => (0, react.createElement)("li", {
|
|
3952
4095
|
key: item.sessionId,
|
|
3953
4096
|
role: "option"
|
|
3954
|
-
}, (0, react.createElement)("button", {
|
|
4097
|
+
}, (0, react.createElement)("div", { className: "dsws_rowLine" }, [(0, react.createElement)("button", {
|
|
4098
|
+
key: "row",
|
|
3955
4099
|
type: "button",
|
|
3956
4100
|
className: "dsws_row",
|
|
3957
4101
|
onClick: () => {
|
|
4102
|
+
setArmedDelete(null);
|
|
3958
4103
|
open(item.sessionId);
|
|
3959
4104
|
}
|
|
3960
4105
|
}, [(0, react.createElement)("span", {
|
|
@@ -3976,7 +4121,7 @@ window.__ModuleLoader__.load({
|
|
|
3976
4121
|
]), item.cwd !== "" && (0, react.createElement)("span", {
|
|
3977
4122
|
key: "c",
|
|
3978
4123
|
className: "dsws_meta"
|
|
3979
|
-
}, item.cwd)])))));
|
|
4124
|
+
}, item.cwd)]), directDelete && deleteBtn(item.sessionId)])))));
|
|
3980
4125
|
} else if (searchStatus.rebuilding) {
|
|
3981
4126
|
const pct = searchStatus.total > 0 ? Math.min(100, Math.round(searchStatus.done / searchStatus.total * 100)) : void 0;
|
|
3982
4127
|
children.push((0, react.createElement)("div", {
|
|
@@ -4027,13 +4172,26 @@ window.__ModuleLoader__.load({
|
|
|
4027
4172
|
role: "listbox",
|
|
4028
4173
|
"aria-label": translate$1(t, "panel.contentSearch")
|
|
4029
4174
|
}, activeContent.items.slice(0, 200).map((item) => (0, react.createElement)("li", {
|
|
4030
|
-
key: item.sessionId
|
|
4175
|
+
key: `${item.sessionId}:${item.seq}`,
|
|
4031
4176
|
role: "option"
|
|
4032
|
-
}, (0, react.createElement)("button", {
|
|
4177
|
+
}, (0, react.createElement)("div", { className: "dsws_rowLine" }, [(0, react.createElement)("button", {
|
|
4178
|
+
key: "row",
|
|
4033
4179
|
type: "button",
|
|
4034
4180
|
className: "dsws_row",
|
|
4035
4181
|
onClick: () => {
|
|
4182
|
+
const hitKey = `${item.sessionId}:${item.seq}`;
|
|
4183
|
+
setArmedDelete(null);
|
|
4036
4184
|
open(item.sessionId);
|
|
4185
|
+
setLocateStatus((s) => ({
|
|
4186
|
+
...s,
|
|
4187
|
+
[hitKey]: "pending"
|
|
4188
|
+
}));
|
|
4189
|
+
locateSearchHit(item).then((ok) => {
|
|
4190
|
+
setLocateStatus((s) => ({
|
|
4191
|
+
...s,
|
|
4192
|
+
[hitKey]: ok ? "ok" : "fail"
|
|
4193
|
+
}));
|
|
4194
|
+
});
|
|
4037
4195
|
}
|
|
4038
4196
|
}, [(0, react.createElement)("span", {
|
|
4039
4197
|
key: "t",
|
|
@@ -4050,11 +4208,16 @@ window.__ModuleLoader__.load({
|
|
|
4050
4208
|
(0, react.createElement)("span", {
|
|
4051
4209
|
key: "time",
|
|
4052
4210
|
className: "dsws_tag"
|
|
4053
|
-
}, fmtTime(item.updatedAt))
|
|
4211
|
+
}, fmtTime(groupMode === "expand" ? item.time : item.updatedAt)),
|
|
4212
|
+
...locateStatus[`${item.sessionId}:${item.seq}`] === void 0 ? [] : [(0, react.createElement)("span", {
|
|
4213
|
+
key: "loc",
|
|
4214
|
+
className: "dsws_tag",
|
|
4215
|
+
title: locateStatus[`${item.sessionId}:${item.seq}`] === "pending" ? "定位巡逻中" : locateStatus[`${item.sessionId}:${item.seq}`] === "ok" ? "已跳转到命中行" : "未能定位(行未渲染/文本被拆分/超时)"
|
|
4216
|
+
}, locateStatus[`${item.sessionId}:${item.seq}`] === "pending" ? "⏳" : locateStatus[`${item.sessionId}:${item.seq}`] === "ok" ? "✓" : "✗")]
|
|
4054
4217
|
]), (0, react.createElement)("span", {
|
|
4055
4218
|
key: "s",
|
|
4056
4219
|
className: "dsws_snippet"
|
|
4057
|
-
}, item.snippet || translate$1(t, "panel.noText"))])))));
|
|
4220
|
+
}, item.snippet || translate$1(t, "panel.noText"))]), directDelete && deleteBtn(item.sessionId)])))));
|
|
4058
4221
|
return (0, react_dom.createPortal)((0, react.createElement)("div", { key: "switch-root" }, [(0, react.createElement)("div", {
|
|
4059
4222
|
key: "backdrop",
|
|
4060
4223
|
className: "dsws_backdrop",
|
|
@@ -4154,6 +4317,16 @@ window.__ModuleLoader__.load({
|
|
|
4154
4317
|
setContentType(chip.id);
|
|
4155
4318
|
}
|
|
4156
4319
|
}, translate$1(t, chip.labelKey))) : [],
|
|
4320
|
+
...mode === "content" ? GROUP_CHIPS.map((chip) => (0, react.createElement)("button", {
|
|
4321
|
+
key: `g-${chip.id}`,
|
|
4322
|
+
type: "button",
|
|
4323
|
+
className: `dsws_chip${groupMode === chip.id ? " dsws_chipActive" : ""}`,
|
|
4324
|
+
"aria-pressed": groupMode === chip.id,
|
|
4325
|
+
title: translate$1(t, chip.id === "expand" ? "group.expand.hint" : "group.merge.hint"),
|
|
4326
|
+
onClick: () => {
|
|
4327
|
+
setGroupMode(chip.id);
|
|
4328
|
+
}
|
|
4329
|
+
}, translate$1(t, chip.labelKey))) : [],
|
|
4157
4330
|
(0, react.createElement)("span", {
|
|
4158
4331
|
key: "gap",
|
|
4159
4332
|
className: "dsws_chipGap"
|
|
@@ -4849,6 +5022,334 @@ window.__ModuleLoader__.load({
|
|
|
4849
5022
|
return children;
|
|
4850
5023
|
}
|
|
4851
5024
|
//#endregion
|
|
5025
|
+
//#region src/client/session-menu-legacy.ts
|
|
5026
|
+
/**
|
|
5027
|
+
* 老线(≤0.1.5)会话「…」菜单删除注入臂(2026-10-20 用户定标)。
|
|
5028
|
+
*
|
|
5029
|
+
* 为什么需要:≤0.1.5 宿主的 ui-workspace 会话菜单是 React **写死**的
|
|
5030
|
+
* `sessionMenuItems`(rename/fork/archive),portal 出 `div[role=menu]`,
|
|
5031
|
+
* **无插件席位**——席位臂(session-menu.tsx)在老线 inject 静默等待、永不落行。
|
|
5032
|
+
* purge 后端两格都通(file 兜底路),缺的只是菜单入口。本臂用 DOM 注入补上:
|
|
5033
|
+
* 监听 portal 菜单出现 → 从被点「…」锚点的行 React fiber 取 sessionId →
|
|
5034
|
+
* 追一行两步确认删除(与管理端/席位臂同款 purge 链路)。
|
|
5035
|
+
*
|
|
5036
|
+
* 现代线(0.1.7+)席位行已渲染并带 `data-steward-menu-delete` 标记,本臂据此
|
|
5037
|
+
* dedup 跳过;故两线共用一个注入器不会出两条删除。
|
|
5038
|
+
*
|
|
5039
|
+
* 纪律:全程 best-effort、任何异常吞掉绝不影响宿主;仅在 directDelete=true 时
|
|
5040
|
+
* 注入(缺省关,破坏性入口不默认铺开)。fiber 取 id 失败即放弃该行(不伪造)。
|
|
5041
|
+
*/
|
|
5042
|
+
/** dedup 标记(与 session-menu.tsx 席位行同串)。 */
|
|
5043
|
+
const STEWARD_MENU_DELETE_ATTR = "data-steward-menu-delete";
|
|
5044
|
+
/**
|
|
5045
|
+
* 删除图标(逐字内联宿主 ui-primitives IconTrashOutline16——工作区「删除」
|
|
5046
|
+
* 同款):单 path + fill=currentColor,16×16,随行文字色变化(上弦变红
|
|
5047
|
+
* 时图标一起红)。插件不能 value-import 宿主图标包(老线 ModuleLoader
|
|
5048
|
+
* 解析不了、未声明 peer),故内联 SVG 串;席位臂与注入臂共用此常量。
|
|
5049
|
+
*/
|
|
5050
|
+
const TRASH_ICON_SVG = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M14.4782 4.84067L14.2138 10.1152C14.1102 12.1872 14.067 13.0115 13.3866 13.9607C13.1044 14.3546 12.7498 14.6912 12.3424 14.9535C11.8239 15.2872 11.2415 15.4316 10.5585 15.4998C9.88727 15.5668 9.04946 15.5656 7.99998 15.5656C6.95051 15.5656 6.1127 15.5668 5.44142 15.4998C4.75851 15.4316 4.17602 15.2872 3.65753 14.9535C3.25012 14.6912 2.89559 14.3546 2.61332 13.9607C1.93296 13.0115 1.88979 12.1872 1.78619 10.1152L1.52179 4.84067L2.89006 4.77277L3.15343 10.0463C3.26221 12.2218 3.32452 12.6015 3.72646 13.1624C3.90825 13.4161 4.13686 13.6334 4.39927 13.8023C4.66204 13.9714 5.00263 14.0792 5.57825 14.1367C6.16562 14.1953 6.92298 14.1963 7.99998 14.1963C9.07699 14.1963 9.83434 14.1953 10.4217 14.1367C10.9973 14.0792 11.3379 13.9714 11.6007 13.8023C11.8631 13.6334 12.0917 13.4161 12.2735 13.1624C12.6755 12.6015 12.7378 12.2218 12.8465 10.0463L13.1099 4.77277L14.4782 4.84067ZM5.43011 6.22849H6.7994V11.3909H5.43011V6.22849ZM9.20056 6.22849H10.5699V11.3909H9.20056V6.22849ZM8.53597 0.434431C9.17976 0.434431 9.6522 0.426926 10.0966 0.571258C10.2357 0.616451 10.3717 0.672554 10.502 0.738948C10.9182 0.951107 11.2464 1.29099 11.7015 1.74612L12.4978 2.54136H15.3742V3.91169H0.625732V2.54136H3.50218L4.29845 1.74612C4.75358 1.29099 5.08174 0.951107 5.49801 0.738948C5.62831 0.672554 5.76425 0.616451 5.90334 0.571258C6.34776 0.426926 6.82021 0.434431 7.46399 0.434431H8.53597ZM7.46399 1.80476C6.73208 1.80476 6.51641 1.81187 6.32617 1.87369C6.25545 1.89667 6.18668 1.92533 6.12041 1.95907C5.96398 2.03878 5.82348 2.16253 5.44142 2.54136H10.5585C10.1765 2.16253 10.036 2.03878 9.87955 1.95907C9.81329 1.92533 9.74452 1.89667 9.6738 1.87369C9.48356 1.81187 9.26789 1.80476 8.53597 1.80476H7.46399Z\" fill=\"currentColor\"/></svg>";
|
|
5051
|
+
/** 图标槽内联样式(对齐宿主 .itemIcon:16×16 inline-flex flex:none)。 */
|
|
5052
|
+
const ICON_SLOT_STYLE = "display:inline-flex;flex:none;width:16px;height:16px;align-items:center";
|
|
5053
|
+
/** 锚点点击 → 菜单出现的有效窗口(超时视为陈旧、不注入)。 */
|
|
5054
|
+
const PENDING_TTL_MS = 1500;
|
|
5055
|
+
/** 按语言前缀挑字典(`zh-CN`→`zh`;未知回退 zh;键缺失回退 zh 同键)。 */
|
|
5056
|
+
function resolveLabels(lang) {
|
|
5057
|
+
const dicts = dictionaries$1;
|
|
5058
|
+
const base = dicts["zh"] ?? {};
|
|
5059
|
+
const pick = (key) => {
|
|
5060
|
+
const norm = (lang ?? "").toLowerCase();
|
|
5061
|
+
const prefix = norm.split("-")[0] ?? "";
|
|
5062
|
+
return dicts[prefix]?.[key] ?? dicts[norm]?.[key] ?? base[key] ?? key;
|
|
5063
|
+
};
|
|
5064
|
+
return {
|
|
5065
|
+
delete: pick("menu.deleteSession"),
|
|
5066
|
+
confirm: pick("menu.deleteSession.confirm")
|
|
5067
|
+
};
|
|
5068
|
+
}
|
|
5069
|
+
/**
|
|
5070
|
+
* 从被点元素向上找 `[role=treeitem]` 会话行,再沿 React fiber `.return` 链
|
|
5071
|
+
* 找 `memoizedProps.node.id`(会话 id)。fiber 键前缀兼容 React 17/18/19
|
|
5072
|
+
* (`__reactFiber$`) 与 16 (`__reactInternalInstance$`)。找不到返回 undefined。
|
|
5073
|
+
* 接受任意带这些字段的对象以便单测(不强依赖真实 DOM 类型)。
|
|
5074
|
+
*/
|
|
5075
|
+
function extractSessionIdFromFiber(startEl) {
|
|
5076
|
+
let el = startEl;
|
|
5077
|
+
let row = null;
|
|
5078
|
+
if (el !== null && el !== void 0 && typeof el.closest === "function") row = el.closest("[role=\"treeitem\"]");
|
|
5079
|
+
if (row === null || row === void 0) return void 0;
|
|
5080
|
+
const fiberKey = Object.keys(row).find((k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$"));
|
|
5081
|
+
if (fiberKey === void 0) return void 0;
|
|
5082
|
+
let fiber = row[fiberKey];
|
|
5083
|
+
for (let i = 0; i < 40 && fiber !== void 0 && fiber !== null; i++) {
|
|
5084
|
+
const props = fiber.memoizedProps;
|
|
5085
|
+
const cand = props?.node?.id ?? props?.row?.id;
|
|
5086
|
+
if (typeof cand === "string" && cand !== "") return cand;
|
|
5087
|
+
fiber = fiber.return;
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
/** 该菜单容器是否已含删除行(席位行或本臂先前注入)。 */
|
|
5091
|
+
function hasStewardDelete(menuEl) {
|
|
5092
|
+
if (menuEl === null || menuEl === void 0) return false;
|
|
5093
|
+
return typeof menuEl.querySelector === "function" && menuEl.querySelector(`[data-steward-menu-delete]`) !== null;
|
|
5094
|
+
}
|
|
5095
|
+
/**
|
|
5096
|
+
* 隐藏侧栏中属于该 sessionId 的会话行(老线文件路 purge 后补位)。
|
|
5097
|
+
* 背景:≤0.1.5 无 registry.deleteSession,purgeHistory 只动磁盘(归档+删目录)
|
|
5098
|
+
* 与 steward 索引,不通知宿主内存 workspaceRegistry → 侧栏行滞留到重启。
|
|
5099
|
+
* 本函数扫 `[role=treeitem]` 行、用 fiber 取 node.id 匹配、置 display:none(不
|
|
5100
|
+
* removeChild,避开与 React 协调冲突;行无 style.display prop,重渲染不覆盖)。
|
|
5101
|
+
* 返回隐藏行数(现代线行已被 deleteSession 真删,此处通常 0,无害)。
|
|
5102
|
+
*/
|
|
5103
|
+
function hideSidebarRowsFor(doc, sessionId) {
|
|
5104
|
+
if (sessionId === "") return 0;
|
|
5105
|
+
let n = 0;
|
|
5106
|
+
for (const row of Array.from(doc.querySelectorAll("[role=\"treeitem\"]"))) if (extractSessionIdFromFiber(row) === sessionId) {
|
|
5107
|
+
row.style.display = "none";
|
|
5108
|
+
n += 1;
|
|
5109
|
+
}
|
|
5110
|
+
return n;
|
|
5111
|
+
}
|
|
5112
|
+
/**
|
|
5113
|
+
* 装配老线菜单注入器。返回 dispose(摘除监听)。任何内部异常都被吞——
|
|
5114
|
+
* 本臂是增强,绝不允许拖垮宿主侧栏。DOM 构造器(MutationObserver/MouseEvent/
|
|
5115
|
+
* KeyboardEvent/Element)走全局(DOM lib 顶层声明;Window 类型不暴露它们)。
|
|
5116
|
+
*/
|
|
5117
|
+
function installLegacySessionMenuDelete(deps) {
|
|
5118
|
+
const { doc } = deps;
|
|
5119
|
+
let pending;
|
|
5120
|
+
const anchored = /* @__PURE__ */ new WeakSet();
|
|
5121
|
+
const onRowAnchorClick = (ev) => {
|
|
5122
|
+
try {
|
|
5123
|
+
const target = ev.target;
|
|
5124
|
+
if (!(target instanceof Element)) return;
|
|
5125
|
+
const btn = target.closest("button");
|
|
5126
|
+
if (btn === null) return;
|
|
5127
|
+
const sid = extractSessionIdFromFiber(btn);
|
|
5128
|
+
if (sid !== void 0) pending = {
|
|
5129
|
+
sessionId: sid,
|
|
5130
|
+
at: Date.now()
|
|
5131
|
+
};
|
|
5132
|
+
} catch (err) {
|
|
5133
|
+
deps.log?.(`anchor click extract failed: ${String(err)}`);
|
|
5134
|
+
}
|
|
5135
|
+
};
|
|
5136
|
+
const closeMenu = () => {
|
|
5137
|
+
try {
|
|
5138
|
+
doc.body.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true }));
|
|
5139
|
+
doc.dispatchEvent(new KeyboardEvent("keydown", {
|
|
5140
|
+
key: "Escape",
|
|
5141
|
+
bubbles: true
|
|
5142
|
+
}));
|
|
5143
|
+
} catch {}
|
|
5144
|
+
};
|
|
5145
|
+
const injectInto = (menuEl, sessionId) => {
|
|
5146
|
+
if (anchored.has(menuEl) || hasStewardDelete(menuEl)) return;
|
|
5147
|
+
const labels = resolveLabels(deps.lang());
|
|
5148
|
+
const row = doc.createElement("div");
|
|
5149
|
+
row.setAttribute("role", "menuitem");
|
|
5150
|
+
row.setAttribute(STEWARD_MENU_DELETE_ATTR, "1");
|
|
5151
|
+
row.style.cssText = "display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;padding:6px 10px;margin:0;color:inherit;font:inherit;font-size:13px;line-height:18px;text-align:start;cursor:pointer;border-radius:6px";
|
|
5152
|
+
const icon = doc.createElement("span");
|
|
5153
|
+
icon.style.cssText = ICON_SLOT_STYLE;
|
|
5154
|
+
icon.innerHTML = TRASH_ICON_SVG;
|
|
5155
|
+
const label = doc.createElement("span");
|
|
5156
|
+
let armed = false;
|
|
5157
|
+
const paint = () => {
|
|
5158
|
+
label.textContent = armed ? labels.confirm : labels.delete;
|
|
5159
|
+
row.style.background = armed ? "rgba(255,99,99,0.12)" : "transparent";
|
|
5160
|
+
row.style.color = armed ? "var(--dsw-alias-state-error-primary,#e5484d)" : "inherit";
|
|
5161
|
+
};
|
|
5162
|
+
row.append(icon, label);
|
|
5163
|
+
paint();
|
|
5164
|
+
row.addEventListener("mouseenter", () => {
|
|
5165
|
+
row.style.background = armed ? "rgba(255,99,99,0.12)" : "var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,0.12))";
|
|
5166
|
+
});
|
|
5167
|
+
row.addEventListener("mouseleave", () => {
|
|
5168
|
+
armed = false;
|
|
5169
|
+
paint();
|
|
5170
|
+
});
|
|
5171
|
+
row.addEventListener("click", (e) => {
|
|
5172
|
+
e.stopPropagation();
|
|
5173
|
+
if (!armed) {
|
|
5174
|
+
armed = true;
|
|
5175
|
+
paint();
|
|
5176
|
+
return;
|
|
5177
|
+
}
|
|
5178
|
+
deps.purge(sessionId).then(() => {
|
|
5179
|
+
hideSidebarRowsFor(doc, sessionId);
|
|
5180
|
+
closeMenu();
|
|
5181
|
+
}, () => {
|
|
5182
|
+
closeMenu();
|
|
5183
|
+
});
|
|
5184
|
+
});
|
|
5185
|
+
menuEl.appendChild(row);
|
|
5186
|
+
anchored.add(menuEl);
|
|
5187
|
+
};
|
|
5188
|
+
const tryInject = () => {
|
|
5189
|
+
try {
|
|
5190
|
+
if (pending === void 0 || Date.now() - pending.at > PENDING_TTL_MS) return;
|
|
5191
|
+
const sid = pending.sessionId;
|
|
5192
|
+
const menus = Array.from(doc.querySelectorAll("[role=\"menu\"]"));
|
|
5193
|
+
for (const menu of menus) {
|
|
5194
|
+
if (menu.querySelector("[role=\"menuitem\"]") === null) continue;
|
|
5195
|
+
deps.isEnabled().then((on) => {
|
|
5196
|
+
if (!on) return;
|
|
5197
|
+
if (!menu.isConnected) return;
|
|
5198
|
+
injectInto(menu, sid);
|
|
5199
|
+
}, () => {});
|
|
5200
|
+
}
|
|
5201
|
+
pending = void 0;
|
|
5202
|
+
} catch (err) {
|
|
5203
|
+
deps.log?.(`menu inject failed: ${String(err)}`);
|
|
5204
|
+
}
|
|
5205
|
+
};
|
|
5206
|
+
let observer;
|
|
5207
|
+
const start = () => {
|
|
5208
|
+
if (typeof MutationObserver !== "function") return;
|
|
5209
|
+
const obs = new MutationObserver(() => {
|
|
5210
|
+
tryInject();
|
|
5211
|
+
});
|
|
5212
|
+
obs.observe(doc.body, {
|
|
5213
|
+
childList: true,
|
|
5214
|
+
subtree: true
|
|
5215
|
+
});
|
|
5216
|
+
observer = obs;
|
|
5217
|
+
};
|
|
5218
|
+
try {
|
|
5219
|
+
doc.addEventListener("click", onRowAnchorClick, true);
|
|
5220
|
+
if (doc.body !== null && doc.body !== void 0) start();
|
|
5221
|
+
else doc.addEventListener("DOMContentLoaded", start, { once: true });
|
|
5222
|
+
} catch (err) {
|
|
5223
|
+
deps.log?.(`legacy menu arm init failed: ${String(err)}`);
|
|
5224
|
+
}
|
|
5225
|
+
return () => {
|
|
5226
|
+
try {
|
|
5227
|
+
doc.removeEventListener("click", onRowAnchorClick, true);
|
|
5228
|
+
observer?.disconnect();
|
|
5229
|
+
} catch {}
|
|
5230
|
+
};
|
|
5231
|
+
}
|
|
5232
|
+
/** directDelete 探针(缓存 30s)——注入器与外部共用一份。 */
|
|
5233
|
+
function makeDirectDeleteProbe() {
|
|
5234
|
+
let cached = false;
|
|
5235
|
+
let at = 0;
|
|
5236
|
+
return async () => {
|
|
5237
|
+
if (Date.now() - at < 3e4) return cached;
|
|
5238
|
+
at = Date.now();
|
|
5239
|
+
try {
|
|
5240
|
+
cached = (await callHostAny("index-status", {})).directDelete === true;
|
|
5241
|
+
} catch {
|
|
5242
|
+
cached = false;
|
|
5243
|
+
}
|
|
5244
|
+
return cached;
|
|
5245
|
+
};
|
|
5246
|
+
}
|
|
5247
|
+
/** 默认删除执行:走 session-history-purge(与管理端/席位臂同链路)。 */
|
|
5248
|
+
async function purgeSessionById(sessionId) {
|
|
5249
|
+
const res = await callSteward("session-history-purge", { sessionIds: [sessionId] });
|
|
5250
|
+
if (res.ok !== true) throw new Error(res.error ?? "purge failed");
|
|
5251
|
+
}
|
|
5252
|
+
//#endregion
|
|
5253
|
+
//#region src/client/session-menu.tsx
|
|
5254
|
+
/**
|
|
5255
|
+
* 侧栏会话「…」菜单删除臂(2026-10-20 用户定标,按左侧边栏实现换代识别)。
|
|
5256
|
+
*
|
|
5257
|
+
* 宿主换代事实(主仓 tag 谱系实证):
|
|
5258
|
+
* - ≤0.1.5:ui-workspace 会话菜单**写死** rename/fork/archive,无席位无删除;
|
|
5259
|
+
* - ≥0.1.7:菜单行席位化 `sidebar.workspaces.session.menu.item`,宿主自带
|
|
5260
|
+
* pin(100)/rename(200)/fork(300)/archive(400)/session-id(450)——插件行按
|
|
5261
|
+
* order 落位,owner 只给行身份(sessionId + useMenuOpenState + t)。
|
|
5262
|
+
* 本臂 order=500(末位,破坏性动作不抢位)。老线(≤0.1.5)inject 静默等待
|
|
5263
|
+
* 无害;删除入口在老线仍由搜索面板行内按钮承担(同一 purge 链路)。
|
|
5264
|
+
*
|
|
5265
|
+
* 可见性:行挂载时探针 index-status.directDelete(管家开关,缺省关)——
|
|
5266
|
+
* 非 true(含探针失败/旧宿主半无字段)整行不渲染;两步确认与管理端/面板
|
|
5267
|
+
* 同款(第一击上弦变红「确认删除」,第二击执行;点其它处菜单收起即解弦)。
|
|
5268
|
+
*/
|
|
5269
|
+
/** 宿主菜单席位名(0.1.7+ 存在)。 */
|
|
5270
|
+
const SESSION_MENU_SEAT = "sidebar.workspaces.session.menu.item";
|
|
5271
|
+
/** 本行注册 id(包名空间,避免遮蔽宿主行)。 */
|
|
5272
|
+
const SESSION_MENU_DELETE_ID = "dsh-manage-session.delete";
|
|
5273
|
+
/** 可见性判定(纯叶):仅显式 true 放行——未知/缺席一律隐藏。 */
|
|
5274
|
+
function menuDeleteVisible(directDelete) {
|
|
5275
|
+
return directDelete === true;
|
|
5276
|
+
}
|
|
5277
|
+
/**
|
|
5278
|
+
* 菜单行组件:普通 `role="menuitem"` button(不 import ui-primitives——
|
|
5279
|
+
* 未声明 peer 在老线 ModuleLoader 下解析有风险,宿主键盘走查只依赖 DOM)。
|
|
5280
|
+
* @param props - owner 行身份与菜单开合面。
|
|
5281
|
+
* @returns 菜单行或 null(开关关/探针未就绪)。
|
|
5282
|
+
*/
|
|
5283
|
+
function StewardDeleteMenuItem(props) {
|
|
5284
|
+
const { sessionId, useMenuOpenState, t } = props;
|
|
5285
|
+
const [visible, setVisible] = (0, react.useState)(false);
|
|
5286
|
+
const [armed, setArmed] = (0, react.useState)(false);
|
|
5287
|
+
const openState = typeof useMenuOpenState === "function" ? useMenuOpenState() : void 0;
|
|
5288
|
+
(0, react.useEffect)(() => {
|
|
5289
|
+
let disposed = false;
|
|
5290
|
+
callHostAny("index-status", {}).then((res) => {
|
|
5291
|
+
if (!disposed) setVisible(menuDeleteVisible(res.directDelete));
|
|
5292
|
+
}).catch(() => {
|
|
5293
|
+
if (!disposed) setVisible(false);
|
|
5294
|
+
});
|
|
5295
|
+
return () => {
|
|
5296
|
+
disposed = true;
|
|
5297
|
+
};
|
|
5298
|
+
}, []);
|
|
5299
|
+
if (sessionId === void 0 || sessionId === "" || !visible) return null;
|
|
5300
|
+
const fire = () => {
|
|
5301
|
+
openState?.[1](false);
|
|
5302
|
+
callSteward("session-history-purge", { sessionIds: [sessionId] }).then((res) => {
|
|
5303
|
+
if (res.ok !== true) console.warn("steward menu delete failed:", res.error ?? "unknown");
|
|
5304
|
+
}).catch((err) => {
|
|
5305
|
+
console.warn("steward menu delete failed:", err);
|
|
5306
|
+
});
|
|
5307
|
+
};
|
|
5308
|
+
return (0, react.createElement)("button", {
|
|
5309
|
+
type: "button",
|
|
5310
|
+
role: "menuitem",
|
|
5311
|
+
"data-steward-menu-delete": "1",
|
|
5312
|
+
style: {
|
|
5313
|
+
display: "flex",
|
|
5314
|
+
alignItems: "center",
|
|
5315
|
+
gap: "8px",
|
|
5316
|
+
width: "100%",
|
|
5317
|
+
boxSizing: "border-box",
|
|
5318
|
+
padding: "6px 10px",
|
|
5319
|
+
margin: 0,
|
|
5320
|
+
border: "none",
|
|
5321
|
+
background: armed ? "rgba(255, 99, 99, 0.12)" : "transparent",
|
|
5322
|
+
color: armed ? "var(--dsw-alias-state-error-primary, #e5484d)" : "inherit",
|
|
5323
|
+
font: "inherit",
|
|
5324
|
+
fontSize: "13px",
|
|
5325
|
+
lineHeight: "18px",
|
|
5326
|
+
textAlign: "start",
|
|
5327
|
+
cursor: "pointer",
|
|
5328
|
+
borderRadius: "6px"
|
|
5329
|
+
},
|
|
5330
|
+
onClick: () => {
|
|
5331
|
+
if (armed) {
|
|
5332
|
+
fire();
|
|
5333
|
+
return;
|
|
5334
|
+
}
|
|
5335
|
+
setArmed(true);
|
|
5336
|
+
},
|
|
5337
|
+
onMouseLeave: () => {
|
|
5338
|
+
setArmed(false);
|
|
5339
|
+
}
|
|
5340
|
+
}, [(0, react.createElement)("span", {
|
|
5341
|
+
key: "icon",
|
|
5342
|
+
style: {
|
|
5343
|
+
display: "inline-flex",
|
|
5344
|
+
flex: "none",
|
|
5345
|
+
width: 16,
|
|
5346
|
+
height: 16,
|
|
5347
|
+
alignItems: "center"
|
|
5348
|
+
},
|
|
5349
|
+
dangerouslySetInnerHTML: { __html: TRASH_ICON_SVG }
|
|
5350
|
+
}), (0, react.createElement)("span", { key: "label" }, armed ? t?.("menu.deleteSession.confirm") ?? "确认删除" : t?.("menu.deleteSession") ?? "删除会话")]);
|
|
5351
|
+
}
|
|
5352
|
+
//#endregion
|
|
4852
5353
|
//#region src/client/index.ts
|
|
4853
5354
|
/** 字典命名空间(locale.register 用)。 */
|
|
4854
5355
|
const NS = "dsh-manage-session";
|
|
@@ -4901,23 +5402,14 @@ window.__ModuleLoader__.load({
|
|
|
4901
5402
|
.dss_tabBody{display:flex;flex-direction:column;gap:8px;padding-top:8px}
|
|
4902
5403
|
.dss_actBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:transparent;border-radius:8px;padding:4px 10px;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-primary,#111827)}
|
|
4903
5404
|
.dss_actBtn:disabled{opacity:.5;cursor:default}
|
|
4904
|
-
.dss_dangerBtn{border-color:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-state-error-primary,#dc2626)}
|
|
4905
|
-
.dss_editActive{background:var(--dsw-alias-interactive-bg-active,rgba(63,99,216,.12))}
|
|
4906
5405
|
.dss_btnRow{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
|
|
4907
5406
|
.dss_list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
|
|
4908
5407
|
.dss_row{display:flex;gap:8px;align-items:center;padding:5px 8px;border-radius:8px;border:1px solid transparent}
|
|
4909
5408
|
.dss_row:hover{border-color:var(--dsw-alias-border-l2,#e5e7eb)}
|
|
4910
|
-
.dss_rowTitle{display:flex;gap:6px;align-items:center;flex:1 1 auto;min-width:0}
|
|
4911
|
-
.dss_titleText{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
4912
|
-
.dss_tag{font-size:11px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
|
|
4913
5409
|
.dss_meta{font-size:11px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
|
|
4914
|
-
.dss_metaLine{font-size:11px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
|
|
4915
5410
|
.dss_uuid{font-family:var(--ds-font-family-code,monospace)}
|
|
4916
5411
|
/* 按行的磁盘占用:右对齐、不参与标题压缩,方便纵向比对「哪条最占地方」。 */
|
|
4917
|
-
.dss_size{flex:none;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
4918
|
-
.dss_check{display:inline-flex;align-items:center}
|
|
4919
5412
|
.dss_status{font-size:12px;color:var(--dsw-alias-label-secondary,#6b7280)}
|
|
4920
|
-
.dss_warnText{color:var(--dsw-alias-state-warning-primary,#d97706)}
|
|
4921
5413
|
.dss_error{font-size:12px;color:var(--dsw-alias-state-error-primary,#dc2626)}
|
|
4922
5414
|
.dss_empty{font-size:12px;color:var(--dsw-alias-label-tertiary,#9ca3af);padding:10px 2px}
|
|
4923
5415
|
.dss_gateList{display:flex;flex-direction:column;gap:6px}
|
|
@@ -4994,6 +5486,16 @@ window.__ModuleLoader__.load({
|
|
|
4994
5486
|
const openSearchHit = (sessionId) => {
|
|
4995
5487
|
openSessionThrough((name) => ctx.get(name), sessionId);
|
|
4996
5488
|
};
|
|
5489
|
+
configureLocator({ loadOlder: async () => {
|
|
5490
|
+
try {
|
|
5491
|
+
const conv = ctx.get("conversation");
|
|
5492
|
+
if (conv === void 0 || typeof conv.loadOlder !== "function") return false;
|
|
5493
|
+
await conv.loadOlder();
|
|
5494
|
+
return true;
|
|
5495
|
+
} catch {
|
|
5496
|
+
return false;
|
|
5497
|
+
}
|
|
5498
|
+
} });
|
|
4997
5499
|
slots.inject("sidebar.footer.action", () => slots.register({
|
|
4998
5500
|
name: "sidebar.footer.action",
|
|
4999
5501
|
id: "dsh-search-index",
|
|
@@ -5045,6 +5547,21 @@ window.__ModuleLoader__.load({
|
|
|
5045
5547
|
scope: bound,
|
|
5046
5548
|
t: props.t
|
|
5047
5549
|
})])), "dsh-manage-session: plugins page config card");
|
|
5550
|
+
slots.inject(SESSION_MENU_SEAT, () => slots.register({
|
|
5551
|
+
name: SESSION_MENU_SEAT,
|
|
5552
|
+
id: SESSION_MENU_DELETE_ID,
|
|
5553
|
+
order: 500,
|
|
5554
|
+
locale: NS
|
|
5555
|
+
}, StewardDeleteMenuItem), "dsh-manage-session: session menu delete row");
|
|
5556
|
+
if (typeof document !== "undefined") {
|
|
5557
|
+
const probe = makeDirectDeleteProbe();
|
|
5558
|
+
ctx.effect(() => installLegacySessionMenuDelete({
|
|
5559
|
+
doc: document,
|
|
5560
|
+
purge: purgeSessionById,
|
|
5561
|
+
isEnabled: probe,
|
|
5562
|
+
lang: () => typeof navigator !== "undefined" ? navigator.language : void 0
|
|
5563
|
+
}), "dsh-manage-session: legacy session menu delete injector");
|
|
5564
|
+
}
|
|
5048
5565
|
const familyTabsHooks = makeFamilyTabsHooks(slots);
|
|
5049
5566
|
ctx.effect(() => {
|
|
5050
5567
|
let claimed;
|
|
@@ -5126,10 +5643,9 @@ window.__ModuleLoader__.load({
|
|
|
5126
5643
|
...DEFAULT_CONFIG,
|
|
5127
5644
|
...snapshot.value ?? {}
|
|
5128
5645
|
};
|
|
5129
|
-
const historyFiles = config.historyFiles !== false;
|
|
5130
5646
|
const healthCheck = config.healthCheck !== false;
|
|
5131
5647
|
const searchEnabled = config.search !== false;
|
|
5132
|
-
const visible = config.enabled !== false && (
|
|
5648
|
+
const visible = config.enabled !== false && (healthCheck || searchEnabled);
|
|
5133
5649
|
const wide = props.wide === true;
|
|
5134
5650
|
const label = translate(void 0, "panel.title");
|
|
5135
5651
|
if (!visible) return (0, react.createElement)("span", { className: "dss_entryWrap" });
|
|
@@ -5143,7 +5659,6 @@ window.__ModuleLoader__.load({
|
|
|
5143
5659
|
}
|
|
5144
5660
|
}), open ? (0, react.createElement)(StewardPanel, {
|
|
5145
5661
|
key: "panel",
|
|
5146
|
-
historyFiles,
|
|
5147
5662
|
healthCheck,
|
|
5148
5663
|
searchEnabled,
|
|
5149
5664
|
openSession: props.openSession,
|
|
@@ -5173,7 +5688,6 @@ window.__ModuleLoader__.load({
|
|
|
5173
5688
|
exports.STEWARD_SETTINGS_NAMESPACE = STEWARD_SETTINGS_NAMESPACE;
|
|
5174
5689
|
exports.StewardEntry = StewardEntry;
|
|
5175
5690
|
exports.TAB_HEALTH = TAB_HEALTH;
|
|
5176
|
-
exports.TAB_HISTORY = TAB_HISTORY;
|
|
5177
5691
|
exports.apply = apply;
|
|
5178
5692
|
exports.inject = inject;
|
|
5179
5693
|
exports.translate = translate;
|