dsh-plugin 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +5 -5
- package/README.md +5 -5
- package/client/client.js +648 -190
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +15 -8
- package/lib/index.js +4 -0
- package/lib/services/install-types.js +8 -2
- package/lib/services/install.js +1 -1
- package/lib/services/installed-versions.js +5 -1
- package/lib/services/loader.js +16 -5
- package/lib/services/npm-resolve.js +4 -0
- package/lib/services/pending-restart.js +1 -1
- package/lib/services/preflight.js +9 -4
- package/lib/services/profile.js +30 -3
- package/lib/services/progress.js +4 -0
- package/lib/services/task-queue.js +21 -10
- package/lib/types/index.d.ts +4 -0
- package/lib/types/services/install-types.d.ts +12 -4
- package/lib/types/services/install.d.ts +5 -1
- package/lib/types/services/loader.d.ts +10 -4
- package/lib/types/services/pending-restart.d.ts +8 -2
- package/lib/types/services/profile.d.ts +4 -2
- package/lib/types/services/progress.d.ts +4 -0
- package/package.json +1 -1
- package/src/client/components/AboutModal.tsx +76 -0
- package/src/client/components/CatalogControls.tsx +4 -0
- package/src/client/components/CatalogHeader.tsx +34 -5
- package/src/client/components/CatalogList.tsx +4 -0
- package/src/client/components/CategoryTabs.tsx +4 -0
- package/src/client/components/Dropdown.tsx +4 -0
- package/src/client/components/HubUpdateModal.tsx +88 -0
- package/src/client/components/NotificationsModal.tsx +25 -8
- package/src/client/components/PluginCard.tsx +8 -1
- package/src/client/components/PluginHubSection.tsx +67 -7
- package/src/client/components/ProgressView.tsx +4 -0
- package/src/client/components/icons.tsx +4 -0
- package/src/client/components/modals.tsx +35 -10
- package/src/client/hooks/useCatalog.ts +89 -17
- package/src/client/hooks/useTaskQueue.ts +52 -35
- package/src/client/index.tsx +4 -0
- package/src/client/lib/catalog.ts +59 -6
- package/src/client/lib/env.ts +4 -0
- package/src/client/lib/failures.ts +5 -1
- package/src/client/lib/format.ts +4 -0
- package/src/client/lib/renderMarkdown.ts +120 -0
- package/src/client/locales.ts +48 -1
- package/src/client/styles/Dropdown.module.css +5 -1
- package/src/client/styles/Header.module.css +88 -4
- package/src/client/styles/List.module.css +5 -1
- package/src/client/styles/Modal.module.css +279 -1
- package/src/client/styles/tokens.module.css +5 -1
- package/src/client/types.ts +7 -1
- package/src/server/http/routes.ts +14 -8
- package/src/server/index.ts +4 -0
- package/src/server/services/install-types.ts +12 -4
- package/src/server/services/install.ts +5 -1
- package/src/server/services/installed-versions.ts +5 -1
- package/src/server/services/loader.ts +16 -5
- package/src/server/services/npm-resolve.ts +4 -0
- package/src/server/services/pending-restart.ts +8 -2
- package/src/server/services/preflight.ts +9 -4
- package/src/server/services/profile.ts +27 -3
- package/src/server/services/progress.ts +4 -0
- package/src/server/services/task-queue.ts +21 -10
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Section header: brand title row (H1 + open-site button) + tagline, followed
|
|
3
7
|
* by the purple ad banner that promotes the catalog stats.
|
|
4
8
|
*/
|
|
@@ -8,12 +12,18 @@ import type { Translate } from '../types.ts'
|
|
|
8
12
|
import { GITHUB_URL, PLUGIN_VERSION, SITE_URL } from '../lib/catalog.ts'
|
|
9
13
|
import { GitHubIcon, LogoIcon } from './icons.tsx'
|
|
10
14
|
|
|
11
|
-
export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggleLang }: {
|
|
15
|
+
export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggleLang, hubUpdate, onVersionClick, onAboutClick }: {
|
|
12
16
|
t: Translate
|
|
13
17
|
langPath: string
|
|
14
18
|
statsTotal: number
|
|
15
19
|
statsVerified: number
|
|
16
20
|
onToggleLang: () => void
|
|
21
|
+
/** Hub 自身是否有可用更新:有则版本号后紧跟红色「可更新」徽标(整体一个可点入口) */
|
|
22
|
+
hubUpdate: boolean
|
|
23
|
+
/** 点击版本号(含红色徽标,同一入口):无论有无更新都打开更新记录弹窗 */
|
|
24
|
+
onVersionClick: () => void
|
|
25
|
+
/** 点击「关注我们」:打开平台介绍 + 用户反馈群二维码弹窗 */
|
|
26
|
+
onAboutClick: () => void
|
|
17
27
|
}) {
|
|
18
28
|
return h(Fragment, null,
|
|
19
29
|
h('div', { className: styles.header },
|
|
@@ -28,10 +38,20 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
|
|
|
28
38
|
'aria-label': t('openHint'),
|
|
29
39
|
},
|
|
30
40
|
h(LogoIcon),
|
|
31
|
-
h('h1', { className: styles.title },
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
h('h1', { className: styles.title }, t('title')),
|
|
42
|
+
),
|
|
43
|
+
// 版本号:紧跟标题「DSH-Plugin Hub」之后,常驻可点入口(<a> 内不能嵌套按钮,故从官网链接里移出放在旁边)。
|
|
44
|
+
// 点击打开更新记录弹窗 —— 有更新看新版本内容,无更新看当前版本内容。
|
|
45
|
+
// 有更新时版本号后直接跟一个红色「可更新」徽标(同一按钮内,不分开成两个元素),静态显示不闪烁。
|
|
46
|
+
h('button', {
|
|
47
|
+
className: styles.versionBtn,
|
|
48
|
+
type: 'button',
|
|
49
|
+
onClick: onVersionClick,
|
|
50
|
+
title: t(hubUpdate ? 'hubUpdateHint' : 'versionHint'),
|
|
51
|
+
'aria-label': t(hubUpdate ? 'hubUpdateHint' : 'versionHint'),
|
|
52
|
+
},
|
|
53
|
+
`v${PLUGIN_VERSION}`,
|
|
54
|
+
hubUpdate && h('span', { className: styles.hubUpdateBadge }, t('update')),
|
|
35
55
|
),
|
|
36
56
|
// 右侧控件组:语言切换 + GitHub 源码图标(独立链接,brandTitle 是官网 <a> 不能嵌套),
|
|
37
57
|
// 一起 space-between 顶到标题行最右
|
|
@@ -44,6 +64,15 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
|
|
|
44
64
|
title: t('toggleLangHint'),
|
|
45
65
|
'aria-label': t('toggleLangHint'),
|
|
46
66
|
}, langPath === 'zh/' ? 'EN' : '中文'),
|
|
67
|
+
// 「关注我们」:点击打开平台介绍 + 用户反馈群二维码弹窗
|
|
68
|
+
h('button', {
|
|
69
|
+
className: styles.aboutBtn,
|
|
70
|
+
type: 'button',
|
|
71
|
+
onClick: onAboutClick,
|
|
72
|
+
title: t('aboutDesc'),
|
|
73
|
+
'aria-label': t('aboutTitle'),
|
|
74
|
+
}, t('followUs')),
|
|
75
|
+
// GitHub 源码图标:独立链接(brandTitle 是官网 <a> 不能嵌套),放在控件组最后
|
|
47
76
|
h('a', {
|
|
48
77
|
className: styles.githubLink,
|
|
49
78
|
href: GITHUB_URL,
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Catalog list: the scrollable container with the loading / failed / empty
|
|
3
7
|
* states, the plugin cards, and the count footer. Resets scroll position when
|
|
4
8
|
* the category or install-status filter changes so the replaced content is
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Category tabs: an "all" chip followed by one chip per catalog category.
|
|
3
7
|
* The "all" chip carries the total plugin count; the per-category chips
|
|
4
8
|
* carry no counts so their widths stay uniform.
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Custom dropdown (native <select> replacement).
|
|
3
7
|
*
|
|
4
8
|
* macOS/浏览器原生下拉无法跟随宿主主题,这里自绘:触发器按钮 + 绝对定位面板,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* Hub 版本信息 / 自我更新说明弹窗:
|
|
7
|
+
* - 有更新(hasUpdate=true):标题「有新版本」,展示新版本号、发布时间与 Worker 下发的
|
|
8
|
+
* Markdown 变更记录(renderMarkdown 渲染,双语言按界面语言取 {zh,en} 对象),
|
|
9
|
+
* 按钮「稍后再说 / 直接更新」,确认后进入安装弹窗执行覆盖重装。
|
|
10
|
+
* - 无更新(hasUpdate=false):标题「当前版本」,展示当前版本信息与更新记录,已是最新。
|
|
11
|
+
* 点击头部版本号(或「可更新」徽标)打开 —— 无论有无更新都能看到更新内容。
|
|
12
|
+
*/
|
|
13
|
+
import { createElement as h } from 'react'
|
|
14
|
+
import type { MouseEvent } from 'react'
|
|
15
|
+
import styles from '../styles/Modal.module.css'
|
|
16
|
+
import type { HubUpdateInfo } from '../lib/catalog.ts'
|
|
17
|
+
import type { LocaleId, Translate } from '../types.ts'
|
|
18
|
+
import { renderMarkdown } from '../lib/renderMarkdown.ts'
|
|
19
|
+
import { CloseIcon } from './icons.tsx'
|
|
20
|
+
|
|
21
|
+
export function HubUpdateModal({ info, lang, t, hasUpdate, onProceed, onClose }: {
|
|
22
|
+
info: HubUpdateInfo
|
|
23
|
+
lang: LocaleId
|
|
24
|
+
t: Translate
|
|
25
|
+
/** 是否有可用更新:有则显示「直接更新」,无则显示「已是最新」 */
|
|
26
|
+
hasUpdate: boolean
|
|
27
|
+
/** 点「直接更新」:关闭本弹窗,进入安装弹窗的更新流程 */
|
|
28
|
+
onProceed: () => void
|
|
29
|
+
onClose: () => void
|
|
30
|
+
}) {
|
|
31
|
+
// 变更记录:字符串直接使用;{zh,en} 对象按界面语言取,缺哪种补哪种,全缺则不出记录区
|
|
32
|
+
const notesRaw = typeof info.notes === 'string'
|
|
33
|
+
? info.notes
|
|
34
|
+
: info.notes && typeof info.notes === 'object'
|
|
35
|
+
? lang === 'en'
|
|
36
|
+
? (info.notes.en ?? info.notes.zh ?? '')
|
|
37
|
+
: (info.notes.zh ?? info.notes.en ?? '')
|
|
38
|
+
: ''
|
|
39
|
+
const notesHtml = notesRaw.trim() ? renderMarkdown(notesRaw) : null
|
|
40
|
+
// 发布时间:ISO 字符串转本地可读格式;非法值静默隐藏
|
|
41
|
+
let published: string | null = null
|
|
42
|
+
if (info.publishedAt) {
|
|
43
|
+
const d = new Date(info.publishedAt)
|
|
44
|
+
if (!Number.isNaN(d.getTime())) {
|
|
45
|
+
published = d.toLocaleString(lang === 'en' ? 'en-US' : 'zh-CN')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return h('div', {
|
|
50
|
+
className: styles.overlay,
|
|
51
|
+
onClick: (e: MouseEvent<HTMLDivElement>) => {
|
|
52
|
+
if (e.target === e.currentTarget) onClose()
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
h('div', { className: `${styles.modal} ${styles.hubUpdateModal}`, role: 'dialog', 'aria-modal': 'true' },
|
|
56
|
+
h('div', { className: styles.modalHead },
|
|
57
|
+
h('div', { className: styles.modalTitle }, t(hasUpdate ? 'hubUpdateTitle' : 'hubCurrentTitle')),
|
|
58
|
+
h('button', {
|
|
59
|
+
className: styles.modalClose,
|
|
60
|
+
'aria-label': t('confirmCancel'),
|
|
61
|
+
onClick: () => onClose(),
|
|
62
|
+
}, h(CloseIcon)),
|
|
63
|
+
),
|
|
64
|
+
h('div', { className: styles.modalDesc },
|
|
65
|
+
t(hasUpdate ? 'hubUpdateDesc' : 'hubCurrentDesc', { version: info.version })),
|
|
66
|
+
h('div', { className: styles.hubUpdateMeta },
|
|
67
|
+
h('span', { className: styles.hubUpdateMetaItem }, `${t('version')} ${info.version}`),
|
|
68
|
+
published
|
|
69
|
+
? h('span', { className: styles.hubUpdateMetaItem }, `${t('hubUpdatePublished')} ${published}`)
|
|
70
|
+
: null,
|
|
71
|
+
),
|
|
72
|
+
notesHtml
|
|
73
|
+
? h('div', {
|
|
74
|
+
className: styles.hubUpdateNotes,
|
|
75
|
+
dangerouslySetInnerHTML: { __html: notesHtml },
|
|
76
|
+
})
|
|
77
|
+
: null,
|
|
78
|
+
h('div', { className: styles.modalActions },
|
|
79
|
+
hasUpdate
|
|
80
|
+
? [
|
|
81
|
+
h('button', { className: styles.restartLater, onClick: onClose }, t('hubUpdateLater')),
|
|
82
|
+
h('button', { className: styles.modalInstall, onClick: onProceed }, t('updateNow')),
|
|
83
|
+
]
|
|
84
|
+
: h('button', { className: styles.modalInstall, onClick: onClose }, t('hubUpToDate')),
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Notification-center dialog: a persistent log of every settled install /
|
|
3
7
|
* remove task — successes and failures alike.
|
|
4
8
|
*
|
|
@@ -156,7 +160,10 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
156
160
|
? h('div', { className: styles.failEmpty }, t('notificationsEmpty'))
|
|
157
161
|
: h('div', { className: styles.noticeList },
|
|
158
162
|
records.map((r) => {
|
|
159
|
-
return h('div', {
|
|
163
|
+
return h('div', {
|
|
164
|
+
key: r.id,
|
|
165
|
+
className: r.ok ? `${styles.noticeRow} ${styles.noticeRowOk}` : styles.noticeRow,
|
|
166
|
+
},
|
|
160
167
|
h('div', { className: styles.noticeRowMain },
|
|
161
168
|
h('div', {
|
|
162
169
|
className: r.ok ? styles.noticeBadgeOk : styles.noticeBadgeFail,
|
|
@@ -182,6 +189,14 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
182
189
|
className: styles.failCopy,
|
|
183
190
|
onClick: () => onCopy(r.message),
|
|
184
191
|
}, t('failCopy')),
|
|
192
|
+
// 成功通知:时间戳并入本行右侧,与图标/文字垂直居中 ——
|
|
193
|
+
// 不再单独占底部一行,卡片只有一行内容,图标文字整体居中不顶头
|
|
194
|
+
r.ok
|
|
195
|
+
? h('span', {
|
|
196
|
+
className: styles.noticeTime,
|
|
197
|
+
title: new Date(r.at).toLocaleString(),
|
|
198
|
+
}, fmtTime(r.at))
|
|
199
|
+
: null,
|
|
185
200
|
// 每条通知右侧的删除按钮:单独移除这一条
|
|
186
201
|
h('button', {
|
|
187
202
|
className: styles.noticeRemove,
|
|
@@ -220,13 +235,15 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
220
235
|
})(),
|
|
221
236
|
),
|
|
222
237
|
),
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
h('
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
238
|
+
// 失败通知:时间戳保留独立底部行(右下角);成功通知已并入头部行右侧
|
|
239
|
+
!r.ok
|
|
240
|
+
? h('div', { className: styles.noticeFoot },
|
|
241
|
+
h('span', {
|
|
242
|
+
className: styles.noticeTime,
|
|
243
|
+
title: new Date(r.at).toLocaleString(),
|
|
244
|
+
}, fmtTime(r.at)),
|
|
245
|
+
)
|
|
246
|
+
: null,
|
|
230
247
|
)
|
|
231
248
|
}),
|
|
232
249
|
),
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* One plugin card in the catalog list: name/category/verified badge,
|
|
3
7
|
* description + topics, star/fork/date stats, and the detail/install/
|
|
4
8
|
* uninstall actions.
|
|
@@ -65,7 +69,7 @@ export function PluginCard({ plugin: p, copied, installedName, installedVersion,
|
|
|
65
69
|
// 查看详情:未安装/已安装都始终显示,跳转官网详情页
|
|
66
70
|
h('a', {
|
|
67
71
|
className: styles.detailBtn,
|
|
68
|
-
// 两级路径:/plugins/{ownerSlug}/{slug}
|
|
72
|
+
// 两级路径:/plugins/{ownerSlug}/{slug};缺 ownerSlug 时从 repo 推导
|
|
69
73
|
href: pluginDetailUrl(p, langPath),
|
|
70
74
|
target: '_blank',
|
|
71
75
|
rel: 'noopener noreferrer',
|
|
@@ -84,6 +88,9 @@ export function PluginCard({ plugin: p, copied, installedName, installedVersion,
|
|
|
84
88
|
disabled: true,
|
|
85
89
|
title: t('installed'),
|
|
86
90
|
}, t('installed')))
|
|
91
|
+
// 未安装:按钮恒为「安装」,点击先弹信任确认;AI 识别为「可能需要命令行辅助」
|
|
92
|
+
// 的插件(webInstallable=false)不拦截,允许尝试一键安装,弹窗内会给出提示与
|
|
93
|
+
// 复制命令通道,安装失败时引导去 dsh 终端手动执行
|
|
87
94
|
: h('button', {
|
|
88
95
|
className: isCopied ? styles.installBtnCopied : styles.installBtn,
|
|
89
96
|
// 文字恒定避免按钮宽度变化导致卡片跳动;点击先弹信任确认,
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Plugin Hub section: wires the catalog data pipeline, the background task
|
|
3
7
|
* queue and the feedback state together. Data/queue logic lives in hooks/,
|
|
4
8
|
* rendering is delegated to the small presentational components in this
|
|
5
|
-
* folder;
|
|
9
|
+
* folder; it hosts the dialogs/toast and the section-level copy actions.
|
|
6
10
|
*/
|
|
7
11
|
import { createElement as h, useEffect, useState } from 'react'
|
|
8
12
|
import styles from '../styles/Header.module.css'
|
|
9
13
|
import { en, zh } from '../locales.ts'
|
|
10
14
|
import type { EnvInfo, HubPlugin, LocaleId, SectionProps, ToastState } from '../types.ts'
|
|
11
|
-
import { langPathOf, installCommandOf } from '../lib/catalog.ts'
|
|
15
|
+
import { langPathOf, installCommandOf, HUB_REPO, PLUGIN_VERSION } from '../lib/catalog.ts'
|
|
12
16
|
import { getEnv } from '../lib/env.ts'
|
|
13
17
|
import { useCatalog } from '../hooks/useCatalog.ts'
|
|
14
18
|
import { useTaskQueue } from '../hooks/useTaskQueue.ts'
|
|
15
19
|
import { ErrorModal, InstallModal, UninstallModal, Toast } from './modals.tsx'
|
|
20
|
+
import { AboutModal } from './AboutModal.tsx'
|
|
21
|
+
import { HubUpdateModal } from './HubUpdateModal.tsx'
|
|
16
22
|
import { NotificationsModal } from './NotificationsModal.tsx'
|
|
17
23
|
import { addFailure, addSuccess, clearNotifications, loadNotifications, removeNotification } from '../lib/failures.ts'
|
|
18
24
|
import type { NotificationRecord } from '../lib/failures.ts'
|
|
@@ -48,11 +54,19 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
48
54
|
const [confirmPlugin, setConfirmPlugin] = useState<HubPlugin | null>(null)
|
|
49
55
|
/** 弹窗动作是否为「更新」:已安装插件点「更新」→ 走同一条 add 命令原位覆盖重装,文案区分安装/更新 */
|
|
50
56
|
const [confirmIsUpdate, setConfirmIsUpdate] = useState(false)
|
|
57
|
+
/** Hub 自我更新说明弹窗:点「可更新」徽标先展示版本 + Markdown 变更记录,确认后再进安装弹窗 */
|
|
58
|
+
const [showHubUpdate, setShowHubUpdate] = useState(false)
|
|
59
|
+
/** 「关注我们」弹窗:GitHub 图标后按钮点击打开,展示平台介绍 + 用户反馈群二维码(Worker /about 推送) */
|
|
60
|
+
const [showAbout, setShowAbout] = useState(false)
|
|
51
61
|
/** 卸载确认弹窗:记录待卸载的插件 */
|
|
52
62
|
const [uninstallPlugin, setUninstallPlugin] = useState<HubPlugin | null>(null)
|
|
53
63
|
/** 安装/卸载完成后的结果视图:停留弹窗内,点「完成」关闭 */
|
|
54
64
|
const [installDone, setInstallDone] = useState(false)
|
|
55
65
|
const [uninstallDone, setUninstallDone] = useState(false)
|
|
66
|
+
/** 结果视图是否给「立即重启」:服务端任务终态带出(卸载时 loader 已即时移除 → false 只给「完成」;
|
|
67
|
+
* true 时通知中心待重启条目同步常驻,直到用户点「立即重启」真正重启后才消失) */
|
|
68
|
+
const [installNeedsRestart, setInstallNeedsRestart] = useState(true)
|
|
69
|
+
const [uninstallNeedsRestart, setUninstallNeedsRestart] = useState(true)
|
|
56
70
|
/** 结果视图「立即重启」:请求宿主重启后进入等待,服务回来后整页刷新 */
|
|
57
71
|
const [restarting, setRestarting] = useState(false)
|
|
58
72
|
/** 操作失败完整信息 + 所属插件仓库 + 失败类型(决定弹窗标题「安装失败/卸载失败」)+ 实际执行的安装命令 + 尝试过的安装方式(issue 预填用) */
|
|
@@ -71,13 +85,15 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
71
85
|
const queue = useTaskQueue({
|
|
72
86
|
t,
|
|
73
87
|
refreshInstalled: catalog.refreshInstalled,
|
|
74
|
-
onInstallDone: (viaModal, repo) => {
|
|
88
|
+
onInstallDone: (viaModal, repo, needsRestart) => {
|
|
89
|
+
setInstallNeedsRestart(needsRestart)
|
|
75
90
|
if (viaModal) setInstallDone(true)
|
|
76
91
|
else setToast({ id: Date.now(), kind: 'done' })
|
|
77
92
|
// 安装成功也写入通知记录:通知中心里成功与失败都能看到
|
|
78
93
|
setNotifications(addSuccess({ kind: 'install', repo: repo ?? '' }))
|
|
79
94
|
},
|
|
80
|
-
onUninstallDone: (viaModal, repo) => {
|
|
95
|
+
onUninstallDone: (viaModal, repo, needsRestart) => {
|
|
96
|
+
setUninstallNeedsRestart(needsRestart)
|
|
81
97
|
if (viaModal) setUninstallDone(true)
|
|
82
98
|
else setToast({ id: Date.now(), kind: 'removed' })
|
|
83
99
|
setNotifications(addSuccess({ kind: 'uninstall', repo: repo ?? '' }))
|
|
@@ -97,20 +113,22 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
97
113
|
},
|
|
98
114
|
})
|
|
99
115
|
|
|
100
|
-
//
|
|
116
|
+
// 信任/卸载/更新说明/通知记录弹窗打开时按 Esc 关闭;任务进入后台队列后可随时关闭(任务继续)
|
|
101
117
|
useEffect(() => {
|
|
102
|
-
if (!confirmPlugin && !uninstallPlugin && !showNotifications) return
|
|
118
|
+
if (!confirmPlugin && !uninstallPlugin && !showNotifications && !showHubUpdate && !showAbout) return
|
|
103
119
|
const onKey = (e: KeyboardEvent) => {
|
|
104
120
|
if (e.key === 'Escape') {
|
|
105
121
|
setConfirmPlugin(null)
|
|
106
122
|
setUninstallPlugin(null)
|
|
107
123
|
setUninstallDone(false)
|
|
124
|
+
setShowHubUpdate(false)
|
|
125
|
+
setShowAbout(false)
|
|
108
126
|
setShowNotifications(false)
|
|
109
127
|
}
|
|
110
128
|
}
|
|
111
129
|
window.addEventListener('keydown', onKey)
|
|
112
130
|
return () => window.removeEventListener('keydown', onKey)
|
|
113
|
-
}, [confirmPlugin, uninstallPlugin, showNotifications])
|
|
131
|
+
}, [confirmPlugin, uninstallPlugin, showNotifications, showHubUpdate, showAbout])
|
|
114
132
|
|
|
115
133
|
// Toast 统一自动消失
|
|
116
134
|
useEffect(() => {
|
|
@@ -204,6 +222,10 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
204
222
|
|
|
205
223
|
const count = catalog.visible.length
|
|
206
224
|
|
|
225
|
+
// Hub 自身条目(目录数据里的 dshplugin/dsh-plugin-hub):头部「可更新」徽标的更新目标。
|
|
226
|
+
// 目录可能尚未加载(此时 hubEntry 为 null,徽标不渲染,加载完成后自然出现)。
|
|
227
|
+
const hubEntry = catalog.plugins?.find((p) => p.source?.repo === HUB_REPO) ?? null
|
|
228
|
+
|
|
207
229
|
return h('div', { className: styles.root },
|
|
208
230
|
h(CatalogHeader, {
|
|
209
231
|
t,
|
|
@@ -211,6 +233,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
211
233
|
statsTotal,
|
|
212
234
|
statsVerified,
|
|
213
235
|
onToggleLang: () => setManualLang(lang === 'en' ? 'zh' : 'en'),
|
|
236
|
+
// Hub 自身版本信息入口:版本号常驻可点(无更新也看得到当前版本更新内容),
|
|
237
|
+
// 有可用更新时版本号后紧跟红色「可更新」徽标(同一入口),点击都打开同一个更新说明弹窗。
|
|
238
|
+
// 有更新弹窗显示新版本 + Markdown 变更记录,点「直接更新」进安装弹窗覆盖重装。
|
|
239
|
+
hubUpdate: catalog.hubHasUpdate,
|
|
240
|
+
onVersionClick: () => setShowHubUpdate(true),
|
|
241
|
+
onAboutClick: () => setShowAbout(true),
|
|
214
242
|
}),
|
|
215
243
|
h(CategoryTabs, {
|
|
216
244
|
category: catalog.category,
|
|
@@ -270,6 +298,34 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
270
298
|
setUninstallPlugin(p)
|
|
271
299
|
},
|
|
272
300
|
}),
|
|
301
|
+
// Hub 版本信息弹窗:版本号/徽标点击打开 —— 有更新显示新版本 + 变更记录 + 「直接更新」,
|
|
302
|
+
// 无更新显示当前版本 + 记录 + 「已是最新」。Worker 拉取失败时兜底展示当前版本号。
|
|
303
|
+
showHubUpdate && h(HubUpdateModal, {
|
|
304
|
+
info: catalog.hubUpdateInfo ?? { version: PLUGIN_VERSION },
|
|
305
|
+
lang,
|
|
306
|
+
t,
|
|
307
|
+
hasUpdate: catalog.hubHasUpdate,
|
|
308
|
+
onClose: () => setShowHubUpdate(false),
|
|
309
|
+
onProceed: () => {
|
|
310
|
+
setShowHubUpdate(false)
|
|
311
|
+
// 与列表内更新同一流程:覆盖重装 dsh-plugin + 结果视图「立即重启」
|
|
312
|
+
if (hubEntry) {
|
|
313
|
+
setInstallDone(false)
|
|
314
|
+
setConfirmIsUpdate(true)
|
|
315
|
+
queue.clearModalTask()
|
|
316
|
+
setConfirmPlugin(hubEntry)
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
}),
|
|
320
|
+
// 「关注我们」弹窗:GitHub 图标后按钮点击打开 —— 平台介绍 + 用户反馈群二维码,
|
|
321
|
+
// 内容由 dsh-update Worker 的 /about 接口以 Markdown 形式推送,作者随时可改,非写死。
|
|
322
|
+
// Worker 未推送(info 为 null)时弹窗仍能打开并展示兜底文案,不影响其他功能。
|
|
323
|
+
showAbout && h(AboutModal, {
|
|
324
|
+
info: catalog.hubAboutInfo ?? null,
|
|
325
|
+
lang,
|
|
326
|
+
t,
|
|
327
|
+
onClose: () => setShowAbout(false),
|
|
328
|
+
}),
|
|
273
329
|
// 安装确认弹窗:锁定/进度/结果视图逻辑收敛在 modals.tsx;更新与安装同入口,仅标记 update
|
|
274
330
|
confirmPlugin && h(InstallModal, {
|
|
275
331
|
plugin: confirmPlugin,
|
|
@@ -279,7 +335,10 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
279
335
|
langPath,
|
|
280
336
|
restarting,
|
|
281
337
|
update: confirmIsUpdate,
|
|
338
|
+
// 仅命令行插件(webInstallable=false):弹窗只展示命令供复制,不提供一键安装
|
|
339
|
+
cliOnly: confirmPlugin.install?.webInstallable === false,
|
|
282
340
|
submitting: queue.submitting,
|
|
341
|
+
needsRestart: installNeedsRestart,
|
|
283
342
|
onClose: () => setConfirmPlugin(null),
|
|
284
343
|
onCopy: () => copyCommand(confirmPlugin),
|
|
285
344
|
onInstall: () => queue.installNow(confirmPlugin, confirmIsUpdate ? { update: true } : undefined),
|
|
@@ -294,6 +353,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
294
353
|
langPath,
|
|
295
354
|
restarting,
|
|
296
355
|
submitting: queue.submitting,
|
|
356
|
+
needsRestart: uninstallNeedsRestart,
|
|
297
357
|
onClose: () => {
|
|
298
358
|
setUninstallDone(false)
|
|
299
359
|
setUninstallPlugin(null)
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Inline progress block shared by the install and uninstall dialogs:
|
|
3
7
|
* a real percentage bar (server-estimated, client-polled). No live terminal
|
|
4
8
|
* window — pnpm emits nothing during git clone/build, so a log pane would be
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Inline SVG icons used across the client. Icons inherit currentColor so
|
|
3
7
|
* they adapt to themes and disabled states automatically.
|
|
4
8
|
*/
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
2
6
|
* Dialog layer for the Plugin Hub: the install-confirm dialog, the uninstall
|
|
3
7
|
* dialog and the global toast. Both dialogs lock themselves while a mutation
|
|
4
8
|
* or restart is running, then switch to a result view offering an immediate
|
|
@@ -13,14 +17,18 @@ import { ProgressView } from './ProgressView.tsx'
|
|
|
13
17
|
import { installCommandOf, pluginDetailUrl, pluginIssueUrl, pluginSiteUrl } from '../lib/catalog.ts'
|
|
14
18
|
import { classifyFailure } from '../lib/failures.ts'
|
|
15
19
|
|
|
16
|
-
/** 完成结果视图:绿色对勾 +
|
|
20
|
+
/** 完成结果视图:绿色对勾 + 标题/描述;
|
|
21
|
+
* needsRestart=true(插件需重启才生效)→ 「稍后重启 / 立即重启」按钮对,点稍后重启后
|
|
22
|
+
* 通知中心待重启条目常驻(服务端登记,内存态),直到用户点「立即重启」真正重启后才消失;
|
|
23
|
+
* needsRestart=false(卸载已即时生效)→ 仅「完成」关闭。 */
|
|
17
24
|
function ResultView({
|
|
18
|
-
title, desc, t, restarting, onRestart, onClose,
|
|
25
|
+
title, desc, t, restarting, needsRestart, onRestart, onClose,
|
|
19
26
|
}: {
|
|
20
27
|
title: string
|
|
21
28
|
desc: string
|
|
22
29
|
t: Translate
|
|
23
30
|
restarting: boolean
|
|
31
|
+
needsRestart: boolean
|
|
24
32
|
onRestart: () => void
|
|
25
33
|
onClose: () => void
|
|
26
34
|
}) {
|
|
@@ -43,12 +51,17 @@ function ResultView({
|
|
|
43
51
|
),
|
|
44
52
|
h('div', { className: styles.resultTitle }, title),
|
|
45
53
|
desc ? h('div', { className: styles.resultDesc }, desc) : null,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
needsRestart
|
|
55
|
+
? h('div', null, [
|
|
56
|
+
h('div', { className: styles.resultRestarting }, restarting ? t('restarting') : t('restartHint')),
|
|
57
|
+
h('div', { className: styles.modalActions },
|
|
58
|
+
h('button', { className: styles.restartLater, onClick: onClose, disabled: restarting }, t('restartLater')),
|
|
59
|
+
h('button', { className: styles.restartNow, onClick: onRestart, disabled: restarting },
|
|
60
|
+
restarting ? t('restarting') : t('restartNow')),
|
|
61
|
+
),
|
|
62
|
+
])
|
|
63
|
+
: h('div', { className: styles.modalActions },
|
|
64
|
+
h('button', { className: styles.restartNow, onClick: onClose }, t('done'))),
|
|
52
65
|
)
|
|
53
66
|
}
|
|
54
67
|
|
|
@@ -61,8 +74,12 @@ export interface InstallModalProps {
|
|
|
61
74
|
restarting: boolean
|
|
62
75
|
/** 已安装插件的覆盖更新(走同一条 add 命令原位重装,文案区分安装/更新) */
|
|
63
76
|
update: boolean
|
|
77
|
+
/** 仅命令行插件(webInstallable=false):不提供一键安装,只展示命令供复制到 dsh 终端 */
|
|
78
|
+
cliOnly: boolean
|
|
64
79
|
/** 安装请求在途(fetch 等待响应):此时任务尚未入队,需禁用确认按钮防止二次点击 */
|
|
65
80
|
submitting: boolean
|
|
81
|
+
/** 完成结果是否需重启才生效:true → 「稍后重启 / 立即重启」;false → 仅「完成」 */
|
|
82
|
+
needsRestart: boolean
|
|
66
83
|
onClose: () => void
|
|
67
84
|
onCopy: () => void
|
|
68
85
|
onInstall: () => void
|
|
@@ -74,7 +91,7 @@ export interface InstallModalProps {
|
|
|
74
91
|
* 只在本任务执行中展示实时进度;完成后切换为结果视图,与卸载一致。
|
|
75
92
|
*/
|
|
76
93
|
export function InstallModal(props: InstallModalProps) {
|
|
77
|
-
const { plugin, done, task, t, langPath, restarting, submitting, update, onClose, onCopy, onInstall, onRestart } = props
|
|
94
|
+
const { plugin, done, task, t, langPath, restarting, submitting, update, cliOnly, needsRestart, onClose, onCopy, onInstall, onRestart } = props
|
|
78
95
|
const busy = submitting || (task !== null && (task.status === 'pending' || task.status === 'running'))
|
|
79
96
|
// 进行中标题带上插件名(中文「XX 插件安装中」;英文状态词在前更自然),并用状态色区分
|
|
80
97
|
const name = plugin.displayName ?? plugin.slug
|
|
@@ -112,6 +129,7 @@ export function InstallModal(props: InstallModalProps) {
|
|
|
112
129
|
desc: update ? t('updateResultDesc') : t('installResultDesc'),
|
|
113
130
|
t,
|
|
114
131
|
restarting,
|
|
132
|
+
needsRestart,
|
|
115
133
|
onRestart,
|
|
116
134
|
onClose,
|
|
117
135
|
})
|
|
@@ -145,8 +163,12 @@ export function InstallModal(props: InstallModalProps) {
|
|
|
145
163
|
h('span', { className: styles.modalCmdText }, installCommandOf(plugin)),
|
|
146
164
|
h('span', { className: styles.modalCmdCopy }, h(CopyIcon), t('copyCmdLabel')),
|
|
147
165
|
),
|
|
166
|
+
// AI 识别可能需要命令行辅助的插件(webInstallable=false):提示但不拦截,仍允许尝试一键安装
|
|
167
|
+
cliOnly ? h('div', { className: styles.cliOnlyHint }, t('cliOnlyHint')) : null,
|
|
148
168
|
task && task.status === 'pending' ? h('div', { className: styles.queuedHint }, t('queuedHint')) : null,
|
|
149
169
|
task ? h(ProgressView, { task }) : null,
|
|
170
|
+
// 安装失败:引导用户复制上方命令到 dsh 终端手动安装(如 node-pty 等原生依赖构建脚本需人工放行)
|
|
171
|
+
task && task.status === 'failed' ? h('div', { className: styles.failedCopyHint }, t('failedCopyHint')) : null,
|
|
150
172
|
h('div', { className: styles.modalActions },
|
|
151
173
|
h('button', {
|
|
152
174
|
className: styles.modalCopy,
|
|
@@ -177,6 +199,8 @@ export interface UninstallModalProps {
|
|
|
177
199
|
restarting: boolean
|
|
178
200
|
/** 卸载请求在途(fetch 等待响应):此时任务尚未入队,需禁用确认按钮防止二次点击 */
|
|
179
201
|
submitting: boolean
|
|
202
|
+
/** 完成结果是否需重启才生效:true → 「稍后重启 / 立即重启」;false(loader 已即时移除)→ 仅「完成」 */
|
|
203
|
+
needsRestart: boolean
|
|
180
204
|
onClose: () => void
|
|
181
205
|
onCancel: () => void
|
|
182
206
|
onCopyCommand: () => void
|
|
@@ -186,7 +210,7 @@ export interface UninstallModalProps {
|
|
|
186
210
|
|
|
187
211
|
/** 卸载确认弹窗:确认/进行中(后台队列,可关闭);完成后切换为结果视图(成功即生效,仅「完成」关闭)。 */
|
|
188
212
|
export function UninstallModal(props: UninstallModalProps) {
|
|
189
|
-
const { plugin, done, task, t, langPath, restarting, submitting, onClose, onCancel, onCopyCommand, onConfirm, onRestart } = props
|
|
213
|
+
const { plugin, done, task, t, langPath, restarting, submitting, needsRestart, onClose, onCancel, onCopyCommand, onConfirm, onRestart } = props
|
|
190
214
|
const busy = submitting || (task !== null && (task.status === 'pending' || task.status === 'running'))
|
|
191
215
|
// 进行中标题带上插件名,与安装弹窗一致,并用状态色区分
|
|
192
216
|
const name = plugin.displayName ?? plugin.slug
|
|
@@ -221,6 +245,7 @@ export function UninstallModal(props: UninstallModalProps) {
|
|
|
221
245
|
desc: t('uninstallResultDesc'),
|
|
222
246
|
t,
|
|
223
247
|
restarting,
|
|
248
|
+
needsRestart,
|
|
224
249
|
onRestart,
|
|
225
250
|
onClose,
|
|
226
251
|
})
|