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/src/client/card.tsx
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 设置卡(插件配置行):一个可折叠抽屉 —— 会话历史文件 / 健康检查。
|
|
3
|
-
*
|
|
4
|
-
* **chrome 必须自己渲染**:宿主不为贡献卡提供任何外壳,标题、说明、
|
|
5
|
-
* 展开/收起全归插件自己所有。早期版本只渲染裸行,与同分区其它插件
|
|
6
|
-
* 的抽屉形态不一致,还让 `card.title` / `card.desc` 成为死文案。
|
|
7
|
-
*
|
|
8
|
-
* 读写走 configForms(`configForms.get(STEWARD_ENTRY_ID)`,以本插件 entry 为键;
|
|
9
|
-
* 旧宿主回退 settingsScope 绑定)的显式 `set`。0.1.7 起本卡挂在插件族共用
|
|
10
|
-
* 设置节(`dsh-family.tab`,dsh-thinking-levels 顶级「起子插件设置」节声明)里,
|
|
11
|
-
* 与同节其它抽屉一样默认展开。
|
|
12
|
-
*/
|
|
13
|
-
import { createElement, useState, useSyncExternalStore, type JSX } from 'react'
|
|
14
|
-
import { DEFAULT_CONFIG, type StewardConfig } from '../config.ts'
|
|
15
|
-
import { translate, type LocaleKey } from './locales.ts'
|
|
16
|
-
|
|
17
|
-
/** 卡片字典面。 */
|
|
18
|
-
export type CardTranslate = (key: LocaleKey, params?: Record<string, unknown>) => string
|
|
19
|
-
|
|
20
|
-
/** 设置 scope 面(结构化镜像,与 toggle 的 SwitchCardScope 同形状)。 */
|
|
21
|
-
export interface StewardCardScope {
|
|
22
|
-
getSnapshot(): {
|
|
23
|
-
status: 'ready' | 'loading' | 'unavailable'
|
|
24
|
-
value: StewardConfig | undefined
|
|
25
|
-
revision?: number
|
|
26
|
-
writable: boolean
|
|
27
|
-
}
|
|
28
|
-
subscribe(listener: () => void): () => void
|
|
29
|
-
set(field: string, value: unknown): Promise<void>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** 卡片入参。 */
|
|
33
|
-
export interface StewardCardProps {
|
|
34
|
-
t?: CardTranslate
|
|
35
|
-
scope: StewardCardScope
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** 一行设置项。 */
|
|
39
|
-
function Row(props: { title: string; desc?: string; control: JSX.Element }): JSX.Element {
|
|
40
|
-
return createElement('div', { className: '
|
|
41
|
-
createElement('div', { key: 'text', className: '
|
|
42
|
-
createElement('span', { key: 't', className: '
|
|
43
|
-
props.desc !== undefined && createElement('span', { key: 'd', className: '
|
|
44
|
-
]),
|
|
45
|
-
createElement('div', { key: 'ctl' }, props.control),
|
|
46
|
-
])
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** 一个布尔开关(原生 checkbox 语义,样式由本插件 CSS 提供)。 */
|
|
50
|
-
function Toggle(props: { checked: boolean; disabled?: boolean; onChange: (checked: boolean) => void }): JSX.Element {
|
|
51
|
-
return createElement('label', { className: `
|
|
52
|
-
createElement('input', {
|
|
53
|
-
key: 'i',
|
|
54
|
-
type: 'checkbox',
|
|
55
|
-
checked: props.checked,
|
|
56
|
-
disabled: props.disabled === true,
|
|
57
|
-
onChange: (event: { target: { checked: boolean } }) => { props.onChange(event.target.checked) },
|
|
58
|
-
}),
|
|
59
|
-
createElement('span', { key: 's', className: '
|
|
60
|
-
])
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** 抽屉头:标题 + 说明 + chevron,整块可点。 */
|
|
64
|
-
function Header(props: { t?: CardTranslate; open: boolean; onToggle: () => void }): JSX.Element {
|
|
65
|
-
return createElement('button', {
|
|
66
|
-
key: 'head',
|
|
67
|
-
type: 'button',
|
|
68
|
-
className: '
|
|
69
|
-
'aria-expanded': props.open,
|
|
70
|
-
onClick: props.onToggle,
|
|
71
|
-
}, [
|
|
72
|
-
createElement('span', { key: 'text', className: '
|
|
73
|
-
createElement('span', { key: 't', className: '
|
|
74
|
-
createElement('span', { key: 'd', className: '
|
|
75
|
-
]),
|
|
76
|
-
createElement('svg', {
|
|
77
|
-
key: 'chev',
|
|
78
|
-
className: '
|
|
79
|
-
width: 16,
|
|
80
|
-
height: 16,
|
|
81
|
-
viewBox: '0 0 16 16',
|
|
82
|
-
'aria-hidden': true,
|
|
83
|
-
}, createElement('path', {
|
|
84
|
-
d: 'M4 6l4 4 4-4',
|
|
85
|
-
fill: 'none',
|
|
86
|
-
stroke: 'currentColor',
|
|
87
|
-
strokeWidth: 1.5,
|
|
88
|
-
strokeLinecap: 'round',
|
|
89
|
-
strokeLinejoin: 'round',
|
|
90
|
-
})),
|
|
91
|
-
])
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** 插件设置卡主体(抽屉)。默认展开,family 设置节内直接呈现完整面板。 */
|
|
95
|
-
export function StewardSettingsCard({ t, scope }: StewardCardProps): JSX.Element {
|
|
96
|
-
const snapshot = useSyncExternalStore(
|
|
97
|
-
(listener) => scope.subscribe(listener),
|
|
98
|
-
() => scope.getSnapshot(),
|
|
99
|
-
)
|
|
100
|
-
const [open, setOpen] = useState(true)
|
|
101
|
-
const value: Partial<StewardConfig> = snapshot.value ?? {}
|
|
102
|
-
const writable = snapshot.writable
|
|
103
|
-
|
|
104
|
-
const rows: JSX.Element[] = []
|
|
105
|
-
if (snapshot.status === 'unavailable') {
|
|
106
|
-
// 拿不到设置服务:抽屉仍要在(否则这个插件在分区里彻底消失),只是里面说明原因。
|
|
107
|
-
rows.push(createElement('div', { key: 'unavailable', className: '
|
|
108
|
-
} else {
|
|
109
|
-
rows.push(
|
|
110
|
-
createElement(Row, {
|
|
111
|
-
key: 'enabled',
|
|
112
|
-
title: translate(t, 'card.enabled'),
|
|
113
|
-
desc: translate(t, 'card.enabled.desc'),
|
|
114
|
-
control: createElement(Toggle, {
|
|
115
|
-
checked: value.enabled ?? DEFAULT_CONFIG.enabled,
|
|
116
|
-
disabled: !writable,
|
|
117
|
-
onChange: (checked) => { void scope.set('enabled', checked) },
|
|
118
|
-
}),
|
|
119
|
-
}),
|
|
120
|
-
// 2026-10-20 养老院页签退场:historyFiles 开关不再暴露(配置字段保留,
|
|
121
|
-
// 宿主侧历史文件能力/路由不动)。
|
|
122
|
-
createElement(Row, {
|
|
123
|
-
key: 'health',
|
|
124
|
-
title: translate(t, 'card.health'),
|
|
125
|
-
desc: translate(t, 'card.health.desc'),
|
|
126
|
-
control: createElement(Toggle, {
|
|
127
|
-
checked: value.healthCheck ?? DEFAULT_CONFIG.healthCheck,
|
|
128
|
-
disabled: !writable,
|
|
129
|
-
onChange: (checked) => { void scope.set('healthCheck', checked) },
|
|
130
|
-
}),
|
|
131
|
-
}),
|
|
132
|
-
// 直接删除入口(功能①,2026-10-20 用户定标):缺省关——破坏性入口
|
|
133
|
-
// 不默认铺开;开启后搜索面板行内出删除按钮(同款两步确认)。
|
|
134
|
-
createElement(Row, {
|
|
135
|
-
key: 'directDelete',
|
|
136
|
-
title: translate(t, 'card.directDelete'),
|
|
137
|
-
desc: translate(t, 'card.directDelete.desc'),
|
|
138
|
-
control: createElement(Toggle, {
|
|
139
|
-
checked: value.directDelete ?? DEFAULT_CONFIG.directDelete,
|
|
140
|
-
disabled: !writable,
|
|
141
|
-
onChange: (checked) => { void scope.set('directDelete', checked) },
|
|
142
|
-
}),
|
|
143
|
-
}),
|
|
144
|
-
)
|
|
145
|
-
if (!writable) rows.push(createElement('div', { key: 'ro', className: '
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return createElement('div', {
|
|
149
|
-
className: `
|
|
150
|
-
}, [
|
|
151
|
-
createElement(Header, { key: 'head', t, open, onToggle: () => { setOpen(current => !current) } }),
|
|
152
|
-
open && createElement('div', { key: 'body', className: '
|
|
153
|
-
])
|
|
154
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 设置卡(插件配置行):一个可折叠抽屉 —— 会话历史文件 / 健康检查。
|
|
3
|
+
*
|
|
4
|
+
* **chrome 必须自己渲染**:宿主不为贡献卡提供任何外壳,标题、说明、
|
|
5
|
+
* 展开/收起全归插件自己所有。早期版本只渲染裸行,与同分区其它插件
|
|
6
|
+
* 的抽屉形态不一致,还让 `card.title` / `card.desc` 成为死文案。
|
|
7
|
+
*
|
|
8
|
+
* 读写走 configForms(`configForms.get(STEWARD_ENTRY_ID)`,以本插件 entry 为键;
|
|
9
|
+
* 旧宿主回退 settingsScope 绑定)的显式 `set`。0.1.7 起本卡挂在插件族共用
|
|
10
|
+
* 设置节(`dsh-family.tab`,dsh-thinking-levels 顶级「起子插件设置」节声明)里,
|
|
11
|
+
* 与同节其它抽屉一样默认展开。
|
|
12
|
+
*/
|
|
13
|
+
import { createElement, useState, useSyncExternalStore, type JSX } from 'react'
|
|
14
|
+
import { DEFAULT_CONFIG, type StewardConfig } from '../config.ts'
|
|
15
|
+
import { translate, type LocaleKey } from './locales.ts'
|
|
16
|
+
|
|
17
|
+
/** 卡片字典面。 */
|
|
18
|
+
export type CardTranslate = (key: LocaleKey, params?: Record<string, unknown>) => string
|
|
19
|
+
|
|
20
|
+
/** 设置 scope 面(结构化镜像,与 toggle 的 SwitchCardScope 同形状)。 */
|
|
21
|
+
export interface StewardCardScope {
|
|
22
|
+
getSnapshot(): {
|
|
23
|
+
status: 'ready' | 'loading' | 'unavailable'
|
|
24
|
+
value: StewardConfig | undefined
|
|
25
|
+
revision?: number
|
|
26
|
+
writable: boolean
|
|
27
|
+
}
|
|
28
|
+
subscribe(listener: () => void): () => void
|
|
29
|
+
set(field: string, value: unknown): Promise<void>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 卡片入参。 */
|
|
33
|
+
export interface StewardCardProps {
|
|
34
|
+
t?: CardTranslate
|
|
35
|
+
scope: StewardCardScope
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 一行设置项。 */
|
|
39
|
+
function Row(props: { title: string; desc?: string; control: JSX.Element }): JSX.Element {
|
|
40
|
+
return createElement('div', { className: 'dms_setRow' }, [
|
|
41
|
+
createElement('div', { key: 'text', className: 'dms_setText' }, [
|
|
42
|
+
createElement('span', { key: 't', className: 'dms_setTitle' }, props.title),
|
|
43
|
+
props.desc !== undefined && createElement('span', { key: 'd', className: 'dms_setDesc' }, props.desc),
|
|
44
|
+
]),
|
|
45
|
+
createElement('div', { key: 'ctl' }, props.control),
|
|
46
|
+
])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 一个布尔开关(原生 checkbox 语义,样式由本插件 CSS 提供)。 */
|
|
50
|
+
function Toggle(props: { checked: boolean; disabled?: boolean; onChange: (checked: boolean) => void }): JSX.Element {
|
|
51
|
+
return createElement('label', { className: `dms_toggle${props.checked ? ' dms_toggleOn' : ''}` }, [
|
|
52
|
+
createElement('input', {
|
|
53
|
+
key: 'i',
|
|
54
|
+
type: 'checkbox',
|
|
55
|
+
checked: props.checked,
|
|
56
|
+
disabled: props.disabled === true,
|
|
57
|
+
onChange: (event: { target: { checked: boolean } }) => { props.onChange(event.target.checked) },
|
|
58
|
+
}),
|
|
59
|
+
createElement('span', { key: 's', className: 'dms_toggleTrack' }, createElement('span', { className: 'dms_toggleKnob' })),
|
|
60
|
+
])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 抽屉头:标题 + 说明 + chevron,整块可点。 */
|
|
64
|
+
function Header(props: { t?: CardTranslate; open: boolean; onToggle: () => void }): JSX.Element {
|
|
65
|
+
return createElement('button', {
|
|
66
|
+
key: 'head',
|
|
67
|
+
type: 'button',
|
|
68
|
+
className: 'dms_setHead',
|
|
69
|
+
'aria-expanded': props.open,
|
|
70
|
+
onClick: props.onToggle,
|
|
71
|
+
}, [
|
|
72
|
+
createElement('span', { key: 'text', className: 'dms_setHeadText' }, [
|
|
73
|
+
createElement('span', { key: 't', className: 'dms_setCardTitle' }, translate(props.t, 'card.title')),
|
|
74
|
+
createElement('span', { key: 'd', className: 'dms_setCardDesc' }, translate(props.t, 'card.desc')),
|
|
75
|
+
]),
|
|
76
|
+
createElement('svg', {
|
|
77
|
+
key: 'chev',
|
|
78
|
+
className: 'dms_setChev',
|
|
79
|
+
width: 16,
|
|
80
|
+
height: 16,
|
|
81
|
+
viewBox: '0 0 16 16',
|
|
82
|
+
'aria-hidden': true,
|
|
83
|
+
}, createElement('path', {
|
|
84
|
+
d: 'M4 6l4 4 4-4',
|
|
85
|
+
fill: 'none',
|
|
86
|
+
stroke: 'currentColor',
|
|
87
|
+
strokeWidth: 1.5,
|
|
88
|
+
strokeLinecap: 'round',
|
|
89
|
+
strokeLinejoin: 'round',
|
|
90
|
+
})),
|
|
91
|
+
])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 插件设置卡主体(抽屉)。默认展开,family 设置节内直接呈现完整面板。 */
|
|
95
|
+
export function StewardSettingsCard({ t, scope }: StewardCardProps): JSX.Element {
|
|
96
|
+
const snapshot = useSyncExternalStore(
|
|
97
|
+
(listener) => scope.subscribe(listener),
|
|
98
|
+
() => scope.getSnapshot(),
|
|
99
|
+
)
|
|
100
|
+
const [open, setOpen] = useState(true)
|
|
101
|
+
const value: Partial<StewardConfig> = snapshot.value ?? {}
|
|
102
|
+
const writable = snapshot.writable
|
|
103
|
+
|
|
104
|
+
const rows: JSX.Element[] = []
|
|
105
|
+
if (snapshot.status === 'unavailable') {
|
|
106
|
+
// 拿不到设置服务:抽屉仍要在(否则这个插件在分区里彻底消失),只是里面说明原因。
|
|
107
|
+
rows.push(createElement('div', { key: 'unavailable', className: 'dms_setDesc' }, translate(t, 'card.unavailable')))
|
|
108
|
+
} else {
|
|
109
|
+
rows.push(
|
|
110
|
+
createElement(Row, {
|
|
111
|
+
key: 'enabled',
|
|
112
|
+
title: translate(t, 'card.enabled'),
|
|
113
|
+
desc: translate(t, 'card.enabled.desc'),
|
|
114
|
+
control: createElement(Toggle, {
|
|
115
|
+
checked: value.enabled ?? DEFAULT_CONFIG.enabled,
|
|
116
|
+
disabled: !writable,
|
|
117
|
+
onChange: (checked) => { void scope.set('enabled', checked) },
|
|
118
|
+
}),
|
|
119
|
+
}),
|
|
120
|
+
// 2026-10-20 养老院页签退场:historyFiles 开关不再暴露(配置字段保留,
|
|
121
|
+
// 宿主侧历史文件能力/路由不动)。
|
|
122
|
+
createElement(Row, {
|
|
123
|
+
key: 'health',
|
|
124
|
+
title: translate(t, 'card.health'),
|
|
125
|
+
desc: translate(t, 'card.health.desc'),
|
|
126
|
+
control: createElement(Toggle, {
|
|
127
|
+
checked: value.healthCheck ?? DEFAULT_CONFIG.healthCheck,
|
|
128
|
+
disabled: !writable,
|
|
129
|
+
onChange: (checked) => { void scope.set('healthCheck', checked) },
|
|
130
|
+
}),
|
|
131
|
+
}),
|
|
132
|
+
// 直接删除入口(功能①,2026-10-20 用户定标):缺省关——破坏性入口
|
|
133
|
+
// 不默认铺开;开启后搜索面板行内出删除按钮(同款两步确认)。
|
|
134
|
+
createElement(Row, {
|
|
135
|
+
key: 'directDelete',
|
|
136
|
+
title: translate(t, 'card.directDelete'),
|
|
137
|
+
desc: translate(t, 'card.directDelete.desc'),
|
|
138
|
+
control: createElement(Toggle, {
|
|
139
|
+
checked: value.directDelete ?? DEFAULT_CONFIG.directDelete,
|
|
140
|
+
disabled: !writable,
|
|
141
|
+
onChange: (checked) => { void scope.set('directDelete', checked) },
|
|
142
|
+
}),
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
if (!writable) rows.push(createElement('div', { key: 'ro', className: 'dms_setDesc' }, translate(t, 'card.readonly')))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return createElement('div', {
|
|
149
|
+
className: `dms_setCard${open ? ' dms_setCardOpen' : ''}`,
|
|
150
|
+
}, [
|
|
151
|
+
createElement(Header, { key: 'head', t, open, onToggle: () => { setOpen(current => !current) } }),
|
|
152
|
+
open && createElement('div', { key: 'body', className: 'dms_setBody' }, rows),
|
|
153
|
+
])
|
|
154
|
+
}
|
|
@@ -261,12 +261,12 @@ export function HealthPanel({ t, onClose }: { t?: PanelTranslate; onClose: () =>
|
|
|
261
261
|
const report = detail?.report ?? discharge?.after
|
|
262
262
|
const lines: (ReactElement | null)[] = []
|
|
263
263
|
|
|
264
|
-
lines.push(createElement('div', { key: 'phase1', className: '
|
|
265
|
-
createElement('span', { key: 'l', className: '
|
|
264
|
+
lines.push(createElement('div', { key: 'phase1', className: 'dms_phaseRow' }, [
|
|
265
|
+
createElement('span', { key: 'l', className: 'dms_phase' }, translate(t, 'health.phase.checkup')),
|
|
266
266
|
createElement('button', {
|
|
267
267
|
key: 'scan',
|
|
268
268
|
type: 'button',
|
|
269
|
-
className: '
|
|
269
|
+
className: 'dms_actBtn',
|
|
270
270
|
disabled: scanning,
|
|
271
271
|
onClick: runScan,
|
|
272
272
|
}, scanning ? translate(t, 'health.scanning') : translate(t, 'health.scan')),
|
|
@@ -278,14 +278,14 @@ export function HealthPanel({ t, onClose }: { t?: PanelTranslate; onClose: () =>
|
|
|
278
278
|
if (scanning && progress !== null) {
|
|
279
279
|
const total = progress.total
|
|
280
280
|
const pct = total > 0 ? Math.min(100, Math.round((progress.done / total) * 100)) : 0
|
|
281
|
-
lines.push(createElement('div', { key: 'progress', className: '
|
|
282
|
-
createElement('div', { key: 'track', className: '
|
|
281
|
+
lines.push(createElement('div', { key: 'progress', className: 'dms_progressRow' }, [
|
|
282
|
+
createElement('div', { key: 'track', className: 'dms_progressTrack' },
|
|
283
283
|
createElement('div', {
|
|
284
284
|
key: 'fill',
|
|
285
|
-
className: total > 0 ? '
|
|
285
|
+
className: total > 0 ? 'dms_progressFill' : 'dms_progressFill dms_progressIndeterminate',
|
|
286
286
|
...(total > 0 ? { style: { width: `${pct}%` } } : {}),
|
|
287
287
|
})),
|
|
288
|
-
createElement('span', { key: 'read', className: '
|
|
288
|
+
createElement('span', { key: 'read', className: 'dms_meta' },
|
|
289
289
|
translate(t, 'health.progress', {
|
|
290
290
|
done: progress.done,
|
|
291
291
|
total: total > 0 ? total : '?',
|
|
@@ -298,47 +298,47 @@ export function HealthPanel({ t, onClose }: { t?: PanelTranslate; onClose: () =>
|
|
|
298
298
|
// 语料总数对不上时顺带提示刷新(枚举语料很便宜,所以这条判断不构成新的失效策略)。
|
|
299
299
|
if (cacheInfo !== null && !scanning && detail === null && discharge === null) {
|
|
300
300
|
const ago = relativeAgo(cacheInfo.generatedAt, Date.now())
|
|
301
|
-
lines.push(createElement('div', { key: 'cache', className: '
|
|
302
|
-
createElement('span', { key: 'h', className: '
|
|
301
|
+
lines.push(createElement('div', { key: 'cache', className: 'dms_cacheRow' }, [
|
|
302
|
+
createElement('span', { key: 'h', className: 'dms_meta' },
|
|
303
303
|
translate(t, 'health.cache.hint', { ago: translate(t, ago.key, { n: ago.n }) })),
|
|
304
|
-
cacheInfo.currentTotal !== cacheInfo.total && createElement('span', { key: 'chg', className: '
|
|
304
|
+
cacheInfo.currentTotal !== cacheInfo.total && createElement('span', { key: 'chg', className: 'dms_why' },
|
|
305
305
|
translate(t, 'health.cache.corpusChanged', { was: cacheInfo.total, now: cacheInfo.currentTotal })),
|
|
306
306
|
createElement('button', {
|
|
307
307
|
key: 'r',
|
|
308
308
|
type: 'button',
|
|
309
|
-
className: '
|
|
309
|
+
className: 'dms_actBtn',
|
|
310
310
|
onClick: () => { runScan({ forceRefresh: true }) },
|
|
311
311
|
}, translate(t, 'health.cache.refresh')),
|
|
312
312
|
]))
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
if (error !== null) lines.push(createElement('div', { key: 'err', className: '
|
|
315
|
+
if (error !== null) lines.push(createElement('div', { key: 'err', className: 'dms_error' }, error))
|
|
316
316
|
if (detail !== null || discharge !== null) {
|
|
317
|
-
lines.push(createElement('div', { key: 'back', className: '
|
|
317
|
+
lines.push(createElement('div', { key: 'back', className: 'dms_btnRow' }, [
|
|
318
318
|
createElement('button', {
|
|
319
319
|
key: 'b',
|
|
320
320
|
type: 'button',
|
|
321
|
-
className: '
|
|
321
|
+
className: 'dms_actBtn',
|
|
322
322
|
onClick: () => { setDetail(null); setDischarge(null) },
|
|
323
323
|
}, translate(t, 'health.back')),
|
|
324
324
|
]))
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
if (report !== undefined) {
|
|
328
|
-
lines.push(createElement('div', { key: 'gates', className: '
|
|
329
|
-
createElement('div', { key: gate.id, className: `
|
|
330
|
-
createElement('span', { key: 'n', className: '
|
|
331
|
-
createElement('span', { key: 'lv', className: `
|
|
332
|
-
createElement('span', { key: 'ev', className: '
|
|
333
|
-
gate.attribution !== undefined && createElement('span', { key: 'at', className: '
|
|
328
|
+
lines.push(createElement('div', { key: 'gates', className: 'dms_gateList' }, report.gates.map(gate =>
|
|
329
|
+
createElement('div', { key: gate.id, className: `dms_gate dms_gate_${gate.level}` }, [
|
|
330
|
+
createElement('span', { key: 'n', className: 'dms_gateName' }, translate(t, GATE_LABEL[gate.id])),
|
|
331
|
+
createElement('span', { key: 'lv', className: `dms_levelPill dms_level_${gate.level}` }, translate(t, LEVEL_LABEL[gate.level])),
|
|
332
|
+
createElement('span', { key: 'ev', className: 'dms_gateEvidence' }, gate.evidence),
|
|
333
|
+
gate.attribution !== undefined && createElement('span', { key: 'at', className: 'dms_meta' },
|
|
334
334
|
`${translate(t, 'health.attribution')}: ${gate.attribution.package ?? 'unknown'}${gate.attribution.projection !== undefined ? ` · ${gate.attribution.projection}` : ''}${gate.attribution.field !== undefined ? ` · ${translate(t, 'health.field')} ${gate.attribution.field}` : ''}`),
|
|
335
335
|
]),
|
|
336
336
|
)))
|
|
337
337
|
} else if (findings !== null) {
|
|
338
338
|
if (findings.length === 0) {
|
|
339
|
-
lines.push(createElement('div', { key: 'clean', className: '
|
|
339
|
+
lines.push(createElement('div', { key: 'clean', className: 'dms_empty' }, translate(t, 'health.empty')))
|
|
340
340
|
} else {
|
|
341
|
-
lines.push(createElement('ul', { key: 'list', className: '
|
|
341
|
+
lines.push(createElement('ul', { key: 'list', className: 'dms_list', role: 'list' }, findings.map((item) => {
|
|
342
342
|
// 行内列出「哪一门·什么档位」。只有总判徽标时,30 行「注意」彼此不可区分,
|
|
343
343
|
// 用户必须逐条点开才发现原因(实测体验问题);门摘要让信号可扫读。
|
|
344
344
|
// `skipped` 一并列出:它是「未检查」而非异常,不应与 warn 混为一谈。
|
|
@@ -346,17 +346,17 @@ export function HealthPanel({ t, onClose }: { t?: PanelTranslate; onClose: () =>
|
|
|
346
346
|
.filter(gate => gate.level !== 'ok')
|
|
347
347
|
.map(gate => `${translate(t, GATE_LABEL[gate.id])}·${translate(t, LEVEL_LABEL[gate.level])}`)
|
|
348
348
|
.join(' · ')
|
|
349
|
-
return createElement('li', { key: item.sessionId, className: '
|
|
350
|
-
createElement('span', { key: 'lv', className: `
|
|
349
|
+
return createElement('li', { key: item.sessionId, className: 'dms_row' }, [
|
|
350
|
+
createElement('span', { key: 'lv', className: `dms_levelPill dms_level_${item.level}` }, translate(t, LEVEL_LABEL[item.level])),
|
|
351
351
|
// 优先级徽标:这些会话的当前代是从迁移暂存(TMP)发布出来的,即本插件
|
|
352
352
|
// 此前读错产物或完全发现不了的那批。列表按优先级排,再标一次让排序可解释。
|
|
353
|
-
item.priority === 'high' && createElement('span', { key: 'p', className: '
|
|
354
|
-
createElement('span', { key: 'id', className: '
|
|
355
|
-
reasons !== '' && createElement('span', { key: 'why', className: '
|
|
353
|
+
item.priority === 'high' && createElement('span', { key: 'p', className: 'dms_meta' }, translate(t, 'health.priority.high')),
|
|
354
|
+
createElement('span', { key: 'id', className: 'dms_meta dms_uuid' }, item.sessionId),
|
|
355
|
+
reasons !== '' && createElement('span', { key: 'why', className: 'dms_why' }, reasons),
|
|
356
356
|
createElement('button', {
|
|
357
357
|
key: 'd',
|
|
358
358
|
type: 'button',
|
|
359
|
-
className: '
|
|
359
|
+
className: 'dms_actBtn',
|
|
360
360
|
onClick: () => { openDetail(item.sessionId) },
|
|
361
361
|
}, translate(t, 'health.detail')),
|
|
362
362
|
])
|
|
@@ -365,50 +365,50 @@ export function HealthPanel({ t, onClose }: { t?: PanelTranslate; onClose: () =>
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
if (report !== undefined) {
|
|
368
|
-
lines.push(createElement('div', { key: 'phase2', className: '
|
|
369
|
-
createElement('span', { key: 'l', className: '
|
|
368
|
+
lines.push(createElement('div', { key: 'phase2', className: 'dms_phaseRow' }, [
|
|
369
|
+
createElement('span', { key: 'l', className: 'dms_phase' }, translate(t, 'health.phase.prescribe')),
|
|
370
370
|
createElement('button', {
|
|
371
371
|
key: 'repair',
|
|
372
372
|
type: 'button',
|
|
373
|
-
className: '
|
|
373
|
+
className: 'dms_actBtn',
|
|
374
374
|
disabled: repairing || report.level === 'ok',
|
|
375
375
|
onClick: () => { runRepair(report.sessionId) },
|
|
376
376
|
}, [
|
|
377
377
|
// 处置只涉及单会话(2 次报告 + 1 次文件操作),耗时不足以支撑进度条;
|
|
378
378
|
// 但必须有**可动的**忙碌反馈,否则点击后界面静止,用户不知道是否在跑。
|
|
379
|
-
repairing ? createElement('span', { key: 'sp', className: '
|
|
379
|
+
repairing ? createElement('span', { key: 'sp', className: 'dms_spinner', 'aria-hidden': 'true' }) : null,
|
|
380
380
|
createElement('span', { key: 'tx' }, repairing ? translate(t, 'health.repairing') : translate(t, 'health.repair')),
|
|
381
381
|
]),
|
|
382
382
|
]))
|
|
383
383
|
// 署名转换按钮:只在 source-kind 门报 warn(v4 日志里观测到旧署名行)时出现,
|
|
384
384
|
// 不随常规处置出现——v3 及更早版本线的旧行由宿主迁移负责,不该一键误触。
|
|
385
385
|
if (report.gates.some(gate => gate.id === 'source-kind' && gate.level === 'warn')) {
|
|
386
|
-
lines.push(createElement('div', { key: 'migrate', className: '
|
|
386
|
+
lines.push(createElement('div', { key: 'migrate', className: 'dms_phaseRow' }, [
|
|
387
387
|
createElement('button', {
|
|
388
388
|
key: 'm',
|
|
389
389
|
type: 'button',
|
|
390
|
-
className: '
|
|
390
|
+
className: 'dms_actBtn',
|
|
391
391
|
disabled: migrating,
|
|
392
392
|
onClick: () => { runSourceMigrate(report.sessionId) },
|
|
393
393
|
}, [
|
|
394
|
-
migrating ? createElement('span', { key: 'sp', className: '
|
|
394
|
+
migrating ? createElement('span', { key: 'sp', className: 'dms_spinner', 'aria-hidden': 'true' }) : null,
|
|
395
395
|
createElement('span', { key: 'tx' }, migrating
|
|
396
396
|
? translate(t, 'health.sourceMigrating')
|
|
397
397
|
: translate(t, 'health.sourceMigrate')),
|
|
398
398
|
]),
|
|
399
|
-
migrateNote !== null && createElement('span', { key: 'note', className: '
|
|
399
|
+
migrateNote !== null && createElement('span', { key: 'note', className: 'dms_status' }, migrateNote),
|
|
400
400
|
]))
|
|
401
401
|
}
|
|
402
402
|
const prescriptions = detail?.prescriptions ?? discharge?.prescriptions ?? []
|
|
403
403
|
if (prescriptions.length > 0) {
|
|
404
|
-
lines.push(createElement('div', { key: 'cmds', className: '
|
|
405
|
-
createElement('div', { key: 'h', className: '
|
|
406
|
-
...prescriptions.map((line, index) => createElement('div', { key: `c${index}`, className: '
|
|
407
|
-
createElement('code', { key: 'code', className: '
|
|
404
|
+
lines.push(createElement('div', { key: 'cmds', className: 'dms_cmdBlock' }, [
|
|
405
|
+
createElement('div', { key: 'h', className: 'dms_meta' }, translate(t, 'health.commands')),
|
|
406
|
+
...prescriptions.map((line, index) => createElement('div', { key: `c${index}`, className: 'dms_cmdLine' }, [
|
|
407
|
+
createElement('code', { key: 'code', className: 'dms_cmd' }, line),
|
|
408
408
|
createElement('button', {
|
|
409
409
|
key: 'cp',
|
|
410
410
|
type: 'button',
|
|
411
|
-
className: '
|
|
411
|
+
className: 'dms_actBtn',
|
|
412
412
|
onClick: () => { copy(line) },
|
|
413
413
|
}, copied === line ? translate(t, 'health.copied') : translate(t, 'health.copy')),
|
|
414
414
|
])),
|
|
@@ -417,11 +417,11 @@ export function HealthPanel({ t, onClose }: { t?: PanelTranslate; onClose: () =>
|
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
if (discharge !== null) {
|
|
420
|
-
lines.push(createElement('div', { key: 'phase3', className: '
|
|
421
|
-
createElement('span', { key: 'l', className: '
|
|
422
|
-
createElement('span', { key: 'b', className: `
|
|
420
|
+
lines.push(createElement('div', { key: 'phase3', className: 'dms_phaseRow' }, [
|
|
421
|
+
createElement('span', { key: 'l', className: 'dms_phase' }, translate(t, 'health.phase.discharge')),
|
|
422
|
+
createElement('span', { key: 'b', className: `dms_levelPill dms_level_${discharge.before?.level ?? 'warn'}` },
|
|
423
423
|
`${translate(t, 'health.before')}: ${translate(t, LEVEL_LABEL[discharge.before?.level ?? 'warn'])}`),
|
|
424
|
-
createElement('span', { key: 'a', className: `
|
|
424
|
+
createElement('span', { key: 'a', className: `dms_levelPill dms_level_${discharge.after?.level ?? 'warn'}` },
|
|
425
425
|
`${translate(t, 'health.after')}: ${translate(t, LEVEL_LABEL[discharge.after?.level ?? 'warn'])}`),
|
|
426
426
|
]))
|
|
427
427
|
// 定性结论优先于机械的 before/after:处置只隔离投影缓存,
|
|
@@ -430,18 +430,18 @@ export function HealthPanel({ t, onClose }: { t?: PanelTranslate; onClose: () =>
|
|
|
430
430
|
if (discharge.explanation !== undefined && discharge.explanation !== '') {
|
|
431
431
|
lines.push(createElement('div', {
|
|
432
432
|
key: 'verdict',
|
|
433
|
-
className: `
|
|
433
|
+
className: `dms_verdict dms_verdict_${discharge.verdict ?? 'failed'}`,
|
|
434
434
|
}, discharge.explanation))
|
|
435
435
|
}
|
|
436
436
|
if (discharge.repair?.ok === true) {
|
|
437
|
-
lines.push(createElement('div', { key: 'repairNote', className: '
|
|
437
|
+
lines.push(createElement('div', { key: 'repairNote', className: 'dms_status' },
|
|
438
438
|
`${translate(t, 'health.quarantined')} → ${discharge.repair.to ?? ''}`))
|
|
439
439
|
} else if (discharge.repair?.error !== undefined) {
|
|
440
|
-
lines.push(createElement('div', { key: 'repairErr', className: '
|
|
440
|
+
lines.push(createElement('div', { key: 'repairErr', className: 'dms_status' }, discharge.repair.error))
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
return createElement('div', { className: '
|
|
444
|
+
return createElement('div', { className: 'dms_tabBody' }, lines)
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
/** 供单测断言:面板依赖的方法名(必须全部以 session- 开头)。 */
|