dsh-manage-session 0.5.7 → 2.0.4
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 +33 -229
- package/CHANGELOG.ko.md +33 -229
- package/CHANGELOG.md +33 -363
- package/INSTALL.ja.md +164 -164
- package/INSTALL.ko.md +164 -164
- package/INSTALL.md +164 -164
- package/INSTALL.zh.md +164 -164
- package/LICENSE +21 -21
- package/README.en.md +41 -46
- package/README.ja.md +33 -57
- package/README.ko.md +32 -55
- package/README.md +64 -64
- package/cordis.patch.yml +19 -16
- package/dsh.plugin.json +4 -4
- package/lib/client.js +661 -3082
- package/lib/index.d.ts +79 -970
- package/lib/index.js +249 -2669
- package/locale/de.json +2 -2
- package/locale/en.json +2 -2
- package/locale/es.json +2 -2
- package/locale/fr.json +2 -2
- package/locale/it.json +2 -2
- package/locale/ja.json +2 -2
- package/locale/ko.json +2 -2
- package/locale/ru.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +15 -25
- package/screenshots.json +3 -3
- package/src/client/bridge-scope.ts +202 -213
- package/src/client/card.tsx +154 -154
- package/src/client/health-panel.tsx +49 -49
- package/src/client/host-api.ts +72 -6
- package/src/client/index.ts +594 -503
- package/src/client/locales.ts +893 -893
- package/src/client/manage-locales.ts +178 -0
- package/src/client/{search/manage.tsx → manage.tsx} +430 -404
- package/src/client/panel.tsx +131 -129
- package/src/client/session-menu-legacy.ts +2 -3
- package/src/client/session-menu.tsx +5 -5
- package/src/config.ts +43 -99
- package/src/host/health/attribution.ts +118 -118
- package/src/host/health/lossless.ts +128 -128
- package/src/host/health/repair.ts +2 -2
- package/src/host/health/source-kind.ts +350 -350
- package/src/host/history/archive-source.ts +250 -250
- package/src/host/history/archive.ts +249 -249
- package/src/host/history/favorites.ts +71 -71
- package/src/host/history/purge.ts +370 -370
- package/src/host/history/tombstones.ts +3 -3
- package/src/host/registry/archive-registry.ts +317 -317
- package/src/host/settings-bridge.ts +222 -264
- package/src/index.ts +66 -694
- package/src/client/search/card.tsx +0 -563
- package/src/client/search/host-api.ts +0 -213
- package/src/client/search/locales.ts +0 -861
- package/src/client/search/locate.ts +0 -330
- package/src/client/search/panel.tsx +0 -940
- package/src/client/search/platform.ts +0 -136
- package/src/host/index/archive-source.ts +0 -90
- package/src/host/index/drs-source.ts +0 -211
- package/src/host/index/engine.ts +0 -699
- package/src/host/index/extract.ts +0 -272
- package/src/host/index/peers.ts +0 -62
- package/src/host/index/rebuild.ts +0 -432
- package/src/host/index/schema.ts +0 -136
- package/src/host/index/snapshot.ts +0 -135
- package/src/host/index/sync.ts +0 -413
package/lib/client.js
CHANGED
|
@@ -6,26 +6,24 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_dom = require("react-dom");
|
|
9
|
-
|
|
9
|
+
// src/config.ts
|
|
10
10
|
/** 缺省值。 */
|
|
11
11
|
const DEFAULT_CONFIG = {
|
|
12
12
|
enabled: true,
|
|
13
13
|
historyFiles: true,
|
|
14
14
|
healthCheck: true,
|
|
15
|
-
search: true,
|
|
16
15
|
directDelete: false
|
|
17
16
|
};
|
|
18
17
|
/** host 半身注册的设置命名空间(与 src/index.ts 保持一致)。 */
|
|
19
|
-
const STEWARD_SETTINGS_NAMESPACE = "session
|
|
18
|
+
const STEWARD_SETTINGS_NAMESPACE = "manage-session";
|
|
20
19
|
/** 侧边栏入口 id(客户端注册 id)。 */
|
|
21
20
|
const STEWARD_ENTRY_ID = "dsh-manage-session";
|
|
22
|
-
|
|
23
|
-
//#region src/client/locales.ts
|
|
21
|
+
// src/client/locales.ts
|
|
24
22
|
/** 简体中文(内置回退字典)。 */
|
|
25
23
|
const zh$1 = {
|
|
26
|
-
"card.title": "
|
|
24
|
+
"card.title": "会话管理",
|
|
27
25
|
"card.desc": "历史文件 · 健康检查",
|
|
28
|
-
"card.enabled": "
|
|
26
|
+
"card.enabled": "启用会话管理",
|
|
29
27
|
"card.enabled.desc": "关闭后不注册任何接口,侧边栏入口整体消失。",
|
|
30
28
|
"card.history": "会话历史文件",
|
|
31
29
|
"card.history.desc": "归档浏览与清理(养老院)。关闭后不注册历史接口、不渲染该页签。",
|
|
@@ -38,7 +36,7 @@ window.__ModuleLoader__.load({
|
|
|
38
36
|
"card.readonly": "当前会话无法修改设置(只读挂载)。",
|
|
39
37
|
"card.unavailable": "设置服务不可用,无法读取配置。",
|
|
40
38
|
"family.title": "起子插件设置",
|
|
41
|
-
"panel.title": "
|
|
39
|
+
"panel.title": "会话管理",
|
|
42
40
|
"panel.tab.history": "养老院",
|
|
43
41
|
"panel.tab.health": "体检",
|
|
44
42
|
"panel.tab.manage": "管理",
|
|
@@ -120,9 +118,9 @@ window.__ModuleLoader__.load({
|
|
|
120
118
|
const dictionaries$1 = {
|
|
121
119
|
zh: zh$1,
|
|
122
120
|
en: {
|
|
123
|
-
"card.title": "Session
|
|
121
|
+
"card.title": "Session Manager",
|
|
124
122
|
"card.desc": "History files · Health check",
|
|
125
|
-
"card.enabled": "Enable Session
|
|
123
|
+
"card.enabled": "Enable Session Manager",
|
|
126
124
|
"card.enabled.desc": "When off, no API is registered and the sidebar entry disappears.",
|
|
127
125
|
"card.history": "Session history files",
|
|
128
126
|
"card.history.desc": "Archive browsing and cleanup (Retirement Home). When off, history routes and the tab are gone.",
|
|
@@ -135,7 +133,7 @@ window.__ModuleLoader__.load({
|
|
|
135
133
|
"card.readonly": "This session cannot edit settings (read-only mount).",
|
|
136
134
|
"card.unavailable": "Settings service unavailable; configuration cannot be read.",
|
|
137
135
|
"family.title": "Screwdriver plugin settings",
|
|
138
|
-
"panel.title": "Session
|
|
136
|
+
"panel.title": "Session Manager",
|
|
139
137
|
"panel.tab.history": "Retirement Home",
|
|
140
138
|
"panel.tab.health": "Checkup",
|
|
141
139
|
"panel.tab.manage": "Manage",
|
|
@@ -211,9 +209,9 @@ window.__ModuleLoader__.load({
|
|
|
211
209
|
"health.ago.days": "{n} d ago"
|
|
212
210
|
},
|
|
213
211
|
ja: {
|
|
214
|
-
"card.title": "
|
|
212
|
+
"card.title": "セッションマネージャー",
|
|
215
213
|
"card.desc": "履歴ファイル · ヘルスチェック",
|
|
216
|
-
"card.enabled": "
|
|
214
|
+
"card.enabled": "セッションマネージャーを有効化",
|
|
217
215
|
"card.enabled.desc": "オフにすると API を一切登録せず、サイドバーの入口も消えます。",
|
|
218
216
|
"card.history": "セッション履歴ファイル",
|
|
219
217
|
"card.history.desc": "アーカイブの閲覧とクリーンアップ(老人ホーム)。オフにすると履歴 API を登録せず、このタブも描画しません。",
|
|
@@ -226,7 +224,7 @@ window.__ModuleLoader__.load({
|
|
|
226
224
|
"card.readonly": "このセッションでは設定を変更できません(読み取り専用マウント)。",
|
|
227
225
|
"card.unavailable": "設定サービスが利用できず、構成を読み取れません。",
|
|
228
226
|
"family.title": "スクリュードライバー プラグイン設定",
|
|
229
|
-
"panel.title": "
|
|
227
|
+
"panel.title": "セッションマネージャー",
|
|
230
228
|
"panel.tab.history": "老人ホーム",
|
|
231
229
|
"panel.tab.health": "健診",
|
|
232
230
|
"panel.close": "閉じる",
|
|
@@ -301,9 +299,9 @@ window.__ModuleLoader__.load({
|
|
|
301
299
|
"health.ago.days": "{n} 日前"
|
|
302
300
|
},
|
|
303
301
|
ko: {
|
|
304
|
-
"card.title": "세션
|
|
302
|
+
"card.title": "세션 매니저",
|
|
305
303
|
"card.desc": "기록 파일 · 건강 점검",
|
|
306
|
-
"card.enabled": "세션
|
|
304
|
+
"card.enabled": "세션 매니저 활성화",
|
|
307
305
|
"card.enabled.desc": "끄면 어떤 API도 등록하지 않고 사이드바 항목이 통째로 사라집니다.",
|
|
308
306
|
"card.history": "세션 기록 파일",
|
|
309
307
|
"card.history.desc": "아카이브 탐색 및 정리(요양소). 끄면 기록 API를 등록하지 않고 이 탭을 렌더링하지 않습니다.",
|
|
@@ -316,7 +314,7 @@ window.__ModuleLoader__.load({
|
|
|
316
314
|
"card.readonly": "이 세션에서는 설정을 수정할 수 없습니다(읽기 전용 마운트).",
|
|
317
315
|
"card.unavailable": "설정 서비스를 사용할 수 없어 구성을 읽을 수 없습니다.",
|
|
318
316
|
"family.title": "스크루드라이버 플러그인 설정",
|
|
319
|
-
"panel.title": "세션
|
|
317
|
+
"panel.title": "세션 매니저",
|
|
320
318
|
"panel.tab.history": "요양소",
|
|
321
319
|
"panel.tab.health": "진료",
|
|
322
320
|
"panel.close": "닫기",
|
|
@@ -391,7 +389,7 @@ window.__ModuleLoader__.load({
|
|
|
391
389
|
"health.ago.days": "{n}일 전"
|
|
392
390
|
},
|
|
393
391
|
fr: {
|
|
394
|
-
"card.title": "
|
|
392
|
+
"card.title": "Gestionnaire de sessions",
|
|
395
393
|
"card.desc": "Fichiers d’historique · Contrôle de santé",
|
|
396
394
|
"card.enabled": "Activer l’intendant de session",
|
|
397
395
|
"card.enabled.desc": "Désactivé, aucune API n’est enregistrée et l’entrée de la barre latérale disparaît.",
|
|
@@ -406,7 +404,7 @@ window.__ModuleLoader__.load({
|
|
|
406
404
|
"card.readonly": "Cette session ne peut pas modifier les réglages (montage en lecture seule).",
|
|
407
405
|
"card.unavailable": "Service de réglages indisponible ; configuration illisible.",
|
|
408
406
|
"family.title": "Réglages du plugin Screwdriver",
|
|
409
|
-
"panel.title": "
|
|
407
|
+
"panel.title": "Gestionnaire de sessions",
|
|
410
408
|
"panel.tab.history": "Maison de retraite",
|
|
411
409
|
"panel.tab.health": "Examen",
|
|
412
410
|
"panel.close": "Fermer",
|
|
@@ -481,9 +479,9 @@ window.__ModuleLoader__.load({
|
|
|
481
479
|
"health.ago.days": "il y a {n} j"
|
|
482
480
|
},
|
|
483
481
|
de: {
|
|
484
|
-
"card.title": "
|
|
482
|
+
"card.title": "Sitzungsmanager",
|
|
485
483
|
"card.desc": "Verlaufsdateien · Health-Check",
|
|
486
|
-
"card.enabled": "
|
|
484
|
+
"card.enabled": "Sitzungsmanager aktivieren",
|
|
487
485
|
"card.enabled.desc": "Wenn aus, wird keine API registriert und der Seitenleisten-Eintrag verschwindet komplett.",
|
|
488
486
|
"card.history": "Sitzungsverlaufsdateien",
|
|
489
487
|
"card.history.desc": "Archivansicht und Bereinigung (Altersheim). Wenn aus, werden keine Verlaufs-Routen registriert und dieser Tab nicht gerendert.",
|
|
@@ -496,7 +494,7 @@ window.__ModuleLoader__.load({
|
|
|
496
494
|
"card.readonly": "Diese Sitzung kann Einstellungen nicht ändern (schreibgeschützter Mount).",
|
|
497
495
|
"card.unavailable": "Einstellungsdienst nicht verfügbar; Konfiguration kann nicht gelesen werden.",
|
|
498
496
|
"family.title": "Screwdriver-Plugin-Einstellungen",
|
|
499
|
-
"panel.title": "
|
|
497
|
+
"panel.title": "Sitzungsmanager",
|
|
500
498
|
"panel.tab.history": "Altersheim",
|
|
501
499
|
"panel.tab.health": "Untersuchung",
|
|
502
500
|
"panel.close": "Schließen",
|
|
@@ -571,7 +569,7 @@ window.__ModuleLoader__.load({
|
|
|
571
569
|
"health.ago.days": "vor {n} Tg"
|
|
572
570
|
},
|
|
573
571
|
it: {
|
|
574
|
-
"card.title": "
|
|
572
|
+
"card.title": "Gestore sessioni",
|
|
575
573
|
"card.desc": "File della cronologia · Controllo di salute",
|
|
576
574
|
"card.enabled": "Abilita l’amministratore di sessione",
|
|
577
575
|
"card.enabled.desc": "Se disattivato, non registra alcuna API e la voce nella barra laterale scompare del tutto.",
|
|
@@ -586,7 +584,7 @@ window.__ModuleLoader__.load({
|
|
|
586
584
|
"card.readonly": "Questa sessione non può modificare le impostazioni (montaggio in sola lettura).",
|
|
587
585
|
"card.unavailable": "Servizio di impostazioni non disponibile; impossibile leggere la configurazione.",
|
|
588
586
|
"family.title": "Impostazioni del plugin Screwdriver",
|
|
589
|
-
"panel.title": "
|
|
587
|
+
"panel.title": "Gestore sessioni",
|
|
590
588
|
"panel.tab.history": "Casa di riposo",
|
|
591
589
|
"panel.tab.health": "Visita",
|
|
592
590
|
"panel.close": "Chiudi",
|
|
@@ -661,7 +659,7 @@ window.__ModuleLoader__.load({
|
|
|
661
659
|
"health.ago.days": "{n} g fa"
|
|
662
660
|
},
|
|
663
661
|
ru: {
|
|
664
|
-
"card.title": "
|
|
662
|
+
"card.title": "Менеджер сессий",
|
|
665
663
|
"card.desc": "Файлы истории · Проверка здоровья",
|
|
666
664
|
"card.enabled": "Включить распорядителя сессий",
|
|
667
665
|
"card.enabled.desc": "При выключении не регистрируется ни один API, и пункт боковой панели полностью исчезает.",
|
|
@@ -676,7 +674,7 @@ window.__ModuleLoader__.load({
|
|
|
676
674
|
"card.readonly": "Эта сессия не может изменять настройки (монтирование только для чтения).",
|
|
677
675
|
"card.unavailable": "Сервис настроек недоступен; конфигурацию невозможно прочитать.",
|
|
678
676
|
"family.title": "Настройки плагинов Screwdriver",
|
|
679
|
-
"panel.title": "
|
|
677
|
+
"panel.title": "Менеджер сессий",
|
|
680
678
|
"panel.tab.history": "Пансионат",
|
|
681
679
|
"panel.tab.health": "Осмотр",
|
|
682
680
|
"panel.close": "Закрыть",
|
|
@@ -751,9 +749,9 @@ window.__ModuleLoader__.load({
|
|
|
751
749
|
"health.ago.days": "{n} дн назад"
|
|
752
750
|
},
|
|
753
751
|
es: {
|
|
754
|
-
"card.title": "
|
|
752
|
+
"card.title": "Gestor de sesiones",
|
|
755
753
|
"card.desc": "Archivos de historial · Comprobación de salud",
|
|
756
|
-
"card.enabled": "Activar el
|
|
754
|
+
"card.enabled": "Activar el gestor de sesiones",
|
|
757
755
|
"card.enabled.desc": "Al desactivarlo no se registra ninguna API y la entrada de la barra lateral desaparece por completo.",
|
|
758
756
|
"card.history": "Archivos de historial de sesión",
|
|
759
757
|
"card.history.desc": "Visualización y limpieza del archivo (Residencia). Al desactivarlo no se registran rutas de historial ni se muestra esa pestaña.",
|
|
@@ -766,7 +764,7 @@ window.__ModuleLoader__.load({
|
|
|
766
764
|
"card.readonly": "Esta sesión no puede modificar la configuración (montaje de solo lectura).",
|
|
767
765
|
"card.unavailable": "Servicio de configuración no disponible; no se puede leer la configuración.",
|
|
768
766
|
"family.title": "Ajustes del plugin Screwdriver",
|
|
769
|
-
"panel.title": "
|
|
767
|
+
"panel.title": "Gestor de sesiones",
|
|
770
768
|
"panel.tab.history": "Residencia",
|
|
771
769
|
"panel.tab.health": "Revisión",
|
|
772
770
|
"panel.close": "Cerrar",
|
|
@@ -853,8 +851,7 @@ window.__ModuleLoader__.load({
|
|
|
853
851
|
if (params === void 0) return template;
|
|
854
852
|
return template.replace(/\{(\w+)\}/g, (_, name) => String(params[name] ?? ""));
|
|
855
853
|
}
|
|
856
|
-
|
|
857
|
-
//#region src/client/card.tsx
|
|
854
|
+
// src/client/card.tsx
|
|
858
855
|
/**
|
|
859
856
|
* 设置卡(插件配置行):一个可折叠抽屉 —— 会话历史文件 / 健康检查。
|
|
860
857
|
*
|
|
@@ -868,21 +865,21 @@ window.__ModuleLoader__.load({
|
|
|
868
865
|
* 与同节其它抽屉一样默认展开。
|
|
869
866
|
*/
|
|
870
867
|
/** 一行设置项。 */
|
|
871
|
-
function Row
|
|
872
|
-
return (0, react.createElement)("div", { className: "
|
|
868
|
+
function Row(props) {
|
|
869
|
+
return (0, react.createElement)("div", { className: "dms_setRow" }, [(0, react.createElement)("div", {
|
|
873
870
|
key: "text",
|
|
874
|
-
className: "
|
|
871
|
+
className: "dms_setText"
|
|
875
872
|
}, [(0, react.createElement)("span", {
|
|
876
873
|
key: "t",
|
|
877
|
-
className: "
|
|
874
|
+
className: "dms_setTitle"
|
|
878
875
|
}, props.title), props.desc !== void 0 && (0, react.createElement)("span", {
|
|
879
876
|
key: "d",
|
|
880
|
-
className: "
|
|
877
|
+
className: "dms_setDesc"
|
|
881
878
|
}, props.desc)]), (0, react.createElement)("div", { key: "ctl" }, props.control)]);
|
|
882
879
|
}
|
|
883
880
|
/** 一个布尔开关(原生 checkbox 语义,样式由本插件 CSS 提供)。 */
|
|
884
|
-
function Toggle
|
|
885
|
-
return (0, react.createElement)("label", { className: `
|
|
881
|
+
function Toggle(props) {
|
|
882
|
+
return (0, react.createElement)("label", { className: `dms_toggle${props.checked ? " dms_toggleOn" : ""}` }, [(0, react.createElement)("input", {
|
|
886
883
|
key: "i",
|
|
887
884
|
type: "checkbox",
|
|
888
885
|
checked: props.checked,
|
|
@@ -892,29 +889,29 @@ window.__ModuleLoader__.load({
|
|
|
892
889
|
}
|
|
893
890
|
}), (0, react.createElement)("span", {
|
|
894
891
|
key: "s",
|
|
895
|
-
className: "
|
|
896
|
-
}, (0, react.createElement)("span", { className: "
|
|
892
|
+
className: "dms_toggleTrack"
|
|
893
|
+
}, (0, react.createElement)("span", { className: "dms_toggleKnob" }))]);
|
|
897
894
|
}
|
|
898
895
|
/** 抽屉头:标题 + 说明 + chevron,整块可点。 */
|
|
899
896
|
function Header(props) {
|
|
900
897
|
return (0, react.createElement)("button", {
|
|
901
898
|
key: "head",
|
|
902
899
|
type: "button",
|
|
903
|
-
className: "
|
|
900
|
+
className: "dms_setHead",
|
|
904
901
|
"aria-expanded": props.open,
|
|
905
902
|
onClick: props.onToggle
|
|
906
903
|
}, [(0, react.createElement)("span", {
|
|
907
904
|
key: "text",
|
|
908
|
-
className: "
|
|
905
|
+
className: "dms_setHeadText"
|
|
909
906
|
}, [(0, react.createElement)("span", {
|
|
910
907
|
key: "t",
|
|
911
|
-
className: "
|
|
908
|
+
className: "dms_setCardTitle"
|
|
912
909
|
}, translate(props.t, "card.title")), (0, react.createElement)("span", {
|
|
913
910
|
key: "d",
|
|
914
|
-
className: "
|
|
911
|
+
className: "dms_setCardDesc"
|
|
915
912
|
}, translate(props.t, "card.desc"))]), (0, react.createElement)("svg", {
|
|
916
913
|
key: "chev",
|
|
917
|
-
className: "
|
|
914
|
+
className: "dms_setChev",
|
|
918
915
|
width: 16,
|
|
919
916
|
height: 16,
|
|
920
917
|
viewBox: "0 0 16 16",
|
|
@@ -937,36 +934,36 @@ window.__ModuleLoader__.load({
|
|
|
937
934
|
const rows = [];
|
|
938
935
|
if (snapshot.status === "unavailable") rows.push((0, react.createElement)("div", {
|
|
939
936
|
key: "unavailable",
|
|
940
|
-
className: "
|
|
937
|
+
className: "dms_setDesc"
|
|
941
938
|
}, translate(t, "card.unavailable")));
|
|
942
939
|
else {
|
|
943
|
-
rows.push((0, react.createElement)(Row
|
|
940
|
+
rows.push((0, react.createElement)(Row, {
|
|
944
941
|
key: "enabled",
|
|
945
942
|
title: translate(t, "card.enabled"),
|
|
946
943
|
desc: translate(t, "card.enabled.desc"),
|
|
947
|
-
control: (0, react.createElement)(Toggle
|
|
944
|
+
control: (0, react.createElement)(Toggle, {
|
|
948
945
|
checked: value.enabled ?? DEFAULT_CONFIG.enabled,
|
|
949
946
|
disabled: !writable,
|
|
950
947
|
onChange: (checked) => {
|
|
951
948
|
scope.set("enabled", checked);
|
|
952
949
|
}
|
|
953
950
|
})
|
|
954
|
-
}), (0, react.createElement)(Row
|
|
951
|
+
}), (0, react.createElement)(Row, {
|
|
955
952
|
key: "health",
|
|
956
953
|
title: translate(t, "card.health"),
|
|
957
954
|
desc: translate(t, "card.health.desc"),
|
|
958
|
-
control: (0, react.createElement)(Toggle
|
|
955
|
+
control: (0, react.createElement)(Toggle, {
|
|
959
956
|
checked: value.healthCheck ?? DEFAULT_CONFIG.healthCheck,
|
|
960
957
|
disabled: !writable,
|
|
961
958
|
onChange: (checked) => {
|
|
962
959
|
scope.set("healthCheck", checked);
|
|
963
960
|
}
|
|
964
961
|
})
|
|
965
|
-
}), (0, react.createElement)(Row
|
|
962
|
+
}), (0, react.createElement)(Row, {
|
|
966
963
|
key: "directDelete",
|
|
967
964
|
title: translate(t, "card.directDelete"),
|
|
968
965
|
desc: translate(t, "card.directDelete.desc"),
|
|
969
|
-
control: (0, react.createElement)(Toggle
|
|
966
|
+
control: (0, react.createElement)(Toggle, {
|
|
970
967
|
checked: value.directDelete ?? DEFAULT_CONFIG.directDelete,
|
|
971
968
|
disabled: !writable,
|
|
972
969
|
onChange: (checked) => {
|
|
@@ -976,10 +973,10 @@ window.__ModuleLoader__.load({
|
|
|
976
973
|
}));
|
|
977
974
|
if (!writable) rows.push((0, react.createElement)("div", {
|
|
978
975
|
key: "ro",
|
|
979
|
-
className: "
|
|
976
|
+
className: "dms_setDesc"
|
|
980
977
|
}, translate(t, "card.readonly")));
|
|
981
978
|
}
|
|
982
|
-
return (0, react.createElement)("div", { className: `
|
|
979
|
+
return (0, react.createElement)("div", { className: `dms_setCard${open ? " dms_setCardOpen" : ""}` }, [(0, react.createElement)(Header, {
|
|
983
980
|
key: "head",
|
|
984
981
|
t,
|
|
985
982
|
open,
|
|
@@ -988,25 +985,24 @@ window.__ModuleLoader__.load({
|
|
|
988
985
|
}
|
|
989
986
|
}), open && (0, react.createElement)("div", {
|
|
990
987
|
key: "body",
|
|
991
|
-
className: "
|
|
988
|
+
className: "dms_setBody"
|
|
992
989
|
}, rows)]);
|
|
993
990
|
}
|
|
994
|
-
|
|
995
|
-
//#region src/client/host-api.ts
|
|
991
|
+
// src/client/host-api.ts
|
|
996
992
|
/**
|
|
997
|
-
* 客户端 host 调用助手:一切经 fenced `/session
|
|
993
|
+
* 客户端 host 调用助手:一切经 fenced `/manage-session/api/<method>` 路由,
|
|
998
994
|
* 不 value-import 任何官方包(与 dsh-session-search-toggle 的 host-api.ts 同范式,
|
|
999
|
-
*
|
|
995
|
+
* 仅把路由前缀换成会话管理自己的)。
|
|
1000
996
|
*/
|
|
1001
997
|
/** 单次 host 调用超时(导入类大响应可单独放宽)。 */
|
|
1002
|
-
const FETCH_TIMEOUT
|
|
998
|
+
const FETCH_TIMEOUT = 2e4;
|
|
1003
999
|
/** POST 一个 JSON body,返回整条记录。 */
|
|
1004
|
-
function callHostAny
|
|
1000
|
+
function callHostAny(method, body, timeout = FETCH_TIMEOUT) {
|
|
1005
1001
|
const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
|
|
1006
1002
|
const timer = typeof setTimeout === "function" ? setTimeout(() => {
|
|
1007
1003
|
controller?.abort();
|
|
1008
1004
|
}, timeout) : void 0;
|
|
1009
|
-
return fetch(`/session
|
|
1005
|
+
return fetch(`/manage-session/api/${method}`, {
|
|
1010
1006
|
method: "POST",
|
|
1011
1007
|
headers: { "content-type": "application/json" },
|
|
1012
1008
|
body: JSON.stringify(body),
|
|
@@ -1018,14 +1014,52 @@ window.__ModuleLoader__.load({
|
|
|
1018
1014
|
if (timer !== void 0) clearTimeout(timer);
|
|
1019
1015
|
});
|
|
1020
1016
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1017
|
+
/**
|
|
1018
|
+
* 兼容别名:合并包时代的 `callSteward` 与 `callHostAny` 同实现(同一 fenced
|
|
1019
|
+
* 路由、同一返回形状),保留调用面不迁移。
|
|
1020
|
+
*/
|
|
1021
|
+
const callSteward = callHostAny;
|
|
1022
|
+
/** POST 到 items 形状的 fenced 方法(管理台语料 session-catalog 用)。 */
|
|
1023
|
+
async function callStewardItems(method, body) {
|
|
1024
|
+
const res = await callHostAny(method, body);
|
|
1025
|
+
if (res.ok === true && Array.isArray(res.items)) return {
|
|
1026
|
+
ok: true,
|
|
1027
|
+
items: res.items
|
|
1028
|
+
};
|
|
1029
|
+
return {
|
|
1030
|
+
ok: false,
|
|
1031
|
+
items: [],
|
|
1032
|
+
error: res.error ?? "请求失败"
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* 直接删除入口开关(`manage-session.directDelete`,缺省关)。
|
|
1037
|
+
* 由 `session-health-status` 的 switches 面读取;旧宿主半无该字段 → false
|
|
1038
|
+
* (破坏性入口不出现是安全方向)。
|
|
1039
|
+
*/
|
|
1040
|
+
async function probeDirectDelete() {
|
|
1041
|
+
const res = await callHostAny("session-health-status", {});
|
|
1042
|
+
return res.ok === true && res.switches?.directDelete === true;
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Open a session through whichever face the running host offers:
|
|
1046
|
+
* `uiWorkspace.openSession` first (0.1.7+), the pre-0.1.7 `sessions.open` as
|
|
1047
|
+
* fallback. Neither present → silent no-op.
|
|
1048
|
+
*/
|
|
1049
|
+
function openSessionThrough(get, sessionId) {
|
|
1050
|
+
const uiWorkspace = get("uiWorkspace");
|
|
1051
|
+
if (uiWorkspace !== void 0 && typeof uiWorkspace.openSession === "function") {
|
|
1052
|
+
uiWorkspace.openSession(sessionId);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const sessions = get("sessions");
|
|
1056
|
+
if (sessions !== void 0 && typeof sessions.open === "function") sessions.open(sessionId);
|
|
1057
|
+
}
|
|
1058
|
+
// src/client/bridge-scope.ts
|
|
1023
1059
|
/** 与宿主 settings-bridge.ts 的命名空间键一致(存储键稳定承诺)。 */
|
|
1024
|
-
const STEWARD_NS = "session
|
|
1025
|
-
const SEARCH_NS = "switch-search";
|
|
1060
|
+
const STEWARD_NS = "manage-session";
|
|
1026
1061
|
const shared = {
|
|
1027
1062
|
steward: {},
|
|
1028
|
-
search: {},
|
|
1029
1063
|
status: "loading",
|
|
1030
1064
|
writable: false,
|
|
1031
1065
|
revision: void 0
|
|
@@ -1044,10 +1078,9 @@ window.__ModuleLoader__.load({
|
|
|
1044
1078
|
/** 首个订阅触发一次 describe;并发调用共享同一 in-flight promise。 */
|
|
1045
1079
|
function ensureDescribe() {
|
|
1046
1080
|
if (describeInFlight !== void 0) return describeInFlight;
|
|
1047
|
-
describeInFlight = callHostAny
|
|
1081
|
+
describeInFlight = callHostAny("settings-describe", {}).then((res) => {
|
|
1048
1082
|
if (res.ok === true && res.namespaces !== null && typeof res.namespaces === "object") replaceDoc({
|
|
1049
1083
|
steward: res.namespaces[STEWARD_NS] ?? {},
|
|
1050
|
-
search: res.namespaces[SEARCH_NS] ?? {},
|
|
1051
1084
|
status: "ready",
|
|
1052
1085
|
writable: res.writable === true,
|
|
1053
1086
|
revision: typeof res.revision === "number" ? res.revision : void 0
|
|
@@ -1100,13 +1133,12 @@ window.__ModuleLoader__.load({
|
|
|
1100
1133
|
const before = keyOf(shared)[field];
|
|
1101
1134
|
keyOf(shared)[field] = value;
|
|
1102
1135
|
refresh();
|
|
1103
|
-
const res = await callHostAny
|
|
1136
|
+
const res = await callHostAny("settings-mutate", {
|
|
1104
1137
|
ns,
|
|
1105
1138
|
fields: { [field]: value }
|
|
1106
1139
|
});
|
|
1107
1140
|
if (res.ok === true && res.namespaces !== null && typeof res.namespaces === "object") replaceDoc({
|
|
1108
1141
|
steward: res.namespaces[STEWARD_NS] ?? shared.steward,
|
|
1109
|
-
search: res.namespaces[SEARCH_NS] ?? shared.search,
|
|
1110
1142
|
revision: typeof res.revision === "number" ? res.revision : shared.revision
|
|
1111
1143
|
});
|
|
1112
1144
|
else {
|
|
@@ -1118,17 +1150,11 @@ window.__ModuleLoader__.load({
|
|
|
1118
1150
|
};
|
|
1119
1151
|
}
|
|
1120
1152
|
let stewardScope;
|
|
1121
|
-
|
|
1122
|
-
/** 管家卡/管家入口的桥 scope(命名空间 session-steward)。 */
|
|
1153
|
+
/** 管家卡/管家入口的桥 scope(命名空间 manage-session)。 */
|
|
1123
1154
|
function stewardBridgeScope() {
|
|
1124
1155
|
stewardScope ??= makeBridgeScope(STEWARD_NS, (doc) => doc.steward);
|
|
1125
1156
|
return stewardScope;
|
|
1126
1157
|
}
|
|
1127
|
-
/** 搜索卡/搜索入口的桥 scope(命名空间 switch-search)。 */
|
|
1128
|
-
function searchBridgeScope() {
|
|
1129
|
-
searchScope ??= makeBridgeScope(SEARCH_NS, (doc) => doc.search);
|
|
1130
|
-
return searchScope;
|
|
1131
|
-
}
|
|
1132
1158
|
/**
|
|
1133
1159
|
* 原生句柄 + 桥的合成 scope:原生快照**在任意时刻**观测到 unavailable(出生
|
|
1134
1160
|
* 死或 loading→死,0.1.7 上 steward 条目的 configForms 实测后者)就地读桥,
|
|
@@ -1157,8 +1183,7 @@ window.__ModuleLoader__.load({
|
|
|
1157
1183
|
}
|
|
1158
1184
|
};
|
|
1159
1185
|
}
|
|
1160
|
-
|
|
1161
|
-
//#region src/client/health-panel.tsx
|
|
1186
|
+
// src/client/health-panel.tsx
|
|
1162
1187
|
/**
|
|
1163
1188
|
* 体检面板:体检(五门)→ 处方(命令清单)→ 出院(可逆处置 + before/after 对照)。
|
|
1164
1189
|
*
|
|
@@ -1244,7 +1269,7 @@ window.__ModuleLoader__.load({
|
|
|
1244
1269
|
};
|
|
1245
1270
|
}, [onClose]);
|
|
1246
1271
|
(0, react.useEffect)(() => {
|
|
1247
|
-
callHostAny
|
|
1272
|
+
callHostAny("session-health-scan", { resume: true }, 3e4).then((res) => {
|
|
1248
1273
|
if (res.ok === true && res.cached === true) {
|
|
1249
1274
|
setFindings(res.findings ?? []);
|
|
1250
1275
|
setCacheInfo({
|
|
@@ -1293,7 +1318,7 @@ window.__ModuleLoader__.load({
|
|
|
1293
1318
|
let offset = 0;
|
|
1294
1319
|
try {
|
|
1295
1320
|
for (;;) {
|
|
1296
|
-
const res = await callHostAny
|
|
1321
|
+
const res = await callHostAny("session-health-scan", {
|
|
1297
1322
|
limit: SCAN_BATCH,
|
|
1298
1323
|
offset,
|
|
1299
1324
|
onlyProblems: true,
|
|
@@ -1349,7 +1374,7 @@ window.__ModuleLoader__.load({
|
|
|
1349
1374
|
setError(null);
|
|
1350
1375
|
setDischarge(null);
|
|
1351
1376
|
setMigrateNote(null);
|
|
1352
|
-
callHostAny
|
|
1377
|
+
callHostAny("session-health-session", { sessionId }, 12e4).then((res) => {
|
|
1353
1378
|
if (res.ok === true) {
|
|
1354
1379
|
setDetail(res);
|
|
1355
1380
|
if (res.report !== void 0) reconcileRow(res.report);
|
|
@@ -1359,7 +1384,7 @@ window.__ModuleLoader__.load({
|
|
|
1359
1384
|
const runRepair = (sessionId) => {
|
|
1360
1385
|
setRepairing(true);
|
|
1361
1386
|
setError(null);
|
|
1362
|
-
callHostAny
|
|
1387
|
+
callHostAny("session-health-repair", { sessionId }, 12e4).then((res) => {
|
|
1363
1388
|
if (res.ok === true) {
|
|
1364
1389
|
setDischarge(res);
|
|
1365
1390
|
if (res.after !== void 0) {
|
|
@@ -1383,7 +1408,7 @@ window.__ModuleLoader__.load({
|
|
|
1383
1408
|
setMigrating(true);
|
|
1384
1409
|
setError(null);
|
|
1385
1410
|
setMigrateNote(null);
|
|
1386
|
-
callHostAny
|
|
1411
|
+
callHostAny("session-health-source-migrate", { sessionId }, 12e4).then((res) => {
|
|
1387
1412
|
if (res.after !== void 0) {
|
|
1388
1413
|
setDetail({
|
|
1389
1414
|
ok: true,
|
|
@@ -1405,14 +1430,14 @@ window.__ModuleLoader__.load({
|
|
|
1405
1430
|
const lines = [];
|
|
1406
1431
|
lines.push((0, react.createElement)("div", {
|
|
1407
1432
|
key: "phase1",
|
|
1408
|
-
className: "
|
|
1433
|
+
className: "dms_phaseRow"
|
|
1409
1434
|
}, [(0, react.createElement)("span", {
|
|
1410
1435
|
key: "l",
|
|
1411
|
-
className: "
|
|
1436
|
+
className: "dms_phase"
|
|
1412
1437
|
}, translate(t, "health.phase.checkup")), (0, react.createElement)("button", {
|
|
1413
1438
|
key: "scan",
|
|
1414
1439
|
type: "button",
|
|
1415
|
-
className: "
|
|
1440
|
+
className: "dms_actBtn",
|
|
1416
1441
|
disabled: scanning,
|
|
1417
1442
|
onClick: runScan
|
|
1418
1443
|
}, scanning ? translate(t, "health.scanning") : translate(t, "health.scan"))]));
|
|
@@ -1421,17 +1446,17 @@ window.__ModuleLoader__.load({
|
|
|
1421
1446
|
const pct = total > 0 ? Math.min(100, Math.round(progress.done / total * 100)) : 0;
|
|
1422
1447
|
lines.push((0, react.createElement)("div", {
|
|
1423
1448
|
key: "progress",
|
|
1424
|
-
className: "
|
|
1449
|
+
className: "dms_progressRow"
|
|
1425
1450
|
}, [(0, react.createElement)("div", {
|
|
1426
1451
|
key: "track",
|
|
1427
|
-
className: "
|
|
1452
|
+
className: "dms_progressTrack"
|
|
1428
1453
|
}, (0, react.createElement)("div", {
|
|
1429
1454
|
key: "fill",
|
|
1430
|
-
className: total > 0 ? "
|
|
1455
|
+
className: total > 0 ? "dms_progressFill" : "dms_progressFill dms_progressIndeterminate",
|
|
1431
1456
|
...total > 0 ? { style: { width: `${pct}%` } } : {}
|
|
1432
1457
|
})), (0, react.createElement)("span", {
|
|
1433
1458
|
key: "read",
|
|
1434
|
-
className: "
|
|
1459
|
+
className: "dms_meta"
|
|
1435
1460
|
}, translate(t, "health.progress", {
|
|
1436
1461
|
done: progress.done,
|
|
1437
1462
|
total: total > 0 ? total : "?",
|
|
@@ -1442,15 +1467,15 @@ window.__ModuleLoader__.load({
|
|
|
1442
1467
|
const ago = relativeAgo(cacheInfo.generatedAt, Date.now());
|
|
1443
1468
|
lines.push((0, react.createElement)("div", {
|
|
1444
1469
|
key: "cache",
|
|
1445
|
-
className: "
|
|
1470
|
+
className: "dms_cacheRow"
|
|
1446
1471
|
}, [
|
|
1447
1472
|
(0, react.createElement)("span", {
|
|
1448
1473
|
key: "h",
|
|
1449
|
-
className: "
|
|
1474
|
+
className: "dms_meta"
|
|
1450
1475
|
}, translate(t, "health.cache.hint", { ago: translate(t, ago.key, { n: ago.n }) })),
|
|
1451
1476
|
cacheInfo.currentTotal !== cacheInfo.total && (0, react.createElement)("span", {
|
|
1452
1477
|
key: "chg",
|
|
1453
|
-
className: "
|
|
1478
|
+
className: "dms_why"
|
|
1454
1479
|
}, translate(t, "health.cache.corpusChanged", {
|
|
1455
1480
|
was: cacheInfo.total,
|
|
1456
1481
|
now: cacheInfo.currentTotal
|
|
@@ -1458,7 +1483,7 @@ window.__ModuleLoader__.load({
|
|
|
1458
1483
|
(0, react.createElement)("button", {
|
|
1459
1484
|
key: "r",
|
|
1460
1485
|
type: "button",
|
|
1461
|
-
className: "
|
|
1486
|
+
className: "dms_actBtn",
|
|
1462
1487
|
onClick: () => {
|
|
1463
1488
|
runScan({ forceRefresh: true });
|
|
1464
1489
|
}
|
|
@@ -1467,15 +1492,15 @@ window.__ModuleLoader__.load({
|
|
|
1467
1492
|
}
|
|
1468
1493
|
if (error !== null) lines.push((0, react.createElement)("div", {
|
|
1469
1494
|
key: "err",
|
|
1470
|
-
className: "
|
|
1495
|
+
className: "dms_error"
|
|
1471
1496
|
}, error));
|
|
1472
1497
|
if (detail !== null || discharge !== null) lines.push((0, react.createElement)("div", {
|
|
1473
1498
|
key: "back",
|
|
1474
|
-
className: "
|
|
1499
|
+
className: "dms_btnRow"
|
|
1475
1500
|
}, [(0, react.createElement)("button", {
|
|
1476
1501
|
key: "b",
|
|
1477
1502
|
type: "button",
|
|
1478
|
-
className: "
|
|
1503
|
+
className: "dms_actBtn",
|
|
1479
1504
|
onClick: () => {
|
|
1480
1505
|
setDetail(null);
|
|
1481
1506
|
setDischarge(null);
|
|
@@ -1483,63 +1508,63 @@ window.__ModuleLoader__.load({
|
|
|
1483
1508
|
}, translate(t, "health.back"))]));
|
|
1484
1509
|
if (report !== void 0) lines.push((0, react.createElement)("div", {
|
|
1485
1510
|
key: "gates",
|
|
1486
|
-
className: "
|
|
1511
|
+
className: "dms_gateList"
|
|
1487
1512
|
}, report.gates.map((gate) => (0, react.createElement)("div", {
|
|
1488
1513
|
key: gate.id,
|
|
1489
|
-
className: `
|
|
1514
|
+
className: `dms_gate dms_gate_${gate.level}`
|
|
1490
1515
|
}, [
|
|
1491
1516
|
(0, react.createElement)("span", {
|
|
1492
1517
|
key: "n",
|
|
1493
|
-
className: "
|
|
1518
|
+
className: "dms_gateName"
|
|
1494
1519
|
}, translate(t, GATE_LABEL[gate.id])),
|
|
1495
1520
|
(0, react.createElement)("span", {
|
|
1496
1521
|
key: "lv",
|
|
1497
|
-
className: `
|
|
1522
|
+
className: `dms_levelPill dms_level_${gate.level}`
|
|
1498
1523
|
}, translate(t, LEVEL_LABEL[gate.level])),
|
|
1499
1524
|
(0, react.createElement)("span", {
|
|
1500
1525
|
key: "ev",
|
|
1501
|
-
className: "
|
|
1526
|
+
className: "dms_gateEvidence"
|
|
1502
1527
|
}, gate.evidence),
|
|
1503
1528
|
gate.attribution !== void 0 && (0, react.createElement)("span", {
|
|
1504
1529
|
key: "at",
|
|
1505
|
-
className: "
|
|
1530
|
+
className: "dms_meta"
|
|
1506
1531
|
}, `${translate(t, "health.attribution")}: ${gate.attribution.package ?? "unknown"}${gate.attribution.projection !== void 0 ? ` · ${gate.attribution.projection}` : ""}${gate.attribution.field !== void 0 ? ` · ${translate(t, "health.field")} ${gate.attribution.field}` : ""}`)
|
|
1507
1532
|
]))));
|
|
1508
1533
|
else if (findings !== null) {
|
|
1509
1534
|
if (findings.length === 0) lines.push((0, react.createElement)("div", {
|
|
1510
1535
|
key: "clean",
|
|
1511
|
-
className: "
|
|
1536
|
+
className: "dms_empty"
|
|
1512
1537
|
}, translate(t, "health.empty")));
|
|
1513
1538
|
else lines.push((0, react.createElement)("ul", {
|
|
1514
1539
|
key: "list",
|
|
1515
|
-
className: "
|
|
1540
|
+
className: "dms_list",
|
|
1516
1541
|
role: "list"
|
|
1517
1542
|
}, findings.map((item) => {
|
|
1518
1543
|
const reasons = item.gates.filter((gate) => gate.level !== "ok").map((gate) => `${translate(t, GATE_LABEL[gate.id])}·${translate(t, LEVEL_LABEL[gate.level])}`).join(" · ");
|
|
1519
1544
|
return (0, react.createElement)("li", {
|
|
1520
1545
|
key: item.sessionId,
|
|
1521
|
-
className: "
|
|
1546
|
+
className: "dms_row"
|
|
1522
1547
|
}, [
|
|
1523
1548
|
(0, react.createElement)("span", {
|
|
1524
1549
|
key: "lv",
|
|
1525
|
-
className: `
|
|
1550
|
+
className: `dms_levelPill dms_level_${item.level}`
|
|
1526
1551
|
}, translate(t, LEVEL_LABEL[item.level])),
|
|
1527
1552
|
item.priority === "high" && (0, react.createElement)("span", {
|
|
1528
1553
|
key: "p",
|
|
1529
|
-
className: "
|
|
1554
|
+
className: "dms_meta"
|
|
1530
1555
|
}, translate(t, "health.priority.high")),
|
|
1531
1556
|
(0, react.createElement)("span", {
|
|
1532
1557
|
key: "id",
|
|
1533
|
-
className: "
|
|
1558
|
+
className: "dms_meta dms_uuid"
|
|
1534
1559
|
}, item.sessionId),
|
|
1535
1560
|
reasons !== "" && (0, react.createElement)("span", {
|
|
1536
1561
|
key: "why",
|
|
1537
|
-
className: "
|
|
1562
|
+
className: "dms_why"
|
|
1538
1563
|
}, reasons),
|
|
1539
1564
|
(0, react.createElement)("button", {
|
|
1540
1565
|
key: "d",
|
|
1541
1566
|
type: "button",
|
|
1542
|
-
className: "
|
|
1567
|
+
className: "dms_actBtn",
|
|
1543
1568
|
onClick: () => {
|
|
1544
1569
|
openDetail(item.sessionId);
|
|
1545
1570
|
}
|
|
@@ -1550,59 +1575,59 @@ window.__ModuleLoader__.load({
|
|
|
1550
1575
|
if (report !== void 0) {
|
|
1551
1576
|
lines.push((0, react.createElement)("div", {
|
|
1552
1577
|
key: "phase2",
|
|
1553
|
-
className: "
|
|
1578
|
+
className: "dms_phaseRow"
|
|
1554
1579
|
}, [(0, react.createElement)("span", {
|
|
1555
1580
|
key: "l",
|
|
1556
|
-
className: "
|
|
1581
|
+
className: "dms_phase"
|
|
1557
1582
|
}, translate(t, "health.phase.prescribe")), (0, react.createElement)("button", {
|
|
1558
1583
|
key: "repair",
|
|
1559
1584
|
type: "button",
|
|
1560
|
-
className: "
|
|
1585
|
+
className: "dms_actBtn",
|
|
1561
1586
|
disabled: repairing || report.level === "ok",
|
|
1562
1587
|
onClick: () => {
|
|
1563
1588
|
runRepair(report.sessionId);
|
|
1564
1589
|
}
|
|
1565
1590
|
}, [repairing ? (0, react.createElement)("span", {
|
|
1566
1591
|
key: "sp",
|
|
1567
|
-
className: "
|
|
1592
|
+
className: "dms_spinner",
|
|
1568
1593
|
"aria-hidden": "true"
|
|
1569
1594
|
}) : null, (0, react.createElement)("span", { key: "tx" }, repairing ? translate(t, "health.repairing") : translate(t, "health.repair"))])]));
|
|
1570
1595
|
if (report.gates.some((gate) => gate.id === "source-kind" && gate.level === "warn")) lines.push((0, react.createElement)("div", {
|
|
1571
1596
|
key: "migrate",
|
|
1572
|
-
className: "
|
|
1597
|
+
className: "dms_phaseRow"
|
|
1573
1598
|
}, [(0, react.createElement)("button", {
|
|
1574
1599
|
key: "m",
|
|
1575
1600
|
type: "button",
|
|
1576
|
-
className: "
|
|
1601
|
+
className: "dms_actBtn",
|
|
1577
1602
|
disabled: migrating,
|
|
1578
1603
|
onClick: () => {
|
|
1579
1604
|
runSourceMigrate(report.sessionId);
|
|
1580
1605
|
}
|
|
1581
1606
|
}, [migrating ? (0, react.createElement)("span", {
|
|
1582
1607
|
key: "sp",
|
|
1583
|
-
className: "
|
|
1608
|
+
className: "dms_spinner",
|
|
1584
1609
|
"aria-hidden": "true"
|
|
1585
1610
|
}) : null, (0, react.createElement)("span", { key: "tx" }, migrating ? translate(t, "health.sourceMigrating") : translate(t, "health.sourceMigrate"))]), migrateNote !== null && (0, react.createElement)("span", {
|
|
1586
1611
|
key: "note",
|
|
1587
|
-
className: "
|
|
1612
|
+
className: "dms_status"
|
|
1588
1613
|
}, migrateNote)]));
|
|
1589
1614
|
const prescriptions = detail?.prescriptions ?? discharge?.prescriptions ?? [];
|
|
1590
1615
|
if (prescriptions.length > 0) lines.push((0, react.createElement)("div", {
|
|
1591
1616
|
key: "cmds",
|
|
1592
|
-
className: "
|
|
1617
|
+
className: "dms_cmdBlock"
|
|
1593
1618
|
}, [(0, react.createElement)("div", {
|
|
1594
1619
|
key: "h",
|
|
1595
|
-
className: "
|
|
1620
|
+
className: "dms_meta"
|
|
1596
1621
|
}, translate(t, "health.commands")), ...prescriptions.map((line, index) => (0, react.createElement)("div", {
|
|
1597
1622
|
key: `c${index}`,
|
|
1598
|
-
className: "
|
|
1623
|
+
className: "dms_cmdLine"
|
|
1599
1624
|
}, [(0, react.createElement)("code", {
|
|
1600
1625
|
key: "code",
|
|
1601
|
-
className: "
|
|
1626
|
+
className: "dms_cmd"
|
|
1602
1627
|
}, line), (0, react.createElement)("button", {
|
|
1603
1628
|
key: "cp",
|
|
1604
1629
|
type: "button",
|
|
1605
|
-
className: "
|
|
1630
|
+
className: "dms_actBtn",
|
|
1606
1631
|
onClick: () => {
|
|
1607
1632
|
copy(line);
|
|
1608
1633
|
}
|
|
@@ -1611,973 +1636,177 @@ window.__ModuleLoader__.load({
|
|
|
1611
1636
|
if (discharge !== null) {
|
|
1612
1637
|
lines.push((0, react.createElement)("div", {
|
|
1613
1638
|
key: "phase3",
|
|
1614
|
-
className: "
|
|
1639
|
+
className: "dms_phaseRow"
|
|
1615
1640
|
}, [
|
|
1616
1641
|
(0, react.createElement)("span", {
|
|
1617
1642
|
key: "l",
|
|
1618
|
-
className: "
|
|
1643
|
+
className: "dms_phase"
|
|
1619
1644
|
}, translate(t, "health.phase.discharge")),
|
|
1620
1645
|
(0, react.createElement)("span", {
|
|
1621
1646
|
key: "b",
|
|
1622
|
-
className: `
|
|
1647
|
+
className: `dms_levelPill dms_level_${discharge.before?.level ?? "warn"}`
|
|
1623
1648
|
}, `${translate(t, "health.before")}: ${translate(t, LEVEL_LABEL[discharge.before?.level ?? "warn"])}`),
|
|
1624
1649
|
(0, react.createElement)("span", {
|
|
1625
1650
|
key: "a",
|
|
1626
|
-
className: `
|
|
1651
|
+
className: `dms_levelPill dms_level_${discharge.after?.level ?? "warn"}`
|
|
1627
1652
|
}, `${translate(t, "health.after")}: ${translate(t, LEVEL_LABEL[discharge.after?.level ?? "warn"])}`)
|
|
1628
1653
|
]));
|
|
1629
1654
|
if (discharge.explanation !== void 0 && discharge.explanation !== "") lines.push((0, react.createElement)("div", {
|
|
1630
1655
|
key: "verdict",
|
|
1631
|
-
className: `
|
|
1656
|
+
className: `dms_verdict dms_verdict_${discharge.verdict ?? "failed"}`
|
|
1632
1657
|
}, discharge.explanation));
|
|
1633
1658
|
if (discharge.repair?.ok === true) lines.push((0, react.createElement)("div", {
|
|
1634
1659
|
key: "repairNote",
|
|
1635
|
-
className: "
|
|
1660
|
+
className: "dms_status"
|
|
1636
1661
|
}, `${translate(t, "health.quarantined")} → ${discharge.repair.to ?? ""}`));
|
|
1637
1662
|
else if (discharge.repair?.error !== void 0) lines.push((0, react.createElement)("div", {
|
|
1638
1663
|
key: "repairErr",
|
|
1639
|
-
className: "
|
|
1664
|
+
className: "dms_status"
|
|
1640
1665
|
}, discharge.repair.error));
|
|
1641
1666
|
}
|
|
1642
|
-
return (0, react.createElement)("div", { className: "
|
|
1643
|
-
}
|
|
1644
|
-
//#endregion
|
|
1645
|
-
//#region src/client/search/host-api.ts
|
|
1646
|
-
/**
|
|
1647
|
-
* Client-side helpers and structural mirrors shared by the search panel and
|
|
1648
|
-
* the settings card. Everything talks to the host through the fenced
|
|
1649
|
-
* `/switch-search/api` route; no official package is value-imported.
|
|
1650
|
-
*/
|
|
1651
|
-
/** Fetch timeout for one host call (long for import: the body can be large). */
|
|
1652
|
-
const FETCH_TIMEOUT = 1e4;
|
|
1653
|
-
/** POST a JSON body to a fenced switch-search API method (items shape). */
|
|
1654
|
-
function callHost(method, body) {
|
|
1655
|
-
const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
|
|
1656
|
-
const timer = controller !== void 0 && typeof setTimeout === "function" ? setTimeout(() => {
|
|
1657
|
-
controller.abort();
|
|
1658
|
-
}, FETCH_TIMEOUT) : void 0;
|
|
1659
|
-
return fetch(`/switch-search/api/${method}`, {
|
|
1660
|
-
method: "POST",
|
|
1661
|
-
headers: { "content-type": "application/json" },
|
|
1662
|
-
body: JSON.stringify(body),
|
|
1663
|
-
signal: controller?.signal
|
|
1664
|
-
}).then((res) => res.ok ? res.json() : Promise.reject(/* @__PURE__ */ new Error(`HTTP ${res.status}`))).then((data) => {
|
|
1665
|
-
const record = data;
|
|
1666
|
-
if (record && record.ok === true && Array.isArray(record.items)) return {
|
|
1667
|
-
ok: true,
|
|
1668
|
-
items: record.items
|
|
1669
|
-
};
|
|
1670
|
-
return {
|
|
1671
|
-
ok: false,
|
|
1672
|
-
items: [],
|
|
1673
|
-
error: record?.error ?? "请求失败"
|
|
1674
|
-
};
|
|
1675
|
-
}).catch((err) => ({
|
|
1676
|
-
ok: false,
|
|
1677
|
-
items: [],
|
|
1678
|
-
error: err instanceof DOMException && err.name === "AbortError" ? "请求超时" : String(err instanceof Error ? err.message : err)
|
|
1679
|
-
})).finally(() => {
|
|
1680
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
1681
|
-
});
|
|
1682
|
-
}
|
|
1683
|
-
/** POST a body to a fenced switch-search API method, returning the whole record. */
|
|
1684
|
-
function callHostAny(method, body, timeout = FETCH_TIMEOUT) {
|
|
1685
|
-
const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
|
|
1686
|
-
const timer = typeof setTimeout === "function" ? setTimeout(() => {
|
|
1687
|
-
controller?.abort();
|
|
1688
|
-
}, timeout) : void 0;
|
|
1689
|
-
return fetch(`/switch-search/api/${method}`, {
|
|
1690
|
-
method: "POST",
|
|
1691
|
-
headers: { "content-type": "application/json" },
|
|
1692
|
-
body: typeof body === "string" ? body : JSON.stringify(body),
|
|
1693
|
-
signal: controller?.signal
|
|
1694
|
-
}).then((res) => res.ok ? res.json() : Promise.reject(/* @__PURE__ */ new Error(`HTTP ${res.status}`))).catch((err) => ({
|
|
1695
|
-
ok: false,
|
|
1696
|
-
error: err instanceof DOMException && err.name === "AbortError" ? "请求超时" : String(err instanceof Error ? err.message : err)
|
|
1697
|
-
})).finally(() => {
|
|
1698
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
/**
|
|
1702
|
-
* POST to the STEWARD fenced route (`/session-steward/api`) — the batch
|
|
1703
|
-
* management console (archive / unarchive / purge) rides the steward subdomain,
|
|
1704
|
-
* per the merged package's route contract (session-* methods stay there).
|
|
1705
|
-
*/
|
|
1706
|
-
function callSteward(method, body, timeout = FETCH_TIMEOUT) {
|
|
1707
|
-
const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
|
|
1708
|
-
const timer = typeof setTimeout === "function" ? setTimeout(() => {
|
|
1709
|
-
controller?.abort();
|
|
1710
|
-
}, timeout) : void 0;
|
|
1711
|
-
return fetch(`/session-steward/api/${method}`, {
|
|
1712
|
-
method: "POST",
|
|
1713
|
-
headers: { "content-type": "application/json" },
|
|
1714
|
-
body: JSON.stringify(body),
|
|
1715
|
-
signal: controller?.signal
|
|
1716
|
-
}).then((res) => res.ok ? res.json() : Promise.reject(/* @__PURE__ */ new Error(`HTTP ${res.status}`))).catch((err) => ({
|
|
1717
|
-
ok: false,
|
|
1718
|
-
error: err instanceof DOMException && err.name === "AbortError" ? "请求超时" : String(err instanceof Error ? err.message : err)
|
|
1719
|
-
})).finally(() => {
|
|
1720
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
1721
|
-
});
|
|
1722
|
-
}
|
|
1723
|
-
/** Trigger a browser download of the index snapshot from the host route. */
|
|
1724
|
-
async function downloadSnapshot() {
|
|
1725
|
-
const res = await fetch("/switch-search/api/index-export", { method: "POST" });
|
|
1726
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1727
|
-
const blob = await res.blob();
|
|
1728
|
-
const url = URL.createObjectURL(blob);
|
|
1729
|
-
const anchor = document.createElement("a");
|
|
1730
|
-
anchor.href = url;
|
|
1731
|
-
anchor.download = `switch-search-snapshot-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`;
|
|
1732
|
-
anchor.click();
|
|
1733
|
-
URL.revokeObjectURL(url);
|
|
1734
|
-
}
|
|
1735
|
-
/**
|
|
1736
|
-
* Open a session from a search hit through whichever face the running host
|
|
1737
|
-
* offers: `uiWorkspace.openSession` first (0.1.7+), the pre-0.1.7
|
|
1738
|
-
* `sessions.open` as fallback. Neither present → silent no-op: a host line
|
|
1739
|
-
* this package does not target must not crash the panel.
|
|
1740
|
-
*
|
|
1741
|
-
* The service names are resolved through `get` at call time, not captured at
|
|
1742
|
-
* apply time: this plugin applies before the session-controller / ui-workspace
|
|
1743
|
-
* client modules in the load order, so an eager lookup captures `undefined`
|
|
1744
|
-
* and every result click silently no-ops. Both are root-context singletons,
|
|
1745
|
-
* so by the time a user clicks a hit they are always mounted.
|
|
1746
|
-
*/
|
|
1747
|
-
function openSessionThrough(get, sessionId) {
|
|
1748
|
-
const uiWorkspace = get("uiWorkspace");
|
|
1749
|
-
if (uiWorkspace !== void 0 && typeof uiWorkspace.openSession === "function") {
|
|
1750
|
-
uiWorkspace.openSession(sessionId);
|
|
1751
|
-
return;
|
|
1752
|
-
}
|
|
1753
|
-
const sessions = get("sessions");
|
|
1754
|
-
if (sessions !== void 0 && typeof sessions.open === "function") sessions.open(sessionId);
|
|
1667
|
+
return (0, react.createElement)("div", { className: "dms_tabBody" }, lines);
|
|
1755
1668
|
}
|
|
1756
|
-
//#endregion
|
|
1757
|
-
//#region src/client/search/locales.ts
|
|
1758
|
-
/** `switch-search` client dictionaries (zh / en / ja / ko / fr / de / it / ru / es), thinking-levels pattern. */
|
|
1759
|
-
/** Dictionary namespace owned by this plugin (the host settings namespace). */
|
|
1760
|
-
const NS$1 = "switch-search";
|
|
1761
1669
|
/** All shipped dictionaries by locale id. */
|
|
1762
1670
|
const dictionaries = {
|
|
1763
1671
|
zh: {
|
|
1764
|
-
"card.title": "搜索索引",
|
|
1765
|
-
"card.description": "侧边栏会话搜索增强:标题搜索与内容搜索一键切换。内容搜索使用插件自建索引,不依赖 DSH 官方全文索引。",
|
|
1766
|
-
"card.unavailable": "设置命名空间不可用:请确认插件已装配进 profile。",
|
|
1767
|
-
"card.readonly": "只读",
|
|
1768
|
-
"card.enabled": "启用会话搜索",
|
|
1769
|
-
"card.enabled.desc": "在侧边栏底部显示\"搜索\"入口。",
|
|
1770
|
-
"card.defaultMode": "默认搜索模式",
|
|
1771
|
-
"card.defaultMode.desc": "面板打开时默认进入标题搜索还是内容搜索。",
|
|
1772
|
-
"card.mode.title": "标题",
|
|
1773
|
-
"card.mode.content": "内容",
|
|
1774
|
-
"card.autoSync": "自动同步索引",
|
|
1775
|
-
"card.autoSync.desc": "后台按水位增量同步会话日志到独立索引。",
|
|
1776
|
-
"card.syncInterval": "同步间隔(秒)",
|
|
1777
|
-
"card.syncInterval.desc": "两次增量同步之间的最小间隔。",
|
|
1778
|
-
"card.archiveKeep": "归档保留份数",
|
|
1779
|
-
"card.archiveKeep.desc": "每次整理索引后保留的历史索引文件数量。",
|
|
1780
|
-
"card.index": "内容搜索索引",
|
|
1781
|
-
"card.index.desc": "索引正常:已收录 {indexed} 个会话。",
|
|
1782
|
-
"card.index.archives": ",归档 {archives} 份。",
|
|
1783
|
-
"card.index.empty": "独立索引尚未建立。点击\"整理索引\"从会话日志全量建立。",
|
|
1784
|
-
"card.index.reading": "正在读取索引状态…",
|
|
1785
|
-
"card.index.rebuilding": "正在整理索引… {done}/{total}(整理期间旧索引仍可搜索)",
|
|
1786
|
-
"card.index.syncing": "正在同步索引…",
|
|
1787
|
-
"card.index.failures": "{count} 个会话同步失败(详见 Host 日志)。",
|
|
1788
|
-
"card.index.rebuildError": "整理失败:{error}",
|
|
1789
|
-
"card.index.rebuild": "整理索引",
|
|
1790
|
-
"card.index.rebuilding.btn": "整理中…",
|
|
1791
|
-
"card.index.rebuild.hint": "非破坏性:构建期间旧索引继续可搜索,完成后原子切换并归档旧索引。",
|
|
1792
|
-
"card.index.archivedOwner": "归档会话的浏览与清理由「会话管家」(dsh-manage-session)负责。本插件只读归档集合,把已归档会话排除出索引。",
|
|
1793
|
-
"card.index.archivedMissing": "归档会话的浏览与清理需要「会话管家」(dsh-manage-session),当前未安装。本插件只读归档集合,把已归档会话排除出索引。",
|
|
1794
|
-
"card.index.export": "导出快照",
|
|
1795
|
-
"card.index.import": "导入快照",
|
|
1796
|
-
"card.index.exported": "快照已导出为下载文件。",
|
|
1797
|
-
"card.index.import.started": "快照导入已开始:正在后台重建索引,完成后自动切换。",
|
|
1798
|
-
"card.index.importParse": "快照解析失败或无有效会话。",
|
|
1799
|
-
"card.action.failed": "操作失败:{error}",
|
|
1800
|
-
"panel.titleSearch": "标题",
|
|
1801
|
-
"panel.contentSearch": "内容",
|
|
1802
|
-
"panel.searchTitle": "搜索会话标题…",
|
|
1803
|
-
"panel.searchContent": "搜索会话内容…",
|
|
1804
|
-
"panel.entry": "搜索",
|
|
1805
|
-
"panel.buildIndex": "建立索引",
|
|
1806
|
-
"panel.archived": "已排除 {count} 个已归档会话",
|
|
1807
|
-
"panel.rebuilding": "正在整理索引… {done}/{total}(整理期间旧索引仍可搜索)",
|
|
1808
|
-
"panel.unavailable": "独立索引服务不可用:Host 未完成初始化。",
|
|
1809
|
-
"panel.notBuilt": "独立索引尚未建立:先建立索引即可启用内容搜索(不依赖 DSH 官方全文索引)。",
|
|
1810
|
-
"panel.loadingSessions": "正在读取会话列表…",
|
|
1811
|
-
"panel.sessionsError": "读取会话列表失败:{error}",
|
|
1812
|
-
"panel.noSessions": "暂无会话",
|
|
1813
|
-
"panel.noMatch": "没有匹配的会话。",
|
|
1814
|
-
"panel.loadingContent": "正在搜索会话内容…",
|
|
1815
|
-
"panel.contentError": "内容搜索失败:{error}",
|
|
1816
|
-
"panel.noContent": "没有匹配的内容。",
|
|
1817
|
-
"panel.contentHint": "输入内容关键词开始搜索。",
|
|
1818
|
-
"panel.noText": "(无文本)",
|
|
1819
|
-
"panel.delete": "删除",
|
|
1820
|
-
"panel.delete.confirm": "确认删除",
|
|
1821
|
-
"panel.delete.hint": "删除该会话(不可逆,与管理端同款链路)",
|
|
1822
|
-
"panel.delete.done": "已删除会话 {n} 个",
|
|
1823
|
-
"panel.delete.failed": "删除失败:{error}",
|
|
1824
|
-
"panel.untitled": "(未命名)",
|
|
1825
|
-
"panel.openSession": "打开会话",
|
|
1826
|
-
"panel.footer.invoke": "唤出",
|
|
1827
|
-
"panel.footer.close": "关闭",
|
|
1828
|
-
"filter.all": "全部",
|
|
1829
|
-
"filter.user": "用户",
|
|
1830
|
-
"filter.reply": "回复",
|
|
1831
|
-
"filter.tool": "工具",
|
|
1832
|
-
"sort.label": "结果排序",
|
|
1833
|
-
"sort.relevance": "相关度",
|
|
1834
|
-
"sort.time": "时间",
|
|
1835
|
-
"sort.relevance.hint": "按匹配强度排序(默认)。",
|
|
1836
|
-
"sort.time.hint": "按会话最后活动时间倒序,更新的排前面;同一时间再按匹配强度。",
|
|
1837
|
-
"group.merge": "合并",
|
|
1838
|
-
"group.expand": "分开",
|
|
1839
|
-
"group.merge.hint": "每个会话只显示最强的一条命中(默认)。",
|
|
1840
|
-
"group.expand.hint": "同一会话的多条命中分行显示(每行仍是可跳转的独立命中)。",
|
|
1841
|
-
"type.user/message": "用户",
|
|
1842
|
-
"type.assistant/message": "回复",
|
|
1843
|
-
"type.tool/call": "工具调用",
|
|
1844
|
-
"type.tool/result": "工具结果",
|
|
1845
|
-
"panel.manage": "管理",
|
|
1846
1672
|
"domain.all": "全部",
|
|
1847
|
-
"
|
|
1848
|
-
"
|
|
1849
|
-
"
|
|
1850
|
-
"
|
|
1851
|
-
"manage.restartHint": "优先走宿主内存 API 即时生效(0.1.7+ 归档/取消归档免重启);不可用时直写存储文件,重启后侧栏生效。",
|
|
1852
|
-
"manage.done.restart": "需重启 DSH 后在侧栏完全生效。",
|
|
1853
|
-
"manage.group.nocwd": "(未分组)",
|
|
1854
|
-
"manage.selectGroup": "全选/取消本组",
|
|
1855
|
-
"manage.selected": "已选 {n}",
|
|
1673
|
+
"filter.favorites": "收藏",
|
|
1674
|
+
"group.collapse": "收拢/展开本组",
|
|
1675
|
+
"group.collapseAll": "全部收拢",
|
|
1676
|
+
"group.expandAll": "全部展开",
|
|
1856
1677
|
"manage.batch.archive": "归档",
|
|
1857
|
-
"manage.batch.unarchive": "取消归档",
|
|
1858
|
-
"manage.batch.delete": "删除",
|
|
1859
1678
|
"manage.batch.confirm": "确认删除",
|
|
1679
|
+
"manage.batch.delete": "删除",
|
|
1680
|
+
"manage.batch.unarchive": "取消归档",
|
|
1860
1681
|
"manage.batch.working": "执行中…",
|
|
1861
1682
|
"manage.done.archive": "已归档 {n} 个会话。",
|
|
1862
|
-
"manage.done.unarchive": "已取消归档 {n} 个会话。",
|
|
1863
|
-
"manage.done.purge": "已清理 {n} 个会话。",
|
|
1864
1683
|
"manage.done.error": "操作失败:{error}",
|
|
1865
|
-
"
|
|
1866
|
-
"
|
|
1867
|
-
"
|
|
1868
|
-
"
|
|
1869
|
-
"
|
|
1870
|
-
"
|
|
1871
|
-
"
|
|
1872
|
-
"
|
|
1873
|
-
"
|
|
1684
|
+
"manage.done.purge": "已清理 {n} 个会话。",
|
|
1685
|
+
"manage.done.restart": "需重启 DSH 后在侧栏完全生效。",
|
|
1686
|
+
"manage.done.unarchive": "已取消归档 {n} 个会话。",
|
|
1687
|
+
"manage.group.nocwd": "(未分组)",
|
|
1688
|
+
"manage.hint": "按工作区分组,勾选后批量操作;删除不可撤销。",
|
|
1689
|
+
"manage.restartHint": "优先走宿主内存 API 即时生效(0.1.7+ 归档/取消归档免重启);不可用时直写存储文件,重启后侧栏生效。",
|
|
1690
|
+
"manage.search": "搜索会话…",
|
|
1691
|
+
"manage.selected": "已选 {n}",
|
|
1692
|
+
"manage.selectGroup": "全选/取消本组",
|
|
1693
|
+
"panel.loadingSessions": "正在读取会话列表…",
|
|
1694
|
+
"panel.noMatch": "没有匹配的会话。",
|
|
1695
|
+
"panel.noSessions": "暂无会话",
|
|
1696
|
+
"panel.untitled": "(未命名)",
|
|
1697
|
+
"tag.archived": "归档",
|
|
1874
1698
|
"workspace.label": "工作区",
|
|
1875
|
-
"workspace.more": "还有 {n} 个",
|
|
1876
1699
|
"workspace.less": "收起",
|
|
1877
|
-
"
|
|
1878
|
-
"
|
|
1879
|
-
"
|
|
1880
|
-
"
|
|
1700
|
+
"workspace.more": "还有 {n} 个",
|
|
1701
|
+
"star.on": "已收藏",
|
|
1702
|
+
"star.off": "未收藏",
|
|
1703
|
+
"domain.active": "活跃",
|
|
1704
|
+
"domain.archived": "归档"
|
|
1881
1705
|
},
|
|
1882
1706
|
en: {
|
|
1883
|
-
"card.title": "Search Index",
|
|
1884
|
-
"card.description": "Sidebar session search with title/content mode switching. Content search uses the plugin-owned index and never depends on the official DSH full-text index.",
|
|
1885
|
-
"card.unavailable": "Settings namespace unavailable: make sure the plugin is assembled into the profile.",
|
|
1886
|
-
"card.readonly": "Read-only",
|
|
1887
|
-
"card.enabled": "Enable session search",
|
|
1888
|
-
"card.enabled.desc": "Show the \"Search\" entry at the bottom of the sidebar.",
|
|
1889
|
-
"card.defaultMode": "Default search mode",
|
|
1890
|
-
"card.defaultMode.desc": "Which mode the panel opens in.",
|
|
1891
|
-
"card.mode.title": "Title",
|
|
1892
|
-
"card.mode.content": "Content",
|
|
1893
|
-
"card.autoSync": "Auto sync index",
|
|
1894
|
-
"card.autoSync.desc": "Incrementally sync session logs into the independent index in the background.",
|
|
1895
|
-
"card.syncInterval": "Sync interval (seconds)",
|
|
1896
|
-
"card.syncInterval.desc": "Minimum interval between two incremental syncs.",
|
|
1897
|
-
"card.archiveKeep": "Archives to keep",
|
|
1898
|
-
"card.archiveKeep.desc": "How many archived index files each rebuild retains.",
|
|
1899
|
-
"card.index": "Content search index",
|
|
1900
|
-
"card.index.desc": "Index ready: {indexed} sessions collected.",
|
|
1901
|
-
"card.index.archives": ", {archives} archive(s).",
|
|
1902
|
-
"card.index.empty": "Independent index not built yet. Click \"Rebuild index\" to build it from session logs.",
|
|
1903
|
-
"card.index.reading": "Reading index status…",
|
|
1904
|
-
"card.index.rebuilding": "Rebuilding index… {done}/{total} (the old index keeps serving during the rebuild)",
|
|
1905
|
-
"card.index.syncing": "Syncing index…",
|
|
1906
|
-
"card.index.failures": "{count} session(s) failed to sync (see Host logs).",
|
|
1907
|
-
"card.index.rebuildError": "Rebuild failed: {error}",
|
|
1908
|
-
"card.index.rebuild": "Rebuild index",
|
|
1909
|
-
"card.index.rebuilding.btn": "Rebuilding…",
|
|
1910
|
-
"card.index.rebuild.hint": "Non-destructive: the old index keeps serving while the shadow builds; the swap is atomic and the old index is archived.",
|
|
1911
|
-
"card.index.archivedOwner": "Browsing and disposing of archived sessions belongs to dsh-manage-session. This plugin only reads the archive set and excludes those sessions from the index.",
|
|
1912
|
-
"card.index.archivedMissing": "Browsing and disposing of archived sessions needs dsh-manage-session, which is not installed. This plugin only reads the archive set and excludes those sessions from the index.",
|
|
1913
|
-
"card.index.export": "Export snapshot",
|
|
1914
|
-
"card.index.import": "Import snapshot",
|
|
1915
|
-
"card.index.exported": "Snapshot downloaded.",
|
|
1916
|
-
"card.index.import.started": "Import started: the index rebuilds in the background and swaps in when done.",
|
|
1917
|
-
"card.index.importParse": "Snapshot parse failed or contains no valid session.",
|
|
1918
|
-
"card.action.failed": "Action failed: {error}",
|
|
1919
|
-
"panel.titleSearch": "Title",
|
|
1920
|
-
"panel.contentSearch": "Content",
|
|
1921
|
-
"panel.searchTitle": "Search session titles…",
|
|
1922
|
-
"panel.searchContent": "Search session content…",
|
|
1923
|
-
"panel.entry": "Search",
|
|
1924
|
-
"panel.buildIndex": "Build index",
|
|
1925
|
-
"panel.archived": "{count} archived session(s) excluded",
|
|
1926
|
-
"panel.rebuilding": "Rebuilding index… {done}/{total} (the old index keeps serving)",
|
|
1927
|
-
"panel.unavailable": "Independent index service unavailable: Host not initialized.",
|
|
1928
|
-
"panel.notBuilt": "Independent index not built yet: build it once to enable content search (no official FTS index needed).",
|
|
1929
|
-
"panel.loadingSessions": "Loading sessions…",
|
|
1930
|
-
"panel.sessionsError": "Failed to load sessions: {error}",
|
|
1931
|
-
"panel.noSessions": "No sessions",
|
|
1932
|
-
"panel.noMatch": "No matching sessions.",
|
|
1933
|
-
"panel.loadingContent": "Searching content…",
|
|
1934
|
-
"panel.contentError": "Content search failed: {error}",
|
|
1935
|
-
"panel.noContent": "No matching content.",
|
|
1936
|
-
"panel.contentHint": "Type keywords to search content.",
|
|
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}",
|
|
1943
|
-
"panel.untitled": "(untitled)",
|
|
1944
|
-
"panel.openSession": "Open session",
|
|
1945
|
-
"panel.footer.invoke": "Open",
|
|
1946
|
-
"panel.footer.close": "Close",
|
|
1947
|
-
"filter.all": "All",
|
|
1948
|
-
"filter.user": "User",
|
|
1949
|
-
"filter.reply": "Reply",
|
|
1950
|
-
"filter.tool": "Tool",
|
|
1951
|
-
"sort.label": "Result ordering",
|
|
1952
|
-
"sort.relevance": "Relevance",
|
|
1953
|
-
"sort.time": "Time",
|
|
1954
|
-
"sort.relevance.hint": "Order by match strength (default).",
|
|
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).",
|
|
1960
|
-
"type.user/message": "User",
|
|
1961
|
-
"type.assistant/message": "Reply",
|
|
1962
|
-
"type.tool/call": "Tool call",
|
|
1963
|
-
"type.tool/result": "Tool result",
|
|
1964
|
-
"panel.manage": "Manage",
|
|
1965
1707
|
"domain.all": "All",
|
|
1966
|
-
"
|
|
1967
|
-
"
|
|
1968
|
-
"
|
|
1969
|
-
"
|
|
1970
|
-
"manage.restartHint": "Prefers host in-memory APIs (0.1.7+ archive/unarchive apply instantly); falls back to direct storage-file edits, which need a restart.",
|
|
1971
|
-
"manage.done.restart": "Restart DSH for the sidebar to fully reflect this.",
|
|
1972
|
-
"manage.group.nocwd": "(no workspace)",
|
|
1973
|
-
"manage.selectGroup": "Toggle the whole group",
|
|
1974
|
-
"manage.selected": "{n} selected",
|
|
1708
|
+
"filter.favorites": "Favorites",
|
|
1709
|
+
"group.collapse": "Collapse/expand group",
|
|
1710
|
+
"group.collapseAll": "Collapse all",
|
|
1711
|
+
"group.expandAll": "Expand all",
|
|
1975
1712
|
"manage.batch.archive": "Archive",
|
|
1976
|
-
"manage.batch.unarchive": "Unarchive",
|
|
1977
|
-
"manage.batch.delete": "Delete",
|
|
1978
1713
|
"manage.batch.confirm": "Confirm delete",
|
|
1714
|
+
"manage.batch.delete": "Delete",
|
|
1715
|
+
"manage.batch.unarchive": "Unarchive",
|
|
1979
1716
|
"manage.batch.working": "Working…",
|
|
1980
1717
|
"manage.done.archive": "Archived {n} sessions.",
|
|
1981
|
-
"manage.done.unarchive": "Unarchived {n} sessions.",
|
|
1982
|
-
"manage.done.purge": "Purged {n} sessions.",
|
|
1983
1718
|
"manage.done.error": "Operation failed: {error}",
|
|
1984
|
-
"
|
|
1985
|
-
"
|
|
1986
|
-
"
|
|
1987
|
-
"
|
|
1988
|
-
"
|
|
1989
|
-
"
|
|
1990
|
-
"
|
|
1991
|
-
"
|
|
1992
|
-
"
|
|
1719
|
+
"manage.done.purge": "Purged {n} sessions.",
|
|
1720
|
+
"manage.done.restart": "Restart DSH for the sidebar to fully reflect this.",
|
|
1721
|
+
"manage.done.unarchive": "Unarchived {n} sessions.",
|
|
1722
|
+
"manage.group.nocwd": "(no workspace)",
|
|
1723
|
+
"manage.hint": "Grouped by workspace; tick rows then batch-operate. Deletion is irreversible.",
|
|
1724
|
+
"manage.restartHint": "Prefers host in-memory APIs (0.1.7+ archive/unarchive apply instantly); falls back to direct storage-file edits, which need a restart.",
|
|
1725
|
+
"manage.search": "Search sessions…",
|
|
1726
|
+
"manage.selected": "{n} selected",
|
|
1727
|
+
"manage.selectGroup": "Toggle the whole group",
|
|
1728
|
+
"panel.loadingSessions": "Loading sessions…",
|
|
1729
|
+
"panel.noMatch": "No matching sessions.",
|
|
1730
|
+
"panel.noSessions": "No sessions",
|
|
1731
|
+
"panel.untitled": "(untitled)",
|
|
1732
|
+
"tag.archived": "Archived",
|
|
1993
1733
|
"workspace.label": "Workspace",
|
|
1994
|
-
"workspace.more": "{n} more",
|
|
1995
1734
|
"workspace.less": "Collapse",
|
|
1996
|
-
"
|
|
1997
|
-
"
|
|
1998
|
-
"
|
|
1999
|
-
"
|
|
1735
|
+
"workspace.more": "{n} more",
|
|
1736
|
+
"star.on": "Favorited",
|
|
1737
|
+
"star.off": "Not favorited",
|
|
1738
|
+
"domain.active": "Active",
|
|
1739
|
+
"domain.archived": "Archived"
|
|
2000
1740
|
},
|
|
2001
1741
|
ja: {
|
|
2002
|
-
"card.title": "検索インデックス",
|
|
2003
|
-
"card.description": "サイドバーセッション検索の強化:タイトル検索とコンテンツ検索のワンクリック切り替え。コンテンツ検索はプラグイン独自のインデックスを使用し、DSH公式全文インデックスに依存しません。",
|
|
2004
|
-
"card.unavailable": "設定名前空間が利用できません:プラグインがプロファイルに組み込まれていることを確認してください。",
|
|
2005
|
-
"card.readonly": "読み取り専用",
|
|
2006
|
-
"card.enabled": "セッション検索を有効化",
|
|
2007
|
-
"card.enabled.desc": "サイドバー下部に「検索」エントリを表示します。",
|
|
2008
|
-
"card.defaultMode": "デフォルト検索モード",
|
|
2009
|
-
"card.defaultMode.desc": "パネルを開いたときの初期モード。",
|
|
2010
|
-
"card.mode.title": "タイトル",
|
|
2011
|
-
"card.mode.content": "コンテンツ",
|
|
2012
|
-
"card.autoSync": "インデックス自動同期",
|
|
2013
|
-
"card.autoSync.desc": "バックグラウンドでセッションログを独立インデックスに増分同期します。",
|
|
2014
|
-
"card.syncInterval": "同期間隔(秒)",
|
|
2015
|
-
"card.syncInterval.desc": "2回の増分同期間の最小間隔。",
|
|
2016
|
-
"card.archiveKeep": "アーカイブ保持数",
|
|
2017
|
-
"card.archiveKeep.desc": "インデックス再構築ごとに保持するアーカイブファイル数。",
|
|
2018
|
-
"card.index": "コンテンツ検索インデックス",
|
|
2019
|
-
"card.index.desc": "インデックス正常:{indexed} セッションを収容済み。",
|
|
2020
|
-
"card.index.archives": "、アーカイブ {archives} 件。",
|
|
2021
|
-
"card.index.empty": "独立インデックスが未構築です。「インデックス再構築」をクリックしてセッションログから構築してください。",
|
|
2022
|
-
"card.index.reading": "インデックス状態を読み取り中…",
|
|
2023
|
-
"card.index.rebuilding": "インデックス再構築中… {done}/{total}(再構築中も旧インデックスで検索可能)",
|
|
2024
|
-
"card.index.syncing": "インデックス同期中…",
|
|
2025
|
-
"card.index.failures": "{count} 件のセッション同期に失敗(Hostログ参照)。",
|
|
2026
|
-
"card.index.rebuildError": "再構築失敗:{error}",
|
|
2027
|
-
"card.index.rebuild": "インデックス再構築",
|
|
2028
|
-
"card.index.rebuilding.btn": "再構築中…",
|
|
2029
|
-
"card.index.rebuild.hint": "非破壊的:構築中も旧インデックスで検索可能、完了後にアトミック切替し旧インデックスをアーカイブ。",
|
|
2030
|
-
"card.index.archivedOwner": "アーカイブ済みセッションの閲覧と清理は「セッション管理者」(dsh-manage-session)が担当。本プラグインはアーカイブ集合を読み取り専用で参照し、アーカイブ済みセッションをインデックスから除外します。",
|
|
2031
|
-
"card.index.archivedMissing": "アーカイブ済みセッションの閲覧と清理には「セッション管理者」(dsh-manage-session)が必要ですが、現在未インストールです。本プラグインはアーカイブ集合を読み取り専用で参照し、アーカイブ済みセッションをインデックスから除外します。",
|
|
2032
|
-
"card.index.export": "スナップショット書出",
|
|
2033
|
-
"card.index.import": "スナップショット読込",
|
|
2034
|
-
"card.index.exported": "スナップショットをダウンロードしました。",
|
|
2035
|
-
"card.index.import.started": "スナップショットのインポートを開始:バックグラウンドでインデックスを再構築し、完了後に自動切替。",
|
|
2036
|
-
"card.index.importParse": "スナップショットの解析に失敗、または有効なセッションがありません。",
|
|
2037
|
-
"card.action.failed": "操作失敗:{error}",
|
|
2038
|
-
"panel.titleSearch": "タイトル",
|
|
2039
|
-
"panel.contentSearch": "コンテンツ",
|
|
2040
|
-
"panel.searchTitle": "セッションタイトルを検索…",
|
|
2041
|
-
"panel.searchContent": "セッション内容を検索…",
|
|
2042
|
-
"panel.entry": "検索",
|
|
2043
|
-
"panel.buildIndex": "インデックス構築",
|
|
2044
|
-
"panel.archived": "アーカイブ済み {count} 件を除外",
|
|
2045
|
-
"panel.rebuilding": "インデックス再構築中… {done}/{total}(再構築中も旧インデックスで検索可能)",
|
|
2046
|
-
"panel.unavailable": "独立インデックスサービス利用不可:Host初期化未完了。",
|
|
2047
|
-
"panel.notBuilt": "独立インデックス未構築:構築すればコンテンツ検索が有効になります(DSH公式全文インデックス不要)。",
|
|
2048
1742
|
"panel.loadingSessions": "セッション一覧を読み込み中…",
|
|
2049
|
-
"panel.sessionsError": "セッション一覧の読み込み失敗:{error}",
|
|
2050
|
-
"panel.noSessions": "セッションなし",
|
|
2051
1743
|
"panel.noMatch": "一致するセッションがありません。",
|
|
2052
|
-
"panel.
|
|
2053
|
-
"panel.contentError": "コンテンツ検索失敗:{error}",
|
|
2054
|
-
"panel.noContent": "一致するコンテンツがありません。",
|
|
2055
|
-
"panel.contentHint": "キーワードを入力してコンテンツを検索。",
|
|
2056
|
-
"panel.noText": "(テキストなし)",
|
|
2057
|
-
"panel.delete": "削除",
|
|
2058
|
-
"panel.delete.confirm": "削除を確認",
|
|
2059
|
-
"panel.delete.hint": "このセッションを削除(不可逆、管理画面と同じ経路)",
|
|
2060
|
-
"panel.delete.done": "セッション {n} 件を削除しました",
|
|
2061
|
-
"panel.delete.failed": "削除に失敗:{error}",
|
|
1744
|
+
"panel.noSessions": "セッションなし",
|
|
2062
1745
|
"panel.untitled": "(無題)",
|
|
2063
|
-
"
|
|
2064
|
-
"
|
|
2065
|
-
"
|
|
2066
|
-
"
|
|
2067
|
-
"filter.user": "ユーザー",
|
|
2068
|
-
"filter.reply": "返信",
|
|
2069
|
-
"filter.tool": "ツール",
|
|
2070
|
-
"sort.label": "結果の並べ替え",
|
|
2071
|
-
"sort.relevance": "関連度",
|
|
2072
|
-
"sort.time": "時間",
|
|
2073
|
-
"sort.relevance.hint": "一致強度順に並べ替え(デフォルト)。",
|
|
2074
|
-
"sort.time.hint": "セッション最終活動日時の降順、同着時は一致強度順。",
|
|
2075
|
-
"group.merge": "まとめる",
|
|
2076
|
-
"group.expand": "展開",
|
|
2077
|
-
"group.merge.hint": "セッションごとに最強の一致1件のみ表示(デフォルト)。",
|
|
2078
|
-
"group.expand.hint": "同一セッションの複数一致を行ごとに展開(各行が個別のジャンプ先)。",
|
|
2079
|
-
"type.user/message": "ユーザー",
|
|
2080
|
-
"type.assistant/message": "返信",
|
|
2081
|
-
"type.tool/call": "ツール呼び出し",
|
|
2082
|
-
"type.tool/result": "ツール結果"
|
|
1746
|
+
"star.on": "お気に入り済み",
|
|
1747
|
+
"star.off": "未お気に入り",
|
|
1748
|
+
"domain.active": "活跃",
|
|
1749
|
+
"domain.archived": "归档"
|
|
2083
1750
|
},
|
|
2084
1751
|
ko: {
|
|
2085
|
-
"card.title": "검색 인덱스",
|
|
2086
|
-
"card.description": "사이드바 세션 검색 강화: 제목 검색과 콘텐츠 검색 간 원클릭 전환. 콘텐츠 검색은 플러그인 자체 인덱스를 사용하며 DSH 공식 전문 인덱스에 의존하지 않습니다.",
|
|
2087
|
-
"card.unavailable": "설정 네임스페이스를 사용할 수 없습니다: 플러그인이 프로파일에 조립되어 있는지 확인하세요.",
|
|
2088
|
-
"card.readonly": "읽기 전용",
|
|
2089
|
-
"card.enabled": "세션 검색 활성화",
|
|
2090
|
-
"card.enabled.desc": "사이드바 하단에 \"검색\" 항목을 표시합니다.",
|
|
2091
|
-
"card.defaultMode": "기본 검색 모드",
|
|
2092
|
-
"card.defaultMode.desc": "패널을 열 때 기본 모드.",
|
|
2093
|
-
"card.mode.title": "제목",
|
|
2094
|
-
"card.mode.content": "콘텐츠",
|
|
2095
|
-
"card.autoSync": "인덱스 자동 동기화",
|
|
2096
|
-
"card.autoSync.desc": "백그라운드에서 세션 로그를 독립 인덱스에 증분 동기화합니다.",
|
|
2097
|
-
"card.syncInterval": "동기화 간격(초)",
|
|
2098
|
-
"card.syncInterval.desc": "두 증분 동기화 사이의 최소 간격.",
|
|
2099
|
-
"card.archiveKeep": "아카이브 보존 수",
|
|
2100
|
-
"card.archiveKeep.desc": "인덱스 재구축 시 보존할 아카이브 파일 수.",
|
|
2101
|
-
"card.index": "콘텐츠 검색 인덱스",
|
|
2102
|
-
"card.index.desc": "인덱스 정상: {indexed}개 세션 수집 완료.",
|
|
2103
|
-
"card.index.archives": ", 아카이브 {archives}건.",
|
|
2104
|
-
"card.index.empty": "독립 인덱스가 아직 구축되지 않았습니다. \"인덱스 재구축\"을 클릭하여 세션 로그에서 구축하세요.",
|
|
2105
|
-
"card.index.reading": "인덱스 상태 읽는 중…",
|
|
2106
|
-
"card.index.rebuilding": "인덱스 재구축 중… {done}/{total} (재구축 중에도 이전 인덱스로 검색 가능)",
|
|
2107
|
-
"card.index.syncing": "인덱스 동기화 중…",
|
|
2108
|
-
"card.index.failures": "{count}개 세션 동기화 실패 (Host 로그 참조).",
|
|
2109
|
-
"card.index.rebuildError": "재구축 실패: {error}",
|
|
2110
|
-
"card.index.rebuild": "인덱스 재구축",
|
|
2111
|
-
"card.index.rebuilding.btn": "재구축 중…",
|
|
2112
|
-
"card.index.rebuild.hint": "비파괴적: 구축 중에도 이전 인덱스로 검색 가능하며, 완료 후 원자적 전환하고 이전 인덱스를 아카이브합니다.",
|
|
2113
|
-
"card.index.archivedOwner": "아카이브된 세션의 탐색 및 정리는 \"세션 관리자\"(dsh-manage-session)가 담당합니다. 본 플러그인은 아카이브 세트를 읽기 전용으로 참조하여 아카이브된 세션을 인덱스에서 제외합니다.",
|
|
2114
|
-
"card.index.archivedMissing": "아카이브된 세션의 탐색 및 정리에는 \"세션 관리자\"(dsh-manage-session)가 필요하지만 현재 미설치 상태입니다. 본 플러그인은 아카이브 세트를 읽기 전용으로 참조하여 아카이브된 세션을 인덱스에서 제외합니다.",
|
|
2115
|
-
"card.index.export": "스냅샷 내보내기",
|
|
2116
|
-
"card.index.import": "스냅샷 가져오기",
|
|
2117
|
-
"card.index.exported": "스냅샷이 다운로드되었습니다.",
|
|
2118
|
-
"card.index.import.started": "스냅샷 가져오기 시작: 백그라운드에서 인덱스를 재구축하며 완료 후 자동 전환됩니다.",
|
|
2119
|
-
"card.index.importParse": "스냅샷 파싱 실패 또는 유효한 세션 없음.",
|
|
2120
|
-
"card.action.failed": "작업 실패: {error}",
|
|
2121
|
-
"panel.titleSearch": "제목",
|
|
2122
|
-
"panel.contentSearch": "콘텐츠",
|
|
2123
|
-
"panel.searchTitle": "세션 제목 검색…",
|
|
2124
|
-
"panel.searchContent": "세션 콘텐츠 검색…",
|
|
2125
|
-
"panel.entry": "검색",
|
|
2126
|
-
"panel.buildIndex": "인덱스 구축",
|
|
2127
|
-
"panel.archived": "아카이브된 {count}개 세션 제외됨",
|
|
2128
|
-
"panel.rebuilding": "인덱스 재구축 중… {done}/{total} (재구축 중에도 이전 인덱스로 검색 가능)",
|
|
2129
|
-
"panel.unavailable": "독립 인덱스 서비스 사용 불가: Host 초기화 미완료.",
|
|
2130
|
-
"panel.notBuilt": "독립 인덱스 미구축: 구축하면 콘텐츠 검색이 활성화됩니다 (DSH 공식 전문 인덱스 불필요).",
|
|
2131
1752
|
"panel.loadingSessions": "세션 목록 읽는 중…",
|
|
2132
|
-
"panel.sessionsError": "세션 목록 읽기 실패: {error}",
|
|
2133
|
-
"panel.noSessions": "세션 없음",
|
|
2134
1753
|
"panel.noMatch": "일치하는 세션이 없습니다.",
|
|
2135
|
-
"panel.
|
|
2136
|
-
"panel.contentError": "콘텐츠 검색 실패: {error}",
|
|
2137
|
-
"panel.noContent": "일치하는 콘텐츠가 없습니다.",
|
|
2138
|
-
"panel.contentHint": "키워드를 입력하여 콘텐츠를 검색하세요.",
|
|
2139
|
-
"panel.noText": "(텍스트 없음)",
|
|
2140
|
-
"panel.delete": "삭제",
|
|
2141
|
-
"panel.delete.confirm": "삭제 확인",
|
|
2142
|
-
"panel.delete.hint": "이 세션 삭제(되돌릴 수 없음, 관리 화면과 동일한 경로)",
|
|
2143
|
-
"panel.delete.done": "세션 {n}개 삭제됨",
|
|
2144
|
-
"panel.delete.failed": "삭제 실패: {error}",
|
|
1754
|
+
"panel.noSessions": "세션 없음",
|
|
2145
1755
|
"panel.untitled": "(제목 없음)",
|
|
2146
|
-
"
|
|
2147
|
-
"
|
|
2148
|
-
"
|
|
2149
|
-
"
|
|
2150
|
-
"filter.user": "사용자",
|
|
2151
|
-
"filter.reply": "답변",
|
|
2152
|
-
"filter.tool": "도구",
|
|
2153
|
-
"sort.label": "결과 정렬",
|
|
2154
|
-
"sort.relevance": "관련도",
|
|
2155
|
-
"sort.time": "시간",
|
|
2156
|
-
"sort.relevance.hint": "일치 강도순 정렬(기본값).",
|
|
2157
|
-
"sort.time.hint": "세션 마지막 활동 시간 내림차순, 동률 시 일치 강도순.",
|
|
2158
|
-
"group.merge": "병합",
|
|
2159
|
-
"group.expand": "분할",
|
|
2160
|
-
"group.merge.hint": "세션별 최강 일치 1건만 표시(기본값).",
|
|
2161
|
-
"group.expand.hint": "같은 세션의 여러 일치를 각 행으로 표시(각 행이 개별 이동 대상).",
|
|
2162
|
-
"type.user/message": "사용자",
|
|
2163
|
-
"type.assistant/message": "답변",
|
|
2164
|
-
"type.tool/call": "도구 호출",
|
|
2165
|
-
"type.tool/result": "도구 결과"
|
|
1756
|
+
"star.on": "즐겨찾기됨",
|
|
1757
|
+
"star.off": "즐겨찾기 안 됨",
|
|
1758
|
+
"domain.active": "活跃",
|
|
1759
|
+
"domain.archived": "归档"
|
|
2166
1760
|
},
|
|
2167
1761
|
fr: {
|
|
2168
|
-
"card.title": "Index de recherche",
|
|
2169
|
-
"card.description": "Recherche de sessions dans la barre latérale avec bascule titre/contenu. La recherche de contenu utilise l'index indépendant du plugin et ne dépend jamais de l'index full-text officiel DSH.",
|
|
2170
|
-
"card.unavailable": "Espace de noms des paramètres indisponible : vérifiez que le plugin est assemblé dans le profil.",
|
|
2171
|
-
"card.readonly": "Lecture seule",
|
|
2172
|
-
"card.enabled": "Activer la recherche de sessions",
|
|
2173
|
-
"card.enabled.desc": "Afficher l'entrée « Recherche » en bas de la barre latérale.",
|
|
2174
|
-
"card.defaultMode": "Mode de recherche par défaut",
|
|
2175
|
-
"card.defaultMode.desc": "Mode d'ouverture du panneau.",
|
|
2176
|
-
"card.mode.title": "Titre",
|
|
2177
|
-
"card.mode.content": "Contenu",
|
|
2178
|
-
"card.autoSync": "Synchronisation automatique de l'index",
|
|
2179
|
-
"card.autoSync.desc": "Synchronisation incrémentale des journaux de session vers l'index indépendant en arrière-plan.",
|
|
2180
|
-
"card.syncInterval": "Intervalle de synchronisation (secondes)",
|
|
2181
|
-
"card.syncInterval.desc": "Intervalle minimum entre deux synchronisations incrémentales.",
|
|
2182
|
-
"card.archiveKeep": "Archives à conserver",
|
|
2183
|
-
"card.archiveKeep.desc": "Nombre de fichiers d'index archivés conservés à chaque reconstruction.",
|
|
2184
|
-
"card.index": "Index de recherche de contenu",
|
|
2185
|
-
"card.index.desc": "Index prêt : {indexed} sessions collectées.",
|
|
2186
|
-
"card.index.archives": ", {archives} archive(s).",
|
|
2187
|
-
"card.index.empty": "Index indépendant pas encore construit. Cliquez sur « Reconstruire l'index » pour le créer à partir des journaux de session.",
|
|
2188
|
-
"card.index.reading": "Lecture de l'état de l'index…",
|
|
2189
|
-
"card.index.rebuilding": "Reconstruction de l'index… {done}/{total} (l'ancien index reste actif pendant la reconstruction)",
|
|
2190
|
-
"card.index.syncing": "Synchronisation de l'index…",
|
|
2191
|
-
"card.index.failures": "{count} session(s) en échec de synchronisation (voir les journaux Host).",
|
|
2192
|
-
"card.index.rebuildError": "Échec de la reconstruction : {error}",
|
|
2193
|
-
"card.index.rebuild": "Reconstruire l'index",
|
|
2194
|
-
"card.index.rebuilding.btn": "Reconstruction…",
|
|
2195
|
-
"card.index.rebuild.hint": "Non destructif : l'ancien index reste actif pendant la construction ; l'échange est atomique et l'ancien index est archivé.",
|
|
2196
|
-
"card.index.archivedOwner": "La consultation et la suppression des sessions archivées relèvent de dsh-manage-session. Ce plugin lit uniquement l'ensemble archivé et exclut ces sessions de l'index.",
|
|
2197
|
-
"card.index.archivedMissing": "La consultation et la suppression des sessions archivées nécessitent dsh-manage-session, qui n'est pas installé. Ce plugin lit uniquement l'ensemble archivé et exclut ces sessions de l'index.",
|
|
2198
|
-
"card.index.export": "Exporter l'instantané",
|
|
2199
|
-
"card.index.import": "Importer l'instantané",
|
|
2200
|
-
"card.index.exported": "Instantané téléchargé.",
|
|
2201
|
-
"card.index.import.started": "Import démarré : l'index se reconstruit en arrière-plan et bascule une fois terminé.",
|
|
2202
|
-
"card.index.importParse": "Échec de l'analyse de l'instantané ou aucune session valide.",
|
|
2203
|
-
"card.action.failed": "Échec de l'opération : {error}",
|
|
2204
|
-
"panel.titleSearch": "Titre",
|
|
2205
|
-
"panel.contentSearch": "Contenu",
|
|
2206
|
-
"panel.searchTitle": "Rechercher dans les titres…",
|
|
2207
|
-
"panel.searchContent": "Rechercher dans le contenu…",
|
|
2208
|
-
"panel.entry": "Recherche",
|
|
2209
|
-
"panel.buildIndex": "Construire l'index",
|
|
2210
|
-
"panel.archived": "{count} session(s) archivée(s) exclue(s)",
|
|
2211
|
-
"panel.rebuilding": "Reconstruction de l'index… {done}/{total} (l'ancien index reste actif)",
|
|
2212
|
-
"panel.unavailable": "Service d'index indépendant indisponible : Host non initialisé.",
|
|
2213
|
-
"panel.notBuilt": "Index indépendant pas encore construit : construisez-le pour activer la recherche de contenu (aucun index FTS officiel requis).",
|
|
2214
1762
|
"panel.loadingSessions": "Chargement des sessions…",
|
|
2215
|
-
"panel.sessionsError": "Échec du chargement des sessions : {error}",
|
|
2216
|
-
"panel.noSessions": "Aucune session",
|
|
2217
1763
|
"panel.noMatch": "Aucune session correspondante.",
|
|
2218
|
-
"panel.
|
|
2219
|
-
"panel.contentError": "Échec de la recherche de contenu : {error}",
|
|
2220
|
-
"panel.noContent": "Aucun contenu correspondant.",
|
|
2221
|
-
"panel.contentHint": "Saisissez des mots-clés pour rechercher dans le contenu.",
|
|
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}",
|
|
1764
|
+
"panel.noSessions": "Aucune session",
|
|
2228
1765
|
"panel.untitled": "(sans titre)",
|
|
2229
|
-
"
|
|
2230
|
-
"
|
|
2231
|
-
"
|
|
2232
|
-
"
|
|
2233
|
-
"filter.user": "Utilisateur",
|
|
2234
|
-
"filter.reply": "Réponse",
|
|
2235
|
-
"filter.tool": "Outil",
|
|
2236
|
-
"sort.label": "Ordre des résultats",
|
|
2237
|
-
"sort.relevance": "Pertinence",
|
|
2238
|
-
"sort.time": "Temps",
|
|
2239
|
-
"sort.relevance.hint": "Trier par force de correspondance (par défaut).",
|
|
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).",
|
|
2245
|
-
"type.user/message": "Utilisateur",
|
|
2246
|
-
"type.assistant/message": "Réponse",
|
|
2247
|
-
"type.tool/call": "Appel d'outil",
|
|
2248
|
-
"type.tool/result": "Résultat d'outil"
|
|
1766
|
+
"star.on": "En favori",
|
|
1767
|
+
"star.off": "Pas en favori",
|
|
1768
|
+
"domain.active": "活跃",
|
|
1769
|
+
"domain.archived": "归档"
|
|
2249
1770
|
},
|
|
2250
1771
|
de: {
|
|
2251
|
-
"card.title": "Suchindex",
|
|
2252
|
-
"card.description": "Sidebar-Sitzungssuche mit Titel-/Inhaltsmodus-Umschaltung. Die Inhaltssuche verwendet den plugin-eigenen Index und hängt nie vom offiziellen DSH-Volltextindex ab.",
|
|
2253
|
-
"card.unavailable": "Einstellungs-Namensraum nicht verfügbar: Stellen Sie sicher, dass das Plugin im Profil zusammengestellt ist.",
|
|
2254
|
-
"card.readonly": "Schreibgeschützt",
|
|
2255
|
-
"card.enabled": "Sitzungssuche aktivieren",
|
|
2256
|
-
"card.enabled.desc": "Den „Suche\"-Eintrag am unteren Rand der Sidebar anzeigen.",
|
|
2257
|
-
"card.defaultMode": "Standardsuchmodus",
|
|
2258
|
-
"card.defaultMode.desc": "In welchem Modus das Panel geöffnet wird.",
|
|
2259
|
-
"card.mode.title": "Titel",
|
|
2260
|
-
"card.mode.content": "Inhalt",
|
|
2261
|
-
"card.autoSync": "Index automatisch synchronisieren",
|
|
2262
|
-
"card.autoSync.desc": "Sitzungsprotokolle inkrementell in den unabhängigen Index im Hintergrund synchronisieren.",
|
|
2263
|
-
"card.syncInterval": "Synchronisierungsintervall (Sekunden)",
|
|
2264
|
-
"card.syncInterval.desc": "Mindestintervall zwischen zwei inkrementellen Synchronisierungen.",
|
|
2265
|
-
"card.archiveKeep": "Aufzubewahrende Archive",
|
|
2266
|
-
"card.archiveKeep.desc": "Anzahl der archivierten Indexdateien, die jeder Neuaufbau beibehält.",
|
|
2267
|
-
"card.index": "Inhaltssuchindex",
|
|
2268
|
-
"card.index.desc": "Index bereit: {indexed} Sitzungen erfasst.",
|
|
2269
|
-
"card.index.archives": ", {archives} Archiv(e).",
|
|
2270
|
-
"card.index.empty": "Unabhängiger Index noch nicht aufgebaut. Klicken Sie auf „Index neu aufbauen\", um ihn aus Sitzungsprotokollen zu erstellen.",
|
|
2271
|
-
"card.index.reading": "Indexstatus wird gelesen…",
|
|
2272
|
-
"card.index.rebuilding": "Index wird neu aufgebaut… {done}/{total} (der alte Index bleibt während des Neuaufbaus aktiv)",
|
|
2273
|
-
"card.index.syncing": "Index wird synchronisiert…",
|
|
2274
|
-
"card.index.failures": "{count} Sitzung(en) konnten nicht synchronisiert werden (siehe Host-Protokolle).",
|
|
2275
|
-
"card.index.rebuildError": "Neuaufbau fehlgeschlagen: {error}",
|
|
2276
|
-
"card.index.rebuild": "Index neu aufbauen",
|
|
2277
|
-
"card.index.rebuilding.btn": "Wird aufgebaut…",
|
|
2278
|
-
"card.index.rebuild.hint": "Zerstörungsfrei: Der alte Index bleibt aktiv, während der Schatten-Index aufgebaut wird; der Tausch erfolgt atomar und der alte Index wird archiviert.",
|
|
2279
|
-
"card.index.archivedOwner": "Das Durchsuchen und Entsorgen archivierter Sitzungen gehört zu dsh-manage-session. Dieses Plugin liest nur das Archiv-Set und schließt diese Sitzungen aus dem Index aus.",
|
|
2280
|
-
"card.index.archivedMissing": "Das Durchsuchen und Entsorgen archivierter Sitzungen erfordert dsh-manage-session, das nicht installiert ist. Dieses Plugin liest nur das Archiv-Set und schließt diese Sitzungen aus dem Index aus.",
|
|
2281
|
-
"card.index.export": "Snapshot exportieren",
|
|
2282
|
-
"card.index.import": "Snapshot importieren",
|
|
2283
|
-
"card.index.exported": "Snapshot heruntergeladen.",
|
|
2284
|
-
"card.index.import.started": "Import gestartet: Der Index wird im Hintergrund neu aufgebaut und nach Fertigstellung atomar ausgetauscht.",
|
|
2285
|
-
"card.index.importParse": "Snapshot-Analyse fehlgeschlagen oder enthält keine gültige Sitzung.",
|
|
2286
|
-
"card.action.failed": "Aktion fehlgeschlagen: {error}",
|
|
2287
|
-
"panel.titleSearch": "Titel",
|
|
2288
|
-
"panel.contentSearch": "Inhalt",
|
|
2289
|
-
"panel.searchTitle": "Sitzungstitel durchsuchen…",
|
|
2290
|
-
"panel.searchContent": "Sitzungsinhalt durchsuchen…",
|
|
2291
|
-
"panel.entry": "Suche",
|
|
2292
|
-
"panel.buildIndex": "Index aufbauen",
|
|
2293
|
-
"panel.archived": "{count} archivierte Sitzung(en) ausgeschlossen",
|
|
2294
|
-
"panel.rebuilding": "Index wird neu aufgebaut… {done}/{total} (der alte Index bleibt aktiv)",
|
|
2295
|
-
"panel.unavailable": "Unabhängiger Indexdienst nicht verfügbar: Host nicht initialisiert.",
|
|
2296
|
-
"panel.notBuilt": "Unabhängiger Index noch nicht aufgebaut: Bauen Sie ihn auf, um die Inhaltssuche zu aktivieren (kein offizieller FTS-Index erforderlich).",
|
|
2297
1772
|
"panel.loadingSessions": "Sitzungen werden geladen…",
|
|
2298
|
-
"panel.sessionsError": "Sitzungen konnten nicht geladen werden: {error}",
|
|
2299
|
-
"panel.noSessions": "Keine Sitzungen",
|
|
2300
1773
|
"panel.noMatch": "Keine passenden Sitzungen.",
|
|
2301
|
-
"panel.
|
|
2302
|
-
"panel.contentError": "Inhaltssuche fehlgeschlagen: {error}",
|
|
2303
|
-
"panel.noContent": "Kein passender Inhalt.",
|
|
2304
|
-
"panel.contentHint": "Geben Sie Stichwörter ein, um Inhalte zu durchsuchen.",
|
|
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}",
|
|
1774
|
+
"panel.noSessions": "Keine Sitzungen",
|
|
2311
1775
|
"panel.untitled": "(unbenannt)",
|
|
2312
|
-
"
|
|
2313
|
-
"
|
|
2314
|
-
"
|
|
2315
|
-
"
|
|
2316
|
-
"filter.user": "Benutzer",
|
|
2317
|
-
"filter.reply": "Antwort",
|
|
2318
|
-
"filter.tool": "Werkzeug",
|
|
2319
|
-
"sort.label": "Ergebnissortierung",
|
|
2320
|
-
"sort.relevance": "Relevanz",
|
|
2321
|
-
"sort.time": "Zeit",
|
|
2322
|
-
"sort.relevance.hint": "Nach Übereinstimmungsstärke sortieren (Standard).",
|
|
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).",
|
|
2328
|
-
"type.user/message": "Benutzer",
|
|
2329
|
-
"type.assistant/message": "Antwort",
|
|
2330
|
-
"type.tool/call": "Werkzeugaufruf",
|
|
2331
|
-
"type.tool/result": "Werkzeugergebnis"
|
|
1776
|
+
"star.on": "Favorit",
|
|
1777
|
+
"star.off": "Kein Favorit",
|
|
1778
|
+
"domain.active": "活跃",
|
|
1779
|
+
"domain.archived": "归档"
|
|
2332
1780
|
},
|
|
2333
1781
|
it: {
|
|
2334
|
-
"card.title": "Indice di ricerca",
|
|
2335
|
-
"card.description": "Ricerca sessioni nella barra laterale con commutazione titolo/contenuto. La ricerca per contenuto utilizza l'indice indipendente del plugin e non dipende mai dall'indice full-text ufficiale DSH.",
|
|
2336
|
-
"card.unavailable": "Namespace delle impostazioni non disponibile: verificare che il plugin sia assemblato nel profilo.",
|
|
2337
|
-
"card.readonly": "Sola lettura",
|
|
2338
|
-
"card.enabled": "Abilita ricerca sessioni",
|
|
2339
|
-
"card.enabled.desc": "Mostra la voce «Ricerca» in fondo alla barra laterale.",
|
|
2340
|
-
"card.defaultMode": "Modalità di ricerca predefinita",
|
|
2341
|
-
"card.defaultMode.desc": "Modalità di apertura del pannello.",
|
|
2342
|
-
"card.mode.title": "Titolo",
|
|
2343
|
-
"card.mode.content": "Contenuto",
|
|
2344
|
-
"card.autoSync": "Sincronizzazione automatica indice",
|
|
2345
|
-
"card.autoSync.desc": "Sincronizza incrementalmente i log delle sessioni nell'indice indipendente in background.",
|
|
2346
|
-
"card.syncInterval": "Intervallo sincronizzazione (secondi)",
|
|
2347
|
-
"card.syncInterval.desc": "Intervallo minimo tra due sincronizzazioni incrementali.",
|
|
2348
|
-
"card.archiveKeep": "Archivi da conservare",
|
|
2349
|
-
"card.archiveKeep.desc": "Numero di file indice archiviati conservati ad ogni ricostruzione.",
|
|
2350
|
-
"card.index": "Indice ricerca contenuti",
|
|
2351
|
-
"card.index.desc": "Indice pronto: {indexed} sessioni raccolte.",
|
|
2352
|
-
"card.index.archives": ", {archives} archivio/i.",
|
|
2353
|
-
"card.index.empty": "Indice indipendente non ancora costruito. Fare clic su «Ricostruisci indice» per crearlo dai log delle sessioni.",
|
|
2354
|
-
"card.index.reading": "Lettura stato indice…",
|
|
2355
|
-
"card.index.rebuilding": "Ricostruzione indice… {done}/{total} (il vecchio indice resta attivo durante la ricostruzione)",
|
|
2356
|
-
"card.index.syncing": "Sincronizzazione indice…",
|
|
2357
|
-
"card.index.failures": "{count} sessione/i non sincronizzata/e (vedi log Host).",
|
|
2358
|
-
"card.index.rebuildError": "Ricostruzione fallita: {error}",
|
|
2359
|
-
"card.index.rebuild": "Ricostruisci indice",
|
|
2360
|
-
"card.index.rebuilding.btn": "Ricostruzione…",
|
|
2361
|
-
"card.index.rebuild.hint": "Non distruttivo: il vecchio indice resta attivo durante la costruzione; lo scambio è atomico e il vecchio indice viene archiviato.",
|
|
2362
|
-
"card.index.archivedOwner": "La consultazione e l'eliminazione delle sessioni archiviate spettano a dsh-manage-session. Questo plugin legge solo l'insieme archiviato ed esclude tali sessioni dall'indice.",
|
|
2363
|
-
"card.index.archivedMissing": "La consultazione e l'eliminazione delle sessioni archiviate richiedono dsh-manage-session, attualmente non installato. Questo plugin legge solo l'insieme archiviato ed esclude tali sessioni dall'indice.",
|
|
2364
|
-
"card.index.export": "Esporta snapshot",
|
|
2365
|
-
"card.index.import": "Importa snapshot",
|
|
2366
|
-
"card.index.exported": "Snapshot scaricato.",
|
|
2367
|
-
"card.index.import.started": "Importazione avviata: l'indice viene ricostruito in background e scambiato al termine.",
|
|
2368
|
-
"card.index.importParse": "Analisi snapshot fallita o nessuna sessione valida.",
|
|
2369
|
-
"card.action.failed": "Operazione fallita: {error}",
|
|
2370
|
-
"panel.titleSearch": "Titolo",
|
|
2371
|
-
"panel.contentSearch": "Contenuto",
|
|
2372
|
-
"panel.searchTitle": "Cerca nei titoli delle sessioni…",
|
|
2373
|
-
"panel.searchContent": "Cerca nel contenuto delle sessioni…",
|
|
2374
|
-
"panel.entry": "Ricerca",
|
|
2375
|
-
"panel.buildIndex": "Costruisci indice",
|
|
2376
|
-
"panel.archived": "{count} sessione/i archiviata/e esclusa/e",
|
|
2377
|
-
"panel.rebuilding": "Ricostruzione indice… {done}/{total} (il vecchio indice resta attivo)",
|
|
2378
|
-
"panel.unavailable": "Servizio indice indipendente non disponibile: Host non inizializzato.",
|
|
2379
|
-
"panel.notBuilt": "Indice indipendente non ancora costruito: costruiscilo per abilitare la ricerca per contenuto (nessun indice FTS ufficiale richiesto).",
|
|
2380
1782
|
"panel.loadingSessions": "Caricamento sessioni…",
|
|
2381
|
-
"panel.sessionsError": "Caricamento sessioni fallito: {error}",
|
|
2382
|
-
"panel.noSessions": "Nessuna sessione",
|
|
2383
1783
|
"panel.noMatch": "Nessuna sessione corrispondente.",
|
|
2384
|
-
"panel.
|
|
2385
|
-
"panel.contentError": "Ricerca contenuto fallita: {error}",
|
|
2386
|
-
"panel.noContent": "Nessun contenuto corrispondente.",
|
|
2387
|
-
"panel.contentHint": "Inserisci parole chiave per cercare nel contenuto.",
|
|
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}",
|
|
1784
|
+
"panel.noSessions": "Nessuna sessione",
|
|
2394
1785
|
"panel.untitled": "(senza titolo)",
|
|
2395
|
-
"
|
|
2396
|
-
"
|
|
2397
|
-
"
|
|
2398
|
-
"
|
|
2399
|
-
"filter.user": "Utente",
|
|
2400
|
-
"filter.reply": "Risposta",
|
|
2401
|
-
"filter.tool": "Strumento",
|
|
2402
|
-
"sort.label": "Ordinamento risultati",
|
|
2403
|
-
"sort.relevance": "Pertinenza",
|
|
2404
|
-
"sort.time": "Tempo",
|
|
2405
|
-
"sort.relevance.hint": "Ordina per forza di corrispondenza (predefinito).",
|
|
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).",
|
|
2411
|
-
"type.user/message": "Utente",
|
|
2412
|
-
"type.assistant/message": "Risposta",
|
|
2413
|
-
"type.tool/call": "Chiamata strumento",
|
|
2414
|
-
"type.tool/result": "Risultato strumento"
|
|
1786
|
+
"star.on": "Nei preferiti",
|
|
1787
|
+
"star.off": "Non nei preferiti",
|
|
1788
|
+
"domain.active": "活跃",
|
|
1789
|
+
"domain.archived": "归档"
|
|
2415
1790
|
},
|
|
2416
1791
|
ru: {
|
|
2417
|
-
"card.title": "Поисковый индекс",
|
|
2418
|
-
"card.description": "Поиск сессий в боковой панели с переключением «заголовки/содержимое». Поиск по содержимому использует собственный индекс плагина и не зависит от официального полнотекстового индекса DSH.",
|
|
2419
|
-
"card.unavailable": "Пространство имён настроек недоступно: убедитесь, что плагин собран в профиле.",
|
|
2420
|
-
"card.readonly": "Только чтение",
|
|
2421
|
-
"card.enabled": "Включить поиск сессий",
|
|
2422
|
-
"card.enabled.desc": "Показывать пункт «Поиск» в нижней части боковой панели.",
|
|
2423
|
-
"card.defaultMode": "Режим поиска по умолчанию",
|
|
2424
|
-
"card.defaultMode.desc": "Режим открытия панели.",
|
|
2425
|
-
"card.mode.title": "Заголовок",
|
|
2426
|
-
"card.mode.content": "Содержимое",
|
|
2427
|
-
"card.autoSync": "Автосинхронизация индекса",
|
|
2428
|
-
"card.autoSync.desc": "Инкрементальная синхронизация журналов сессий в независимый индекс в фоновом режиме.",
|
|
2429
|
-
"card.syncInterval": "Интервал синхронизации (сек)",
|
|
2430
|
-
"card.syncInterval.desc": "Минимальный интервал между двумя инкрементальными синхронизациями.",
|
|
2431
|
-
"card.archiveKeep": "Хранимых архивов",
|
|
2432
|
-
"card.archiveKeep.desc": "Сколько архивных файлов индекса сохранять при каждой пересборке.",
|
|
2433
|
-
"card.index": "Индекс поиска по содержимому",
|
|
2434
|
-
"card.index.desc": "Индекс готов: собрано {indexed} сессий.",
|
|
2435
|
-
"card.index.archives": ", архивов: {archives}.",
|
|
2436
|
-
"card.index.empty": "Независимый индекс ещё не построен. Нажмите «Пересобрать индекс», чтобы создать его из журналов сессий.",
|
|
2437
|
-
"card.index.reading": "Чтение состояния индекса…",
|
|
2438
|
-
"card.index.rebuilding": "Пересборка индекса… {done}/{total} (старый индекс остаётся доступным во время пересборки)",
|
|
2439
|
-
"card.index.syncing": "Синхронизация индекса…",
|
|
2440
|
-
"card.index.failures": "{count} сессий не синхронизировано (см. журналы Host).",
|
|
2441
|
-
"card.index.rebuildError": "Пересборка не удалась: {error}",
|
|
2442
|
-
"card.index.rebuild": "Пересобрать индекс",
|
|
2443
|
-
"card.index.rebuilding.btn": "Пересборка…",
|
|
2444
|
-
"card.index.rebuild.hint": "Недеструктивно: старый индекс остаётся доступным, пока строится теневой; замена атомарна, старый индекс архивируется.",
|
|
2445
|
-
"card.index.archivedOwner": "Просмотр и удаление архивных сессий — задача dsh-manage-session. Этот плагин только читает набор архива и исключает архивные сессии из индекса.",
|
|
2446
|
-
"card.index.archivedMissing": "Для просмотра и удаления архивных сессий требуется dsh-manage-session, который не установлен. Этот плагин только читает набор архива и исключает архивные сессии из индекса.",
|
|
2447
|
-
"card.index.export": "Экспорт снимка",
|
|
2448
|
-
"card.index.import": "Импорт снимка",
|
|
2449
|
-
"card.index.exported": "Снимок скачан.",
|
|
2450
|
-
"card.index.import.started": "Импорт начат: индекс пересобирается в фоне и переключается по завершении.",
|
|
2451
|
-
"card.index.importParse": "Ошибка разбора снимка или нет допустимых сессий.",
|
|
2452
|
-
"card.action.failed": "Действие не выполнено: {error}",
|
|
2453
|
-
"panel.titleSearch": "Заголовок",
|
|
2454
|
-
"panel.contentSearch": "Содержимое",
|
|
2455
|
-
"panel.searchTitle": "Поиск по заголовкам сессий…",
|
|
2456
|
-
"panel.searchContent": "Поиск по содержимому сессий…",
|
|
2457
|
-
"panel.entry": "Поиск",
|
|
2458
|
-
"panel.buildIndex": "Построить индекс",
|
|
2459
|
-
"panel.archived": "Исключено {count} архивных сессий",
|
|
2460
|
-
"panel.rebuilding": "Пересборка индекса… {done}/{total} (старый индекс остаётся доступным)",
|
|
2461
|
-
"panel.unavailable": "Сервис независимого индекса недоступен: Host не инициализирован.",
|
|
2462
|
-
"panel.notBuilt": "Независимый индекс не построен: постройте его, чтобы включить поиск по содержимому (официальный FTS-индекс не требуется).",
|
|
2463
1792
|
"panel.loadingSessions": "Загрузка сессий…",
|
|
2464
|
-
"panel.sessionsError": "Не удалось загрузить сессии: {error}",
|
|
2465
|
-
"panel.noSessions": "Нет сессий",
|
|
2466
1793
|
"panel.noMatch": "Нет подходящих сессий.",
|
|
2467
|
-
"panel.
|
|
2468
|
-
"panel.contentError": "Поиск по содержимому не удался: {error}",
|
|
2469
|
-
"panel.noContent": "Нет подходящего содержимого.",
|
|
2470
|
-
"panel.contentHint": "Введите ключевые слова для поиска по содержимому.",
|
|
2471
|
-
"panel.noText": "(без текста)",
|
|
2472
|
-
"panel.delete": "Удалить",
|
|
2473
|
-
"panel.delete.confirm": "Подтвердить удаление",
|
|
2474
|
-
"panel.delete.hint": "Удалить эту сессию (необратимо, тот же конвейер, что и в администраторе)",
|
|
2475
|
-
"panel.delete.done": "Удалено сессий: {n}",
|
|
2476
|
-
"panel.delete.failed": "Ошибка удаления: {error}",
|
|
1794
|
+
"panel.noSessions": "Нет сессий",
|
|
2477
1795
|
"panel.untitled": "(без названия)",
|
|
2478
|
-
"
|
|
2479
|
-
"
|
|
2480
|
-
"
|
|
2481
|
-
"
|
|
2482
|
-
"filter.user": "Пользователь",
|
|
2483
|
-
"filter.reply": "Ответ",
|
|
2484
|
-
"filter.tool": "Инструмент",
|
|
2485
|
-
"sort.label": "Сортировка результатов",
|
|
2486
|
-
"sort.relevance": "Релевантность",
|
|
2487
|
-
"sort.time": "Время",
|
|
2488
|
-
"sort.relevance.hint": "Сортировать по силе совпадения (по умолчанию).",
|
|
2489
|
-
"sort.time.hint": "Сортировать по последней активности, сначала новые; при равенстве — по силе совпадения.",
|
|
2490
|
-
"group.merge": "Объединить",
|
|
2491
|
-
"group.expand": "Развернуть",
|
|
2492
|
-
"group.merge.hint": "По одному сильнейшему совпадению на сессию (по умолчанию).",
|
|
2493
|
-
"group.expand.hint": "Каждое совпадение сессии — отдельной строкой (каждая строка — цель перехода).",
|
|
2494
|
-
"type.user/message": "Пользователь",
|
|
2495
|
-
"type.assistant/message": "Ответ",
|
|
2496
|
-
"type.tool/call": "Вызов инструмента",
|
|
2497
|
-
"type.tool/result": "Результат инструмента"
|
|
1796
|
+
"star.on": "В избранном",
|
|
1797
|
+
"star.off": "Не в избранном",
|
|
1798
|
+
"domain.active": "活跃",
|
|
1799
|
+
"domain.archived": "归档"
|
|
2498
1800
|
},
|
|
2499
1801
|
es: {
|
|
2500
|
-
"card.title": "Índice de búsqueda",
|
|
2501
|
-
"card.description": "Búsqueda de sesiones en la barra lateral con cambio entre título y contenido. La búsqueda de contenido usa el índice propio del plugin y nunca depende del índice de texto completo oficial de DSH.",
|
|
2502
|
-
"card.unavailable": "Espacio de nombres de configuración no disponible: verifique que el plugin esté ensamblado en el perfil.",
|
|
2503
|
-
"card.readonly": "Solo lectura",
|
|
2504
|
-
"card.enabled": "Habilitar búsqueda de sesiones",
|
|
2505
|
-
"card.enabled.desc": "Mostrar la entrada «Buscar» en la parte inferior de la barra lateral.",
|
|
2506
|
-
"card.defaultMode": "Modo de búsqueda predeterminado",
|
|
2507
|
-
"card.defaultMode.desc": "Modo en que se abre el panel.",
|
|
2508
|
-
"card.mode.title": "Título",
|
|
2509
|
-
"card.mode.content": "Contenido",
|
|
2510
|
-
"card.autoSync": "Sincronización automática del índice",
|
|
2511
|
-
"card.autoSync.desc": "Sincroniza incrementalmente los registros de sesión al índice independiente en segundo plano.",
|
|
2512
|
-
"card.syncInterval": "Intervalo de sincronización (segundos)",
|
|
2513
|
-
"card.syncInterval.desc": "Intervalo mínimo entre dos sincronizaciones incrementales.",
|
|
2514
|
-
"card.archiveKeep": "Archivos a conservar",
|
|
2515
|
-
"card.archiveKeep.desc": "Cuántos archivos de índice archivados se conservan en cada reconstrucción.",
|
|
2516
|
-
"card.index": "Índice de búsqueda de contenido",
|
|
2517
|
-
"card.index.desc": "Índice listo: {indexed} sesiones recopiladas.",
|
|
2518
|
-
"card.index.archives": ", {archives} archivo(s) de respaldo.",
|
|
2519
|
-
"card.index.empty": "El índice independiente aún no está construido. Haga clic en «Reconstruir índice» para crearlo desde los registros de sesión.",
|
|
2520
|
-
"card.index.reading": "Leyendo estado del índice…",
|
|
2521
|
-
"card.index.rebuilding": "Reconstruyendo índice… {done}/{total} (el índice anterior sigue activo durante la reconstrucción)",
|
|
2522
|
-
"card.index.syncing": "Sincronizando índice…",
|
|
2523
|
-
"card.index.failures": "{count} sesión(es) fallaron al sincronizar (ver registros del Host).",
|
|
2524
|
-
"card.index.rebuildError": "Reconstrucción fallida: {error}",
|
|
2525
|
-
"card.index.rebuild": "Reconstruir índice",
|
|
2526
|
-
"card.index.rebuilding.btn": "Reconstruyendo…",
|
|
2527
|
-
"card.index.rebuild.hint": "No destructivo: el índice anterior sigue activo mientras se construye el nuevo; el intercambio es atómico y el índice anterior se archiva.",
|
|
2528
|
-
"card.index.archivedOwner": "La consulta y eliminación de sesiones archivadas corresponde a dsh-manage-session. Este plugin solo lee el conjunto archivado y excluye esas sesiones del índice.",
|
|
2529
|
-
"card.index.archivedMissing": "La consulta y eliminación de sesiones archivadas requiere dsh-manage-session, que no está instalado. Este plugin solo lee el conjunto archivado y excluye esas sesiones del índice.",
|
|
2530
|
-
"card.index.export": "Exportar instantánea",
|
|
2531
|
-
"card.index.import": "Importar instantánea",
|
|
2532
|
-
"card.index.exported": "Instantánea descargada.",
|
|
2533
|
-
"card.index.import.started": "Importación iniciada: el índice se reconstruye en segundo plano y se intercambia al finalizar.",
|
|
2534
|
-
"card.index.importParse": "Error al analizar la instantánea o sin sesiones válidas.",
|
|
2535
|
-
"card.action.failed": "Acción fallida: {error}",
|
|
2536
|
-
"panel.titleSearch": "Título",
|
|
2537
|
-
"panel.contentSearch": "Contenido",
|
|
2538
|
-
"panel.searchTitle": "Buscar títulos de sesión…",
|
|
2539
|
-
"panel.searchContent": "Buscar contenido de sesión…",
|
|
2540
|
-
"panel.entry": "Buscar",
|
|
2541
|
-
"panel.buildIndex": "Construir índice",
|
|
2542
|
-
"panel.archived": "{count} sesión(es) archivada(s) excluida(s)",
|
|
2543
|
-
"panel.rebuilding": "Reconstruyendo índice… {done}/{total} (el índice anterior sigue activo)",
|
|
2544
|
-
"panel.unavailable": "Servicio de índice independiente no disponible: Host no inicializado.",
|
|
2545
|
-
"panel.notBuilt": "Índice independiente no construido: constrúyalo para habilitar la búsqueda de contenido (no se requiere índice FTS oficial).",
|
|
2546
1802
|
"panel.loadingSessions": "Cargando sesiones…",
|
|
2547
|
-
"panel.sessionsError": "Error al cargar sesiones: {error}",
|
|
2548
|
-
"panel.noSessions": "Sin sesiones",
|
|
2549
1803
|
"panel.noMatch": "Sin sesiones coincidentes.",
|
|
2550
|
-
"panel.
|
|
2551
|
-
"panel.contentError": "Búsqueda de contenido fallida: {error}",
|
|
2552
|
-
"panel.noContent": "Sin contenido coincidente.",
|
|
2553
|
-
"panel.contentHint": "Escriba palabras clave para buscar contenido.",
|
|
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}",
|
|
1804
|
+
"panel.noSessions": "Sin sesiones",
|
|
2560
1805
|
"panel.untitled": "(sin título)",
|
|
2561
|
-
"
|
|
2562
|
-
"
|
|
2563
|
-
"
|
|
2564
|
-
"
|
|
2565
|
-
"filter.user": "Usuario",
|
|
2566
|
-
"filter.reply": "Respuesta",
|
|
2567
|
-
"filter.tool": "Herramienta",
|
|
2568
|
-
"sort.label": "Orden de resultados",
|
|
2569
|
-
"sort.relevance": "Relevancia",
|
|
2570
|
-
"sort.time": "Tiempo",
|
|
2571
|
-
"sort.relevance.hint": "Ordenar por fuerza de coincidencia (predeterminado).",
|
|
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).",
|
|
2577
|
-
"type.user/message": "Usuario",
|
|
2578
|
-
"type.assistant/message": "Respuesta",
|
|
2579
|
-
"type.tool/call": "Llamada de herramienta",
|
|
2580
|
-
"type.tool/result": "Resultado de herramienta"
|
|
1806
|
+
"star.on": "En favoritos",
|
|
1807
|
+
"star.off": "No en favoritos",
|
|
1808
|
+
"domain.active": "活跃",
|
|
1809
|
+
"domain.archived": "归档"
|
|
2581
1810
|
}
|
|
2582
1811
|
};
|
|
2583
1812
|
/** Translate with {param} interpolation; falls back to zh, then the key itself. */
|
|
@@ -2588,8 +1817,7 @@ window.__ModuleLoader__.load({
|
|
|
2588
1817
|
if (params === void 0) return template;
|
|
2589
1818
|
return template.replace(/\{(\w+)\}/gu, (_, name) => String(params[name] ?? `{${name}}`));
|
|
2590
1819
|
}
|
|
2591
|
-
|
|
2592
|
-
//#region src/client/search/manage.tsx
|
|
1820
|
+
// src/client/manage.tsx
|
|
2593
1821
|
/**
|
|
2594
1822
|
* 批量管理台(workspace 视角)—— 管家面板「管理」页签的主体。
|
|
2595
1823
|
*
|
|
@@ -2601,7 +1829,7 @@ window.__ModuleLoader__.load({
|
|
|
2601
1829
|
* 动作面:session-history-archive / prune / purge(写官方存储文件,重启后
|
|
2602
1830
|
* 侧栏完全生效——宿主半在成功路径上已同步联动索引)。
|
|
2603
1831
|
*/
|
|
2604
|
-
/** 样式(幂等注入;类名沿用
|
|
1832
|
+
/** 样式(幂等注入;类名沿用 dmss_ 前缀,与搜索面板共享视觉语言)。 */
|
|
2605
1833
|
function injectManageStyles() {
|
|
2606
1834
|
if (typeof document === "undefined") return () => {};
|
|
2607
1835
|
if (document.querySelector("style[data-plugin-css=\"dsh-manage-session/manage\"]") !== null) return () => {};
|
|
@@ -2609,24 +1837,44 @@ window.__ModuleLoader__.load({
|
|
|
2609
1837
|
tag.dataset.plugin = "dsh-manage-session";
|
|
2610
1838
|
tag.dataset.pluginCss = "dsh-manage-session/manage";
|
|
2611
1839
|
tag.textContent = `
|
|
2612
|
-
.
|
|
2613
|
-
.
|
|
2614
|
-
.
|
|
2615
|
-
.
|
|
2616
|
-
.
|
|
2617
|
-
.
|
|
2618
|
-
|
|
2619
|
-
.
|
|
2620
|
-
.
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
.
|
|
2624
|
-
.
|
|
2625
|
-
.
|
|
2626
|
-
.
|
|
2627
|
-
.
|
|
2628
|
-
.
|
|
2629
|
-
.
|
|
1840
|
+
.dmss_manageRoot{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:0}
|
|
1841
|
+
.dmss_manageList{flex:1 1 auto;min-height:0;overflow-y:auto;margin:8px 0 0;padding:0 6px 10px}
|
|
1842
|
+
.dmss_group{margin-bottom:10px}
|
|
1843
|
+
.dmss_groupHead{position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:8px;padding:6px 8px;background:var(--dsw-specific-tip);border-bottom:1px solid var(--dsw-alias-border-l2);cursor:pointer;user-select:none}
|
|
1844
|
+
.dmss_groupTitle{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary)}
|
|
1845
|
+
.dmss_groupCount{flex:none;color:var(--dsw-alias-label-caption);font-size:11px;font-variant-numeric:tabular-nums}
|
|
1846
|
+
/* 会话卡片网格:一行三张(1/3 宽),窄屏降两列 / 单列。 */
|
|
1847
|
+
.dmss_grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;padding:8px}
|
|
1848
|
+
@media (max-width:1180px){.dmss_grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
1849
|
+
@media (max-width:760px){.dmss_grid{grid-template-columns:minmax(0,1fr)}}
|
|
1850
|
+
/* 卡片本体:标题行 + 工作区元信息两行,整块可点(打开会话)。 */
|
|
1851
|
+
.dmss_row{box-sizing:border-box;display:flex;flex-direction:column;align-items:stretch;gap:4px;min-width:0;height:auto;margin:0;padding:9px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3,rgba(127,127,127,.05));color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;transition:border-color .14s ease-out,background-color .14s ease-out}
|
|
1852
|
+
.dmss_row:hover{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
1853
|
+
.dmss_rowTitle{display:flex;align-items:center;gap:6px;min-width:0;width:100%}
|
|
1854
|
+
.dmss_titleText{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:18px;font-weight:500}
|
|
1855
|
+
.dmss_tag{flex:none;color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
1856
|
+
.dmss_meta{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
1857
|
+
.dmss_check{flex:none;display:inline-flex;align-items:center}
|
|
1858
|
+
.dmss_check input{width:13px;height:13px;accent-color:var(--dsw-alias-state-business-primary);cursor:pointer}
|
|
1859
|
+
.dmss_tagArch{flex:none;color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-caption));font-size:10px;line-height:16px;border:1px solid currentColor;border-radius:999px;padding:0 6px;white-space:nowrap}
|
|
1860
|
+
.dmss_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)}
|
|
1861
|
+
.dmss_batchInfo{flex:1 1 auto;min-width:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
1862
|
+
.dmss_actBtn{height:26px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;padding:0 10px;font-size:12px;line-height:24px;white-space:nowrap}
|
|
1863
|
+
.dmss_actBtn:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1864
|
+
.dmss_actBtn:disabled{cursor:not-allowed;opacity:.5}
|
|
1865
|
+
.dmss_btnDanger{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
1866
|
+
.dmss_btnDangerSolid{background:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);color:#fff;font-weight:600}
|
|
1867
|
+
.dmss_btnDangerSolid:hover{background:var(--dsw-alias-state-error-primary);filter:brightness(1.08);color:#fff}
|
|
1868
|
+
.dmss_manageHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;padding:8px 10px 0}
|
|
1869
|
+
.dmss_chips{display:flex;align-items:center;gap:6px;padding:8px 10px 0;flex:none;flex-wrap:wrap}
|
|
1870
|
+
.dmss_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}
|
|
1871
|
+
.dmss_chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
1872
|
+
.dmss_chipActive{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary)}
|
|
1873
|
+
.dmss_search{height:24px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 8px;font-size:12px;line-height:22px}
|
|
1874
|
+
.dmss_search:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}
|
|
1875
|
+
.dmss_status{color:var(--dsw-alias-label-tertiary);padding:10px 8px 8px;font-size:12px;line-height:18px}
|
|
1876
|
+
.dmss_empty{color:var(--dsw-alias-label-tertiary);padding:10px 8px 8px;font-size:12px;line-height:18px}
|
|
1877
|
+
.dmss_error{color:var(--dsw-alias-state-error-primary);padding:8px;font-size:12px;line-height:18px}
|
|
2630
1878
|
`;
|
|
2631
1879
|
document.head.appendChild(tag);
|
|
2632
1880
|
return () => {
|
|
@@ -2678,7 +1926,7 @@ window.__ModuleLoader__.load({
|
|
|
2678
1926
|
(0, react.useEffect)(() => {
|
|
2679
1927
|
if (sessions !== null) return;
|
|
2680
1928
|
let cancelled = false;
|
|
2681
|
-
|
|
1929
|
+
callStewardItems("session-catalog", {}).then((res) => {
|
|
2682
1930
|
if (cancelled) return;
|
|
2683
1931
|
if (res.ok) {
|
|
2684
1932
|
setSessions(res.items);
|
|
@@ -2820,14 +2068,14 @@ window.__ModuleLoader__.load({
|
|
|
2820
2068
|
(0, react.useEffect)(() => {
|
|
2821
2069
|
injectManageStyles();
|
|
2822
2070
|
}, []);
|
|
2823
|
-
if (sessionsError !== null) return (0, react.createElement)("div", { className: "
|
|
2824
|
-
if (sessions === null) return (0, react.createElement)("div", { className: "
|
|
2825
|
-
if (sessions !== null && manageGroups.length === 0 && (workspaceFilter !== "" || textFilter.trim() !== "" || domain !== "all" || favoritesOnly)) return (0, react.createElement)("div", { className: "
|
|
2826
|
-
if (manageGroups.length === 0) return (0, react.createElement)("div", { className: "
|
|
2071
|
+
if (sessionsError !== null) return (0, react.createElement)("div", { className: "dmss_error" }, sessionsError);
|
|
2072
|
+
if (sessions === null) return (0, react.createElement)("div", { className: "dmss_status" }, translate$1(t, "panel.loadingSessions"));
|
|
2073
|
+
if (sessions !== null && manageGroups.length === 0 && (workspaceFilter !== "" || textFilter.trim() !== "" || domain !== "all" || favoritesOnly)) return (0, react.createElement)("div", { className: "dmss_empty" }, translate$1(t, "panel.noMatch"));
|
|
2074
|
+
if (manageGroups.length === 0) return (0, react.createElement)("div", { className: "dmss_empty" }, translate$1(t, "panel.noSessions"));
|
|
2827
2075
|
const children = [
|
|
2828
2076
|
(0, react.createElement)("input", {
|
|
2829
2077
|
key: "search",
|
|
2830
|
-
className: "
|
|
2078
|
+
className: "dmss_search",
|
|
2831
2079
|
style: {
|
|
2832
2080
|
margin: "8px 10px 0",
|
|
2833
2081
|
flex: "none"
|
|
@@ -2841,7 +2089,7 @@ window.__ModuleLoader__.load({
|
|
|
2841
2089
|
}),
|
|
2842
2090
|
(0, react.createElement)("div", {
|
|
2843
2091
|
key: "wsChips",
|
|
2844
|
-
className: "
|
|
2092
|
+
className: "dmss_chips",
|
|
2845
2093
|
role: "group",
|
|
2846
2094
|
"aria-label": translate$1(t, "workspace.label")
|
|
2847
2095
|
}, [
|
|
@@ -2856,7 +2104,7 @@ window.__ModuleLoader__.load({
|
|
|
2856
2104
|
(0, react.createElement)("button", {
|
|
2857
2105
|
key: "all",
|
|
2858
2106
|
type: "button",
|
|
2859
|
-
className: `
|
|
2107
|
+
className: `dmss_chip${workspaceFilter === "" ? " dmss_chipActive" : ""}`,
|
|
2860
2108
|
"aria-pressed": workspaceFilter === "",
|
|
2861
2109
|
onClick: () => {
|
|
2862
2110
|
setWorkspaceFilter("");
|
|
@@ -2865,7 +2113,7 @@ window.__ModuleLoader__.load({
|
|
|
2865
2113
|
...visibleChips.chips.map((chip) => (0, react.createElement)("button", {
|
|
2866
2114
|
key: chip.cwd === "" ? "(nocwd)" : chip.cwd,
|
|
2867
2115
|
type: "button",
|
|
2868
|
-
className: `
|
|
2116
|
+
className: `dmss_chip${workspaceFilter === chip.cwd ? " dmss_chipActive" : ""}`,
|
|
2869
2117
|
"aria-pressed": workspaceFilter === chip.cwd,
|
|
2870
2118
|
onClick: () => {
|
|
2871
2119
|
setWorkspaceFilter((current) => current === chip.cwd ? "" : chip.cwd);
|
|
@@ -2874,7 +2122,7 @@ window.__ModuleLoader__.load({
|
|
|
2874
2122
|
visibleChips.hidden > 0 && (0, react.createElement)("button", {
|
|
2875
2123
|
key: "more",
|
|
2876
2124
|
type: "button",
|
|
2877
|
-
className: "
|
|
2125
|
+
className: "dmss_chip",
|
|
2878
2126
|
onClick: () => {
|
|
2879
2127
|
setShowAllWorkspaces(true);
|
|
2880
2128
|
}
|
|
@@ -2882,7 +2130,7 @@ window.__ModuleLoader__.load({
|
|
|
2882
2130
|
showAllWorkspaces && workspaceChips.length > 8 && (0, react.createElement)("button", {
|
|
2883
2131
|
key: "less",
|
|
2884
2132
|
type: "button",
|
|
2885
|
-
className: "
|
|
2133
|
+
className: "dmss_chip",
|
|
2886
2134
|
onClick: () => {
|
|
2887
2135
|
setShowAllWorkspaces(false);
|
|
2888
2136
|
}
|
|
@@ -2894,7 +2142,7 @@ window.__ModuleLoader__.load({
|
|
|
2894
2142
|
manageGroups.length > 1 && (0, react.createElement)("button", {
|
|
2895
2143
|
key: "collapseAll",
|
|
2896
2144
|
type: "button",
|
|
2897
|
-
className: "
|
|
2145
|
+
className: "dmss_chip",
|
|
2898
2146
|
onClick: () => {
|
|
2899
2147
|
setAllCollapsed(true);
|
|
2900
2148
|
}
|
|
@@ -2902,7 +2150,7 @@ window.__ModuleLoader__.load({
|
|
|
2902
2150
|
manageGroups.length > 1 && (0, react.createElement)("button", {
|
|
2903
2151
|
key: "expandAll",
|
|
2904
2152
|
type: "button",
|
|
2905
|
-
className: "
|
|
2153
|
+
className: "dmss_chip",
|
|
2906
2154
|
onClick: () => {
|
|
2907
2155
|
setAllCollapsed(false);
|
|
2908
2156
|
}
|
|
@@ -2910,7 +2158,7 @@ window.__ModuleLoader__.load({
|
|
|
2910
2158
|
]),
|
|
2911
2159
|
(0, react.createElement)("div", {
|
|
2912
2160
|
key: "chips",
|
|
2913
|
-
className: "
|
|
2161
|
+
className: "dmss_chips",
|
|
2914
2162
|
role: "group",
|
|
2915
2163
|
"aria-label": translate$1(t, "domain.all")
|
|
2916
2164
|
}, [...[
|
|
@@ -2920,7 +2168,7 @@ window.__ModuleLoader__.load({
|
|
|
2920
2168
|
].map((id) => (0, react.createElement)("button", {
|
|
2921
2169
|
key: id,
|
|
2922
2170
|
type: "button",
|
|
2923
|
-
className: `
|
|
2171
|
+
className: `dmss_chip${domain === id ? " dmss_chipActive" : ""}`,
|
|
2924
2172
|
"aria-pressed": domain === id,
|
|
2925
2173
|
onClick: () => {
|
|
2926
2174
|
setDomain(id);
|
|
@@ -2928,7 +2176,7 @@ window.__ModuleLoader__.load({
|
|
|
2928
2176
|
}, translate$1(t, `domain.${id}`))), (0, react.createElement)("button", {
|
|
2929
2177
|
key: "favorites",
|
|
2930
2178
|
type: "button",
|
|
2931
|
-
className: `
|
|
2179
|
+
className: `dmss_chip${favoritesOnly ? " dmss_chipActive" : ""}`,
|
|
2932
2180
|
"aria-pressed": favoritesOnly,
|
|
2933
2181
|
onClick: () => {
|
|
2934
2182
|
setFavoritesOnly((v) => !v);
|
|
@@ -2936,7 +2184,7 @@ window.__ModuleLoader__.load({
|
|
|
2936
2184
|
}, `★ ${translate$1(t, "filter.favorites")}`)]),
|
|
2937
2185
|
(0, react.createElement)("div", {
|
|
2938
2186
|
key: "hint",
|
|
2939
|
-
className: "
|
|
2187
|
+
className: "dmss_manageHint"
|
|
2940
2188
|
}, [translate$1(t, "manage.hint"), (0, react.createElement)("span", {
|
|
2941
2189
|
key: "sep",
|
|
2942
2190
|
style: {
|
|
@@ -2946,13 +2194,13 @@ window.__ModuleLoader__.load({
|
|
|
2946
2194
|
}, translate$1(t, "manage.restartHint"))]),
|
|
2947
2195
|
(0, react.createElement)("div", {
|
|
2948
2196
|
key: "groups",
|
|
2949
|
-
className: "
|
|
2197
|
+
className: "dmss_manageList"
|
|
2950
2198
|
}, ...manageGroups.map((group) => (0, react.createElement)("div", {
|
|
2951
2199
|
key: group.cwd === "" ? "(nocwd)" : group.cwd,
|
|
2952
|
-
className: "
|
|
2200
|
+
className: "dmss_group"
|
|
2953
2201
|
}, [(0, react.createElement)("div", {
|
|
2954
2202
|
key: "head",
|
|
2955
|
-
className: "
|
|
2203
|
+
className: "dmss_groupHead",
|
|
2956
2204
|
title: translate$1(t, "group.collapse"),
|
|
2957
2205
|
onClick: () => {
|
|
2958
2206
|
toggleCollapsed(group.cwd);
|
|
@@ -2970,7 +2218,7 @@ window.__ModuleLoader__.load({
|
|
|
2970
2218
|
}, "▶"),
|
|
2971
2219
|
(0, react.createElement)("span", {
|
|
2972
2220
|
key: "check",
|
|
2973
|
-
className: "
|
|
2221
|
+
className: "dmss_check"
|
|
2974
2222
|
}, (0, react.createElement)("input", {
|
|
2975
2223
|
type: "checkbox",
|
|
2976
2224
|
checked: group.items.length > 0 && group.items.every((item) => selected.has(item.sessionId)),
|
|
@@ -2984,26 +2232,34 @@ window.__ModuleLoader__.load({
|
|
|
2984
2232
|
})),
|
|
2985
2233
|
(0, react.createElement)("span", {
|
|
2986
2234
|
key: "title",
|
|
2987
|
-
className: "
|
|
2235
|
+
className: "dmss_groupTitle"
|
|
2988
2236
|
}, group.cwd === "" ? translate$1(t, "manage.group.nocwd") : group.cwd),
|
|
2989
2237
|
(0, react.createElement)("span", {
|
|
2990
2238
|
key: "count",
|
|
2991
|
-
className: "
|
|
2239
|
+
className: "dmss_groupCount"
|
|
2992
2240
|
}, `${group.items.filter((item) => matchesText(item)).length}/${group.items.length}`)
|
|
2993
|
-
]), ...collapsedGroups.has(group.cwd) ? [] :
|
|
2241
|
+
]), ...collapsedGroups.has(group.cwd) ? [] : [(0, react.createElement)("div", {
|
|
2242
|
+
key: "grid",
|
|
2243
|
+
className: "dmss_grid"
|
|
2244
|
+
}, group.items.filter((item) => matchesText(item)).map((item) => (0, react.createElement)("div", {
|
|
2994
2245
|
key: item.sessionId,
|
|
2995
|
-
|
|
2996
|
-
|
|
2246
|
+
role: "button",
|
|
2247
|
+
tabIndex: 0,
|
|
2248
|
+
className: "dmss_row",
|
|
2249
|
+
title: item.title || translate$1(t, "panel.untitled"),
|
|
2997
2250
|
onClick: () => {
|
|
2998
2251
|
open(item.sessionId);
|
|
2252
|
+
},
|
|
2253
|
+
onKeyDown: (e) => {
|
|
2254
|
+
if (e.key === "Enter" || e.key === " ") open(item.sessionId);
|
|
2999
2255
|
}
|
|
3000
2256
|
}, [(0, react.createElement)("span", {
|
|
3001
2257
|
key: "line",
|
|
3002
|
-
className: "
|
|
2258
|
+
className: "dmss_rowTitle"
|
|
3003
2259
|
}, [
|
|
3004
2260
|
(0, react.createElement)("span", {
|
|
3005
2261
|
key: "check",
|
|
3006
|
-
className: "
|
|
2262
|
+
className: "dmss_check"
|
|
3007
2263
|
}, (0, react.createElement)("input", {
|
|
3008
2264
|
type: "checkbox",
|
|
3009
2265
|
checked: selected.has(item.sessionId),
|
|
@@ -3017,7 +2273,7 @@ window.__ModuleLoader__.load({
|
|
|
3017
2273
|
(0, react.createElement)("button", {
|
|
3018
2274
|
key: "star",
|
|
3019
2275
|
type: "button",
|
|
3020
|
-
className: "
|
|
2276
|
+
className: "dmss_check",
|
|
3021
2277
|
title: translate$1(t, favorites.has(item.sessionId) ? "star.on" : "star.off"),
|
|
3022
2278
|
style: {
|
|
3023
2279
|
border: "none",
|
|
@@ -3035,33 +2291,33 @@ window.__ModuleLoader__.load({
|
|
|
3035
2291
|
}, favorites.has(item.sessionId) ? "★" : "☆"),
|
|
3036
2292
|
(0, react.createElement)("span", {
|
|
3037
2293
|
key: "x",
|
|
3038
|
-
className: "
|
|
2294
|
+
className: "dmss_titleText"
|
|
3039
2295
|
}, item.title || translate$1(t, "panel.untitled")),
|
|
3040
2296
|
item.archived === true && (0, react.createElement)("span", {
|
|
3041
2297
|
key: "arch",
|
|
3042
|
-
className: "
|
|
2298
|
+
className: "dmss_tagArch"
|
|
3043
2299
|
}, translate$1(t, "tag.archived")),
|
|
3044
2300
|
(0, react.createElement)("span", {
|
|
3045
2301
|
key: "tag",
|
|
3046
|
-
className: "
|
|
3047
|
-
}, fmtTime
|
|
2302
|
+
className: "dmss_tag"
|
|
2303
|
+
}, fmtTime(item.updatedAt))
|
|
3048
2304
|
]), (0, react.createElement)("span", {
|
|
3049
2305
|
key: "meta",
|
|
3050
|
-
className: "
|
|
3051
|
-
}, item.cwd)]))])))
|
|
2306
|
+
className: "dmss_meta"
|
|
2307
|
+
}, item.cwd)])))]])))
|
|
3052
2308
|
];
|
|
3053
2309
|
if (selected.size > 0) children.push((0, react.createElement)("div", {
|
|
3054
2310
|
key: "batch",
|
|
3055
|
-
className: "
|
|
2311
|
+
className: "dmss_batchBar"
|
|
3056
2312
|
}, [
|
|
3057
2313
|
(0, react.createElement)("span", {
|
|
3058
2314
|
key: "info",
|
|
3059
|
-
className: "
|
|
2315
|
+
className: "dmss_batchInfo"
|
|
3060
2316
|
}, batch.message !== "" ? batch.message : translate$1(t, "manage.selected", { n: selected.size })),
|
|
3061
2317
|
(0, react.createElement)("button", {
|
|
3062
2318
|
key: "archive",
|
|
3063
2319
|
type: "button",
|
|
3064
|
-
className: "
|
|
2320
|
+
className: "dmss_actBtn",
|
|
3065
2321
|
disabled: batch.busy,
|
|
3066
2322
|
onClick: () => {
|
|
3067
2323
|
runBatch("archive");
|
|
@@ -3070,7 +2326,7 @@ window.__ModuleLoader__.load({
|
|
|
3070
2326
|
(0, react.createElement)("button", {
|
|
3071
2327
|
key: "unarchive",
|
|
3072
2328
|
type: "button",
|
|
3073
|
-
className: "
|
|
2329
|
+
className: "dmss_actBtn",
|
|
3074
2330
|
disabled: batch.busy,
|
|
3075
2331
|
onClick: () => {
|
|
3076
2332
|
runBatch("unarchive");
|
|
@@ -3079,7 +2335,7 @@ window.__ModuleLoader__.load({
|
|
|
3079
2335
|
(0, react.createElement)("button", {
|
|
3080
2336
|
key: "purge",
|
|
3081
2337
|
type: "button",
|
|
3082
|
-
className: `
|
|
2338
|
+
className: `dmss_actBtn${confirmPurge ? " dmss_btnDangerSolid" : " dmss_btnDanger"}`,
|
|
3083
2339
|
disabled: batch.busy,
|
|
3084
2340
|
onClick: () => {
|
|
3085
2341
|
runBatch("purge");
|
|
@@ -3087,11 +2343,11 @@ window.__ModuleLoader__.load({
|
|
|
3087
2343
|
}, confirmPurge ? translate$1(t, "manage.batch.confirm") : translate$1(t, "manage.batch.delete")),
|
|
3088
2344
|
batch.busy && (0, react.createElement)("span", {
|
|
3089
2345
|
key: "busy",
|
|
3090
|
-
className: "
|
|
2346
|
+
className: "dmss_batchInfo"
|
|
3091
2347
|
}, translate$1(t, "manage.batch.working"))
|
|
3092
2348
|
]));
|
|
3093
2349
|
return (0, react.createElement)("div", {
|
|
3094
|
-
className: "
|
|
2350
|
+
className: "dmss_manageRoot",
|
|
3095
2351
|
style: {
|
|
3096
2352
|
display: "flex",
|
|
3097
2353
|
flexDirection: "column",
|
|
@@ -3101,7 +2357,7 @@ window.__ModuleLoader__.load({
|
|
|
3101
2357
|
}, children);
|
|
3102
2358
|
}
|
|
3103
2359
|
/** Format an epoch-ms timestamp: today → HH:mm, else YYYY-MM-DD HH:mm. */
|
|
3104
|
-
function fmtTime
|
|
2360
|
+
function fmtTime(ms) {
|
|
3105
2361
|
if (!ms || typeof ms !== "number") return "";
|
|
3106
2362
|
try {
|
|
3107
2363
|
const d = new Date(ms);
|
|
@@ -3115,10 +2371,9 @@ window.__ModuleLoader__.load({
|
|
|
3115
2371
|
return "";
|
|
3116
2372
|
}
|
|
3117
2373
|
}
|
|
3118
|
-
|
|
3119
|
-
//#region src/client/panel.tsx
|
|
2374
|
+
// src/client/panel.tsx
|
|
3120
2375
|
/**
|
|
3121
|
-
*
|
|
2376
|
+
* 会话管理面板壳:一个侧边栏入口,两个页签(管理=主页面 / 体检=备用)。
|
|
3122
2377
|
*
|
|
3123
2378
|
* 2026-10-20 页面调整(用户定标):「养老院」页签取消——管理台已覆盖其全部
|
|
3124
2379
|
* 动作面(archive/unarchive/purge + 归档域筛选),独立页签属重复入口;
|
|
@@ -3130,19 +2385,14 @@ window.__ModuleLoader__.load({
|
|
|
3130
2385
|
* (客户端也会隐藏入口)。
|
|
3131
2386
|
*/
|
|
3132
2387
|
/** 页签 id(导出供测试断言)。 */
|
|
3133
|
-
const TAB_HEALTH = "
|
|
3134
|
-
const TAB_MANAGE = "
|
|
3135
|
-
/**
|
|
3136
|
-
function StewardPanel({ t, healthCheck,
|
|
3137
|
-
const [tab, setTab] = (0, react.useState)(
|
|
2388
|
+
const TAB_HEALTH = "dms-tab-health";
|
|
2389
|
+
const TAB_MANAGE = "dms-tab-manage";
|
|
2390
|
+
/** 会话管理对话框 / 整页。 */
|
|
2391
|
+
function StewardPanel({ t, healthCheck, openSession, onClose, variant = "dialog" }) {
|
|
2392
|
+
const [tab, setTab] = (0, react.useState)("manage");
|
|
3138
2393
|
(0, react.useEffect)(() => {
|
|
3139
|
-
if (tab === "
|
|
3140
|
-
|
|
3141
|
-
}, [
|
|
3142
|
-
tab,
|
|
3143
|
-
healthCheck,
|
|
3144
|
-
searchEnabled
|
|
3145
|
-
]);
|
|
2394
|
+
if (tab === "health" && !healthCheck) setTab("manage");
|
|
2395
|
+
}, [tab, healthCheck]);
|
|
3146
2396
|
(0, react.useEffect)(() => {
|
|
3147
2397
|
const onKey = (event) => {
|
|
3148
2398
|
if (event.key === "Escape") onClose();
|
|
@@ -3153,11 +2403,11 @@ window.__ModuleLoader__.load({
|
|
|
3153
2403
|
};
|
|
3154
2404
|
}, [onClose]);
|
|
3155
2405
|
const tabs = [];
|
|
3156
|
-
|
|
2406
|
+
tabs.push((0, react.createElement)("button", {
|
|
3157
2407
|
key: TAB_MANAGE,
|
|
3158
2408
|
id: TAB_MANAGE,
|
|
3159
2409
|
type: "button",
|
|
3160
|
-
className: `
|
|
2410
|
+
className: `dms_tab${tab === "manage" ? " dms_tabActive" : ""}`,
|
|
3161
2411
|
"aria-selected": tab === "manage",
|
|
3162
2412
|
onClick: () => {
|
|
3163
2413
|
setTab("manage");
|
|
@@ -3167,29 +2417,25 @@ window.__ModuleLoader__.load({
|
|
|
3167
2417
|
key: TAB_HEALTH,
|
|
3168
2418
|
id: TAB_HEALTH,
|
|
3169
2419
|
type: "button",
|
|
3170
|
-
className: `
|
|
2420
|
+
className: `dms_tab${tab === "health" ? " dms_tabActive" : ""}`,
|
|
3171
2421
|
"aria-selected": tab === "health",
|
|
3172
2422
|
onClick: () => {
|
|
3173
2423
|
setTab("health");
|
|
3174
2424
|
}
|
|
3175
2425
|
}, translate(t, "panel.tab.health")));
|
|
3176
|
-
const body = tab === "manage"
|
|
2426
|
+
const body = tab === "manage" ? (0, react.createElement)("div", { style: {
|
|
3177
2427
|
display: "flex",
|
|
3178
2428
|
flexDirection: "column",
|
|
3179
2429
|
flex: "1 1 auto",
|
|
3180
2430
|
minHeight: 0
|
|
3181
|
-
} }, (0, react.createElement)(ManageConsole, { open: openSession ?? (() => {}) })) :
|
|
2431
|
+
} }, (0, react.createElement)(ManageConsole, { open: openSession ?? (() => {}) })) : (0, react.createElement)(HealthPanel, {
|
|
3182
2432
|
t,
|
|
3183
2433
|
onClose
|
|
3184
|
-
})
|
|
3185
|
-
|
|
3186
|
-
key: "backdrop",
|
|
3187
|
-
className: "dss_backdrop",
|
|
3188
|
-
onClick: onClose
|
|
3189
|
-
}), (0, react.createElement)("div", {
|
|
2434
|
+
});
|
|
2435
|
+
const panelEl = (0, react.createElement)("div", {
|
|
3190
2436
|
key: "panel",
|
|
3191
|
-
className: "
|
|
3192
|
-
role: "dialog",
|
|
2437
|
+
className: variant === "page" ? "dms_panel dms_panelPage" : "dms_panel",
|
|
2438
|
+
role: variant === "page" ? "region" : "dialog",
|
|
3193
2439
|
"aria-label": translate(t, "panel.title"),
|
|
3194
2440
|
style: {
|
|
3195
2441
|
display: "flex",
|
|
@@ -3197,1855 +2443,72 @@ window.__ModuleLoader__.load({
|
|
|
3197
2443
|
}
|
|
3198
2444
|
}, [(0, react.createElement)("div", {
|
|
3199
2445
|
key: "head",
|
|
3200
|
-
className: "
|
|
2446
|
+
className: "dms_dialogHead"
|
|
3201
2447
|
}, [
|
|
3202
2448
|
(0, react.createElement)("span", {
|
|
3203
2449
|
key: "title",
|
|
3204
|
-
className: "
|
|
2450
|
+
className: "dms_dialogTitle"
|
|
3205
2451
|
}, translate(t, "panel.title")),
|
|
3206
2452
|
(0, react.createElement)("span", {
|
|
3207
2453
|
key: "tabs",
|
|
3208
|
-
className: "
|
|
2454
|
+
className: "dms_tabRow"
|
|
3209
2455
|
}, tabs),
|
|
3210
2456
|
(0, react.createElement)("button", {
|
|
3211
2457
|
key: "close",
|
|
3212
2458
|
type: "button",
|
|
3213
|
-
className: "
|
|
2459
|
+
className: "dms_actBtn",
|
|
3214
2460
|
onClick: onClose
|
|
3215
2461
|
}, translate(t, "panel.close"))
|
|
3216
|
-
]), body])
|
|
2462
|
+
]), body]);
|
|
2463
|
+
if (variant === "page") return panelEl;
|
|
2464
|
+
return (0, react_dom.createPortal)((0, react.createElement)("div", { key: "steward-root" }, [(0, react.createElement)("div", {
|
|
2465
|
+
key: "backdrop",
|
|
2466
|
+
className: "dms_backdrop",
|
|
2467
|
+
onClick: onClose
|
|
2468
|
+
}), panelEl]), document.body);
|
|
3217
2469
|
}
|
|
3218
2470
|
/** 侧边栏脚部入口:宽栏带文案成行控件,收起轨道退化为 36x36 图标钮。 */
|
|
3219
2471
|
function StewardFooter(props) {
|
|
3220
2472
|
const wide = props.wide === true;
|
|
3221
2473
|
return (0, react.createElement)("button", {
|
|
3222
2474
|
type: "button",
|
|
3223
|
-
className: wide ? "
|
|
2475
|
+
className: wide ? "dms_footerEntry" : "dms_footerEntry dms_footerEntryRail",
|
|
3224
2476
|
title: props.title,
|
|
3225
2477
|
"aria-label": props.title,
|
|
3226
2478
|
onClick: props.onClick
|
|
3227
2479
|
}, [(0, react.createElement)("span", {
|
|
3228
2480
|
key: "icon",
|
|
3229
|
-
className: "
|
|
2481
|
+
className: "dms_footerIcon",
|
|
3230
2482
|
"aria-hidden": true
|
|
3231
2483
|
}, "🧭"), wide && props.label !== void 0 ? (0, react.createElement)("span", {
|
|
3232
2484
|
key: "label",
|
|
3233
|
-
className: "
|
|
2485
|
+
className: "dms_footerLabel"
|
|
3234
2486
|
}, props.label) : null]);
|
|
3235
2487
|
}
|
|
3236
|
-
|
|
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
|
-
}
|
|
2488
|
+
// src/client/session-menu-legacy.ts
|
|
3344
2489
|
/**
|
|
3345
|
-
*
|
|
3346
|
-
*
|
|
3347
|
-
*
|
|
2490
|
+
* 老线(≤0.1.5)会话「…」菜单删除注入臂(2026-10-20 用户定标)。
|
|
2491
|
+
*
|
|
2492
|
+
* 为什么需要:≤0.1.5 宿主的 ui-workspace 会话菜单是 React **写死**的
|
|
2493
|
+
* `sessionMenuItems`(rename/fork/archive),portal 出 `div[role=menu]`,
|
|
2494
|
+
* **无插件席位**——席位臂(session-menu.tsx)在老线 inject 静默等待、永不落行。
|
|
2495
|
+
* purge 后端两格都通(file 兜底路),缺的只是菜单入口。本臂用 DOM 注入补上:
|
|
2496
|
+
* 监听 portal 菜单出现 → 从被点「…」锚点的行 React fiber 取 sessionId →
|
|
2497
|
+
* 追一行两步确认删除(与管理端/席位臂同款 purge 链路)。
|
|
2498
|
+
*
|
|
2499
|
+
* 现代线(0.1.7+)席位行已渲染并带 `data-steward-menu-delete` 标记,本臂据此
|
|
2500
|
+
* dedup 跳过;故两线共用一个注入器不会出两条删除。
|
|
2501
|
+
*
|
|
2502
|
+
* 纪律:全程 best-effort、任何异常吞掉绝不影响宿主;仅在 directDelete=true 时
|
|
2503
|
+
* 注入(缺省关,破坏性入口不默认铺开)。fiber 取 id 失败即放弃该行(不伪造)。
|
|
3348
2504
|
*/
|
|
3349
|
-
|
|
3350
|
-
|
|
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 判定;旧视口函数已废弃。 */
|
|
2505
|
+
/** dedup 标记(与 session-menu.tsx 席位行同串)。 */
|
|
2506
|
+
const STEWARD_MENU_DELETE_ATTR = "data-steward-menu-delete";
|
|
3396
2507
|
/**
|
|
3397
|
-
*
|
|
3398
|
-
*
|
|
3399
|
-
|
|
3400
|
-
|
|
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
|
|
3500
|
-
//#region src/client/search/platform.ts
|
|
3501
|
-
/**
|
|
3502
|
-
* Resolve the running platform string, three tiers deep.
|
|
3503
|
-
*
|
|
3504
|
-
* 1. UA-CH (`navigator.userAgentData.platform`) — modern and precise.
|
|
3505
|
-
* 2. `navigator.platform` — broadly available, deprecated, and empty in some
|
|
3506
|
-
* privacy modes.
|
|
3507
|
-
* 3. The UA string — last resort, still non-empty in practice.
|
|
3508
|
-
*
|
|
3509
|
-
* @param nav - the navigator to read; `undefined` is allowed (non-DOM host).
|
|
3510
|
-
* @returns a lower-cased platform string, or `''` when nothing could be read.
|
|
3511
|
-
*/
|
|
3512
|
-
function detectPlatform(nav) {
|
|
3513
|
-
const chPlatform = nav?.userAgentData?.platform;
|
|
3514
|
-
if (typeof chPlatform === "string" && chPlatform !== "") return chPlatform.toLowerCase();
|
|
3515
|
-
const legacy = nav?.platform;
|
|
3516
|
-
if (typeof legacy === "string" && legacy !== "") return legacy.toLowerCase();
|
|
3517
|
-
const agent = nav?.userAgent;
|
|
3518
|
-
if (typeof agent === "string" && agent !== "") return agent.toLowerCase();
|
|
3519
|
-
return "";
|
|
3520
|
-
}
|
|
3521
|
-
/**
|
|
3522
|
-
* Derive the label vocabulary from a platform string.
|
|
3523
|
-
*
|
|
3524
|
-
* An unrecognised platform — an empty string, or a UA that names neither
|
|
3525
|
-
* system — takes the non-Mac vocabulary on purpose: `Ctrl` is legible to
|
|
3526
|
-
* everyone, while `⌘` is opaque to anyone who has never used a Mac.
|
|
3527
|
-
*
|
|
3528
|
-
* @param platform - the string produced by {@link detectPlatform}.
|
|
3529
|
-
* @returns the label vocabulary for that platform.
|
|
3530
|
-
*/
|
|
3531
|
-
function platformLabels(platform) {
|
|
3532
|
-
const isMac = /mac|iphone|ipad|ipod/u.test(platform);
|
|
3533
|
-
const isWindows = /win/u.test(platform);
|
|
3534
|
-
const modLabel = isMac ? "⌘" : "Ctrl";
|
|
3535
|
-
return {
|
|
3536
|
-
platform,
|
|
3537
|
-
isMac,
|
|
3538
|
-
isWindows,
|
|
3539
|
-
modLabel,
|
|
3540
|
-
altLabel: isMac ? "⌥" : "Alt",
|
|
3541
|
-
shiftLabel: isMac ? "⇧" : "Shift",
|
|
3542
|
-
enterLabel: "↵",
|
|
3543
|
-
escLabel: isMac ? "esc" : "Esc",
|
|
3544
|
-
invokeLabel: isMac ? `${modLabel}K` : `${modLabel} K`
|
|
3545
|
-
};
|
|
3546
|
-
}
|
|
3547
|
-
/** Read `globalThis.navigator` without assuming a DOM (or a DOM lib). */
|
|
3548
|
-
function currentNavigator() {
|
|
3549
|
-
const candidate = globalThis.navigator;
|
|
3550
|
-
if (candidate === null || typeof candidate !== "object") return void 0;
|
|
3551
|
-
return candidate;
|
|
3552
|
-
}
|
|
3553
|
-
/** The labels for the platform this bundle is running on. */
|
|
3554
|
-
const LABELS = platformLabels(detectPlatform(currentNavigator()));
|
|
3555
|
-
/**
|
|
3556
|
-
* Whether a keydown event is the invoke chord advertised by
|
|
3557
|
-
* {@link PlatformLabels.invokeLabel}.
|
|
3558
|
-
*
|
|
3559
|
-
* The hint and the binding have to agree: a footer promising `⌘K` while the
|
|
3560
|
-
* handler matches `Ctrl+K` is worse than showing no hint at all. Both sides
|
|
3561
|
-
* call this, so neither can be changed alone. Requiring the other modifier to
|
|
3562
|
-
* be *absent* keeps the two chords distinct instead of letting either one
|
|
3563
|
-
* satisfy both platforms.
|
|
3564
|
-
*
|
|
3565
|
-
* @param event - the keydown payload.
|
|
3566
|
-
* @param isMac - the running platform's macOS-ness, from {@link LABELS}.
|
|
3567
|
-
* @returns `true` when the event is the invoke chord.
|
|
3568
|
-
*/
|
|
3569
|
-
function isInvokeChord(event, isMac) {
|
|
3570
|
-
if (event.key !== "k" && event.key !== "K") return false;
|
|
3571
|
-
if (event.altKey || event.shiftKey) return false;
|
|
3572
|
-
return isMac ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
|
|
3573
|
-
}
|
|
3574
|
-
//#endregion
|
|
3575
|
-
//#region src/client/search/panel.tsx
|
|
3576
|
-
/**
|
|
3577
|
-
* 搜索子域 client 组件(原 dsh-search-index client 半身的面板部分)。
|
|
3578
|
-
*
|
|
3579
|
-
* 注册职责(侧栏入口 / 家族 tab / 插件页配置卡)在合并包的 client/index.ts;
|
|
3580
|
-
* 本文件只持有:样式表、悬浮搜索面板(标题/内容双模式)、侧栏 footer 按钮、
|
|
3581
|
-
* 唤出和弦绑定。
|
|
3582
|
-
*
|
|
3583
|
-
* The `locale` and config services are consumed structurally: when
|
|
3584
|
-
* the host release lacks them the footer falls back to the bundled zh
|
|
3585
|
-
* dictionary and the host-composition config layer.
|
|
3586
|
-
*/
|
|
3587
|
-
/** The coarse filter chips rendered above content results. */
|
|
3588
|
-
const CONTENT_TYPE_CHIPS = [
|
|
3589
|
-
{
|
|
3590
|
-
id: "all",
|
|
3591
|
-
labelKey: "filter.all"
|
|
3592
|
-
},
|
|
3593
|
-
{
|
|
3594
|
-
id: "user",
|
|
3595
|
-
labelKey: "filter.user"
|
|
3596
|
-
},
|
|
3597
|
-
{
|
|
3598
|
-
id: "reply",
|
|
3599
|
-
labelKey: "filter.reply"
|
|
3600
|
-
},
|
|
3601
|
-
{
|
|
3602
|
-
id: "tool",
|
|
3603
|
-
labelKey: "filter.tool"
|
|
3604
|
-
}
|
|
3605
|
-
];
|
|
3606
|
-
/** 检索域 chips(标题/内容两模式共用)。 */
|
|
3607
|
-
const ARCHIVED_CHIPS = [
|
|
3608
|
-
{
|
|
3609
|
-
id: "all",
|
|
3610
|
-
labelKey: "domain.all"
|
|
3611
|
-
},
|
|
3612
|
-
{
|
|
3613
|
-
id: "active",
|
|
3614
|
-
labelKey: "domain.active"
|
|
3615
|
-
},
|
|
3616
|
-
{
|
|
3617
|
-
id: "archived",
|
|
3618
|
-
labelKey: "domain.archived"
|
|
3619
|
-
}
|
|
3620
|
-
];
|
|
3621
|
-
/** Result-ordering chips rendered at the right of the same filter row. */
|
|
3622
|
-
const SORT_CHIPS = [{
|
|
3623
|
-
id: "relevance",
|
|
3624
|
-
labelKey: "sort.relevance"
|
|
3625
|
-
}, {
|
|
3626
|
-
id: "time",
|
|
3627
|
-
labelKey: "sort.time"
|
|
3628
|
-
}];
|
|
3629
|
-
/** 命中粒度 chips(仅内容模式)。 */
|
|
3630
|
-
const GROUP_CHIPS = [{
|
|
3631
|
-
id: "merge",
|
|
3632
|
-
labelKey: "group.merge"
|
|
3633
|
-
}, {
|
|
3634
|
-
id: "expand",
|
|
3635
|
-
labelKey: "group.expand"
|
|
3636
|
-
}];
|
|
3637
|
-
/**
|
|
3638
|
-
* The identity of one content-search request, derived from every input that
|
|
3639
|
-
* changes the result set.
|
|
3640
|
-
*
|
|
3641
|
-
* This is the single owner of that identity. The effect that issues the
|
|
3642
|
-
* request and the render path that decides whether the stored result belongs
|
|
3643
|
-
* to the current inputs must both come through here: two hand-written copies
|
|
3644
|
-
* of the same template literal drift apart silently, and the render path then
|
|
3645
|
-
* falls through to its empty `loading` state for every query — results arrive,
|
|
3646
|
-
* parse, and are never shown.
|
|
3647
|
-
*
|
|
3648
|
-
* @param normalized - the trimmed query text.
|
|
3649
|
-
* @param contentType - the active content-type filter.
|
|
3650
|
-
* @param sortBy - the active result ordering.
|
|
3651
|
-
* @param archivedFilter - the retrieval-domain chip (all/active/archived).
|
|
3652
|
-
* @returns an opaque key, stable for equal inputs.
|
|
3653
|
-
*/
|
|
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}`;
|
|
3656
|
-
}
|
|
3657
|
-
/**
|
|
3658
|
-
* Persisted ordering preference. Unlike `lastPanelMode` this one survives a
|
|
3659
|
-
* page reload — an ordering is a durable preference, not a session mood.
|
|
3660
|
-
*/
|
|
3661
|
-
const SORT_STORE_KEY = "dsh-search-index.sortBy";
|
|
3662
|
-
/** Read the persisted ordering; private mode or a bad value degrades to relevance. */
|
|
3663
|
-
function readStoredSort() {
|
|
3664
|
-
try {
|
|
3665
|
-
return window.localStorage.getItem(SORT_STORE_KEY) === "time" ? "time" : "relevance";
|
|
3666
|
-
} catch {
|
|
3667
|
-
return "relevance";
|
|
3668
|
-
}
|
|
3669
|
-
}
|
|
3670
|
-
/** Persist the ordering; a storage failure still leaves this session working. */
|
|
3671
|
-
function writeStoredSort(next) {
|
|
3672
|
-
try {
|
|
3673
|
-
window.localStorage.setItem(SORT_STORE_KEY, next);
|
|
3674
|
-
} catch {}
|
|
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
|
-
}
|
|
3690
|
-
/**
|
|
3691
|
-
* Local re-sort so the page honours the chosen ordering even against an older
|
|
3692
|
-
* host half that ignores `sortBy` and therefore omits `updatedAt` entirely —
|
|
3693
|
-
* in that case the host order is left untouched rather than scrambled to NaN.
|
|
3694
|
-
*/
|
|
3695
|
-
function sortHits(items, sortBy) {
|
|
3696
|
-
if (sortBy !== "time") return [...items];
|
|
3697
|
-
if (!items.every((item) => typeof item.updatedAt === "number" && Number.isFinite(item.updatedAt))) return [...items];
|
|
3698
|
-
return [...items].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
3699
|
-
}
|
|
3700
|
-
/** Last panel mode used this web session (mode memory, not persisted). */
|
|
3701
|
-
let lastPanelMode = "title";
|
|
3702
|
-
/** ------------------------------------------------------------------ styles */
|
|
3703
|
-
const CSS = `
|
|
3704
|
-
/* 侧栏 footer 槽位公约(2026-09-26;2026-10-01 收紧行距并强制居中):一行多
|
|
3705
|
-
入口(第三方 dsh-context 等)会互相挤占 —— 宿主 .footerActions 是 nowrap
|
|
3706
|
-
flex 行。这里允许容器换行,并把本插件入口钉成独占一整行(flex-basis:100%);
|
|
3707
|
-
其余入口(含第三方)自然落到后续行。justify-content:center 让行内所有入口
|
|
3708
|
-
(含不占满行的)统一居中;row-gap:0 配合入口自身 32px 高度压缩纵向占位。
|
|
3709
|
-
类名用 [class*=] 中段匹配:宿主是 CSS Module 哈希类名(实测形如
|
|
3710
|
-
hHd-Xa_footerActions —— <hash>_<name>,哈希在前),中段跨版本稳定。 */
|
|
3711
|
-
[class*="footerActions"]{flex-wrap:wrap;justify-content:center;row-gap:0;height:auto;min-height:0}
|
|
3712
|
-
/* —— 第三方矫正:dsh-context「上下文洞察」入口(2026-10-01)——
|
|
3713
|
-
.lc-ov-entry 按"独占整行"设计(width:calc(100% + 4px)、无 justify-content、
|
|
3714
|
-
42px 高、不对称 padding),与本槽位公约(每个入口独占一行、行内居中、32px)
|
|
3715
|
-
冲突,表现为文字靠左、纵向松散。这里按公约强制矫正;:not() 排除收起轨道的
|
|
3716
|
-
36px 圆钮形态。lc-ov-* 是 dsh-context 源码硬编码类名(非构建哈希),跨版本
|
|
3717
|
-
稳定(实测 0.56.1 / 0.60.0 规则一致)。 */
|
|
3718
|
-
.lc-ov-entry:not(.lc-ov-entry-rail){width:auto!important;flex:0 0 100%!important;min-width:0!important;justify-content:center!important;height:32px!important;margin:0!important;padding:0 10px!important}
|
|
3719
|
-
.lc-ov-entry-label{flex:0 1 auto!important}
|
|
3720
|
-
.dsws_root{box-sizing:border-box;position:relative;display:flex;align-items:center;justify-content:center;flex:0 0 100%;width:100%;min-width:0;container-type:inline-size}
|
|
3721
|
-
/* 收起轨道:回落自然宽度(根类的 100% 基准只属于宽栏形态),放弃收缩。 */
|
|
3722
|
-
.dsws_rootRail{flex:none;width:auto;container-type:normal}
|
|
3723
|
-
/* 高度 32px(内容 22px 行高 + 上下各 5px):宿主默认 42px 的上下裕度在
|
|
3724
|
-
多行堆叠后过于松散;左右内边距对称(10px/10px),否则整行居中时按钮内容
|
|
3725
|
-
会因不对称 padding 向左偏 1px。 */
|
|
3726
|
-
.dsws_button{box-sizing:border-box;display:inline-flex;align-items:center;flex:0 0 auto;min-width:0;gap:8px;height:32px;border:none;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;padding:0 10px;font-family:inherit;font-size:14px;line-height:22px;white-space:nowrap;overflow:hidden;transition:background-color 160ms ease-out,color 160ms ease-out}
|
|
3727
|
-
.dsws_buttonRail{flex:none;width:28px;height:28px;padding:0;gap:0;justify-content:center;border-radius:50%}
|
|
3728
|
-
.dsws_button:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
3729
|
-
.dsws_button svg{flex:none}
|
|
3730
|
-
.dsws_trigger{position:fixed;z-index:2147483000;left:50%;top:50%;transform:translate(-50%,-50%);width:860px;max-width:calc(100vw - 32px);max-height:min(84vh,880px);box-sizing:border-box;background:var(--dsw-specific-tip);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;box-shadow:var(--dsw-shadow-lv3,0 8px 28px rgba(0,0,0,.16));overflow:hidden;display:flex;flex-direction:column;font-family:Inter,var(--dsw-font-family)}
|
|
3731
|
-
.dsws_toolrow{display:flex;align-items:center;gap:8px;padding:10px 10px 0}
|
|
3732
|
-
.dsws_mode{display:inline-flex;align-items:center;gap:2px;flex:none;background:var(--dsw-alias-interactive-bg-hover);border-radius:8px;padding:2px}
|
|
3733
|
-
.dsws_modeBtn{height:24px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:6px;padding:0 8px;font-size:12px;font-weight:500;line-height:20px}
|
|
3734
|
-
.dsws_modeBtn:hover{color:var(--dsw-alias-label-primary)}
|
|
3735
|
-
.dsws_modeBtnActive{background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgba(0,0,0,.08)}
|
|
3736
|
-
.dsws_search{flex:auto;min-width:0;height:30px;box-sizing:border-box;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;padding:0 10px;font:inherit;font-size:13px;line-height:20px}
|
|
3737
|
-
.dsws_search:focus{border-color:var(--dsw-alias-state-business-primary)}
|
|
3738
|
-
.dsws_search::placeholder{color:var(--dsw-alias-label-caption)}
|
|
3739
|
-
.dsws_chips{display:flex;align-items:center;gap:6px;padding:8px 10px 0;flex:none}
|
|
3740
|
-
.dsws_chipGap{flex:1;min-width:0}
|
|
3741
|
-
.dsws_sortGroup{display:flex;align-items:center;gap:6px;flex:none}
|
|
3742
|
-
.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}
|
|
3743
|
-
.dsws_chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
3744
|
-
.dsws_chipActive{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary)}
|
|
3745
|
-
.dsws_list{flex:1 1 auto;min-height:0;overflow-y:auto;margin:8px 0 0;padding:0 6px 8px;list-style:none}
|
|
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}
|
|
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}
|
|
3754
|
-
.dsws_rowTitle{display:flex;align-items:center;gap:8px;min-width:0}
|
|
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}
|
|
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}
|
|
3757
|
-
.dsws_snippet{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:17px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}
|
|
3758
|
-
.dsws_meta{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3759
|
-
.dsws_status{color:var(--dsw-alias-label-tertiary);padding:10px 8px 8px;font-size:12px;line-height:18px}
|
|
3760
|
-
.dsws_error{color:var(--dsw-alias-state-error-primary);padding:8px;font-size:12px;line-height:18px}
|
|
3761
|
-
.dsws_empty{color:var(--dsw-alias-label-tertiary);padding:10px 8px 8px;font-size:12px;line-height:18px}
|
|
3762
|
-
.dsws_backdrop{position:fixed;inset:0;z-index:2147482999;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.24));backdrop-filter:blur(var(--dsw-mask-blur,4px))}
|
|
3763
|
-
.dsws_setRow{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
3764
|
-
.dsws_setRow:last-child{border-bottom:none}
|
|
3765
|
-
.dsws_setText{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
3766
|
-
.dsws_setTitle{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
|
|
3767
|
-
.dsws_setDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
3768
|
-
.dsws_seg{display:inline-flex;align-items:center;gap:2px;background:var(--dsw-alias-interactive-bg-hover);border-radius:8px;padding:2px;flex:none}
|
|
3769
|
-
.dsws_segBtn{height:24px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:6px;padding:0 10px;font-size:12px;font-weight:500;line-height:20px}
|
|
3770
|
-
.dsws_segBtn:hover{color:var(--dsw-alias-label-primary)}
|
|
3771
|
-
.dsws_segBtn:disabled{cursor:not-allowed;opacity:.5}
|
|
3772
|
-
.dsws_segBtnActive{background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgba(0,0,0,.08)}
|
|
3773
|
-
.dsws_actBtn{height:26px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;padding:0 10px;font-size:12px;line-height:24px;white-space:nowrap}
|
|
3774
|
-
.dsws_actBtn:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
3775
|
-
.dsws_actBtn:disabled{cursor:not-allowed;opacity:.5}
|
|
3776
|
-
.dsws_btnRow{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
|
3777
|
-
.dsws_progressWrap{display:flex;flex-direction:column;gap:4px;padding:8px 10px 0}
|
|
3778
|
-
.dsws_progress{height:6px;border-radius:3px;background:var(--dsw-alias-interactive-bg-hover);overflow:hidden}
|
|
3779
|
-
.dsws_progressFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-business-primary);transition:width .4s ease}
|
|
3780
|
-
.dsws_progressLabel{color:var(--dsw-alias-state-business-primary);font-size:12px;line-height:18px;font-weight:600;font-variant-numeric:tabular-nums}
|
|
3781
|
-
.dsws_panelFoot{flex:none;display:flex;align-items:center;gap:14px;padding:8px 14px;border-top:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
3782
|
-
.dsws_footItem{display:inline-flex;align-items:center}
|
|
3783
|
-
.dsws_footItem>.dsws_kbd{margin-right:5px}
|
|
3784
|
-
.dsws_footGap{flex:1;min-width:0}
|
|
3785
|
-
.dsws_buttonLabel{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3786
|
-
.dsws_kbd{flex:none;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-width:18px;width:auto;height:18px;padding:0 4px;border:1px solid var(--dsw-alias-border-l2);border-radius:5px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:10px;line-height:1;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
3787
|
-
/*
|
|
3788
|
-
* The row shares its width with the session-steward entry (order 12), so at
|
|
3789
|
-
* narrow sidebar widths the shortcut chip, not the label, is what yields: the
|
|
3790
|
-
* chip is a hint repeated by the entry tooltip and the panel footer, while the
|
|
3791
|
-
* label is the entry's identity. Measured in Chrome: the pair needs 230px for
|
|
3792
|
-
* both labels plus the chip; below that the chip's 43px is what makes the
|
|
3793
|
-
* difference. The query is scoped to the wide form: container-type also
|
|
3794
|
-
* applies inline-size containment, which zeroes a flex:none rail root.
|
|
3795
|
-
*/
|
|
3796
|
-
@container (max-width: 132px){.dsws_kbd{display:none}}
|
|
3797
|
-
.dsws_pill{flex:none;display:inline-grid;grid-template-columns:14px max-content;align-items:center;column-gap:4px;height:26px;padding:0 10px;box-sizing:border-box;border:none;border-radius:8px;font-size:12px;font-weight:500;line-height:18px;white-space:nowrap;transition:background-color 160ms ease-out,color 160ms ease-out}
|
|
3798
|
-
.dsws_pill .dsws_pillIcon{display:grid;place-items:center;width:14px;height:14px}
|
|
3799
|
-
.dsws_pill .dsws_pillLabel{display:grid;text-align:left}
|
|
3800
|
-
.dsws_pill .dsws_pillLabel>span{grid-area:1/1}
|
|
3801
|
-
.dsws_pillReady{background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary)}
|
|
3802
|
-
.dsws_pillWarn{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}
|
|
3803
|
-
.dsws_pillError{background:var(--dsw-alias-state-error-tertiary,var(--dsw-alias-state-warn-tertiary));color:var(--dsw-alias-state-error-primary,var(--dsw-alias-state-warn-label))}
|
|
3804
|
-
.dsws_pillNeutral{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
|
|
3805
|
-
.dsws_pillDots span{opacity:0;animation:dsws-reveal-dot 1.5s step-end infinite}
|
|
3806
|
-
.dsws_pillDots span:nth-child(2){animation-delay:.5s}
|
|
3807
|
-
.dsws_pillDots span:nth-child(3){animation-delay:1s}
|
|
3808
|
-
@keyframes dsws-reveal-dot{0%,32%{opacity:0}33%,100%{opacity:1}}
|
|
3809
|
-
@media (prefers-reduced-motion:reduce){.dsws_pillDots span{animation:none;opacity:1}}
|
|
3810
|
-
`;
|
|
3811
|
-
/** Inject the plugin stylesheet once per activation (removed on disposal). */
|
|
3812
|
-
function injectStyles$1() {
|
|
3813
|
-
if (typeof document === "undefined") return () => {};
|
|
3814
|
-
if (document.querySelector("style[data-plugin-css=\"dsw-session-search-toggle/styles\"]") !== null) return () => {};
|
|
3815
|
-
const tag = document.createElement("style");
|
|
3816
|
-
tag.dataset.plugin = "dsh-search-index";
|
|
3817
|
-
tag.dataset.pluginCss = "dsw-session-search-toggle/styles";
|
|
3818
|
-
tag.textContent = CSS;
|
|
3819
|
-
document.head.appendChild(tag);
|
|
3820
|
-
return () => {
|
|
3821
|
-
if (tag.parentNode !== null) tag.parentNode.removeChild(tag);
|
|
3822
|
-
};
|
|
3823
|
-
}
|
|
3824
|
-
/** ------------------------------------------------------------------ view */
|
|
3825
|
-
/** The floating search panel. */
|
|
3826
|
-
function SwitchPanel({ t, onClose, open }) {
|
|
3827
|
-
const [mode, setModeState] = (0, react.useState)(lastPanelMode);
|
|
3828
|
-
const setMode = (next) => {
|
|
3829
|
-
lastPanelMode = next;
|
|
3830
|
-
setModeState(next);
|
|
3831
|
-
};
|
|
3832
|
-
const [query, setQuery] = (0, react.useState)("");
|
|
3833
|
-
const [contentType, setContentType] = (0, react.useState)("all");
|
|
3834
|
-
const [archivedFilter, setArchivedFilter] = (0, react.useState)("all");
|
|
3835
|
-
const [dateFrom, setDateFrom] = (0, react.useState)("");
|
|
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);
|
|
3842
|
-
const [sortBy, setSortByState] = (0, react.useState)(readStoredSort);
|
|
3843
|
-
const setSortBy = (next) => {
|
|
3844
|
-
writeStoredSort(next);
|
|
3845
|
-
setSortByState(next);
|
|
3846
|
-
};
|
|
3847
|
-
const [groupMode, setGroupModeState] = (0, react.useState)(readStoredGroup);
|
|
3848
|
-
const setGroupMode = (next) => {
|
|
3849
|
-
writeStoredGroup(next);
|
|
3850
|
-
setGroupModeState(next);
|
|
3851
|
-
};
|
|
3852
|
-
const [sessions, setSessions] = (0, react.useState)(null);
|
|
3853
|
-
const [sessionsError, setSessionsError] = (0, react.useState)(null);
|
|
3854
|
-
const [content, setContent] = (0, react.useState)({
|
|
3855
|
-
query: "",
|
|
3856
|
-
status: "idle",
|
|
3857
|
-
items: []
|
|
3858
|
-
});
|
|
3859
|
-
const [searchStatus, setSearchStatus] = (0, react.useState)({
|
|
3860
|
-
available: null,
|
|
3861
|
-
rebuilding: false,
|
|
3862
|
-
done: 0,
|
|
3863
|
-
total: 0,
|
|
3864
|
-
count: 0
|
|
3865
|
-
});
|
|
3866
|
-
const inputRef = (0, react.useRef)(null);
|
|
3867
|
-
const listRef = (0, react.useRef)(null);
|
|
3868
|
-
const normalized = query.trim().toLowerCase();
|
|
3869
|
-
(0, react.useEffect)(() => {
|
|
3870
|
-
let cancelled = false;
|
|
3871
|
-
let timer;
|
|
3872
|
-
const probe = () => {
|
|
3873
|
-
callHostAny("index-status", {}).then((res) => {
|
|
3874
|
-
if (cancelled) return;
|
|
3875
|
-
if (!res.ok) {
|
|
3876
|
-
setSearchStatus({
|
|
3877
|
-
available: false,
|
|
3878
|
-
reason: "unreachable",
|
|
3879
|
-
rebuilding: false,
|
|
3880
|
-
done: 0,
|
|
3881
|
-
total: 0,
|
|
3882
|
-
count: 0
|
|
3883
|
-
});
|
|
3884
|
-
return;
|
|
3885
|
-
}
|
|
3886
|
-
const status = res;
|
|
3887
|
-
const rebuilding = status.rebuild?.state === "building" || status.rebuild?.state === "swapping";
|
|
3888
|
-
setDirectDelete(status.directDelete === true);
|
|
3889
|
-
setSearchStatus({
|
|
3890
|
-
available: status.available ?? false,
|
|
3891
|
-
reason: status.reason,
|
|
3892
|
-
rebuilding,
|
|
3893
|
-
done: status.rebuild?.done ?? 0,
|
|
3894
|
-
total: status.rebuild?.total ?? 0,
|
|
3895
|
-
count: status.sync?.indexed ?? 0
|
|
3896
|
-
});
|
|
3897
|
-
if (rebuilding) timer = window.setTimeout(probe, 1500);
|
|
3898
|
-
});
|
|
3899
|
-
};
|
|
3900
|
-
probe();
|
|
3901
|
-
return () => {
|
|
3902
|
-
cancelled = true;
|
|
3903
|
-
if (timer !== void 0) window.clearTimeout(timer);
|
|
3904
|
-
};
|
|
3905
|
-
}, []);
|
|
3906
|
-
const startIndexBuild = () => {
|
|
3907
|
-
setSearchStatus((prev) => ({
|
|
3908
|
-
...prev,
|
|
3909
|
-
rebuilding: true
|
|
3910
|
-
}));
|
|
3911
|
-
callHostAny("index-rebuild", {});
|
|
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"));
|
|
3953
|
-
(0, react.useEffect)(() => {
|
|
3954
|
-
if (sessions !== null) return;
|
|
3955
|
-
let cancelled = false;
|
|
3956
|
-
callHost("list-sessions", {}).then((res) => {
|
|
3957
|
-
if (cancelled) return;
|
|
3958
|
-
if (res.ok) {
|
|
3959
|
-
setSessions(res.items);
|
|
3960
|
-
setSessionsError(null);
|
|
3961
|
-
} else setSessionsError(res.error ?? "读取会话列表失败");
|
|
3962
|
-
});
|
|
3963
|
-
return () => {
|
|
3964
|
-
cancelled = true;
|
|
3965
|
-
};
|
|
3966
|
-
}, [sessions]);
|
|
3967
|
-
(0, react.useEffect)(() => {
|
|
3968
|
-
if (mode !== "content" || normalized === "") {
|
|
3969
|
-
if (mode !== "content") setContent({
|
|
3970
|
-
query: normalized,
|
|
3971
|
-
status: "idle",
|
|
3972
|
-
items: []
|
|
3973
|
-
});
|
|
3974
|
-
return;
|
|
3975
|
-
}
|
|
3976
|
-
let cancelled = false;
|
|
3977
|
-
const requestType = contentType;
|
|
3978
|
-
const requestKey = contentRequestKey(normalized, requestType, sortBy, archivedFilter, {
|
|
3979
|
-
from: dateFrom,
|
|
3980
|
-
to: dateTo
|
|
3981
|
-
}, groupMode);
|
|
3982
|
-
setContent((prev) => ({
|
|
3983
|
-
query: requestKey,
|
|
3984
|
-
status: "loading",
|
|
3985
|
-
items: prev.query === requestKey ? prev.items : []
|
|
3986
|
-
}));
|
|
3987
|
-
const timer = window.setTimeout(() => {
|
|
3988
|
-
callHost("content-search", {
|
|
3989
|
-
query: normalized,
|
|
3990
|
-
limit: 50,
|
|
3991
|
-
types: requestType === "all" ? void 0 : [requestType],
|
|
3992
|
-
sortBy,
|
|
3993
|
-
archived: archivedFilter,
|
|
3994
|
-
expand: groupMode === "expand" || void 0,
|
|
3995
|
-
from: dateFrom === "" ? void 0 : dateFrom,
|
|
3996
|
-
to: dateTo === "" ? void 0 : dateTo
|
|
3997
|
-
}).then((res) => {
|
|
3998
|
-
if (cancelled) return;
|
|
3999
|
-
setContent({
|
|
4000
|
-
query: requestKey,
|
|
4001
|
-
status: res.ok ? "ready" : "error",
|
|
4002
|
-
items: res.ok ? sortHits(res.items, sortBy) : [],
|
|
4003
|
-
error: res.ok ? void 0 : res.error ?? "搜索失败"
|
|
4004
|
-
});
|
|
4005
|
-
});
|
|
4006
|
-
}, 250);
|
|
4007
|
-
return () => {
|
|
4008
|
-
cancelled = true;
|
|
4009
|
-
window.clearTimeout(timer);
|
|
4010
|
-
};
|
|
4011
|
-
}, [
|
|
4012
|
-
mode,
|
|
4013
|
-
normalized,
|
|
4014
|
-
contentType,
|
|
4015
|
-
sortBy,
|
|
4016
|
-
archivedFilter,
|
|
4017
|
-
dateFrom,
|
|
4018
|
-
dateTo,
|
|
4019
|
-
groupMode
|
|
4020
|
-
]);
|
|
4021
|
-
(0, react.useEffect)(() => {
|
|
4022
|
-
inputRef.current?.focus();
|
|
4023
|
-
}, []);
|
|
4024
|
-
(0, react.useEffect)(() => {
|
|
4025
|
-
const onKey = (e) => {
|
|
4026
|
-
if (e.key === "Escape") onClose();
|
|
4027
|
-
};
|
|
4028
|
-
document.addEventListener("keydown", onKey);
|
|
4029
|
-
return () => {
|
|
4030
|
-
document.removeEventListener("keydown", onKey);
|
|
4031
|
-
};
|
|
4032
|
-
}, [onClose]);
|
|
4033
|
-
const dateRangeMs = (0, react.useMemo)(() => {
|
|
4034
|
-
const parse = (value, nextDay) => {
|
|
4035
|
-
if (value === "") return void 0;
|
|
4036
|
-
const [year, month, day] = value.split("-").map(Number);
|
|
4037
|
-
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return void 0;
|
|
4038
|
-
const date = new Date(year, month - 1, day);
|
|
4039
|
-
if (nextDay) date.setDate(date.getDate() + 1);
|
|
4040
|
-
return date.getTime();
|
|
4041
|
-
};
|
|
4042
|
-
const from = parse(dateFrom, false);
|
|
4043
|
-
const to = parse(dateTo, true);
|
|
4044
|
-
return {
|
|
4045
|
-
...from === void 0 ? {} : { from },
|
|
4046
|
-
...to === void 0 ? {} : { to }
|
|
4047
|
-
};
|
|
4048
|
-
}, [dateFrom, dateTo]);
|
|
4049
|
-
const titleRows = (0, react.useMemo)(() => {
|
|
4050
|
-
if (sessions === null) return [];
|
|
4051
|
-
const byDomain = sessions.filter((item) => archivedFilter === "active" ? item.archived !== true : archivedFilter === "archived" ? item.archived === true : true);
|
|
4052
|
-
const byDate = byDomain.filter((item) => (dateRangeMs.from === void 0 || item.updatedAt >= dateRangeMs.from) && (dateRangeMs.to === void 0 || item.updatedAt < dateRangeMs.to));
|
|
4053
|
-
if (normalized === "") return byDate;
|
|
4054
|
-
return byDomain.filter((item) => item.title.toLowerCase().includes(normalized) || item.cwd.toLowerCase().includes(normalized));
|
|
4055
|
-
}, [
|
|
4056
|
-
sessions,
|
|
4057
|
-
normalized,
|
|
4058
|
-
archivedFilter,
|
|
4059
|
-
dateRangeMs
|
|
4060
|
-
]);
|
|
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));
|
|
4066
|
-
if (sessionsError !== null) children.push((0, react.createElement)("div", {
|
|
4067
|
-
key: "err",
|
|
4068
|
-
className: "dsws_error"
|
|
4069
|
-
}, translate$1(t, "panel.sessionsError", { error: sessionsError })));
|
|
4070
|
-
const activeRequestKey = contentRequestKey(normalized, contentType, sortBy, archivedFilter, {
|
|
4071
|
-
from: dateFrom,
|
|
4072
|
-
to: dateTo
|
|
4073
|
-
}, groupMode);
|
|
4074
|
-
const activeContent = content.query === activeRequestKey ? content : {
|
|
4075
|
-
query: activeRequestKey,
|
|
4076
|
-
status: "loading",
|
|
4077
|
-
items: []
|
|
4078
|
-
};
|
|
4079
|
-
if (mode === "title") {
|
|
4080
|
-
if (sessions === null) children.push((0, react.createElement)("div", {
|
|
4081
|
-
key: "loading",
|
|
4082
|
-
className: "dsws_status"
|
|
4083
|
-
}, translate$1(t, "panel.loadingSessions")));
|
|
4084
|
-
else if (titleRows.length === 0) children.push((0, react.createElement)("div", {
|
|
4085
|
-
key: "empty",
|
|
4086
|
-
className: "dsws_empty"
|
|
4087
|
-
}, normalized === "" ? translate$1(t, "panel.noSessions") : translate$1(t, "panel.noMatch")));
|
|
4088
|
-
else children.push((0, react.createElement)("ul", {
|
|
4089
|
-
key: "list",
|
|
4090
|
-
ref: listRef,
|
|
4091
|
-
className: "dsws_list",
|
|
4092
|
-
role: "listbox",
|
|
4093
|
-
"aria-label": translate$1(t, "panel.titleSearch")
|
|
4094
|
-
}, titleRows.slice(0, 200).map((item) => (0, react.createElement)("li", {
|
|
4095
|
-
key: item.sessionId,
|
|
4096
|
-
role: "option"
|
|
4097
|
-
}, (0, react.createElement)("div", { className: "dsws_rowLine" }, [(0, react.createElement)("button", {
|
|
4098
|
-
key: "row",
|
|
4099
|
-
type: "button",
|
|
4100
|
-
className: "dsws_row",
|
|
4101
|
-
onClick: () => {
|
|
4102
|
-
setArmedDelete(null);
|
|
4103
|
-
open(item.sessionId);
|
|
4104
|
-
}
|
|
4105
|
-
}, [(0, react.createElement)("span", {
|
|
4106
|
-
key: "t",
|
|
4107
|
-
className: "dsws_rowTitle"
|
|
4108
|
-
}, [
|
|
4109
|
-
(0, react.createElement)("span", {
|
|
4110
|
-
key: "x",
|
|
4111
|
-
className: "dsws_titleText"
|
|
4112
|
-
}, item.title || translate$1(t, "panel.untitled")),
|
|
4113
|
-
item.archived === true && (0, react.createElement)("span", {
|
|
4114
|
-
key: "arch",
|
|
4115
|
-
className: "dsws_tagArch"
|
|
4116
|
-
}, translate$1(t, "tag.archived")),
|
|
4117
|
-
(0, react.createElement)("span", {
|
|
4118
|
-
key: "tag",
|
|
4119
|
-
className: "dsws_tag"
|
|
4120
|
-
}, fmtTime(item.updatedAt))
|
|
4121
|
-
]), item.cwd !== "" && (0, react.createElement)("span", {
|
|
4122
|
-
key: "c",
|
|
4123
|
-
className: "dsws_meta"
|
|
4124
|
-
}, item.cwd)]), directDelete && deleteBtn(item.sessionId)])))));
|
|
4125
|
-
} else if (searchStatus.rebuilding) {
|
|
4126
|
-
const pct = searchStatus.total > 0 ? Math.min(100, Math.round(searchStatus.done / searchStatus.total * 100)) : void 0;
|
|
4127
|
-
children.push((0, react.createElement)("div", {
|
|
4128
|
-
key: "rebuilding",
|
|
4129
|
-
className: "dsws_progressWrap"
|
|
4130
|
-
}, [(0, react.createElement)("div", {
|
|
4131
|
-
key: "bar",
|
|
4132
|
-
className: "dsws_progress"
|
|
4133
|
-
}, (0, react.createElement)("div", {
|
|
4134
|
-
className: "dsws_progressFill",
|
|
4135
|
-
style: pct === void 0 ? {
|
|
4136
|
-
width: "30%",
|
|
4137
|
-
opacity: .6
|
|
4138
|
-
} : { width: `${pct}%` }
|
|
4139
|
-
})), (0, react.createElement)("span", {
|
|
4140
|
-
key: "label",
|
|
4141
|
-
className: "dsws_progressLabel"
|
|
4142
|
-
}, translate$1(t, "panel.rebuilding", {
|
|
4143
|
-
done: searchStatus.done,
|
|
4144
|
-
total: searchStatus.total || "?"
|
|
4145
|
-
}))]));
|
|
4146
|
-
} else if (searchStatus.available === false) children.push((0, react.createElement)("div", {
|
|
4147
|
-
key: "unavailable",
|
|
4148
|
-
className: "dsws_error"
|
|
4149
|
-
}, [(0, react.createElement)("div", { key: "msg" }, searchStatus.reason === "unreachable" ? "索引状态不可达:Host 半可能是旧进程。请完全重启 dsh web(浏览器刷新不重载 Host)后重试。" : searchStatus.reason === "unavailable" ? translate$1(t, "panel.unavailable") : translate$1(t, "panel.notBuilt")), (0, react.createElement)("button", {
|
|
4150
|
-
key: "build",
|
|
4151
|
-
type: "button",
|
|
4152
|
-
className: "dsws_actBtn",
|
|
4153
|
-
style: { marginTop: "6px" },
|
|
4154
|
-
onClick: startIndexBuild
|
|
4155
|
-
}, translate$1(t, "panel.buildIndex"))]));
|
|
4156
|
-
else if (activeContent.status === "loading") children.push((0, react.createElement)("div", {
|
|
4157
|
-
key: "loading",
|
|
4158
|
-
className: "dsws_status"
|
|
4159
|
-
}, translate$1(t, "panel.loadingContent")));
|
|
4160
|
-
else if (activeContent.status === "error") children.push((0, react.createElement)("div", {
|
|
4161
|
-
key: "error",
|
|
4162
|
-
className: "dsws_error"
|
|
4163
|
-
}, translate$1(t, "panel.contentError", { error: activeContent.error ?? "未知错误" })));
|
|
4164
|
-
else if (activeContent.items.length === 0) children.push((0, react.createElement)("div", {
|
|
4165
|
-
key: "empty",
|
|
4166
|
-
className: "dsws_empty"
|
|
4167
|
-
}, normalized === "" ? translate$1(t, "panel.contentHint") : translate$1(t, "panel.noContent")));
|
|
4168
|
-
else children.push((0, react.createElement)("ul", {
|
|
4169
|
-
key: "list",
|
|
4170
|
-
ref: listRef,
|
|
4171
|
-
className: "dsws_list",
|
|
4172
|
-
role: "listbox",
|
|
4173
|
-
"aria-label": translate$1(t, "panel.contentSearch")
|
|
4174
|
-
}, activeContent.items.slice(0, 200).map((item) => (0, react.createElement)("li", {
|
|
4175
|
-
key: `${item.sessionId}:${item.seq}`,
|
|
4176
|
-
role: "option"
|
|
4177
|
-
}, (0, react.createElement)("div", { className: "dsws_rowLine" }, [(0, react.createElement)("button", {
|
|
4178
|
-
key: "row",
|
|
4179
|
-
type: "button",
|
|
4180
|
-
className: "dsws_row",
|
|
4181
|
-
onClick: () => {
|
|
4182
|
-
const hitKey = `${item.sessionId}:${item.seq}`;
|
|
4183
|
-
setArmedDelete(null);
|
|
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
|
-
});
|
|
4195
|
-
}
|
|
4196
|
-
}, [(0, react.createElement)("span", {
|
|
4197
|
-
key: "t",
|
|
4198
|
-
className: "dsws_rowTitle"
|
|
4199
|
-
}, [
|
|
4200
|
-
(0, react.createElement)("span", {
|
|
4201
|
-
key: "x",
|
|
4202
|
-
className: "dsws_titleText"
|
|
4203
|
-
}, item.title || translate$1(t, "panel.untitled")),
|
|
4204
|
-
(0, react.createElement)("span", {
|
|
4205
|
-
key: "tag",
|
|
4206
|
-
className: "dsws_tag"
|
|
4207
|
-
}, typeLabel(t, item.type)),
|
|
4208
|
-
(0, react.createElement)("span", {
|
|
4209
|
-
key: "time",
|
|
4210
|
-
className: "dsws_tag"
|
|
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" ? "✓" : "✗")]
|
|
4217
|
-
]), (0, react.createElement)("span", {
|
|
4218
|
-
key: "s",
|
|
4219
|
-
className: "dsws_snippet"
|
|
4220
|
-
}, item.snippet || translate$1(t, "panel.noText"))]), directDelete && deleteBtn(item.sessionId)])))));
|
|
4221
|
-
return (0, react_dom.createPortal)((0, react.createElement)("div", { key: "switch-root" }, [(0, react.createElement)("div", {
|
|
4222
|
-
key: "backdrop",
|
|
4223
|
-
className: "dsws_backdrop",
|
|
4224
|
-
onClick: onClose
|
|
4225
|
-
}), (0, react.createElement)("div", {
|
|
4226
|
-
key: "panel",
|
|
4227
|
-
className: "dsws_trigger",
|
|
4228
|
-
role: "dialog",
|
|
4229
|
-
"aria-label": translate$1(t, "card.title")
|
|
4230
|
-
}, [
|
|
4231
|
-
(0, react.createElement)("div", {
|
|
4232
|
-
key: "tools",
|
|
4233
|
-
className: "dsws_toolrow"
|
|
4234
|
-
}, [(0, react.createElement)("div", {
|
|
4235
|
-
key: "mode",
|
|
4236
|
-
className: "dsws_mode",
|
|
4237
|
-
role: "group",
|
|
4238
|
-
"aria-label": translate$1(t, "card.defaultMode")
|
|
4239
|
-
}, [(0, react.createElement)("button", {
|
|
4240
|
-
key: "title",
|
|
4241
|
-
type: "button",
|
|
4242
|
-
className: `dsws_modeBtn${mode === "title" ? " dsws_modeBtnActive" : ""}`,
|
|
4243
|
-
onClick: () => {
|
|
4244
|
-
setMode("title");
|
|
4245
|
-
}
|
|
4246
|
-
}, translate$1(t, "panel.titleSearch")), (0, react.createElement)("button", {
|
|
4247
|
-
key: "content",
|
|
4248
|
-
type: "button",
|
|
4249
|
-
className: `dsws_modeBtn${mode === "content" ? " dsws_modeBtnActive" : ""}`,
|
|
4250
|
-
onClick: () => {
|
|
4251
|
-
setMode("content");
|
|
4252
|
-
}
|
|
4253
|
-
}, translate$1(t, "panel.contentSearch"))]), (0, react.createElement)("input", {
|
|
4254
|
-
key: "search",
|
|
4255
|
-
ref: inputRef,
|
|
4256
|
-
className: "dsws_search",
|
|
4257
|
-
type: "text",
|
|
4258
|
-
placeholder: mode === "title" ? translate$1(t, "panel.searchTitle") : translate$1(t, "panel.searchContent"),
|
|
4259
|
-
value: query,
|
|
4260
|
-
onChange: (e) => setQuery(e.target.value)
|
|
4261
|
-
})]),
|
|
4262
|
-
(0, react.createElement)("div", {
|
|
4263
|
-
key: "chips",
|
|
4264
|
-
className: "dsws_chips",
|
|
4265
|
-
role: "group",
|
|
4266
|
-
"aria-label": translate$1(t, "domain.all")
|
|
4267
|
-
}, [
|
|
4268
|
-
...ARCHIVED_CHIPS.map((chip) => (0, react.createElement)("button", {
|
|
4269
|
-
key: chip.id,
|
|
4270
|
-
type: "button",
|
|
4271
|
-
className: `dsws_chip${archivedFilter === chip.id ? " dsws_chipActive" : ""}`,
|
|
4272
|
-
"aria-pressed": archivedFilter === chip.id,
|
|
4273
|
-
onClick: () => {
|
|
4274
|
-
setArchivedFilter(chip.id);
|
|
4275
|
-
}
|
|
4276
|
-
}, translate$1(t, chip.labelKey))),
|
|
4277
|
-
(0, react.createElement)("input", {
|
|
4278
|
-
key: "from",
|
|
4279
|
-
type: "date",
|
|
4280
|
-
className: "dsws_search",
|
|
4281
|
-
style: {
|
|
4282
|
-
flex: "none",
|
|
4283
|
-
width: "128px",
|
|
4284
|
-
height: "24px",
|
|
4285
|
-
padding: "0 6px",
|
|
4286
|
-
fontSize: "12px"
|
|
4287
|
-
},
|
|
4288
|
-
value: dateFrom,
|
|
4289
|
-
title: translate$1(t, "date.from"),
|
|
4290
|
-
onChange: (e) => {
|
|
4291
|
-
setDateFrom(e.target.value);
|
|
4292
|
-
}
|
|
4293
|
-
}),
|
|
4294
|
-
(0, react.createElement)("input", {
|
|
4295
|
-
key: "to",
|
|
4296
|
-
type: "date",
|
|
4297
|
-
className: "dsws_search",
|
|
4298
|
-
style: {
|
|
4299
|
-
flex: "none",
|
|
4300
|
-
width: "128px",
|
|
4301
|
-
height: "24px",
|
|
4302
|
-
padding: "0 6px",
|
|
4303
|
-
fontSize: "12px"
|
|
4304
|
-
},
|
|
4305
|
-
value: dateTo,
|
|
4306
|
-
title: translate$1(t, "date.to"),
|
|
4307
|
-
onChange: (e) => {
|
|
4308
|
-
setDateTo(e.target.value);
|
|
4309
|
-
}
|
|
4310
|
-
}),
|
|
4311
|
-
...mode === "content" ? CONTENT_TYPE_CHIPS.map((chip) => (0, react.createElement)("button", {
|
|
4312
|
-
key: chip.id,
|
|
4313
|
-
type: "button",
|
|
4314
|
-
className: `dsws_chip${contentType === chip.id ? " dsws_chipActive" : ""}`,
|
|
4315
|
-
"aria-pressed": contentType === chip.id,
|
|
4316
|
-
onClick: () => {
|
|
4317
|
-
setContentType(chip.id);
|
|
4318
|
-
}
|
|
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))) : [],
|
|
4330
|
-
(0, react.createElement)("span", {
|
|
4331
|
-
key: "gap",
|
|
4332
|
-
className: "dsws_chipGap"
|
|
4333
|
-
}),
|
|
4334
|
-
...mode === "content" ? [(0, react.createElement)("span", {
|
|
4335
|
-
key: "sort",
|
|
4336
|
-
className: "dsws_sortGroup",
|
|
4337
|
-
role: "group",
|
|
4338
|
-
"aria-label": translate$1(t, "sort.label")
|
|
4339
|
-
}, SORT_CHIPS.map((chip) => (0, react.createElement)("button", {
|
|
4340
|
-
key: chip.id,
|
|
4341
|
-
type: "button",
|
|
4342
|
-
className: `dsws_chip${sortBy === chip.id ? " dsws_chipActive" : ""}`,
|
|
4343
|
-
"aria-pressed": sortBy === chip.id,
|
|
4344
|
-
title: chip.id === "time" ? translate$1(t, "sort.time.hint") : translate$1(t, "sort.relevance.hint"),
|
|
4345
|
-
onClick: () => {
|
|
4346
|
-
setSortBy(chip.id);
|
|
4347
|
-
}
|
|
4348
|
-
}, translate$1(t, chip.labelKey))))] : []
|
|
4349
|
-
]),
|
|
4350
|
-
children,
|
|
4351
|
-
(0, react.createElement)("div", {
|
|
4352
|
-
key: "foot",
|
|
4353
|
-
className: "dsws_panelFoot"
|
|
4354
|
-
}, [
|
|
4355
|
-
(0, react.createElement)("span", {
|
|
4356
|
-
key: "index",
|
|
4357
|
-
className: "dsws_footItem"
|
|
4358
|
-
}, [(0, react.createElement)("span", { key: "label" }, searchStatus.rebuilding ? translate$1(t, "panel.index.rebuilding", {
|
|
4359
|
-
done: searchStatus.done,
|
|
4360
|
-
total: searchStatus.total || "?"
|
|
4361
|
-
}) : searchStatus.available === false ? translate$1(t, "panel.index.none") : translate$1(t, "panel.index.count", { n: searchStatus.count })), (0, react.createElement)("button", {
|
|
4362
|
-
key: "rebuild",
|
|
4363
|
-
type: "button",
|
|
4364
|
-
className: "dsws_actBtn",
|
|
4365
|
-
style: {
|
|
4366
|
-
height: "20px",
|
|
4367
|
-
padding: "0 8px",
|
|
4368
|
-
fontSize: "11px",
|
|
4369
|
-
lineHeight: "18px"
|
|
4370
|
-
},
|
|
4371
|
-
disabled: searchStatus.rebuilding,
|
|
4372
|
-
title: translate$1(t, "card.index.rebuild.hint"),
|
|
4373
|
-
onClick: startIndexBuild
|
|
4374
|
-
}, searchStatus.rebuilding ? translate$1(t, "card.index.rebuilding.btn") : translate$1(t, "card.index.rebuild"))]),
|
|
4375
|
-
(0, react.createElement)("span", {
|
|
4376
|
-
key: "invoke",
|
|
4377
|
-
className: "dsws_footItem"
|
|
4378
|
-
}, [(0, react.createElement)("kbd", {
|
|
4379
|
-
key: "k",
|
|
4380
|
-
className: "dsws_kbd"
|
|
4381
|
-
}, LABELS.invokeLabel), translate$1(t, "panel.footer.invoke")]),
|
|
4382
|
-
(0, react.createElement)("span", {
|
|
4383
|
-
key: "gap",
|
|
4384
|
-
className: "dsws_footGap"
|
|
4385
|
-
}),
|
|
4386
|
-
(0, react.createElement)("span", {
|
|
4387
|
-
key: "close",
|
|
4388
|
-
className: "dsws_footItem"
|
|
4389
|
-
}, [(0, react.createElement)("kbd", {
|
|
4390
|
-
key: "k",
|
|
4391
|
-
className: "dsws_kbd"
|
|
4392
|
-
}, LABELS.escLabel), translate$1(t, "panel.footer.close")])
|
|
4393
|
-
])
|
|
4394
|
-
])]), document.body);
|
|
4395
|
-
}
|
|
4396
|
-
/**
|
|
4397
|
-
* Whether the user has the sidebar entry switched on.
|
|
4398
|
-
*
|
|
4399
|
-
* The `enabled` field has existed since the card shipped, but nothing read it:
|
|
4400
|
-
* the switch promised "show the search entry at the bottom of the sidebar" and
|
|
4401
|
-
* controlled nothing at all. Reading it here is the whole fix.
|
|
4402
|
-
*
|
|
4403
|
-
* An absent or unreadable scope keeps the entry visible — a settings service we
|
|
4404
|
-
* cannot read is not a user asking for the feature off, and hiding the entry
|
|
4405
|
-
* would also hide the only way back to the panel.
|
|
4406
|
-
*
|
|
4407
|
-
* @param scope - the bound `switch-search` namespace scope, when available.
|
|
4408
|
-
* @returns `false` only when a readable scope says the entry is switched off.
|
|
4409
|
-
*/
|
|
4410
|
-
function useEntryEnabled(scope) {
|
|
4411
|
-
return (0, react.useSyncExternalStore)((listener) => scope?.subscribe(listener) ?? (() => {}), () => scope?.getSnapshot())?.value?.enabled !== false;
|
|
4412
|
-
}
|
|
4413
|
-
/** The footer entry: one icon button that opens the search panel. */
|
|
4414
|
-
function SwitchFooter({ t, wide, open, scope }) {
|
|
4415
|
-
const [openPanel, setOpenPanel] = (0, react.useState)(false);
|
|
4416
|
-
const enabled = useEntryEnabled(scope);
|
|
4417
|
-
(0, react.useEffect)(() => {
|
|
4418
|
-
if (!enabled) return void 0;
|
|
4419
|
-
const onKey = (event) => {
|
|
4420
|
-
if (!isInvokeChord(event, LABELS.isMac)) return;
|
|
4421
|
-
event.preventDefault();
|
|
4422
|
-
setOpenPanel(true);
|
|
4423
|
-
};
|
|
4424
|
-
document.addEventListener("keydown", onKey);
|
|
4425
|
-
return () => {
|
|
4426
|
-
document.removeEventListener("keydown", onKey);
|
|
4427
|
-
};
|
|
4428
|
-
}, [enabled]);
|
|
4429
|
-
if (!enabled) return null;
|
|
4430
|
-
return (0, react.createElement)("div", { className: wide ? "dsws_root" : "dsws_root dsws_rootRail" }, [(0, react.createElement)("button", {
|
|
4431
|
-
key: "btn",
|
|
4432
|
-
type: "button",
|
|
4433
|
-
className: wide ? "dsws_button" : "dsws_button dsws_buttonRail",
|
|
4434
|
-
title: `${translate$1(t, "panel.entry")}(${LABELS.invokeLabel})`,
|
|
4435
|
-
"aria-label": `${translate$1(t, "panel.entry")}(${translate$1(t, "panel.titleSearch")} / ${translate$1(t, "panel.contentSearch")},${LABELS.invokeLabel})`,
|
|
4436
|
-
"aria-expanded": openPanel,
|
|
4437
|
-
onClick: () => {
|
|
4438
|
-
setOpenPanel(true);
|
|
4439
|
-
}
|
|
4440
|
-
}, [
|
|
4441
|
-
searchIcon(),
|
|
4442
|
-
wide && (0, react.createElement)("span", {
|
|
4443
|
-
key: "label",
|
|
4444
|
-
className: "dsws_buttonLabel"
|
|
4445
|
-
}, translate$1(t, "panel.entry")),
|
|
4446
|
-
wide && (0, react.createElement)("kbd", {
|
|
4447
|
-
key: "key",
|
|
4448
|
-
className: "dsws_kbd"
|
|
4449
|
-
}, LABELS.invokeLabel)
|
|
4450
|
-
]), openPanel && (0, react.createElement)(SwitchPanel, {
|
|
4451
|
-
key: "panel",
|
|
4452
|
-
t,
|
|
4453
|
-
onClose: () => {
|
|
4454
|
-
setOpenPanel(false);
|
|
4455
|
-
},
|
|
4456
|
-
open: (sessionId) => {
|
|
4457
|
-
setOpenPanel(false);
|
|
4458
|
-
open(sessionId);
|
|
4459
|
-
}
|
|
4460
|
-
})]);
|
|
4461
|
-
}
|
|
4462
|
-
/** ------------------------------------------------------------------ helpers */
|
|
4463
|
-
/** Format an epoch-ms timestamp: today → HH:mm, else YYYY-MM-DD HH:mm. */
|
|
4464
|
-
function fmtTime(ms) {
|
|
4465
|
-
if (!ms || typeof ms !== "number") return "";
|
|
4466
|
-
try {
|
|
4467
|
-
const d = new Date(ms);
|
|
4468
|
-
const now = /* @__PURE__ */ new Date();
|
|
4469
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
4470
|
-
const sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
|
|
4471
|
-
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
4472
|
-
if (sameDay) return time;
|
|
4473
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${time}`;
|
|
4474
|
-
} catch {
|
|
4475
|
-
return "";
|
|
4476
|
-
}
|
|
4477
|
-
}
|
|
4478
|
-
/** Short label for a content-hit event type. */
|
|
4479
|
-
function typeLabel(t, type) {
|
|
4480
|
-
if (type === "user/message" || type === "assistant/message" || type === "tool/call" || type === "tool/result") return translate$1(t, `type.${type}`);
|
|
4481
|
-
return type;
|
|
4482
|
-
}
|
|
4483
|
-
/** Inline search icon (stroke aligned with the product's 1.75 hairline). */
|
|
4484
|
-
function searchIcon() {
|
|
4485
|
-
return (0, react.createElement)("svg", {
|
|
4486
|
-
width: 14,
|
|
4487
|
-
height: 14,
|
|
4488
|
-
viewBox: "0 0 16 16",
|
|
4489
|
-
fill: "none",
|
|
4490
|
-
"aria-hidden": true
|
|
4491
|
-
}, (0, react.createElement)("circle", {
|
|
4492
|
-
cx: 7,
|
|
4493
|
-
cy: 7,
|
|
4494
|
-
r: 4.5,
|
|
4495
|
-
stroke: "currentColor",
|
|
4496
|
-
strokeWidth: 1.75,
|
|
4497
|
-
fill: "none"
|
|
4498
|
-
}), (0, react.createElement)("path", {
|
|
4499
|
-
d: "M10.5 10.5 L14 14",
|
|
4500
|
-
stroke: "currentColor",
|
|
4501
|
-
strokeWidth: 1.75,
|
|
4502
|
-
strokeLinecap: "round"
|
|
4503
|
-
}));
|
|
4504
|
-
}
|
|
4505
|
-
//#endregion
|
|
4506
|
-
//#region src/client/search/card.tsx
|
|
4507
|
-
/**
|
|
4508
|
-
* Session-search settings card(0.1.7 起不再挂载:设置表单由 host 侧 .volatile() 字段自动生成)。
|
|
4509
|
-
* plugin, following the dsh-thinking-levels card pattern.
|
|
4510
|
-
*
|
|
4511
|
-
* The card binds the `switch-search` settings namespace through the
|
|
4512
|
-
* `settingsScope` cordis service and renders its fields as one editable card:
|
|
4513
|
-
* the enable switch, the default panel mode, the independent-index sync
|
|
4514
|
-
* knobs, and the index-lifecycle block (status, the non-destructive 整理
|
|
4515
|
-
* button, and the JSON snapshot export/import seam). Every change commits
|
|
4516
|
-
* immediately through the scope (no staged form).
|
|
4517
|
-
*
|
|
4518
|
-
* Kept dependency-free beyond react: the scope is subscribed with
|
|
4519
|
-
* `useSyncExternalStore`, and the controls are plain HTML reusing the
|
|
4520
|
-
* stylesheet the client half injects.
|
|
4521
|
-
*/
|
|
4522
|
-
/** Row shared by every field of the card. */
|
|
4523
|
-
function Row(props) {
|
|
4524
|
-
return (0, react.createElement)("div", { className: "dsws_setRow" }, [(0, react.createElement)("div", {
|
|
4525
|
-
key: "text",
|
|
4526
|
-
className: "dsws_setText"
|
|
4527
|
-
}, [(0, react.createElement)("span", {
|
|
4528
|
-
key: "t",
|
|
4529
|
-
className: "dsws_setTitle"
|
|
4530
|
-
}, props.title), props.desc !== void 0 && (0, react.createElement)("span", {
|
|
4531
|
-
key: "d",
|
|
4532
|
-
className: "dsws_setDesc"
|
|
4533
|
-
}, props.desc)]), (0, react.createElement)("div", { key: "ctl" }, props.control)]);
|
|
4534
|
-
}
|
|
4535
|
-
/**
|
|
4536
|
-
* A boolean switch editing one namespace field.
|
|
4537
|
-
*
|
|
4538
|
-
* Drawn entirely with inline styles (thinking-levels discipline): the card
|
|
4539
|
-
* must not depend on the injected stylesheet — scoped or late-loaded settings
|
|
4540
|
-
* pages left the class-based switch rendering as a bare checkbox dot.
|
|
4541
|
-
*/
|
|
4542
|
-
function Toggle(props) {
|
|
4543
|
-
const checked = props.checked;
|
|
4544
|
-
return (0, react.createElement)("label", { style: {
|
|
4545
|
-
position: "relative",
|
|
4546
|
-
width: "40px",
|
|
4547
|
-
height: "22px",
|
|
4548
|
-
flex: "none",
|
|
4549
|
-
display: "inline-block",
|
|
4550
|
-
cursor: props.writable ? "pointer" : "not-allowed"
|
|
4551
|
-
} }, [
|
|
4552
|
-
(0, react.createElement)("input", {
|
|
4553
|
-
key: "input",
|
|
4554
|
-
type: "checkbox",
|
|
4555
|
-
checked,
|
|
4556
|
-
disabled: !props.writable,
|
|
4557
|
-
onChange: (e) => props.onChange(e.target.checked),
|
|
4558
|
-
style: {
|
|
4559
|
-
position: "absolute",
|
|
4560
|
-
inset: 0,
|
|
4561
|
-
width: "100%",
|
|
4562
|
-
height: "100%",
|
|
4563
|
-
opacity: 0,
|
|
4564
|
-
margin: 0,
|
|
4565
|
-
cursor: props.writable ? "pointer" : "not-allowed"
|
|
4566
|
-
}
|
|
4567
|
-
}),
|
|
4568
|
-
(0, react.createElement)("span", {
|
|
4569
|
-
key: "track",
|
|
4570
|
-
style: {
|
|
4571
|
-
position: "absolute",
|
|
4572
|
-
inset: 0,
|
|
4573
|
-
background: checked ? "var(--dsw-alias-state-business-primary, #4c6ef5)" : "var(--dsw-alias-bg-module-platform, rgba(127,127,127,0.25))",
|
|
4574
|
-
border: `1px solid ${checked ? "var(--dsw-alias-state-business-primary, #4c6ef5)" : "var(--dsw-alias-border-l2, rgba(127,127,127,0.35))"}`,
|
|
4575
|
-
borderRadius: "11px",
|
|
4576
|
-
transition: "background .15s ease, border-color .15s ease",
|
|
4577
|
-
pointerEvents: "none"
|
|
4578
|
-
}
|
|
4579
|
-
}),
|
|
4580
|
-
(0, react.createElement)("span", {
|
|
4581
|
-
key: "thumb",
|
|
4582
|
-
style: {
|
|
4583
|
-
position: "absolute",
|
|
4584
|
-
top: "2px",
|
|
4585
|
-
left: "2px",
|
|
4586
|
-
width: "16px",
|
|
4587
|
-
height: "16px",
|
|
4588
|
-
background: "#fff",
|
|
4589
|
-
borderRadius: "50%",
|
|
4590
|
-
boxShadow: "0 1px 2px rgba(0,0,0,.2)",
|
|
4591
|
-
transition: "transform .15s ease",
|
|
4592
|
-
transform: checked ? "translateX(18px)" : "none",
|
|
4593
|
-
pointerEvents: "none"
|
|
4594
|
-
}
|
|
4595
|
-
})
|
|
4596
|
-
]);
|
|
4597
|
-
}
|
|
4598
|
-
/**
|
|
4599
|
-
* A status pill in the official ConnectionIndicator visual language: rounded
|
|
4600
|
-
* chip, semantic state tokens, animated dots while syncing.
|
|
4601
|
-
*/
|
|
4602
|
-
function StatusPill(props) {
|
|
4603
|
-
const stateClass = props.state === "ready" ? "dsws_pillReady" : props.state === "syncing" ? "dsws_pillWarn" : props.state === "error" ? "dsws_pillError" : "dsws_pillNeutral";
|
|
4604
|
-
return (0, react.createElement)("span", { className: `dsws_pill ${stateClass}` }, [(0, react.createElement)("span", {
|
|
4605
|
-
key: "icon",
|
|
4606
|
-
className: "dsws_pillIcon",
|
|
4607
|
-
"aria-hidden": true
|
|
4608
|
-
}, props.icon), (0, react.createElement)("span", {
|
|
4609
|
-
key: "label",
|
|
4610
|
-
className: "dsws_pillLabel"
|
|
4611
|
-
}, [(0, react.createElement)("span", { key: "text" }, props.label), props.dots === true && (0, react.createElement)("span", {
|
|
4612
|
-
key: "dots",
|
|
4613
|
-
className: "dsws_pillDots",
|
|
4614
|
-
"aria-hidden": true
|
|
4615
|
-
}, (0, react.createElement)("span", {}, "."), (0, react.createElement)("span", {}, "."), (0, react.createElement)("span", {}, "."))])]);
|
|
4616
|
-
}
|
|
4617
|
-
/** The independent-index lifecycle block (status + 整理 + snapshot seam). */
|
|
4618
|
-
function IndexBlock(props) {
|
|
4619
|
-
const [status, setStatus] = (0, react.useState)(null);
|
|
4620
|
-
const [fetchError, setFetchError] = (0, react.useState)(null);
|
|
4621
|
-
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
4622
|
-
const [note, setNote] = (0, react.useState)(null);
|
|
4623
|
-
const [busy, setBusy] = (0, react.useState)(false);
|
|
4624
|
-
(0, react.useEffect)(() => {
|
|
4625
|
-
let cancelled = false;
|
|
4626
|
-
let timer;
|
|
4627
|
-
const refresh = () => {
|
|
4628
|
-
callHostAny("index-status", {}).then((res) => {
|
|
4629
|
-
if (cancelled) return;
|
|
4630
|
-
if (res.ok) {
|
|
4631
|
-
setStatus(res);
|
|
4632
|
-
setFetchError(null);
|
|
4633
|
-
} else {
|
|
4634
|
-
setStatus(null);
|
|
4635
|
-
setFetchError(res.error ?? "index-status 请求失败");
|
|
4636
|
-
}
|
|
4637
|
-
if (res.rebuild?.state === "building" || res.rebuild?.state === "swapping") timer = window.setTimeout(refresh, 2e3);
|
|
4638
|
-
else setBusy(false);
|
|
4639
|
-
});
|
|
4640
|
-
};
|
|
4641
|
-
refresh();
|
|
4642
|
-
return () => {
|
|
4643
|
-
cancelled = true;
|
|
4644
|
-
if (timer !== void 0) window.clearTimeout(timer);
|
|
4645
|
-
};
|
|
4646
|
-
}, [attempt]);
|
|
4647
|
-
const t = props.t;
|
|
4648
|
-
const rebuilding = status?.rebuild?.state === "building" || status?.rebuild?.state === "swapping";
|
|
4649
|
-
const rebuildError = status?.rebuild?.state === "error" ? status.rebuild.error : void 0;
|
|
4650
|
-
const syncFailures = status?.sync?.failures?.length ?? 0;
|
|
4651
|
-
const blocking = busy || rebuilding;
|
|
4652
|
-
const onRebuild = () => {
|
|
4653
|
-
setBusy(true);
|
|
4654
|
-
setNote(null);
|
|
4655
|
-
callHostAny("index-rebuild", {}).then((res) => {
|
|
4656
|
-
if (!res.ok) {
|
|
4657
|
-
setNote(translate$1(t, "card.action.failed", { error: res.error ?? "?" }));
|
|
4658
|
-
setBusy(false);
|
|
4659
|
-
}
|
|
4660
|
-
});
|
|
4661
|
-
};
|
|
4662
|
-
const onExport = () => {
|
|
4663
|
-
setBusy(true);
|
|
4664
|
-
setNote(null);
|
|
4665
|
-
downloadSnapshot().then(() => setNote(translate$1(t, "card.index.exported"))).catch((err) => setNote(translate$1(t, "card.action.failed", { error: String(err instanceof Error ? err.message : err) }))).finally(() => setBusy(false));
|
|
4666
|
-
};
|
|
4667
|
-
const onImportFile = (file) => {
|
|
4668
|
-
setBusy(true);
|
|
4669
|
-
setNote(null);
|
|
4670
|
-
file.text().then((text) => callHostAny("index-import", text, 3e4)).then((res) => {
|
|
4671
|
-
if (res.ok) setNote(translate$1(t, "card.index.import.started"));
|
|
4672
|
-
else setNote(res.error ?? translate$1(t, "card.index.importParse"));
|
|
4673
|
-
}).catch((err) => setNote(translate$1(t, "card.action.failed", { error: String(err instanceof Error ? err.message : err) }))).finally(() => setBusy(false));
|
|
4674
|
-
};
|
|
4675
|
-
const pillState = rebuilding ? "syncing" : status?.rebuild?.state === "error" || fetchError !== null ? "error" : status === null || status.available !== true ? "neutral" : "ready";
|
|
4676
|
-
const pillLabel = fetchError !== null ? "状态读取失败" : rebuilding ? translate$1(t, "card.index.rebuilding", {
|
|
4677
|
-
done: status?.rebuild?.done ?? 0,
|
|
4678
|
-
total: status?.rebuild?.total || "?"
|
|
4679
|
-
}) : status === null ? translate$1(t, "card.index.reading") : status.available === true ? translate$1(t, "card.index.desc", { indexed: status.sync?.indexed ?? "?" }) : translate$1(t, "card.index.empty");
|
|
4680
|
-
const statusLine = fetchError !== null ? `索引状态读取失败:${fetchError}。请完全重启 dsh web(浏览器刷新不会重载 Host 半)后重试。` : status === null ? translate$1(t, "card.index.reading") : rebuilding ? translate$1(t, "card.index.rebuilding", {
|
|
4681
|
-
done: status.rebuild?.done ?? 0,
|
|
4682
|
-
total: status.rebuild?.total || "?"
|
|
4683
|
-
}) : status.available === true ? `${translate$1(t, "card.index.desc", { indexed: status.sync?.indexed ?? "?" })}${(status.archives?.length ?? 0) > 0 ? translate$1(t, "card.index.archives", { archives: status.archives?.length }) : ""}` : translate$1(t, "card.index.empty");
|
|
4684
|
-
const rebuildDone = status?.rebuild?.done ?? 0;
|
|
4685
|
-
const rebuildTotal = status?.rebuild?.total ?? 0;
|
|
4686
|
-
const progressPct = rebuilding && rebuildTotal > 0 ? Math.min(100, Math.round(rebuildDone / rebuildTotal * 100)) : void 0;
|
|
4687
|
-
const progressBlock = rebuilding ? (0, react.createElement)("div", {
|
|
4688
|
-
key: "progress",
|
|
4689
|
-
style: {
|
|
4690
|
-
display: "flex",
|
|
4691
|
-
flexDirection: "column",
|
|
4692
|
-
gap: "4px",
|
|
4693
|
-
margin: "2px 0 6px",
|
|
4694
|
-
width: "100%"
|
|
4695
|
-
}
|
|
4696
|
-
}, [(0, react.createElement)("div", {
|
|
4697
|
-
key: "bar",
|
|
4698
|
-
style: {
|
|
4699
|
-
height: "6px",
|
|
4700
|
-
borderRadius: "3px",
|
|
4701
|
-
background: "var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,0.2))",
|
|
4702
|
-
overflow: "hidden"
|
|
4703
|
-
}
|
|
4704
|
-
}, (0, react.createElement)("div", { style: progressPct === void 0 ? {
|
|
4705
|
-
height: "100%",
|
|
4706
|
-
width: "30%",
|
|
4707
|
-
borderRadius: "3px",
|
|
4708
|
-
background: "var(--dsw-alias-state-business-primary, #4c6ef5)",
|
|
4709
|
-
opacity: .6
|
|
4710
|
-
} : {
|
|
4711
|
-
height: "100%",
|
|
4712
|
-
width: `${progressPct}%`,
|
|
4713
|
-
borderRadius: "3px",
|
|
4714
|
-
background: "var(--dsw-alias-state-business-primary, #4c6ef5)",
|
|
4715
|
-
transition: "width .4s ease"
|
|
4716
|
-
} })), (0, react.createElement)("span", { style: {
|
|
4717
|
-
color: "var(--dsw-alias-state-business-primary, #4c6ef5)",
|
|
4718
|
-
fontSize: "12px",
|
|
4719
|
-
lineHeight: "18px",
|
|
4720
|
-
fontWeight: 600,
|
|
4721
|
-
fontVariantNumeric: "tabular-nums"
|
|
4722
|
-
} }, progressPct === void 0 ? translate$1(t, "card.index.rebuilding", {
|
|
4723
|
-
done: rebuildDone,
|
|
4724
|
-
total: "?"
|
|
4725
|
-
}) : `${progressPct}% · ${translate$1(t, "card.index.rebuilding", {
|
|
4726
|
-
done: rebuildDone,
|
|
4727
|
-
total: rebuildTotal
|
|
4728
|
-
})}`)]) : null;
|
|
4729
|
-
return (0, react.createElement)("div", {
|
|
4730
|
-
className: "dsws_setRow",
|
|
4731
|
-
style: rebuilding ? {
|
|
4732
|
-
flexDirection: "column",
|
|
4733
|
-
alignItems: "stretch"
|
|
4734
|
-
} : void 0
|
|
4735
|
-
}, [
|
|
4736
|
-
(0, react.createElement)("div", {
|
|
4737
|
-
key: "text",
|
|
4738
|
-
className: "dsws_setText"
|
|
4739
|
-
}, [
|
|
4740
|
-
(0, react.createElement)("span", {
|
|
4741
|
-
key: "t",
|
|
4742
|
-
className: "dsws_setTitle"
|
|
4743
|
-
}, translate$1(t, "card.index")),
|
|
4744
|
-
(0, react.createElement)("span", {
|
|
4745
|
-
key: "p",
|
|
4746
|
-
style: {
|
|
4747
|
-
display: "flex",
|
|
4748
|
-
gap: "8px",
|
|
4749
|
-
alignItems: "center",
|
|
4750
|
-
flexWrap: "wrap"
|
|
4751
|
-
}
|
|
4752
|
-
}, [(0, react.createElement)(StatusPill, {
|
|
4753
|
-
key: "pill",
|
|
4754
|
-
state: pillState,
|
|
4755
|
-
label: pillLabel,
|
|
4756
|
-
icon: pillState === "ready" ? "✓" : pillState === "syncing" || pillState === "error" ? "!" : "·",
|
|
4757
|
-
dots: pillState === "syncing"
|
|
4758
|
-
}), (status?.archivedSessions ?? 0) > 0 && (0, react.createElement)("span", {
|
|
4759
|
-
key: "archived",
|
|
4760
|
-
className: "dsws_pill dsws_pillNeutral"
|
|
4761
|
-
}, translate$1(t, "panel.archived", { count: status?.archivedSessions ?? 0 }))]),
|
|
4762
|
-
(0, react.createElement)("span", {
|
|
4763
|
-
key: "d",
|
|
4764
|
-
className: "dsws_setDesc"
|
|
4765
|
-
}, statusLine),
|
|
4766
|
-
(status?.archivedSessions ?? 0) > 0 && (0, react.createElement)("span", {
|
|
4767
|
-
key: "archHint",
|
|
4768
|
-
className: "dsws_setDesc"
|
|
4769
|
-
}, status?.steward === "missing" ? translate$1(t, "card.index.archivedMissing") : translate$1(t, "card.index.archivedOwner")),
|
|
4770
|
-
rebuildError !== null && rebuildError !== void 0 && (0, react.createElement)("span", {
|
|
4771
|
-
key: "err",
|
|
4772
|
-
className: "dsws_setDesc"
|
|
4773
|
-
}, translate$1(t, "card.index.rebuildError", { error: rebuildError })),
|
|
4774
|
-
syncFailures > 0 && (0, react.createElement)("span", {
|
|
4775
|
-
key: "warn",
|
|
4776
|
-
className: "dsws_setDesc"
|
|
4777
|
-
}, translate$1(t, "card.index.failures", { count: syncFailures })),
|
|
4778
|
-
note !== null && (0, react.createElement)("span", {
|
|
4779
|
-
key: "note",
|
|
4780
|
-
className: "dsws_setDesc"
|
|
4781
|
-
}, note),
|
|
4782
|
-
(0, react.createElement)("span", {
|
|
4783
|
-
key: "hint",
|
|
4784
|
-
className: "dsws_setDesc"
|
|
4785
|
-
}, translate$1(t, "card.index.rebuild.hint"))
|
|
4786
|
-
]),
|
|
4787
|
-
progressBlock,
|
|
4788
|
-
(0, react.createElement)("div", {
|
|
4789
|
-
key: "btns",
|
|
4790
|
-
className: "dsws_btnRow"
|
|
4791
|
-
}, [
|
|
4792
|
-
(0, react.createElement)("button", {
|
|
4793
|
-
key: "rebuild",
|
|
4794
|
-
type: "button",
|
|
4795
|
-
className: "dsws_actBtn",
|
|
4796
|
-
disabled: blocking,
|
|
4797
|
-
onClick: onRebuild
|
|
4798
|
-
}, rebuilding ? translate$1(t, "card.index.rebuilding.btn") : translate$1(t, "card.index.rebuild")),
|
|
4799
|
-
(0, react.createElement)("button", {
|
|
4800
|
-
key: "export",
|
|
4801
|
-
type: "button",
|
|
4802
|
-
className: "dsws_actBtn",
|
|
4803
|
-
disabled: blocking || status?.available !== true,
|
|
4804
|
-
onClick: onExport
|
|
4805
|
-
}, translate$1(t, "card.index.export")),
|
|
4806
|
-
fetchError !== null && (0, react.createElement)("button", {
|
|
4807
|
-
key: "retryStatus",
|
|
4808
|
-
type: "button",
|
|
4809
|
-
className: "dsws_actBtn",
|
|
4810
|
-
onClick: () => {
|
|
4811
|
-
setAttempt((n) => n + 1);
|
|
4812
|
-
}
|
|
4813
|
-
}, "重试状态"),
|
|
4814
|
-
(0, react.createElement)("label", {
|
|
4815
|
-
key: "import",
|
|
4816
|
-
className: "dsws_actBtn"
|
|
4817
|
-
}, [translate$1(t, "card.index.import"), (0, react.createElement)("input", {
|
|
4818
|
-
key: "file",
|
|
4819
|
-
type: "file",
|
|
4820
|
-
accept: ".jsonl,.json,text/plain,application/json",
|
|
4821
|
-
style: { display: "none" },
|
|
4822
|
-
onChange: (e) => {
|
|
4823
|
-
const file = e.target.files?.[0] ?? void 0;
|
|
4824
|
-
e.target.value = "";
|
|
4825
|
-
if (file !== void 0 && file !== null) onImportFile(file);
|
|
4826
|
-
}
|
|
4827
|
-
})])
|
|
4828
|
-
])
|
|
4829
|
-
]);
|
|
4830
|
-
}
|
|
4831
|
-
/**
|
|
4832
|
-
* The settings card body: a collapsed drawer shell (title + description +
|
|
4833
|
-
* chevron, thinking-levels pattern) expanding into the namespace fields and
|
|
4834
|
-
* the index-lifecycle block.
|
|
4835
|
-
* @param props - locale seat (optional) and the bound namespace scope.
|
|
4836
|
-
*/
|
|
4837
|
-
function SearchSettingsCard(props) {
|
|
4838
|
-
const { scope } = props;
|
|
4839
|
-
const t = props.t;
|
|
4840
|
-
const [open, setOpen] = (0, react.useState)(true);
|
|
4841
|
-
const body = createCardBody({
|
|
4842
|
-
t,
|
|
4843
|
-
snapshot: (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot()),
|
|
4844
|
-
scope
|
|
4845
|
-
});
|
|
4846
|
-
return (0, react.createElement)("div", { style: {
|
|
4847
|
-
border: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35))",
|
|
4848
|
-
background: "var(--dsw-alias-bg-layer-3, rgba(127,127,127,0.05))",
|
|
4849
|
-
borderRadius: "12px",
|
|
4850
|
-
transition: "border-color 0.16s, background 0.16s"
|
|
4851
|
-
} }, [(0, react.createElement)("button", {
|
|
4852
|
-
key: "head",
|
|
4853
|
-
type: "button",
|
|
4854
|
-
"aria-expanded": open,
|
|
4855
|
-
style: {
|
|
4856
|
-
appearance: "none",
|
|
4857
|
-
width: "100%",
|
|
4858
|
-
font: "inherit",
|
|
4859
|
-
color: "inherit",
|
|
4860
|
-
textAlign: "left",
|
|
4861
|
-
cursor: "pointer",
|
|
4862
|
-
background: "none",
|
|
4863
|
-
border: 0,
|
|
4864
|
-
borderRadius: "12px",
|
|
4865
|
-
display: "flex",
|
|
4866
|
-
alignItems: "center",
|
|
4867
|
-
gap: "12px",
|
|
4868
|
-
padding: "14px 16px"
|
|
4869
|
-
},
|
|
4870
|
-
onClick: () => {
|
|
4871
|
-
setOpen((current) => !current);
|
|
4872
|
-
}
|
|
4873
|
-
}, [(0, react.createElement)("span", {
|
|
4874
|
-
key: "text",
|
|
4875
|
-
style: {
|
|
4876
|
-
flex: "1 1 0%",
|
|
4877
|
-
minWidth: 0
|
|
4878
|
-
}
|
|
4879
|
-
}, [(0, react.createElement)("div", {
|
|
4880
|
-
key: "title",
|
|
4881
|
-
style: {
|
|
4882
|
-
fontSize: "14px",
|
|
4883
|
-
fontWeight: 600,
|
|
4884
|
-
color: "var(--dsw-alias-label-primary)"
|
|
4885
|
-
}
|
|
4886
|
-
}, translate$1(t, "card.title")), (0, react.createElement)("div", {
|
|
4887
|
-
key: "desc",
|
|
4888
|
-
style: {
|
|
4889
|
-
color: "var(--dsw-alias-label-tertiary, rgba(127,127,127,0.8))",
|
|
4890
|
-
fontSize: "13px",
|
|
4891
|
-
lineHeight: 1.5
|
|
4892
|
-
}
|
|
4893
|
-
}, translate$1(t, "card.description"))]), (0, react.createElement)("svg", {
|
|
4894
|
-
key: "chev",
|
|
4895
|
-
width: 16,
|
|
4896
|
-
height: 16,
|
|
4897
|
-
viewBox: "0 0 16 16",
|
|
4898
|
-
"aria-hidden": true,
|
|
4899
|
-
style: {
|
|
4900
|
-
color: "var(--dsw-alias-label-tertiary, rgba(127,127,127,0.8))",
|
|
4901
|
-
flex: "0 0 auto",
|
|
4902
|
-
transition: "transform 0.16s",
|
|
4903
|
-
transform: open ? "rotate(180deg)" : "none"
|
|
4904
|
-
}
|
|
4905
|
-
}, (0, react.createElement)("path", {
|
|
4906
|
-
d: "M4 6l4 4 4-4",
|
|
4907
|
-
fill: "none",
|
|
4908
|
-
stroke: "currentColor",
|
|
4909
|
-
strokeWidth: 1.5,
|
|
4910
|
-
strokeLinecap: "round",
|
|
4911
|
-
strokeLinejoin: "round"
|
|
4912
|
-
}))]), open && (0, react.createElement)("div", {
|
|
4913
|
-
key: "body",
|
|
4914
|
-
style: { padding: "12px 16px" }
|
|
4915
|
-
}, body)]);
|
|
4916
|
-
}
|
|
4917
|
-
/** The card's expandable content: namespace fields plus the index block. */
|
|
4918
|
-
function createCardBody(props) {
|
|
4919
|
-
const t = props.t;
|
|
4920
|
-
const snapshot = props.snapshot;
|
|
4921
|
-
const scope = props.scope;
|
|
4922
|
-
if (snapshot.status === "unavailable") return [(0, react.createElement)("div", {
|
|
4923
|
-
className: "dsws_setRow",
|
|
4924
|
-
key: "unavailable"
|
|
4925
|
-
}, (0, react.createElement)("span", { className: "dsws_setTitle" }, translate$1(t, "card.unavailable")))];
|
|
4926
|
-
const value = snapshot.value ?? {};
|
|
4927
|
-
const writable = snapshot.writable;
|
|
4928
|
-
const syncIntervalSeconds = Math.round((value.syncIntervalMs ?? 3e4) / 1e3);
|
|
4929
|
-
const archiveKeep = value.archiveKeep ?? 2;
|
|
4930
|
-
const children = [
|
|
4931
|
-
(0, react.createElement)(Row, {
|
|
4932
|
-
key: "enable",
|
|
4933
|
-
title: translate$1(t, "card.enabled"),
|
|
4934
|
-
desc: translate$1(t, "card.enabled.desc"),
|
|
4935
|
-
control: (0, react.createElement)(Toggle, {
|
|
4936
|
-
checked: value.enabled ?? true,
|
|
4937
|
-
writable,
|
|
4938
|
-
onChange: (checked) => {
|
|
4939
|
-
scope.set("enabled", checked);
|
|
4940
|
-
}
|
|
4941
|
-
})
|
|
4942
|
-
}),
|
|
4943
|
-
(0, react.createElement)(Row, {
|
|
4944
|
-
key: "mode",
|
|
4945
|
-
title: translate$1(t, "card.defaultMode"),
|
|
4946
|
-
desc: translate$1(t, "card.defaultMode.desc"),
|
|
4947
|
-
control: (0, react.createElement)("div", {
|
|
4948
|
-
className: "dsws_seg",
|
|
4949
|
-
role: "group"
|
|
4950
|
-
}, ["title", "content"].map((mode) => (0, react.createElement)("button", {
|
|
4951
|
-
key: mode,
|
|
4952
|
-
type: "button",
|
|
4953
|
-
className: `dsws_segBtn${(value.defaultMode ?? "title") === mode ? " dsws_segBtnActive" : ""}`,
|
|
4954
|
-
"aria-pressed": (value.defaultMode ?? "title") === mode,
|
|
4955
|
-
disabled: !writable,
|
|
4956
|
-
onClick: () => {
|
|
4957
|
-
scope.set("defaultMode", mode);
|
|
4958
|
-
}
|
|
4959
|
-
}, mode === "title" ? translate$1(t, "card.mode.title") : translate$1(t, "card.mode.content"))))
|
|
4960
|
-
}),
|
|
4961
|
-
(0, react.createElement)(Row, {
|
|
4962
|
-
key: "autoSync",
|
|
4963
|
-
title: translate$1(t, "card.autoSync"),
|
|
4964
|
-
desc: translate$1(t, "card.autoSync.desc"),
|
|
4965
|
-
control: (0, react.createElement)(Toggle, {
|
|
4966
|
-
checked: value.autoSync ?? true,
|
|
4967
|
-
writable,
|
|
4968
|
-
onChange: (checked) => {
|
|
4969
|
-
scope.set("autoSync", checked);
|
|
4970
|
-
}
|
|
4971
|
-
})
|
|
4972
|
-
}),
|
|
4973
|
-
(0, react.createElement)(Row, {
|
|
4974
|
-
key: "interval",
|
|
4975
|
-
title: translate$1(t, "card.syncInterval"),
|
|
4976
|
-
desc: translate$1(t, "card.syncInterval.desc"),
|
|
4977
|
-
control: (0, react.createElement)("input", {
|
|
4978
|
-
type: "number",
|
|
4979
|
-
min: 5,
|
|
4980
|
-
max: 3600,
|
|
4981
|
-
disabled: !writable,
|
|
4982
|
-
value: syncIntervalSeconds,
|
|
4983
|
-
style: {
|
|
4984
|
-
width: "72px",
|
|
4985
|
-
boxSizing: "border-box"
|
|
4986
|
-
},
|
|
4987
|
-
className: "dsws_search",
|
|
4988
|
-
onChange: (e) => {
|
|
4989
|
-
const seconds = Number(e.target.value);
|
|
4990
|
-
if (Number.isFinite(seconds) && seconds >= 5) scope.set("syncIntervalMs", Math.round(seconds * 1e3));
|
|
4991
|
-
}
|
|
4992
|
-
})
|
|
4993
|
-
}),
|
|
4994
|
-
(0, react.createElement)(Row, {
|
|
4995
|
-
key: "archiveKeep",
|
|
4996
|
-
title: translate$1(t, "card.archiveKeep"),
|
|
4997
|
-
desc: translate$1(t, "card.archiveKeep.desc"),
|
|
4998
|
-
control: (0, react.createElement)("input", {
|
|
4999
|
-
type: "number",
|
|
5000
|
-
min: 0,
|
|
5001
|
-
max: 20,
|
|
5002
|
-
disabled: !writable,
|
|
5003
|
-
value: archiveKeep,
|
|
5004
|
-
style: {
|
|
5005
|
-
width: "72px",
|
|
5006
|
-
boxSizing: "border-box"
|
|
5007
|
-
},
|
|
5008
|
-
className: "dsws_search",
|
|
5009
|
-
onChange: (e) => {
|
|
5010
|
-
const count = Number(e.target.value);
|
|
5011
|
-
if (Number.isFinite(count) && count >= 0) scope.set("archiveKeep", Math.round(count));
|
|
5012
|
-
}
|
|
5013
|
-
})
|
|
5014
|
-
})
|
|
5015
|
-
];
|
|
5016
|
-
const indexBlock = IndexBlock({ t });
|
|
5017
|
-
children.push(indexBlock);
|
|
5018
|
-
if (!writable) children.push((0, react.createElement)("div", {
|
|
5019
|
-
key: "ro",
|
|
5020
|
-
className: "dsws_setDesc"
|
|
5021
|
-
}, translate$1(t, "card.readonly")));
|
|
5022
|
-
return children;
|
|
5023
|
-
}
|
|
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 串;席位臂与注入臂共用此常量。
|
|
2508
|
+
* 删除图标(逐字内联宿主 ui-primitives IconTrashOutline16——工作区「删除」
|
|
2509
|
+
* 同款):单 path + fill=currentColor,16×16,随行文字色变化(上弦变红
|
|
2510
|
+
* 时图标一起红)。插件不能 value-import 宿主图标包(老线 ModuleLoader
|
|
2511
|
+
* 解析不了、未声明 peer),故内联 SVG 串;席位臂与注入臂共用此常量。
|
|
5049
2512
|
*/
|
|
5050
2513
|
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
2514
|
/** 图标槽内联样式(对齐宿主 .itemIcon:16×16 inline-flex flex:none)。 */
|
|
@@ -5237,7 +2700,7 @@ window.__ModuleLoader__.load({
|
|
|
5237
2700
|
if (Date.now() - at < 3e4) return cached;
|
|
5238
2701
|
at = Date.now();
|
|
5239
2702
|
try {
|
|
5240
|
-
cached =
|
|
2703
|
+
cached = await probeDirectDelete() === true;
|
|
5241
2704
|
} catch {
|
|
5242
2705
|
cached = false;
|
|
5243
2706
|
}
|
|
@@ -5249,8 +2712,7 @@ window.__ModuleLoader__.load({
|
|
|
5249
2712
|
const res = await callSteward("session-history-purge", { sessionIds: [sessionId] });
|
|
5250
2713
|
if (res.ok !== true) throw new Error(res.error ?? "purge failed");
|
|
5251
2714
|
}
|
|
5252
|
-
|
|
5253
|
-
//#region src/client/session-menu.tsx
|
|
2715
|
+
// src/client/session-menu.tsx
|
|
5254
2716
|
/**
|
|
5255
2717
|
* 侧栏会话「…」菜单删除臂(2026-10-20 用户定标,按左侧边栏实现换代识别)。
|
|
5256
2718
|
*
|
|
@@ -5262,7 +2724,7 @@ window.__ModuleLoader__.load({
|
|
|
5262
2724
|
* 本臂 order=500(末位,破坏性动作不抢位)。老线(≤0.1.5)inject 静默等待
|
|
5263
2725
|
* 无害;删除入口在老线仍由搜索面板行内按钮承担(同一 purge 链路)。
|
|
5264
2726
|
*
|
|
5265
|
-
* 可见性:行挂载时探针
|
|
2727
|
+
* 可见性:行挂载时探针 `session-health-status.switches.directDelete`(管理开关,缺省关)——
|
|
5266
2728
|
* 非 true(含探针失败/旧宿主半无字段)整行不渲染;两步确认与管理端/面板
|
|
5267
2729
|
* 同款(第一击上弦变红「确认删除」,第二击执行;点其它处菜单收起即解弦)。
|
|
5268
2730
|
*/
|
|
@@ -5287,8 +2749,8 @@ window.__ModuleLoader__.load({
|
|
|
5287
2749
|
const openState = typeof useMenuOpenState === "function" ? useMenuOpenState() : void 0;
|
|
5288
2750
|
(0, react.useEffect)(() => {
|
|
5289
2751
|
let disposed = false;
|
|
5290
|
-
|
|
5291
|
-
if (!disposed) setVisible(menuDeleteVisible(
|
|
2752
|
+
probeDirectDelete().then((enabled) => {
|
|
2753
|
+
if (!disposed) setVisible(menuDeleteVisible(enabled));
|
|
5292
2754
|
}).catch(() => {
|
|
5293
2755
|
if (!disposed) setVisible(false);
|
|
5294
2756
|
});
|
|
@@ -5349,8 +2811,7 @@ window.__ModuleLoader__.load({
|
|
|
5349
2811
|
dangerouslySetInnerHTML: { __html: TRASH_ICON_SVG }
|
|
5350
2812
|
}), (0, react.createElement)("span", { key: "label" }, armed ? t?.("menu.deleteSession.confirm") ?? "确认删除" : t?.("menu.deleteSession") ?? "删除会话")]);
|
|
5351
2813
|
}
|
|
5352
|
-
|
|
5353
|
-
//#region src/client/index.ts
|
|
2814
|
+
// src/client/index.ts
|
|
5354
2815
|
/** 字典命名空间(locale.register 用)。 */
|
|
5355
2816
|
const NS = "dsh-manage-session";
|
|
5356
2817
|
/** 客户端插件声明的注入面。 */
|
|
@@ -5382,84 +2843,86 @@ window.__ModuleLoader__.load({
|
|
|
5382
2843
|
.lc-ov-entry:not(.lc-ov-entry-rail){width:auto!important;flex:0 0 100%!important;min-width:0!important;justify-content:center!important;height:32px!important;margin:0!important;padding:0 10px!important}
|
|
5383
2844
|
.lc-ov-entry-label{flex:0 1 auto!important}
|
|
5384
2845
|
/* 宽栏:独占一整行、放弃主动收缩(flex-shrink:0),行内居中;收起轨道回落自然宽度。 */
|
|
5385
|
-
.
|
|
5386
|
-
.
|
|
2846
|
+
.dms_entryWrap{flex:0 1 auto;display:inline-flex;align-items:center;min-width:0}
|
|
2847
|
+
.dms_entryWrapWide{flex:0 0 100%;width:100%;justify-content:center;margin-left:0}
|
|
5387
2848
|
/* 高度 32px(内容 22px 行高 + 上下各 5px):宿主默认 42px 的上下裕度在多行
|
|
5388
2849
|
堆叠后过于松散;左右内边距对称(10px/10px),否则整行居中时按钮内容会因
|
|
5389
2850
|
不对称 padding 向左偏 1px。 */
|
|
5390
|
-
.
|
|
5391
|
-
.
|
|
5392
|
-
.
|
|
5393
|
-
.
|
|
5394
|
-
.
|
|
5395
|
-
.
|
|
5396
|
-
.
|
|
5397
|
-
|
|
5398
|
-
.
|
|
5399
|
-
.
|
|
5400
|
-
.
|
|
5401
|
-
.
|
|
5402
|
-
.
|
|
5403
|
-
.
|
|
5404
|
-
.
|
|
5405
|
-
.
|
|
5406
|
-
.
|
|
5407
|
-
.
|
|
5408
|
-
.
|
|
5409
|
-
.
|
|
5410
|
-
.
|
|
2851
|
+
.dms_footerEntry{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 10px;border:none;border-radius:12px;background:transparent;cursor:pointer;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:14px;line-height:22px;white-space:nowrap;overflow:hidden;transition:background-color 160ms ease-out,color 160ms ease-out}
|
|
2852
|
+
.dms_footerEntry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
2853
|
+
.dms_footerEntryRail{width:28px;height:28px;padding:0;gap:0;border-radius:50%}
|
|
2854
|
+
.dms_footerIcon{flex:none;font-size:15px;line-height:1}
|
|
2855
|
+
.dms_footerLabel{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2856
|
+
.dms_backdrop{position:fixed;inset:0;background:rgba(15,20,30,.42);z-index:1000}
|
|
2857
|
+
.dms_panel{position:fixed;z-index:1001;left:50%;top:6vh;transform:translateX(-50%);width:min(860px,94vw);max-height:86vh;overflow:auto;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:14px;box-shadow:0 18px 48px rgba(0,0,0,.18);padding:10px 14px 14px}
|
|
2858
|
+
/* 整页形态(顶栏工具行入口):取消浮动定位与遮罩观感,填满主栏。 */
|
|
2859
|
+
.dms_panel.dms_panelPage{position:relative;left:auto;top:auto;transform:none;z-index:auto;width:100%;max-width:none;height:100%;max-height:none;box-sizing:border-box;border:none;border-radius:0;box-shadow:none;background:transparent;padding:14px 18px 16px}
|
|
2860
|
+
.dms_dialogHead{display:flex;align-items:center;gap:10px;min-height:34px}
|
|
2861
|
+
.dms_dialogTitle{font-weight:600;color:var(--dsw-alias-label-primary,#111827);flex:1 1 auto;min-width:0}
|
|
2862
|
+
.dms_tabRow{display:inline-flex;gap:4px}
|
|
2863
|
+
.dms_tab{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:transparent;border-radius:8px;padding:3px 10px;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-secondary,#6b7280)}
|
|
2864
|
+
.dms_tabActive{background:var(--dsw-alias-interactive-bg-active,rgba(63,99,216,.12));color:var(--dsw-alias-label-primary,#111827)}
|
|
2865
|
+
.dms_tabBody{display:flex;flex-direction:column;gap:8px;padding-top:8px}
|
|
2866
|
+
.dms_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)}
|
|
2867
|
+
.dms_actBtn:disabled{opacity:.5;cursor:default}
|
|
2868
|
+
.dms_btnRow{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
|
|
2869
|
+
.dms_list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
|
|
2870
|
+
.dms_row{display:flex;gap:8px;align-items:center;padding:5px 8px;border-radius:8px;border:1px solid transparent}
|
|
2871
|
+
.dms_row:hover{border-color:var(--dsw-alias-border-l2,#e5e7eb)}
|
|
2872
|
+
.dms_meta{font-size:11px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
|
|
2873
|
+
.dms_uuid{font-family:var(--ds-font-family-code,monospace)}
|
|
5411
2874
|
/* 按行的磁盘占用:右对齐、不参与标题压缩,方便纵向比对「哪条最占地方」。 */
|
|
5412
|
-
.
|
|
5413
|
-
.
|
|
5414
|
-
.
|
|
5415
|
-
.
|
|
5416
|
-
.
|
|
5417
|
-
.
|
|
5418
|
-
.
|
|
5419
|
-
.
|
|
5420
|
-
.
|
|
5421
|
-
.
|
|
5422
|
-
.
|
|
5423
|
-
.
|
|
5424
|
-
.
|
|
5425
|
-
.
|
|
5426
|
-
.
|
|
5427
|
-
.
|
|
5428
|
-
.
|
|
5429
|
-
.
|
|
5430
|
-
.
|
|
2875
|
+
.dms_status{font-size:12px;color:var(--dsw-alias-label-secondary,#6b7280)}
|
|
2876
|
+
.dms_error{font-size:12px;color:var(--dsw-alias-state-error-primary,#dc2626)}
|
|
2877
|
+
.dms_empty{font-size:12px;color:var(--dsw-alias-label-tertiary,#9ca3af);padding:10px 2px}
|
|
2878
|
+
.dms_gateList{display:flex;flex-direction:column;gap:6px}
|
|
2879
|
+
.dms_gate{display:flex;flex-wrap:wrap;gap:6px;align-items:baseline;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:10px;padding:6px 10px}
|
|
2880
|
+
.dms_gateName{font-weight:600;font-size:12px}
|
|
2881
|
+
.dms_gateEvidence{font-size:12px;color:var(--dsw-alias-label-secondary,#6b7280);flex:1 1 100%}
|
|
2882
|
+
.dms_levelPill{font-size:11px;border-radius:999px;padding:1px 8px;border:1px solid transparent}
|
|
2883
|
+
.dms_level_ok{background:rgba(22,163,74,.12);color:#15803d}
|
|
2884
|
+
.dms_level_warn{background:rgba(217,119,6,.14);color:#b45309}
|
|
2885
|
+
.dms_level_fail{background:rgba(220,38,38,.14);color:#b91c1c}
|
|
2886
|
+
.dms_level_skipped{background:rgba(107,114,128,.14);color:#4b5563}
|
|
2887
|
+
.dms_progressRow{display:flex;align-items:center;gap:8px;margin-top:2px}
|
|
2888
|
+
.dms_progressTrack{flex:1;height:6px;border-radius:999px;background:var(--dsw-alias-bg-layer-1,#eef0f3);overflow:hidden}
|
|
2889
|
+
.dms_progressFill{height:100%;border-radius:999px;background:var(--dsw-alias-state-business-primary,#3d5be0);transition:width .2s ease}
|
|
2890
|
+
.dms_progressIndeterminate{width:35%;animation:dssSlide 1.1s ease-in-out infinite}
|
|
2891
|
+
.dms_why{font-size:12px;color:var(--dsw-alias-label-secondary,#6b7280);flex:1;min-width:0}
|
|
2892
|
+
.dms_cacheRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px}
|
|
2893
|
+
.dms_spinner{width:11px;height:11px;margin-right:6px;border-radius:50%;display:inline-block;vertical-align:-1px;border:2px solid currentColor;border-top-color:transparent;animation:dssSpin .7s linear infinite}
|
|
5431
2894
|
@keyframes dssSpin{to{transform:rotate(360deg)}}
|
|
5432
2895
|
@keyframes dssSlide{0%{margin-left:-35%}100%{margin-left:100%}}
|
|
5433
|
-
@media (prefers-reduced-motion:reduce){.
|
|
5434
|
-
.
|
|
5435
|
-
.
|
|
5436
|
-
.
|
|
5437
|
-
.
|
|
5438
|
-
.
|
|
5439
|
-
.
|
|
5440
|
-
.
|
|
5441
|
-
.
|
|
5442
|
-
.
|
|
5443
|
-
.
|
|
5444
|
-
.
|
|
5445
|
-
.
|
|
5446
|
-
.
|
|
5447
|
-
.
|
|
5448
|
-
.
|
|
5449
|
-
.
|
|
5450
|
-
.
|
|
5451
|
-
.
|
|
5452
|
-
.
|
|
5453
|
-
.
|
|
5454
|
-
.
|
|
5455
|
-
.
|
|
5456
|
-
.
|
|
5457
|
-
.
|
|
5458
|
-
.
|
|
5459
|
-
.
|
|
5460
|
-
.
|
|
5461
|
-
.
|
|
5462
|
-
.
|
|
2896
|
+
@media (prefers-reduced-motion:reduce){.dms_spinner{animation:none}.dms_progressIndeterminate{animation:none;width:100%;opacity:.5}.dms_setChev{transition:none}.dms_setCard{transition:none}}
|
|
2897
|
+
.dms_verdict{font-size:12px;line-height:18px;border-radius:8px;padding:6px 10px;margin-top:6px;border:1px solid transparent}
|
|
2898
|
+
.dms_verdict_repaired{background:rgba(22,163,74,.10);color:#15803d;border-color:rgba(22,163,74,.28)}
|
|
2899
|
+
.dms_verdict_repaired-with-residual{background:rgba(217,119,6,.10);color:#b45309;border-color:rgba(217,119,6,.28)}
|
|
2900
|
+
.dms_verdict_nothing-to-do{background:rgba(107,114,128,.10);color:#4b5563;border-color:rgba(107,114,128,.28)}
|
|
2901
|
+
.dms_verdict_not-applicable{background:rgba(107,114,128,.10);color:#4b5563;border-color:rgba(107,114,128,.28)}
|
|
2902
|
+
.dms_verdict_failed{background:rgba(220,38,38,.10);color:#b91c1c;border-color:rgba(220,38,38,.28)}
|
|
2903
|
+
.dms_phaseRow{display:flex;gap:8px;align-items:center}
|
|
2904
|
+
.dms_phase{font-weight:600;font-size:12px}
|
|
2905
|
+
.dms_cmdBlock{display:flex;flex-direction:column;gap:4px;border:1px dashed var(--dsw-alias-border-l2,#e5e7eb);border-radius:10px;padding:6px 8px}
|
|
2906
|
+
.dms_cmdLine{display:flex;gap:6px;align-items:center}
|
|
2907
|
+
.dms_cmd{flex:1 1 auto;font-family:var(--ds-font-family-code,monospace);font-size:11px;white-space:pre-wrap;word-break:break-all}
|
|
2908
|
+
.dms_setCard{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-3,rgba(127,127,127,.05));border-radius:12px;transition:border-color .16s,background .16s}
|
|
2909
|
+
.dms_setHead{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:none;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}
|
|
2910
|
+
.dms_setHeadText{display:flex;flex-direction:column;gap:2px;flex:1 1 0%;min-width:0}
|
|
2911
|
+
.dms_setCardTitle{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#111827)}
|
|
2912
|
+
.dms_setCardDesc{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#9ca3af)}
|
|
2913
|
+
.dms_setChev{color:var(--dsw-alias-label-tertiary,#9ca3af);flex:0 0 auto;transition:transform .16s}
|
|
2914
|
+
.dms_setCardOpen .dms_setChev{transform:rotate(180deg)}
|
|
2915
|
+
.dms_setBody{display:flex;flex-direction:column;gap:2px;padding:0 16px 12px}
|
|
2916
|
+
.dms_setRow{display:flex;align-items:center;gap:10px;padding:6px 0}
|
|
2917
|
+
.dms_setText{display:flex;flex-direction:column;flex:1 1 auto;min-width:0}
|
|
2918
|
+
.dms_setTitle{font-size:13px;color:var(--dsw-alias-label-primary,#111827)}
|
|
2919
|
+
.dms_setDesc{font-size:12px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
|
|
2920
|
+
.dms_toggle{display:inline-flex;align-items:center;cursor:pointer;position:relative}
|
|
2921
|
+
.dms_toggle input{position:absolute;opacity:0;width:0;height:0}
|
|
2922
|
+
.dms_toggleTrack{width:34px;height:18px;border-radius:999px;background:var(--dsw-alias-border-l2,#d1d5db);position:relative;transition:background .15s ease}
|
|
2923
|
+
.dms_toggleKnob{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:left .15s ease}
|
|
2924
|
+
.dms_toggleOn .dms_toggleTrack{background:var(--dsw-alias-button-primary-fill,#3f63d8)}
|
|
2925
|
+
.dms_toggleOn .dms_toggleKnob{left:18px}
|
|
5463
2926
|
`;
|
|
5464
2927
|
document.head.appendChild(style);
|
|
5465
2928
|
return () => {
|
|
@@ -5473,57 +2936,71 @@ window.__ModuleLoader__.load({
|
|
|
5473
2936
|
function apply(ctx) {
|
|
5474
2937
|
ctx.effect(() => injectStyles(), "dsh-manage-session: stylesheet");
|
|
5475
2938
|
const locale = ctx.get("locale");
|
|
5476
|
-
if (locale !== void 0 && typeof locale.register === "function")
|
|
5477
|
-
|
|
5478
|
-
ctx.effect(() => locale.register(NS$1, dictionaries), "dsh-manage-session: search dictionaries");
|
|
5479
|
-
}
|
|
5480
|
-
ctx.effect(() => injectStyles$1(), "dsh-manage-session: search stylesheet");
|
|
5481
|
-
const configForms = ctx.get("configForms");
|
|
5482
|
-
const bound = withBridgeFallback(configForms?.get(STEWARD_ENTRY_ID), stewardBridgeScope());
|
|
5483
|
-
const searchBound = withBridgeFallback(configForms?.get(STEWARD_ENTRY_ID), searchBridgeScope());
|
|
2939
|
+
if (locale !== void 0 && typeof locale.register === "function") ctx.effect(() => locale.register(NS, dictionaries$1), "dsh-manage-session: dictionaries");
|
|
2940
|
+
const bound = withBridgeFallback(ctx.get("configForms")?.get(STEWARD_ENTRY_ID), stewardBridgeScope());
|
|
5484
2941
|
const slots = ctx.get("slots");
|
|
5485
2942
|
if (slots === void 0) return;
|
|
5486
|
-
const
|
|
2943
|
+
const openSession = (sessionId) => {
|
|
5487
2944
|
openSessionThrough((name) => ctx.get(name), sessionId);
|
|
5488
2945
|
};
|
|
5489
|
-
|
|
2946
|
+
const closeToolPanel = () => {
|
|
5490
2947
|
try {
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
2948
|
+
ctx.get("layout")?.selectPanel?.(null);
|
|
2949
|
+
} catch {}
|
|
2950
|
+
};
|
|
2951
|
+
ctx.effect(() => {
|
|
2952
|
+
let rowSeat = false;
|
|
2953
|
+
let mainSeat = false;
|
|
2954
|
+
let footerOff;
|
|
2955
|
+
let topOff;
|
|
2956
|
+
const sync = () => {
|
|
2957
|
+
const topReady = rowSeat && mainSeat;
|
|
2958
|
+
if (topReady && topOff === void 0) {
|
|
2959
|
+
if (footerOff !== void 0) {
|
|
2960
|
+
const off = footerOff;
|
|
2961
|
+
footerOff = void 0;
|
|
2962
|
+
off();
|
|
2963
|
+
}
|
|
2964
|
+
topOff = mountTopToolEntries(slots, bound, openSession, closeToolPanel);
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
if (!topReady && footerOff === void 0 && topOff === void 0) footerOff = mountFooterEntries(slots, bound, openSession);
|
|
2968
|
+
};
|
|
2969
|
+
let wired = false;
|
|
2970
|
+
const onSeat = () => {
|
|
2971
|
+
if (wired) sync();
|
|
2972
|
+
};
|
|
2973
|
+
const offRowProbe = slots.inject(TOP_TOOL_SEAT, () => {
|
|
2974
|
+
rowSeat = true;
|
|
2975
|
+
onSeat();
|
|
2976
|
+
return () => {
|
|
2977
|
+
rowSeat = false;
|
|
2978
|
+
};
|
|
2979
|
+
}, "dsh-manage-session: probe top tool seat");
|
|
2980
|
+
const offMainProbe = slots.inject(MAIN_SEAT, () => {
|
|
2981
|
+
mainSeat = true;
|
|
2982
|
+
onSeat();
|
|
2983
|
+
return () => {
|
|
2984
|
+
mainSeat = false;
|
|
2985
|
+
};
|
|
2986
|
+
}, "dsh-manage-session: probe main page seat");
|
|
2987
|
+
wired = true;
|
|
2988
|
+
sync();
|
|
2989
|
+
return () => {
|
|
2990
|
+
offRowProbe();
|
|
2991
|
+
offMainProbe();
|
|
2992
|
+
if (footerOff !== void 0) {
|
|
2993
|
+
const off = footerOff;
|
|
2994
|
+
footerOff = void 0;
|
|
2995
|
+
off();
|
|
2996
|
+
}
|
|
2997
|
+
if (topOff !== void 0) {
|
|
2998
|
+
const off = topOff;
|
|
2999
|
+
topOff = void 0;
|
|
3000
|
+
off();
|
|
3001
|
+
}
|
|
3002
|
+
};
|
|
3003
|
+
}, "dsh-manage-session: sidebar entry");
|
|
5527
3004
|
slots.inject("dsh-family.tab", () => slots.register({
|
|
5528
3005
|
name: "dsh-family.tab",
|
|
5529
3006
|
id: STEWARD_ENTRY_ID,
|
|
@@ -5537,16 +3014,10 @@ window.__ModuleLoader__.load({
|
|
|
5537
3014
|
slots.inject("plugins.bundle.config", () => slots.register({
|
|
5538
3015
|
name: "plugins.bundle.config",
|
|
5539
3016
|
key: STEWARD_ENTRY_ID
|
|
5540
|
-
}, (props) => (0, react.createElement)(
|
|
5541
|
-
display: "grid",
|
|
5542
|
-
gap: "12px"
|
|
5543
|
-
} }, [(0, react.createElement)(SearchSettingsCard, {
|
|
5544
|
-
scope: searchBound,
|
|
5545
|
-
t: props.t
|
|
5546
|
-
}), (0, react.createElement)(StewardSettingsCard, {
|
|
3017
|
+
}, (props) => (0, react.createElement)(StewardSettingsCard, {
|
|
5547
3018
|
scope: bound,
|
|
5548
3019
|
t: props.t
|
|
5549
|
-
})
|
|
3020
|
+
})), "dsh-manage-session: plugins page config card");
|
|
5550
3021
|
slots.inject(SESSION_MENU_SEAT, () => slots.register({
|
|
5551
3022
|
name: SESSION_MENU_SEAT,
|
|
5552
3023
|
id: SESSION_MENU_DELETE_ID,
|
|
@@ -5594,7 +3065,7 @@ window.__ModuleLoader__.load({
|
|
|
5594
3065
|
const FAMILY_CHILD_KEY = "dsh-family.tab";
|
|
5595
3066
|
/** 接管宽限期(由分级派生)。 */
|
|
5596
3067
|
const FAMILY_HOST_GRACE_MS = 2900;
|
|
5597
|
-
/** 接管节的账本投影 hooks(与 thinking-levels 的 FamilySectionInjected
|
|
3068
|
+
/** 接管节的账本投影 hooks(与 thinking-levels 的 FamilySectionInjected 同形)。 */
|
|
5598
3069
|
function makeFamilyTabsHooks(slots) {
|
|
5599
3070
|
let version = -1;
|
|
5600
3071
|
let tabs = [];
|
|
@@ -5620,11 +3091,7 @@ window.__ModuleLoader__.load({
|
|
|
5620
3091
|
subscribe: (listener) => slots.subscribe(FAMILY_CHILD_KEY, listener)
|
|
5621
3092
|
};
|
|
5622
3093
|
}
|
|
5623
|
-
/**
|
|
5624
|
-
* 家族节接管组件(fallback host section):纯通用渲染 —— 把 `dsh-family.tab`
|
|
5625
|
-
* 账本里的每张贡献卡(含本插件自己的,接管时本插件也声明了该子席位)按
|
|
5626
|
-
* order 依次 renderSlot;各卡本身是默认展开的抽屉,整节即一页抽屉。
|
|
5627
|
-
*/
|
|
3094
|
+
/** 家族节接管组件:纯通用渲染——把 `dsh-family.tab` 账本里的每张贡献卡按 order 渲染。 */
|
|
5628
3095
|
function StewardFamilySection(props) {
|
|
5629
3096
|
const tabs = (0, react.useSyncExternalStore)(props.hooks.tabs.subscribe, props.hooks.tabs.getSnapshot);
|
|
5630
3097
|
return (0, react.createElement)("div", { style: {
|
|
@@ -5644,12 +3111,11 @@ window.__ModuleLoader__.load({
|
|
|
5644
3111
|
...snapshot.value ?? {}
|
|
5645
3112
|
};
|
|
5646
3113
|
const healthCheck = config.healthCheck !== false;
|
|
5647
|
-
const
|
|
5648
|
-
const visible = config.enabled !== false && (healthCheck || searchEnabled);
|
|
3114
|
+
const visible = config.enabled !== false && healthCheck;
|
|
5649
3115
|
const wide = props.wide === true;
|
|
5650
3116
|
const label = translate(void 0, "panel.title");
|
|
5651
|
-
if (!visible) return (0, react.createElement)("span", { className: "
|
|
5652
|
-
return (0, react.createElement)("span", { className: wide ? "
|
|
3117
|
+
if (!visible) return (0, react.createElement)("span", { className: "dms_entryWrap" });
|
|
3118
|
+
return (0, react.createElement)("span", { className: wide ? "dms_entryWrap dms_entryWrapWide" : "dms_entryWrap" }, [(0, react.createElement)(StewardFooter, {
|
|
5653
3119
|
key: "btn",
|
|
5654
3120
|
title: label,
|
|
5655
3121
|
label,
|
|
@@ -5660,7 +3126,6 @@ window.__ModuleLoader__.load({
|
|
|
5660
3126
|
}), open ? (0, react.createElement)(StewardPanel, {
|
|
5661
3127
|
key: "panel",
|
|
5662
3128
|
healthCheck,
|
|
5663
|
-
searchEnabled,
|
|
5664
3129
|
openSession: props.openSession,
|
|
5665
3130
|
onClose: () => {
|
|
5666
3131
|
setOpen(false);
|
|
@@ -5668,27 +3133,141 @@ window.__ModuleLoader__.load({
|
|
|
5668
3133
|
}) : null]);
|
|
5669
3134
|
}
|
|
5670
3135
|
/**
|
|
5671
|
-
*
|
|
5672
|
-
*
|
|
5673
|
-
|
|
3136
|
+
* 顶栏工具槽位:面板行列表。宿主在「插件」(order 0)与「更多」启动器之间渲染
|
|
3137
|
+
* 这些行,行内 icon + label 默认左对齐;点击切换主栏面板。
|
|
3138
|
+
*/
|
|
3139
|
+
const TOP_TOOL_SEAT = "sidebar.panellist";
|
|
3140
|
+
/** 主栏整页槽位(keyed):面板行点击后在此渲染整页内容。 */
|
|
3141
|
+
const MAIN_SEAT = "main";
|
|
3142
|
+
/** 管理台页 id(同时作为 `sidebar.panellist` 行的 id 与 `main` 的 key)。 */
|
|
3143
|
+
const MANAGER_PANEL_ID = "manage-session";
|
|
3144
|
+
/**
|
|
3145
|
+
* 宿主是否提供顶栏工具槽位(诊断用):面板行席位 + 主栏整页席位同时被声明才算。
|
|
3146
|
+
* 实际位次判定走 apply 内的 inject 探测(服务面不保证暴露 specDynamic)。
|
|
3147
|
+
* @param slots - 槽位服务面。
|
|
3148
|
+
* @returns 两席都已声明时 `true`。
|
|
3149
|
+
*/
|
|
3150
|
+
function hasPanelToolSeat(slots) {
|
|
3151
|
+
if (typeof slots.specDynamic !== "function") return false;
|
|
3152
|
+
try {
|
|
3153
|
+
return slots.specDynamic(TOP_TOOL_SEAT) !== void 0 && slots.specDynamic(MAIN_SEAT) !== void 0;
|
|
3154
|
+
} catch {
|
|
3155
|
+
return false;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
/** 组合注销函数(逆序执行,幂等)。 */
|
|
3159
|
+
function composeDisposers(offs) {
|
|
3160
|
+
let done = false;
|
|
3161
|
+
return () => {
|
|
3162
|
+
if (done) return;
|
|
3163
|
+
done = true;
|
|
3164
|
+
for (const off of offs.reverse()) try {
|
|
3165
|
+
off();
|
|
3166
|
+
} catch {}
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
/**
|
|
3170
|
+
* 按开关状态维持一对注册(主栏页 + 面板行)。快照缺字段一律按「可见」处理。
|
|
3171
|
+
* @param scope - 设置 scope(缺失时视为缺省值)。
|
|
3172
|
+
* @param visible - 可见性判定。
|
|
3173
|
+
* @param mount - 可见时注册并返回注销函数。
|
|
3174
|
+
* @returns 解装函数。
|
|
3175
|
+
*/
|
|
3176
|
+
function gatedMount(scope, visible, mount) {
|
|
3177
|
+
let dispose;
|
|
3178
|
+
const apply = () => {
|
|
3179
|
+
const want = visible(scope?.getSnapshot?.().value ?? {});
|
|
3180
|
+
if (want && dispose === void 0) dispose = mount();
|
|
3181
|
+
else if (!want && dispose !== void 0) {
|
|
3182
|
+
const off = dispose;
|
|
3183
|
+
dispose = void 0;
|
|
3184
|
+
off();
|
|
3185
|
+
}
|
|
3186
|
+
};
|
|
3187
|
+
apply();
|
|
3188
|
+
const unsubscribe = scope?.subscribe(apply);
|
|
3189
|
+
return () => {
|
|
3190
|
+
unsubscribe?.();
|
|
3191
|
+
if (dispose !== void 0) {
|
|
3192
|
+
const off = dispose;
|
|
3193
|
+
dispose = void 0;
|
|
3194
|
+
off();
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
/** 底栏回落形态(老宿主 / 无顶栏工具行):一枚入口开对话框。 */
|
|
3199
|
+
function mountFooterEntries(slots, bound, openSession) {
|
|
3200
|
+
return composeDisposers([slots.inject("sidebar.footer.action", () => slots.register({
|
|
3201
|
+
name: "sidebar.footer.action",
|
|
3202
|
+
id: STEWARD_ENTRY_ID,
|
|
3203
|
+
order: 6
|
|
3204
|
+
}, (props) => (0, react.createElement)(StewardEntry, {
|
|
3205
|
+
...props,
|
|
3206
|
+
scope: bound,
|
|
3207
|
+
openSession
|
|
3208
|
+
})), "dsh-manage-session: sidebar footer entry")]);
|
|
3209
|
+
}
|
|
3210
|
+
/**
|
|
3211
|
+
* 顶栏工具行形态:面板行(order 6,落在「插件」行之后、「更多」之前)加主栏整页。
|
|
3212
|
+
* 点击行 → 宿主 `selectPanel(id)` 在主栏渲染整页;页内关闭动作回退到会话。
|
|
3213
|
+
* @param closePanel - 关闭页回退动作(`layout.selectPanel(null)`)。
|
|
5674
3214
|
*/
|
|
5675
|
-
function
|
|
5676
|
-
|
|
5677
|
-
const
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
3215
|
+
function mountTopToolEntries(slots, bound, openSession, closePanel) {
|
|
3216
|
+
/** 管理页:管理台 / 体检 双页签,页签可见性与底栏入口同源。 */
|
|
3217
|
+
const ManagerPage = () => {
|
|
3218
|
+
const snapshot = (0, react.useSyncExternalStore)((listener) => bound.subscribe(listener), () => bound.getSnapshot());
|
|
3219
|
+
const config = {
|
|
3220
|
+
...DEFAULT_CONFIG,
|
|
3221
|
+
...snapshot.value ?? {}
|
|
3222
|
+
};
|
|
3223
|
+
return (0, react.createElement)(StewardPanel, {
|
|
3224
|
+
variant: "page",
|
|
3225
|
+
healthCheck: config.healthCheck !== false,
|
|
3226
|
+
openSession,
|
|
3227
|
+
onClose: closePanel
|
|
3228
|
+
});
|
|
3229
|
+
};
|
|
3230
|
+
return gatedMount(bound, (value) => value.enabled !== false && value.healthCheck !== false, () => composeDisposers([slots.inject(MAIN_SEAT, () => slots.register({
|
|
3231
|
+
name: MAIN_SEAT,
|
|
3232
|
+
key: MANAGER_PANEL_ID,
|
|
3233
|
+
locale: NS
|
|
3234
|
+
}, ManagerPage), `dsh-manage-session: main page ${MANAGER_PANEL_ID}`), slots.inject(TOP_TOOL_SEAT, () => slots.register({
|
|
3235
|
+
name: TOP_TOOL_SEAT,
|
|
3236
|
+
id: MANAGER_PANEL_ID,
|
|
3237
|
+
order: 6,
|
|
3238
|
+
label: () => translate(void 0, "card.title"),
|
|
3239
|
+
locale: NS
|
|
3240
|
+
}, ManagerToolIcon), `dsh-manage-session: top tool row ${MANAGER_PANEL_ID}`)]));
|
|
3241
|
+
}
|
|
3242
|
+
/** 顶栏行图标:会话管理(列表行 + 勾选,随行字色)。 */
|
|
3243
|
+
function ManagerToolIcon({ size }) {
|
|
3244
|
+
const edge = size ?? 16;
|
|
3245
|
+
return (0, react.createElement)("svg", {
|
|
3246
|
+
width: edge,
|
|
3247
|
+
height: edge,
|
|
3248
|
+
viewBox: "0 0 16 16",
|
|
3249
|
+
fill: "none",
|
|
3250
|
+
"aria-hidden": true
|
|
3251
|
+
}, (0, react.createElement)("path", {
|
|
3252
|
+
d: "M2 4h12M2 8h12M2 12h7",
|
|
3253
|
+
stroke: "currentColor",
|
|
3254
|
+
strokeWidth: 1.75,
|
|
3255
|
+
strokeLinecap: "round"
|
|
3256
|
+
}), (0, react.createElement)("path", {
|
|
3257
|
+
d: "M11.2 11.2l1.5 1.5 2.6-3",
|
|
3258
|
+
stroke: "currentColor",
|
|
3259
|
+
strokeWidth: 1.6,
|
|
3260
|
+
strokeLinecap: "round",
|
|
3261
|
+
strokeLinejoin: "round"
|
|
5683
3262
|
}));
|
|
5684
3263
|
}
|
|
5685
|
-
//#endregion
|
|
5686
3264
|
exports.NS = NS;
|
|
5687
3265
|
exports.STEWARD_ENTRY_ID = STEWARD_ENTRY_ID;
|
|
5688
3266
|
exports.STEWARD_SETTINGS_NAMESPACE = STEWARD_SETTINGS_NAMESPACE;
|
|
5689
3267
|
exports.StewardEntry = StewardEntry;
|
|
5690
3268
|
exports.TAB_HEALTH = TAB_HEALTH;
|
|
5691
3269
|
exports.apply = apply;
|
|
3270
|
+
exports.hasPanelToolSeat = hasPanelToolSeat;
|
|
5692
3271
|
exports.inject = inject;
|
|
5693
3272
|
exports.translate = translate;
|
|
5694
3273
|
return module.exports;
|