dsh-showcase 0.1.3 → 0.1.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.md +8 -0
- package/README.md +27 -10
- package/README.zh-CN.md +3 -1
- package/package.json +8 -11
- package/plugin/client.js +29 -33
- package/plugin/index.d.ts +20 -7
- package/plugin/index.js +64 -66
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
后续变更将在下一版本发布前继续记录在这里。
|
|
8
|
+
|
|
9
|
+
## [0.1.4] - 2026-10-04
|
|
10
|
+
|
|
7
11
|
### Fixed
|
|
8
12
|
|
|
13
|
+
- 迁移到 DSH `0.2.0-rc.2` 的 SettingsForms 与 Plugins 配置槽位:主题和海报开关改用 `.volatile()` 配置及官方行配置页,保留现有 profile 选择并支持修订号冲突保护。
|
|
14
|
+
- 适配 DSH `0.2` 的严格工具输出 JSON Schema,移除旧版属性级 `required` 写法;插件不再抢占 Connection 的私有 RPC 通道,避免 Web/API Gateway 冲突。
|
|
15
|
+
- 将客户端入口从旧 Settings 列表迁移到 `plugins.row.config`,在 summary/page 两种视图下均保持单一、可用的配置卡。
|
|
9
16
|
- Markdown-only 摘要遇到项目外部 symlink 时静默跳过越界记录,不再把外部视口标签写入文档;海报生成仍严格拒绝越界路径。
|
|
10
17
|
- GitHub Actions 改用兼容 Node 24 的官方 Action 版本,避免旧运行时弃用提醒。
|
|
11
18
|
- 浏览器拒绝本地存储或偏好 JSON 损坏时,离线报告保留导出时的明暗、强调色和动效设置;无效设置不会覆盖有效值。
|
|
@@ -35,6 +42,7 @@
|
|
|
35
42
|
|
|
36
43
|
### Changed
|
|
37
44
|
|
|
45
|
+
- 更新 README 的鸽子中转站介绍,补充当前模型覆盖、订阅方式与不定期抽奖赠 Token 福利说明。
|
|
38
46
|
- 加强终末地首屏主视觉:扩大公告式标题、提高等高线存在感,加入黑色结构面与斜切信号黄带,让工业编辑风格形成明确视觉峰值。
|
|
39
47
|
- 终末地交付报告收敛状态色为黑 / 灰 / 信号黄,减少红绿仪表盘噪音;桌面标题、摘要和分区增加留白,移动端阶段导航改为文档流内的吸顶栏,避免遮挡 Diff 与回执。
|
|
40
48
|
- 加强终末地主屏层次:将交付中继融入黑灰标题带,局部复用等高线素材,使用深色命令输出与浅色文件清单,并补充导航、回执和按钮的交互反馈。
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/DoveLi-Gu/dsh-showcase/actions/workflows/ci.yml)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](https://nodejs.org/)
|
|
6
6
|
|
|
7
7
|
**把编程 Agent 的改动和测试结果,整理成可核验的中文交付报告。**
|
|
8
8
|
|
|
@@ -36,8 +36,8 @@ dsh-showcase 是一个本地运行的 DSH 插件。它读取目标项目的 Git
|
|
|
36
36
|
|
|
37
37
|
## 你需要什么
|
|
38
38
|
|
|
39
|
-
- **必须**:已经可以运行的 DSH Web
|
|
40
|
-
- **必须**:Node.js
|
|
39
|
+
- **必须**:已经可以运行的 DSH Web 环境(当前插件兼容 DSH `0.2.0-rc.2` 及同一 0.2 系列);
|
|
40
|
+
- **必须**:Node.js `22.19+` 或 `24+` 和 npm,用于安装本仓库及运行采集 CLI;
|
|
41
41
|
- **可选**:Git。没有 Git 时仍会生成报告,但状态会明确标为 `partial`;
|
|
42
42
|
- **可选**:Playwright 或其他浏览器验收工具。截图不是后端、CLI、库项目的必需项。
|
|
43
43
|
|
|
@@ -88,7 +88,7 @@ dsh plugin --profile web add "C:\tools\dsh-showcase"
|
|
|
88
88
|
|
|
89
89
|
然后重启 `dsh web`,再新建一个会话。若命令提示 `dsh` 不存在,先安装并确认 DSH CLI 已加入 `PATH`;这不是本插件自身的安装错误。
|
|
90
90
|
|
|
91
|
-
打开 DSH
|
|
91
|
+
打开 DSH 侧栏的 **插件**,进入 `dsh-showcase`,点击组件 `dsh-showcase/plugin` 的 **配置**,找到 **布局证据产物**。这里可以选择:
|
|
92
92
|
|
|
93
93
|
- **终末地帝江号**:机械工业、密集等高线和黄黑校准色;
|
|
94
94
|
- **蓝色大肥鱼**:浅蓝背景、角色主视觉和钴蓝强调;
|
|
@@ -189,6 +189,20 @@ $showcaseRepo = "C:\tools\dsh-showcase"
|
|
|
189
189
|
|
|
190
190
|
</details>
|
|
191
191
|
|
|
192
|
+
## 在 DSH 生态中被发现
|
|
193
|
+
|
|
194
|
+
DeepSeek Harness 官方目前没有单独的插件提交审核页面;官方仓库建议插件作者给 GitHub 仓库添加 `dsh-plugin` Topic,用于社区发现。本仓库已经配置 `dsh-plugin`、`deepseek-harness`、`dsh` 和 `cordis` 等主题标签。
|
|
195
|
+
|
|
196
|
+
部分社区插件目录会定期扫描 `dsh-plugin` Topic,并根据公开仓库的 `package.json`、`dsh.bundle.patch`、入口文件和版本信息生成卡片。收录通常存在延迟,目录属于社区项目,不代表 DeepSeek 官方背书;用户仍可直接使用 DSH 官方命令安装:
|
|
197
|
+
|
|
198
|
+
~~~powershell
|
|
199
|
+
dsh plugin --profile web add dsh-showcase
|
|
200
|
+
~~~
|
|
201
|
+
|
|
202
|
+
- 官方生态说明:[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
203
|
+
- 社区目录示例:[DSH Plugins Marketplace](https://github.com/bradeGithub/DSH-Plugins-Marketplace)
|
|
204
|
+
- 社区展示与反馈:DeepSeek Harness 仓库的 **Show Your Plugins** Discussions
|
|
205
|
+
|
|
192
206
|
## 赞助商
|
|
193
207
|
|
|
194
208
|
<div align="center">
|
|
@@ -200,12 +214,14 @@ $showcaseRepo = "C:\tools\dsh-showcase"
|
|
|
200
214
|
<p><strong>稳定、透明、易接入的 AI API 中转服务</strong></p>
|
|
201
215
|
</div>
|
|
202
216
|
|
|
203
|
-
鸽子中转站(Pigeon API Relay)兼容 OpenAI API
|
|
217
|
+
鸽子中转站(Pigeon API Relay)兼容 OpenAI API 格式,持续跟进主流模型与可用线路。当前重点覆盖 GPT-6.1 Sol、GPT-6 Astra / Sol / Luna、GPT-5.6 全系列、GPT-5.5 / 5.4,Claude Opus 5、Fable 5、Sonnet 5,DeepSeek V4 Pro / Flash、Gemini、Grok,以及 Qwen、Kimi、GLM 等国产模型,适用于 AI 编程、Agent 工具调用、客户端接入、日常对话、内容创作和图像生成等场景。
|
|
218
|
+
|
|
219
|
+
平台支持余额按量与订阅套餐两种方式,提供灵活的日卡、月卡方案;你可以在一个控制台中统一管理 API Key、模型调用、余额、用量和订阅权益,并查看模型价格、服务状态与详细接入文档。模型列表、可用线路、价格和套餐权益会随站内运营持续更新,最终以鸽子中转站实时信息为准。
|
|
204
220
|
|
|
205
|
-
|
|
221
|
+
站长也会不定期组织抽奖和用户福利,赠送 Token、体验额度或其他使用权益。活动时间、参与方式与发放规则以鸽子中转站公告为准,欢迎关注站内动态。
|
|
206
222
|
|
|
207
223
|
<p align="center">
|
|
208
|
-
<code>兼容 OpenAI API</code> · <code>GPT-5.6
|
|
224
|
+
<code>兼容 OpenAI API</code> · <code>GPT-6 / GPT-5.6</code> · <code>DeepSeek V4</code> · <code>余额按量</code> · <code>日卡/月卡订阅</code> · <code>不定期抽奖送 Token</code>
|
|
209
225
|
</p>
|
|
210
226
|
|
|
211
227
|
<p align="center">
|
|
@@ -233,8 +249,9 @@ $showcaseRepo = "C:\tools\dsh-showcase"
|
|
|
233
249
|
</td>
|
|
234
250
|
<td>
|
|
235
251
|
<p><strong>稳定、透明、易接入的 AI API 中转服务,兼容 OpenAI API 格式。</strong></p>
|
|
236
|
-
<p
|
|
237
|
-
<p
|
|
252
|
+
<p>当前重点覆盖 GPT-6.1 Sol、GPT-6 Astra / Sol / Luna、GPT-5.6 全系列、GPT-5.5 / 5.4,Claude Opus 5、Fable 5、Sonnet 5,DeepSeek V4 Pro / Flash、Gemini、Grok,以及 Qwen、Kimi、GLM 等国产模型,适用于 AI 编程、Agent 工具调用、客户端接入、日常对话、内容创作和图像生成等场景。</p>
|
|
253
|
+
<p>平台支持余额按量与订阅套餐,提供日卡、月卡等灵活方案;用户可以统一管理 API Key、模型调用、余额、用量和订阅权益,并查看模型价格、服务状态与详细接入文档。模型列表、线路、价格和套餐权益会持续更新,最终以站内实时信息为准。</p>
|
|
254
|
+
<p><strong>不定期福利:</strong>站长会组织抽奖并赠送 Token、体验额度或其他使用权益,活动时间和参与规则以站内公告为准。</p>
|
|
238
255
|
</td>
|
|
239
256
|
</tr>
|
|
240
257
|
</tbody>
|
|
@@ -419,7 +436,7 @@ CLI 会保留 `report.json` 中已经存在的截图记录,但不会自动启
|
|
|
419
436
|
|
|
420
437
|
## 从源码开发
|
|
421
438
|
|
|
422
|
-
|
|
439
|
+
构建工具和 DSH `0.2` 插件运行时要求 Node.js `22.19+` 或 `24+`;建议使用 Node.js 24。DSH `0.1` 旧版运行时不再作为本版本的兼容目标。
|
|
423
440
|
|
|
424
441
|
~~~powershell
|
|
425
442
|
npm ci
|
package/README.zh-CN.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[打开 README.md](README.md)
|
|
8
8
|
|
|
9
|
-
赞助商:**鸽子中转站(Pigeon API Relay)**。它兼容 OpenAI API
|
|
9
|
+
赞助商:**鸽子中转站(Pigeon API Relay)**。它兼容 OpenAI API 格式,当前重点覆盖 GPT-6、GPT-5.6、Claude Opus/Fable/Sonnet 5、DeepSeek V4、Gemini、Grok 及多种国产模型,并支持余额按量、日卡/月卡订阅及统一权益管理。站长还会不定期组织抽奖,赠送 Token、体验额度等福利;模型与活动详情以站内实时信息为准,完整介绍见主 README 快速开始后的赞助商区域。
|
|
10
10
|
|
|
11
11
|
本项目提供两套主题:
|
|
12
12
|
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
主题只在 DSH 插件设置里选择,目标项目不需要增加 `theme` 字段。
|
|
17
17
|
|
|
18
|
+
当前兼容 DSH `0.2.0-rc.2` 及同一 0.2 系列。安装后从 DSH 侧栏进入 **插件**,打开 `dsh-showcase`,在 `dsh-showcase/plugin` 行点击 **配置**;Node.js 使用 `22.19+` 或 `24+`。旧版 DSH 0.1 不属于本版本的兼容目标。
|
|
19
|
+
|
|
18
20
|
维护与发布相关文档:
|
|
19
21
|
|
|
20
22
|
- [GitHub 上传与维护指南](docs/GITHUB_UPLOAD_GUIDE_ZH.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-showcase",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Local, verifiable delivery evidence reports for coding-agent work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./plugin/client-host.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"developer-tools"
|
|
24
24
|
],
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
26
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
27
27
|
},
|
|
28
28
|
"packageManager": "npm@11.15.0",
|
|
29
29
|
"publishConfig": {
|
|
@@ -41,11 +41,9 @@
|
|
|
41
41
|
},
|
|
42
42
|
"client": {
|
|
43
43
|
"inject": [
|
|
44
|
-
"@deepseek-ai/dsh-client-connection",
|
|
45
44
|
"@deepseek-ai/dsh-client-locale",
|
|
46
45
|
"@deepseek-ai/dsh-client-runtime",
|
|
47
|
-
"@deepseek-ai/dsh-client-ui-
|
|
48
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins"
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager"
|
|
49
47
|
],
|
|
50
48
|
"platform": "web"
|
|
51
49
|
}
|
|
@@ -89,17 +87,16 @@
|
|
|
89
87
|
"capture": "tsx src/cli/index.ts capture"
|
|
90
88
|
},
|
|
91
89
|
"dependencies": {
|
|
92
|
-
"@deepseek-ai/
|
|
93
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
90
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
94
91
|
"zod": "^4.4.3"
|
|
95
92
|
},
|
|
96
93
|
"peerDependencies": {
|
|
97
|
-
"@deepseek-ai/dsh-tools": "^0.
|
|
98
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
94
|
+
"@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
|
|
95
|
+
"@deepseek-ai/cordis": "^4.0.4"
|
|
99
96
|
},
|
|
100
97
|
"devDependencies": {
|
|
101
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
102
|
-
"@deepseek-ai/dsh-tools": "^0.
|
|
98
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
99
|
+
"@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
|
|
103
100
|
"@types/node": "latest",
|
|
104
101
|
"@types/react": "latest",
|
|
105
102
|
"@types/react-dom": "latest",
|
package/plugin/client.js
CHANGED
|
@@ -6,9 +6,7 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
const React = require("react");
|
|
7
7
|
const { useEffect, useRef, useState } = React;
|
|
8
8
|
|
|
9
|
-
const inject = ["slots", "locale"
|
|
10
|
-
|
|
11
|
-
const RPC_CHANNEL = "/showcase-layout-summary";
|
|
9
|
+
const inject = ["slots", "locale"];
|
|
12
10
|
const LOCALE_NAMESPACE = "showcase.layoutSummary.settings";
|
|
13
11
|
const STYLE_ID = "dsh-showcase-settings-style";
|
|
14
12
|
const THEME_VALUES = ["frontier-signal", "blue-big-fish"];
|
|
@@ -16,6 +14,7 @@ const THEME_VALUES = ["frontier-signal", "blue-big-fish"];
|
|
|
16
14
|
const messages = {
|
|
17
15
|
zh: {
|
|
18
16
|
title: "布局证据产物",
|
|
17
|
+
summary: "配置布局证据产物的海报主题与生成策略。",
|
|
19
18
|
description: "先完成采集与测试,再在交付或视觉审核节点生成摘要;海报是可选的重产物。",
|
|
20
19
|
legend: "海报风格",
|
|
21
20
|
dijiang: "终末地帝江号",
|
|
@@ -36,6 +35,7 @@ const messages = {
|
|
|
36
35
|
},
|
|
37
36
|
en: {
|
|
38
37
|
title: "Layout evidence artifacts",
|
|
38
|
+
summary: "Configure the poster theme and generation policy for layout evidence.",
|
|
39
39
|
description: "Capture and test first, then generate at the delivery or visual-review checkpoint. The poster is optional and heavier.",
|
|
40
40
|
legend: "Poster theme",
|
|
41
41
|
dijiang: "Dijiang",
|
|
@@ -124,13 +124,15 @@ function installStyles() {
|
|
|
124
124
|
document.head.append(tag);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
function createThemeCard(
|
|
128
|
-
return function ThemeCard({ t }) {
|
|
127
|
+
function createThemeCard() {
|
|
128
|
+
return function ThemeCard({ t, form, view }) {
|
|
129
|
+
if (view === "summary") return React.createElement("span", { className: "dsh-showcase-settings__summary" }, t("summary"));
|
|
129
130
|
const [theme, setTheme] = useState();
|
|
130
131
|
const [generatePoster, setGeneratePoster] = useState(false);
|
|
131
132
|
const [loadState, setLoadState] = useState("loading");
|
|
132
133
|
const [saveState, setSaveState] = useState("idle");
|
|
133
134
|
const [loadAttempt, setLoadAttempt] = useState(0);
|
|
135
|
+
const [writable, setWritable] = useState(false);
|
|
134
136
|
const mountedRef = useRef(true);
|
|
135
137
|
const savingRef = useRef(false);
|
|
136
138
|
const choiceRefs = useRef({});
|
|
@@ -138,30 +140,32 @@ function createThemeCard(rpc) {
|
|
|
138
140
|
let active = true;
|
|
139
141
|
mountedRef.current = true;
|
|
140
142
|
setLoadState("loading");
|
|
141
|
-
|
|
143
|
+
const sync = () => {
|
|
142
144
|
if (!active) return;
|
|
143
|
-
const
|
|
145
|
+
const snapshot = form?.state;
|
|
146
|
+
const value = snapshot?.value;
|
|
144
147
|
const validTheme = value?.theme === "frontier-signal" || value?.theme === "blue-big-fish";
|
|
145
148
|
const validPosterSetting = value?.generatePoster === undefined || typeof value?.generatePoster === "boolean";
|
|
146
149
|
if (validTheme && validPosterSetting) {
|
|
147
150
|
setTheme(value.theme);
|
|
148
151
|
if (typeof value.generatePoster === "boolean") setGeneratePoster(value.generatePoster);
|
|
149
|
-
|
|
152
|
+
setWritable(snapshot.writable === true);
|
|
153
|
+
setLoadState(snapshot.status === "ready" ? "ready" : "unavailable");
|
|
150
154
|
} else {
|
|
155
|
+
setWritable(false);
|
|
151
156
|
setLoadState("unavailable");
|
|
152
157
|
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
});
|
|
158
|
+
};
|
|
159
|
+
sync();
|
|
156
160
|
return () => {
|
|
157
161
|
active = false;
|
|
158
162
|
mountedRef.current = false;
|
|
159
163
|
};
|
|
160
|
-
}, [loadAttempt]);
|
|
164
|
+
}, [form, form?.state, loadAttempt]);
|
|
161
165
|
|
|
162
166
|
if (loadState !== "ready") {
|
|
163
167
|
const loading = loadState === "loading";
|
|
164
|
-
return React.createElement("
|
|
168
|
+
return React.createElement("div", {
|
|
165
169
|
className: "dsh-showcase-settings",
|
|
166
170
|
"data-load-state": loadState,
|
|
167
171
|
"aria-busy": loading,
|
|
@@ -193,25 +197,18 @@ function createThemeCard(rpc) {
|
|
|
193
197
|
React.createElement("div", { className: "dsh-showcase-settings__loading-switch" }))));
|
|
194
198
|
}
|
|
195
199
|
|
|
196
|
-
const disabled = saveState === "saving";
|
|
200
|
+
const disabled = saveState === "saving" || !writable;
|
|
197
201
|
const save = async (patch, rollback = () => {}) => {
|
|
198
|
-
if (savingRef.current) return;
|
|
202
|
+
if (savingRef.current || !form?.mutate) return;
|
|
199
203
|
savingRef.current = true;
|
|
200
204
|
setSaveState("saving");
|
|
201
205
|
try {
|
|
202
|
-
const
|
|
206
|
+
const snapshot = form.state;
|
|
207
|
+
const operations = Object.entries(patch).map(([path, value]) => ({ op: "set", path: [path], value }));
|
|
208
|
+
const accepted = await form.mutate(operations, snapshot.revision);
|
|
203
209
|
if (!mountedRef.current) return;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const validPosterSetting = value?.generatePoster === undefined || typeof value?.generatePoster === "boolean";
|
|
207
|
-
if (validTheme && validPosterSetting) {
|
|
208
|
-
setTheme(value.theme);
|
|
209
|
-
if (typeof value.generatePoster === "boolean") setGeneratePoster(value.generatePoster);
|
|
210
|
-
setSaveState("saved");
|
|
211
|
-
} else {
|
|
212
|
-
rollback();
|
|
213
|
-
setSaveState("failed");
|
|
214
|
-
}
|
|
210
|
+
if (!accepted) throw new Error("The configuration was rejected.");
|
|
211
|
+
setSaveState("saved");
|
|
215
212
|
} catch {
|
|
216
213
|
if (mountedRef.current) {
|
|
217
214
|
rollback();
|
|
@@ -271,7 +268,7 @@ function createThemeCard(rpc) {
|
|
|
271
268
|
? t("failed")
|
|
272
269
|
: "";
|
|
273
270
|
|
|
274
|
-
return React.createElement("
|
|
271
|
+
return React.createElement("div", {
|
|
275
272
|
className: "dsh-showcase-settings",
|
|
276
273
|
"data-save-state": saveState,
|
|
277
274
|
"aria-busy": disabled,
|
|
@@ -321,11 +318,10 @@ function createThemeCard(rpc) {
|
|
|
321
318
|
function apply(ctx) {
|
|
322
319
|
installStyles();
|
|
323
320
|
ctx.effect(() => ctx.locale.register(LOCALE_NAMESPACE, messages), "dsh-showcase: settings locale");
|
|
324
|
-
const ThemeCard = createThemeCard(
|
|
325
|
-
ctx.slots.inject("
|
|
326
|
-
name: "
|
|
327
|
-
|
|
328
|
-
order: 30,
|
|
321
|
+
const ThemeCard = createThemeCard();
|
|
322
|
+
ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
|
|
323
|
+
name: "plugins.row.config",
|
|
324
|
+
key: "dsh-showcase#showcase-layout-summary",
|
|
329
325
|
locale: LOCALE_NAMESPACE,
|
|
330
326
|
}, ThemeCard));
|
|
331
327
|
}
|
package/plugin/index.d.ts
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
export const name: string;
|
|
2
2
|
export const inject: string[];
|
|
3
3
|
export type ShowcaseTheme = "frontier-signal" | "blue-big-fish";
|
|
4
|
+
export interface VolatileRef<T> {
|
|
5
|
+
get(): T;
|
|
6
|
+
}
|
|
4
7
|
export type ShowcaseSettings = {
|
|
5
|
-
theme: ShowcaseTheme
|
|
6
|
-
generatePoster: boolean
|
|
8
|
+
theme: VolatileRef<ShowcaseTheme>;
|
|
9
|
+
generatePoster: VolatileRef<boolean>;
|
|
10
|
+
};
|
|
11
|
+
export type ShowcaseSettingsInput = {
|
|
12
|
+
theme?: ShowcaseTheme;
|
|
13
|
+
generatePoster?: boolean;
|
|
7
14
|
};
|
|
8
15
|
export const SETTINGS_NAMESPACE: string;
|
|
9
|
-
export const SETTINGS_RPC_CHANNEL: string;
|
|
10
16
|
export const Config: {
|
|
11
|
-
(value?:
|
|
17
|
+
(value?: ShowcaseSettingsInput): ShowcaseSettings;
|
|
12
18
|
toJSON(): unknown;
|
|
13
19
|
};
|
|
14
20
|
export function apply(ctx: {
|
|
15
|
-
connection
|
|
16
|
-
settings: {
|
|
21
|
+
connection?: unknown;
|
|
22
|
+
settings: {
|
|
23
|
+
describe?(): Array<{ ns: string; value?: Partial<Record<"theme" | "generatePoster", unknown>>; revision?: number }>;
|
|
24
|
+
update?(namespace: string, patch: ShowcaseSettingsInput, expectedRevision?: number): Promise<void>;
|
|
25
|
+
register?(namespace: string, schema: unknown, options: { base: ShowcaseSettingsInput }): { get(): ShowcaseSettingsInput; update(patch: ShowcaseSettingsInput): Promise<void> };
|
|
26
|
+
};
|
|
17
27
|
tools: { register(definition: unknown): void };
|
|
18
|
-
|
|
28
|
+
inject?: (services: string[], apply: (ctx: { settings: { configure(options: { auto?: boolean }, owner?: unknown): () => void } }) => unknown) => unknown;
|
|
29
|
+
fiber?: unknown;
|
|
30
|
+
effect?: (callback: () => unknown, label?: string) => unknown;
|
|
31
|
+
}, config?: ShowcaseSettingsInput | ShowcaseSettings): void;
|
package/plugin/index.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
3
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
3
|
import { generateLayoutSummary } from "./layout-summary.js";
|
|
5
4
|
import { isAbsolute, resolve } from "node:path";
|
|
6
5
|
|
|
7
6
|
export const name = "showcase-layout-summary";
|
|
8
|
-
export const inject = ["tools", "settings"
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export const inject = ["tools", "settings"];
|
|
8
|
+
// The settings service addresses forms by the profile entry id declared in
|
|
9
|
+
// cordis.patch.yml. Keep this stable across plugin releases so old profiles
|
|
10
|
+
// keep their selected theme and poster preference.
|
|
11
|
+
export const SETTINGS_NAMESPACE = "showcase-layout-summary";
|
|
11
12
|
export const Config = z.object({
|
|
12
13
|
theme: z.union(["frontier-signal", "blue-big-fish"])
|
|
13
14
|
.default("frontier-signal")
|
|
15
|
+
.volatile()
|
|
14
16
|
.description("生成 HTML 海报时使用的视觉风格:终末地帝江号或蓝色大肥鱼。主题只从插件设置读取。"),
|
|
15
17
|
generatePoster: z.boolean()
|
|
16
18
|
.default(false)
|
|
19
|
+
.volatile()
|
|
17
20
|
.description("是否生成自包含 HTML 海报。关闭时只生成 Markdown 摘要。"),
|
|
18
21
|
});
|
|
19
22
|
|
|
@@ -21,81 +24,63 @@ const outputSchema = {
|
|
|
21
24
|
type: "object",
|
|
22
25
|
additionalProperties: false,
|
|
23
26
|
properties: {
|
|
24
|
-
locale: { type: "string",
|
|
25
|
-
outputPath: { type: "string",
|
|
27
|
+
locale: { type: "string", enum: ["zh-CN", "en"], required: true },
|
|
28
|
+
outputPath: { type: "string", description: "Project-relative path to the generated Markdown file.", required: true },
|
|
26
29
|
posterPath: { type: "string", description: "Project-relative path to the self-contained evidence poster when generated." },
|
|
27
|
-
posterGenerated: { type: "boolean",
|
|
28
|
-
sections: { type: "array",
|
|
30
|
+
posterGenerated: { type: "boolean", description: "Whether this call wrote a self-contained HTML poster.", required: true },
|
|
31
|
+
sections: { type: "array", items: { type: "string" }, required: true },
|
|
29
32
|
theme: { type: "string", required: true },
|
|
30
|
-
themeKey: { type: "string",
|
|
31
|
-
breakpoints: { type: "array",
|
|
32
|
-
stages: { type: "array",
|
|
33
|
-
freshnessWarnings: { type: "array",
|
|
33
|
+
themeKey: { type: "string", enum: ["frontier-signal", "blue-big-fish"], required: true },
|
|
34
|
+
breakpoints: { type: "array", items: { type: "string" }, required: true },
|
|
35
|
+
stages: { type: "array", items: { type: "string" }, required: true },
|
|
36
|
+
freshnessWarnings: { type: "array", items: { type: "string" }, required: true },
|
|
34
37
|
testCount: { type: "integer", required: true },
|
|
35
38
|
redactionCount: { type: "integer", required: true },
|
|
36
39
|
},
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
export function apply(ctx, config = {}) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
// Cordis supplies a parsed Config whose volatile fields are stable refs. The
|
|
44
|
+
// plain-object branch keeps direct consumers and older test harnesses useful.
|
|
45
|
+
const entry = hasVolatileFields(config) ? config : Config(config);
|
|
46
|
+
const initial = {
|
|
47
|
+
theme: readConfigValue(entry.theme, "frontier-signal"),
|
|
48
|
+
generatePoster: readConfigValue(entry.generatePoster, false),
|
|
49
|
+
};
|
|
50
|
+
const legacySettings = typeof ctx.settings?.register === "function";
|
|
51
|
+
let legacyRegistrationError;
|
|
52
|
+
let legacy;
|
|
53
|
+
if (legacySettings) {
|
|
54
|
+
try {
|
|
55
|
+
legacy = ctx.settings.register(SETTINGS_NAMESPACE, Config, { base: initial });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Keep the tool available when an older host has an invalid persisted
|
|
58
|
+
// section; the new SettingsForms service rejects the same write itself.
|
|
59
|
+
legacyRegistrationError = error instanceof Error ? error.message : String(error);
|
|
60
|
+
legacy = { get: () => initial, async update() { throw new Error(`Stored plugin settings could not be loaded: ${legacyRegistrationError}`); } };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!legacySettings && typeof ctx.inject === "function") {
|
|
64
|
+
// This plugin owns a custom Plugins-page form. Disable the generic form so
|
|
65
|
+
// DSH does not render a second, competing editor for the same fields.
|
|
66
|
+
ctx.inject(["settings"], (child) => {
|
|
67
|
+
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
68
|
+
});
|
|
56
69
|
}
|
|
57
70
|
const readSettings = () => {
|
|
58
|
-
const
|
|
71
|
+
const descriptor = !legacySettings && typeof ctx.settings?.describe === "function"
|
|
72
|
+
? ctx.settings.describe().find((candidate) => candidate.ns === SETTINGS_NAMESPACE)
|
|
73
|
+
: undefined;
|
|
74
|
+
const value = legacySettings ? legacy.get?.() ?? initial : descriptor?.value ?? {
|
|
75
|
+
theme: readConfigValue(entry.theme, initial.theme),
|
|
76
|
+
generatePoster: readConfigValue(entry.generatePoster, initial.generatePoster),
|
|
77
|
+
};
|
|
59
78
|
return {
|
|
60
|
-
theme: value.theme === "blue-big-fish" || value.theme === "frontier-signal" ? value.theme :
|
|
61
|
-
generatePoster: typeof value.generatePoster === "boolean" ? value.generatePoster :
|
|
79
|
+
theme: value.theme === "blue-big-fish" || value.theme === "frontier-signal" ? value.theme : initial.theme,
|
|
80
|
+
generatePoster: typeof value.generatePoster === "boolean" ? value.generatePoster : initial.generatePoster,
|
|
81
|
+
revision: descriptor?.revision,
|
|
62
82
|
};
|
|
63
83
|
};
|
|
64
|
-
ctx.connection.rpc.handle(SETTINGS_RPC_CHANNEL, async (endpoint, payload) => {
|
|
65
|
-
if (endpoint === "get") {
|
|
66
|
-
return { ok: true, value: readSettings() };
|
|
67
|
-
}
|
|
68
|
-
if (endpoint !== "set" || typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
69
|
-
return { ok: false, error: { code: "bad-request", message: "Expected get or set with theme or generatePoster.", details: { issues: [] } } };
|
|
70
|
-
}
|
|
71
|
-
const unknownKeys = Object.keys(payload).filter((key) => key !== "theme" && key !== "generatePoster");
|
|
72
|
-
if (unknownKeys.length) {
|
|
73
|
-
return { ok: false, error: { code: "bad-request", message: `Unsupported settings fields: ${unknownKeys.join(", ")}.`, details: { issues: [] } } };
|
|
74
|
-
}
|
|
75
|
-
const patch = {};
|
|
76
|
-
if ("theme" in payload) {
|
|
77
|
-
const theme = payload.theme;
|
|
78
|
-
if (theme !== "frontier-signal" && theme !== "blue-big-fish") {
|
|
79
|
-
return { ok: false, error: { code: "bad-request", message: "Unsupported showcase theme.", details: { issues: [] } } };
|
|
80
|
-
}
|
|
81
|
-
patch.theme = theme;
|
|
82
|
-
}
|
|
83
|
-
if ("generatePoster" in payload) {
|
|
84
|
-
if (typeof payload.generatePoster !== "boolean") {
|
|
85
|
-
return { ok: false, error: { code: "bad-request", message: "generatePoster must be a boolean.", details: { issues: [] } } };
|
|
86
|
-
}
|
|
87
|
-
patch.generatePoster = payload.generatePoster;
|
|
88
|
-
}
|
|
89
|
-
if (!Object.keys(patch).length) {
|
|
90
|
-
return { ok: false, error: { code: "bad-request", message: "Provide theme or generatePoster to update.", details: { issues: [] } } };
|
|
91
|
-
}
|
|
92
|
-
try {
|
|
93
|
-
await settings.update(patch);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
return { ok: false, error: { code: "settings-rejected", message: error instanceof Error ? error.message : String(error), details: { ns: SETTINGS_NAMESPACE } } };
|
|
96
|
-
}
|
|
97
|
-
return { ok: true, value: readSettings() };
|
|
98
|
-
}, { authority: "loopback" });
|
|
99
84
|
ctx.tools.register(defineTool({
|
|
100
85
|
name: "showcase_layout_summary",
|
|
101
86
|
description: "Generate a local Markdown layout summary from the current .showcase/report.json checkpoint. For visual UI projects, call it once after responsive captures are ready; for non-visual projects, call it after the report and tests are ready. Partial or failed reports are valid review checkpoints, but do not call after every code edit, status check, or intermediate tweak. Markdown is the lightweight default; generate an HTML poster only when the plugin setting allows it or the user explicitly requests a poster for this call. Reads only project files, writes only inside the project, and never uploads content.",
|
|
@@ -134,3 +119,16 @@ export function apply(ctx, config = {}) {
|
|
|
134
119
|
},
|
|
135
120
|
}));
|
|
136
121
|
}
|
|
122
|
+
|
|
123
|
+
function hasVolatileFields(value) {
|
|
124
|
+
return value !== null && typeof value === "object" && hasVolatile(value.theme) && hasVolatile(value.generatePoster);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function hasVolatile(value) {
|
|
128
|
+
return value !== null && typeof value === "object" && typeof value.get === "function";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readConfigValue(value, fallback) {
|
|
132
|
+
const current = hasVolatile(value) ? value.get() : value;
|
|
133
|
+
return current === undefined ? fallback : current;
|
|
134
|
+
}
|