dsh-plugin-deck-studio 0.2.4 → 0.2.5

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/client.js CHANGED
@@ -361,9 +361,88 @@ div[data-sidebar-collapsed]{grid-template-columns:0px minmax(0px,1fr) 0px!import
361
361
  return () => window.removeEventListener("message", onMessage);
362
362
  }, "deck-studio: prompt bridge");
363
363
 
364
+ /**
365
+ * F-164:自动新建的会话要落进一个 DSH 工作区,而不是「未分组」。
366
+ * F-165:显式 workspacePath → 启动目录所属工作区 / 登记启动目录 → 源目录工作区 → 默认工作区。
367
+ * 工作区创建失败必须报错,不能悄悄建立未分组会话。
368
+ */
369
+ /** 取一个可选服务:没 inject 的服务用 ctx.xxx 直取会 throw,这里统一吞掉。 */
370
+ function optionalService(name) {
371
+ try {
372
+ const viaGet = ctx.get?.(name);
373
+ if (viaGet) return viaGet;
374
+ } catch {
375
+ /* ctx.get 不可用 */
376
+ }
377
+ try {
378
+ return ctx[name] ?? null;
379
+ } catch {
380
+ return null;
381
+ }
382
+ }
383
+
384
+ async function resolveSessionWorkspace(data) {
385
+ const workspaces = optionalService("workspaces");
386
+ if (!workspaces?.list?.getSnapshot || !workspaces?.create) {
387
+ throw new Error("工作区服务不可用,未创建会话;请检查 DSH 版本并重试");
388
+ }
389
+ await workspaces.refresh?.();
390
+ const items = workspaces.list.getSnapshot().items ?? [];
391
+ const normalize = (path) => String(path ?? "").replace(/\\/g, "/").replace(/\/+$/, "");
392
+ const containing = (path) => items
393
+ .filter((item) => normalize(path) === normalize(item.path) || normalize(path).startsWith(`${normalize(item.path)}/`))
394
+ .sort((a, b) => b.path.length - a.path.length)[0];
395
+ const register = async (path) => {
396
+ const workspace = await workspaces.create({ path });
397
+ if (!workspace?.workspaceId) throw new Error("工作区创建未返回 workspaceId");
398
+ return workspace.workspaceId;
399
+ };
400
+ const configured = String(data.workspacePath ?? "").trim();
401
+ if (configured) return register(configured);
402
+ const cwd = String(data.cwd ?? "").trim();
403
+ if (cwd) {
404
+ const hit = containing(cwd);
405
+ if (hit) return hit.workspaceId;
406
+ // 不把插件安装目录或 DSH 私有数据目录登记成用户工作区。
407
+ const path = normalize(cwd);
408
+ const internal = /(^|\/)node_modules(\/|$)|(^|\/)\.dsh(\/|$)/.test(path);
409
+ if (path && path !== "/" && !internal) return register(cwd);
410
+ }
411
+ const sourceRoot = String(data.sourceRoot ?? "").trim();
412
+ const sourceWorkspace = sourceRoot ? containing(sourceRoot) : null;
413
+ if (sourceWorkspace) return sourceWorkspace.workspaceId;
414
+ const fallback = await workspaces.initializeDefault?.();
415
+ if (fallback?.workspaceId) return fallback.workspaceId;
416
+ throw new Error("无法确定启动工作区,未创建会话;请配置 workspacePath 后重试");
417
+ }
418
+
419
+ async function ensureConversationSession(data) {
420
+ const sessions = ctx.sessions;
421
+ if (!sessions?.create) throw new Error("sessions service unavailable");
422
+ const sessionId = String(data.sessionId ?? "").trim();
423
+ if (sessionId) {
424
+ await sessions.refresh?.();
425
+ const readPreset = () => {
426
+ const state = sessions.list?.getSnapshot?.();
427
+ const row = state?.byId?.[sessionId] || state?.items?.find((item) => (item.sessionId || item.id) === sessionId);
428
+ return row?.projectionValues?.agentPreset ?? row?.projections?.values?.agentPreset
429
+ ?? state?.projectionsBySession?.[sessionId]?.values?.agentPreset;
430
+ };
431
+ await sessions.refreshProjections?.(sessionId);
432
+ const preset = readPreset();
433
+ if (preset === "deck-studio") return sessionId;
434
+ if (!preset) throw new Error("无法核对会话模式,请刷新后重试;原会话已保留");
435
+ // 已有非空会话不能换 preset:保留原历史,另建制作会话,工作台负责追加绑定。
436
+ }
437
+ const workspaceId = await resolveSessionWorkspace(data);
438
+ const created = await sessions.create({ workspaceId, agentPreset: "deck-studio" });
439
+ if (!created) throw new Error("session create returned no id");
440
+ return created;
441
+ }
442
+
364
443
  /* 工作台 → 宿主:建/切会话并绑定 deck|design(F-43:每个会话必须有绑定)。
365
444
  * - action=open:切到给定 sessionId;
366
- * - action=ensure:有 sessionId 就切过去;没有就 sessions.create({cwd}) 新建再切,
445
+ * - action=ensure:核对制作模式后复用,否则指定工作区和 Deckline preset 新建再切,
367
446
  * 然后可选地把首条 prompt 发进去(design 会话确认后「chat 开始工作」)。 */
368
447
  ctx.effect(() => {
369
448
  const onMessage = async (event) => {
@@ -377,19 +456,15 @@ div[data-sidebar-collapsed]{grid-template-columns:0px minmax(0px,1fr) 0px!import
377
456
  }
378
457
  };
379
458
  try {
380
- const uiWorkspace = ctx.uiWorkspace ?? ctx.get?.("uiWorkspace");
459
+ const uiWorkspace = optionalService("uiWorkspace");
381
460
  if (!uiWorkspace?.openSession) throw new Error("uiWorkspace service unavailable");
382
461
  let sessionId = String(data.sessionId ?? "").trim();
383
- if (data.action === "ensure" && !sessionId) {
384
- const sessions = ctx.sessions;
385
- if (!sessions?.create) throw new Error("sessions service unavailable");
386
- // 注意:客户端 sessions.create 直接 resolve 新 sessionId,失败才 throw(不是 {ok,value})。
387
- sessionId = await sessions.create({ cwd: String(data.cwd ?? "") || undefined });
388
- if (!sessionId) throw new Error("session create returned no id");
389
- }
462
+ if (data.action === "ensure") sessionId = await ensureConversationSession(data);
390
463
  if (!sessionId) throw new Error("没有目标会话");
391
464
  if (data.action === "open" || data.action === "ensure") uiWorkspace.openSession(sessionId);
392
- if (data.prompt && data.action === "ensure") await sendToSession(sessionId, String(data.prompt));
465
+ if (data.prompt && data.action === "ensure" && sessionId !== String(data.sessionId ?? "")) {
466
+ await sendToSession(sessionId, String(data.prompt));
467
+ }
393
468
  reply({ ok: true, sessionId });
394
469
  } catch (error) {
395
470
  ctx.logger?.warn?.("deck-studio: conversation bridge failed", error);
@@ -16,6 +16,9 @@
16
16
  # dataRoot: '~/.dsh/deckline'
17
17
  # 可选:默认收起 DSH 外壳左侧栏(只建议在专门的产品 profile 里开;默认 false)
18
18
  hideShellSidebar: true # 收起 DSH 外壳左侧栏(工作区/会话);不想收就改 false
19
+ # F-164:工作台自动新建的会话落在哪个 DSH 工作区。留空 = 自动:
20
+ # 宿主启动目录所属工作区(没有则登记)→ deck 源目录所在工作区 → DSH 默认工作区。
21
+ # workspacePath: '~/Documents/我的 deck'
19
22
  bootstrap: true # 首次启动自动登记示例 deck / 资源库 / 预设风格
20
23
  userName: '' # 留空 = 首页只显示「欢迎你」
21
24
  # 新建 deck 的落盘根;留空 = 插件数据目录 .deck/projects/<id>/source
package/cordis.patch.yml CHANGED
@@ -12,6 +12,10 @@
12
12
  userName: '' # 留空 = 首页只显示「欢迎你」;填了就显示「XX,欢迎你」
13
13
  # F-157:产品默认收起 DSH 外壳左侧栏(工作区/会话那栏),要保留就改 false。
14
14
  hideShellSidebar: true
15
+ # F-164:工作台自动新建的会话落在哪个 DSH 工作区(目录)。
16
+ # 留空 = 自动:宿主启动目录所属工作区(没有则登记)→ deck 源目录所在工作区 → DSH 默认工作区。
17
+ # 想固定就填绝对路径(如 '~/Documents/我的 deck'),会自动登记成工作区。
18
+ # workspacePath: ''
15
19
  # F-157:首次启动自动登记示例 deck / 拷资源库 / 导入 8 个预设风格;不想要就改 false。
16
20
  bootstrap: true
17
21
  # 新建 deck 落盘根:留空 = 插件数据目录 .deck/projects/<id>/source
package/index.js CHANGED
@@ -1705,6 +1705,7 @@ export function createService(config, logger, env = process.env) {
1705
1705
  uiVersion: uiVersion(),
1706
1706
  displayName: config.displayName,
1707
1707
  hideShellSidebar: config.hideShellSidebar === true,
1708
+ workspacePath: config.workspacePath,
1708
1709
  dataRoot: config.dataRoot,
1709
1710
  toolsMounted: config.tools,
1710
1711
  renderMode: config.render.mode,
@@ -1968,7 +1969,10 @@ export function createService(config, logger, env = process.env) {
1968
1969
  }
1969
1970
  return {
1970
1971
  ...base,
1971
- cwd: repoRootFrom(PLUGIN_DIR),
1972
+ // F-164:会话默认落在哪个目录 / 工作区。npm 布局下 repoRootFrom 会退到插件包内的 seeds/,
1973
+ // 不能拿来当会话 cwd(会落到「未分组」)。优先显式配置的 workspacePath,否则用宿主启动目录。
1974
+ cwd: config.workspacePath || process.cwd(),
1975
+ workspacePath: config.workspacePath,
1972
1976
  config: {
1973
1977
  preset: "deck-studio",
1974
1978
  renderMode: config.render.mode,
@@ -2273,9 +2277,16 @@ export function apply(ctx, rawConfig) {
2273
2277
  const url = new URL(request?.url ?? "/", "http://localhost");
2274
2278
  const deckId = url.searchParams.get("deckId") ?? "";
2275
2279
  const docId = url.searchParams.get("docId") ?? "";
2276
- const doc = service.deckDoc(deckId, docId);
2277
- if (!doc) return json({ ok: false, error: { message: "unknown deck or doc" } }, 404);
2278
- return json({ ok: true, deckId, ...doc });
2280
+ // F-163:读取失败要把真实原因吐给前端,不能 500 空手而归(前端以前会一直转圈)。
2281
+ try {
2282
+ const doc = service.deckDoc(deckId, docId);
2283
+ if (!doc) return json({ ok: false, error: { message: `unknown deck or doc:deckId=${deckId || "(空)"} docId=${docId || "(空)"}` } }, 404);
2284
+ return json({ ok: true, deckId, ...doc });
2285
+ } catch (error) {
2286
+ const message = String(error?.message ?? error);
2287
+ logger?.warn?.(`deck-studio.doc 读取失败 deckId=${deckId} docId=${docId}:${message}`);
2288
+ return json({ ok: false, error: { message } }, 500);
2289
+ }
2279
2290
  });
2280
2291
 
2281
2292
  registerRoute(ROUTES.docfile, (request) => {
package/lib/config.js CHANGED
@@ -18,7 +18,7 @@ import { fileURLToPath } from "node:url";
18
18
  export const PLUGIN_DIR = dirname(dirname(fileURLToPath(import.meta.url)));
19
19
 
20
20
  /**
21
- * 资产根:开发仓库根 `<repo>/plugin/deck-studio/` 的上一级。
21
+ * 资产根:源码树根 `<repo>/plugin/deck-studio/` 的上一级。
22
22
  *
23
23
  * npm 安装时插件在 `<profile>/node_modules/dsh-plugin-deck-studio`,上两级是 node_modules,
24
24
  * 那里没有 samples / docs;公开包把手册与示例 deck 一起打进包内 `seeds/`(prepack 生成),
@@ -82,7 +82,7 @@ function normalizeDeck(raw, env) {
82
82
  }
83
83
 
84
84
  /** 展开 `~/` 前缀;其余原样。 */
85
- /** 默认数据根:安装用户的可写位置(不落进插件包 / node_modules)。开发仓库可在 profile 里显式配 dataRoot。 */
85
+ /** 默认数据根:安装用户的可写位置(不落进插件包 / node_modules)。源码调试时可在 profile 里显式配 dataRoot。 */
86
86
  export function defaultDataRoot(env = process.env) {
87
87
  return join(expandHome(env.DSH_HOME || "~/.dsh", env), "deckline");
88
88
  }
@@ -179,6 +179,17 @@ export function mergeConfig(raw, options = {}) {
179
179
  * 开发/其他 profile 的 DSH 界面不受影响。
180
180
  */
181
181
  hideShellSidebar: input.hideShellSidebar === true,
182
+ /**
183
+ * F-164:工作台自动新建的会话落在哪个 DSH 工作区(目录)。
184
+ * 留空 = 自动:先找 deck 源目录所在工作区,再找宿主 cwd 工作区,再退回 DSH 默认工作区;
185
+ * 填了就把这个目录登记/复用一个工作区,会话固定落在那里(不再出现「未分组」)。
186
+ */
187
+ workspacePath: expandHome(
188
+ typeof input.workspacePath === "string" && input.workspacePath.trim()
189
+ ? input.workspacePath.trim()
190
+ : (env.DSH_DECK_WORKSPACE_PATH ?? ""),
191
+ env,
192
+ ),
182
193
  /**
183
194
  * 新建 Deck 弹窗默认预选的登记风格(F-112):填 design 缩写(如 GRD)。
184
195
  * 留空 = 不预选。只在 UI 预选,不改变任何已有 deck 的关联风格。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-deck-studio",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "private": false,
5
5
  "description": "Deckline:DSH 的 image deck / PPT 工作台插件——原生 Chat + 整页图片 deck 工作区 + 设计风格库;用对话做 PPT、按页出图、版本可追踪、导出 PDF/PPTX。",
6
6
  "license": "MIT",
@@ -1,7 +1,7 @@
1
1
  # DSH 兼容矩阵(定期回归记录)
2
2
 
3
3
  > 口径:DSH 处于 prerelease 阶段,没有 stable 线;本项目**定期批次**回归,不逐 rc 跟。
4
- > 每个批次记录:证实的版本、验证范围、断点、复现方式。已知限制与路线见 [productization.md](productization.md) 与 [install.md](install.md)。
4
+ > 每个批次记录:证实的版本、验证范围、断点、复现方式。已知限制见 [install.md](install.md) 的故障排查部分。
5
5
 
6
6
  ## 当前支持声明
7
7
 
@@ -105,8 +105,7 @@ Drop a `deck.rules.md` at the root of a deck project to describe the creative co
105
105
  - It **cannot override** the product's hard rules (read-only source decks / QA gates / no fabrication /
106
106
  user confirmation). In a conflict the hard rules win and the agent says so.
107
107
  - Max 8 KB; it is never written back and never triggers any automatic action.
108
- - Do not name it `AGENTS.md`: that belongs to DSH workspace instructions and Deckline does not depend on it
109
- (see `docs/adr/0020` in the development repo).
108
+ - Do not name it `AGENTS.md`: that belongs to DSH workspace instructions and Deckline does not depend on it.
110
109
 
111
110
  ## 2.5 Where the data lives: source deck / documents / images / exports
112
111
 
@@ -132,8 +131,8 @@ creating a deck, check this table first.
132
131
  ```sh
133
132
  tar czf deckline-backup-$(date +%Y%m%d).tgz "$DSH_HOME/deckline" "<your projectsRoot>"
134
133
  ```
135
- - The workbench **Settings** panel shows the current data directory; in the development repo the data root
136
- is the repo's `.deck/` (configured explicitly in the profile).
134
+ - The workbench **Settings** panel shows the current data directory; a dev profile may point `dataRoot` at
135
+ a directory inside the source tree (e.g. `.deck/`) explicitly.
137
136
 
138
137
  ## 3. Start
139
138
 
@@ -137,7 +137,7 @@ Deck Agent 每次 `deck_source_overview` 时会读到并遵守:
137
137
  - 只约束**这一份 deck**,不影响其他 deck;不写就等于没有规则(可选能力)。
138
138
  - 它**不能覆盖**产品硬纪律(客户源只读 / QA 门禁 / 不编造 / 需用户确认)——冲突时以硬纪律为准,Agent 会说明。
139
139
  - 上限 8 KB;读取时不会回写,也不会因为文件内容自动执行任何操作。
140
- - 不叫 `AGENTS.md`:`AGENTS.md` 属于 DSH 的工作区指令,Deckline 不依赖它(见开发仓库的 `docs/adr/0020`)。
140
+ - 不叫 `AGENTS.md`:`AGENTS.md` 属于 DSH 的工作区指令,Deckline 不依赖它。
141
141
 
142
142
  ## 2.5 数据在哪里:源 deck / 文档 / 图片 / 导出
143
143
 
@@ -194,7 +194,7 @@ Deckline 支持:
194
194
  3. 填:**模式**(fake / real)、**生图节点 endpoint**、**模型**、**key 环境变量名**(或 **key 文件路径**)、**出图尺寸**;
195
195
  4. 点 **保存并生效**。
196
196
 
197
- 保存后写入本机数据目录 `~/.dsh/deckline/render-settings.json`(开发仓库里是 `.deck/render-settings.json`,
197
+ 保存后写入本机数据目录 `~/.dsh/deckline/render-settings.json`(开发调试时也可用 profile 显式配 `dataRoot`,例如仓库内 `.deck/`;
198
198
  已 gitignore):节点可直存 API key(0600,界面只显示「已保存」,payload 不回显),也可改用变量名 / key 文件;
199
199
  下次启动自动生效,并覆盖 profile 里的 render 配置。
200
200
 
package/ui/workbench.html CHANGED
@@ -1411,14 +1411,30 @@ async function fetchDoc(docId) {
1411
1411
  if (STATE.docCache[docId]) return STATE.docCache[docId];
1412
1412
  if (STATE.docLoading === docId) return null;
1413
1413
  STATE.docLoading = docId;
1414
+ const deckId = STATE.data?.active?.deckId ?? "";
1415
+ // F-163:加载失败必须可见。以前只 throw,中间的 spinner 永远不停,用户不知道是 404、500 还是超时。
1416
+ const controller = typeof AbortController === "function" ? new AbortController() : null;
1417
+ const timer = controller ? setTimeout(() => controller.abort(), 15000) : null;
1414
1418
  try {
1415
- const deckId = STATE.data.active.deckId;
1416
- const res = await fetch(`/api/deck-studio.doc?` + new URLSearchParams({ deckId, docId }));
1417
- if (!res.ok) throw new Error("HTTP " + res.status);
1418
- const doc = await res.json();
1419
- if (doc.ok) STATE.docCache[docId] = doc;
1419
+ const res = await fetch(
1420
+ `/api/deck-studio.doc?` + new URLSearchParams({ deckId, docId }),
1421
+ controller ? { signal: controller.signal } : undefined,
1422
+ );
1423
+ const text = await res.text();
1424
+ let doc = null;
1425
+ try { doc = JSON.parse(text); } catch { doc = null; }
1426
+ if (!res.ok || !doc || doc.ok === false) {
1427
+ const detail = doc?.error?.message || text.replace(/\s+/g, " ").trim().slice(0, 200) || res.statusText;
1428
+ throw new Error(`${res.status} · ${detail}`);
1429
+ }
1430
+ STATE.docCache[docId] = doc;
1420
1431
  return doc;
1432
+ } catch (error) {
1433
+ const message = error?.name === "AbortError" ? "请求超时(15 秒没有响应)" : String(error?.message ?? error);
1434
+ console.warn(`[deck-studio] 读取文档失败 docId=${docId} deckId=${deckId}:${message}`);
1435
+ return { ok: false, docId, error: { message } };
1421
1436
  } finally {
1437
+ if (timer) clearTimeout(timer);
1422
1438
  STATE.docLoading = null;
1423
1439
  }
1424
1440
  }
@@ -1430,6 +1446,18 @@ async function renderDocTab(docId) {
1430
1446
  if (!cached) view.innerHTML = `<div class="observe-body"><div class="doc-loading">读取 ${esc(index?.label ?? docId)} …</div></div>`;
1431
1447
  const doc = cached ?? (await fetchDoc(docId));
1432
1448
  if (!doc || STATE.tab !== docId) return; // 加载期间用户已切走
1449
+ if (doc.ok === false) {
1450
+ view.innerHTML = `<div class="observe-body">
1451
+ <div class="doc-loading">读取「${esc(index?.label ?? docId)}」失败:${esc(doc.error?.message || "未知错误")}</div>
1452
+ <p style="text-align:center;margin-top:10px"><button type="button" class="btn" data-doc-retry="${esc(docId)}">重试</button></p>
1453
+ <p class="small" style="text-align:center">先在终端看服务端日志;若一直失败,把上面这行错误发我。</p>
1454
+ </div>`;
1455
+ view.querySelectorAll("[data-doc-retry]").forEach((btn) => btn.addEventListener("click", () => {
1456
+ STATE.docCache[docId] = null;
1457
+ renderDocTab(docId);
1458
+ }));
1459
+ return;
1460
+ }
1433
1461
  view.innerHTML = `<div class="observe-body">${renderDoc(doc, index)}</div>`;
1434
1462
  bindDocActions(view);
1435
1463
  }
@@ -2780,15 +2808,19 @@ async function ensurePromptConversation() {
2780
2808
  if (!target) return;
2781
2809
  const bound = chatSessionsForTarget(target);
2782
2810
  if (bound.length) {
2783
- const current = currentChatSessionId();
2784
- if (bound.some((session) => session.sessionId === current)) return;
2785
- await openBoundConversation(bound[0].sessionId);
2811
+ // 发送操作也要核对 preset,不能只因绑定存在就向标准模式发制作指令。
2812
+ const result = target.kind === "deck"
2813
+ ? await ensureDeckConversation(target.id)
2814
+ : await openDesignProject(target.id);
2815
+ if (!result?.ok) throw new Error(result?.reason || "无法打开 Deckline 制作会话");
2786
2816
  await new Promise((resolve) => setTimeout(resolve, 250));
2787
2817
  return;
2788
2818
  }
2789
2819
  const active = STATE.data?.active;
2790
- if (active?.kind === "design-canvas") await openDesignProject(active.designId);
2791
- else if (active?.deckId) await ensureDeckConversation(active.deckId);
2820
+ const result = active?.kind === "design-canvas"
2821
+ ? await openDesignProject(active.designId)
2822
+ : active?.deckId ? await ensureDeckConversation(active.deckId) : null;
2823
+ if (!result?.ok) throw new Error(result?.reason || "无法打开 Deckline 制作会话");
2792
2824
  await new Promise((resolve) => setTimeout(resolve, 250));
2793
2825
  }
2794
2826
 
@@ -2809,6 +2841,8 @@ async function sendAgentPrompt() {
2809
2841
  }
2810
2842
  state.textContent = "已发送到左侧对话 ✓";
2811
2843
  setTimeout(closeAgentPrompt, 900);
2844
+ } catch (error) {
2845
+ state.textContent = `未能发送:${error?.message || error}`;
2812
2846
  } finally {
2813
2847
  button.disabled = false;
2814
2848
  }
@@ -3788,7 +3822,14 @@ function requestConversation(payload) {
3788
3822
  resolve({ ok: Boolean(event.data.ok), sessionId: event.data.sessionId || "", reason: event.data.reason || "" });
3789
3823
  };
3790
3824
  window.addEventListener("message", onAck);
3791
- target.postMessage({ type: "dsh-deck-studio:conversation", requestId, cwd: STATE.data?.cwd || "", ...payload }, "*");
3825
+ target.postMessage({
3826
+ type: "dsh-deck-studio:conversation",
3827
+ requestId,
3828
+ cwd: STATE.data?.cwd || "",
3829
+ workspacePath: STATE.data?.workspacePath || "",
3830
+ sourceRoot: STATE.data?.active?.sourceRoot || "",
3831
+ ...payload,
3832
+ }, "*");
3792
3833
  setTimeout(() => {
3793
3834
  if (settled) return;
3794
3835
  cleanup();
@@ -3850,14 +3891,17 @@ async function ensureDeckConversation(deckId) {
3850
3891
  const chosen = bound[0] || null;
3851
3892
  const binding = chosen ? { sessionId: chosen.sessionId } : null;
3852
3893
  const result = await requestConversation({
3853
- action: chosen ? "open" : "ensure",
3894
+ action: "ensure",
3854
3895
  sessionId: chosen?.sessionId || "",
3855
3896
  kind: "deck",
3856
3897
  id: deckId,
3857
3898
  title: label,
3858
- prompt: chosen ? "" : `本对话绑定 deck ${deckId}(${label})。后续所有修改都在这个 deck 上进行;先读 intent 与 script 概要,等我的指令,不要直接出图。`,
3899
+ prompt: `本对话绑定 deck ${deckId}(${label})。后续所有修改都在这个 deck 上进行;先读 intent 与 script 概要,等我的指令,不要直接出图。`,
3859
3900
  });
3860
- if (!result.ok) return result;
3901
+ if (!result.ok) {
3902
+ getEl("chatBindChip").textContent = `会话打开失败:${result.reason || "未知原因"}`;
3903
+ return result;
3904
+ }
3861
3905
  if (!binding || binding.sessionId !== result.sessionId) {
3862
3906
  await bindConversation({ sessionId: result.sessionId, kind: "deck", id: deckId, title: label });
3863
3907
  }
@@ -3872,25 +3916,28 @@ async function openDesignProject(designId) {
3872
3916
  const project = (STATE.data?.designProjects || []).find((item) => item.designId === designId);
3873
3917
  if (!project) return;
3874
3918
  showDeck(project.canvasDeckId);
3875
- const binding = bindingOf("design", designId);
3919
+ const target = chatTargetForBinding({ kind: "design", id: designId });
3920
+ const binding = chatSessionsForTarget(target)[0] || null;
3876
3921
  const title = `风格 · ${project.name}`;
3877
3922
  const result = await requestConversation({
3878
- action: binding ? "open" : "ensure",
3923
+ action: "ensure",
3879
3924
  sessionId: binding?.sessionId || "",
3880
3925
  kind: "design",
3881
3926
  id: designId,
3882
3927
  title,
3883
- prompt: binding ? "" : buildDesignPrompt(project, project.canvasDeckId),
3928
+ prompt: buildDesignPrompt(project, project.canvasDeckId),
3884
3929
  });
3885
3930
  if (result.ok && (!binding || binding.sessionId !== result.sessionId)) {
3886
3931
  await bindConversation({ sessionId: result.sessionId, kind: "design", id: designId, title });
3887
3932
  } else if (!result.ok) {
3888
3933
  STATE.lastConvError = result.reason || "未知原因";
3934
+ getEl("chatBindChip").textContent = `会话打开失败:${STATE.lastConvError}`;
3889
3935
  }
3890
3936
  if (result.ok) {
3891
3937
  STATE.chatSessionId = result.sessionId;
3892
3938
  renderChatHeader();
3893
3939
  }
3940
+ return result;
3894
3941
  }
3895
3942
 
3896
3943
  /** Conversations 页点会话:切过去,并把工作台切到它绑定的 deck / design 画布。 */
@@ -1,212 +0,0 @@
1
- # Productization plan (Deckline · DSH image-deck plugin)
2
-
3
- 简体中文 · [English](productization.en.md)
4
-
5
- > Goal: turn "a DSH plugin that works on Yuna's machine" into a product that **anyone can install in
6
- > 5 minutes, configure with their own decks and image key, and start using**.
7
- >
8
- > This document is the full plan + current-state inventory + phased acceptance. Execution is tracked in
9
- > `docs/feedback.md` (F-54…F-57) and `tasks.md` (P27).
10
-
11
- ## 1. Product shape and positioning
12
-
13
- - **Shape**: a DSH (DeepSeek Harness) **bundle plugin** + a dedicated profile (`deckline`).
14
- Not a standalone app; DSH owns chat / models / permissions, Deckline owns the "full-page image deck"
15
- workbench.
16
- - **One-liner**: turn "who is this for, what is the point, how do slides get made" into a traceable
17
- pipeline — discuss in chat → script / intent / governance documents → per-page image prompt →
18
- one-page renders + revisions → export / handoff.
19
- - **Non-goals**: no HTML deck renderer (separate project); no default batch rendering of a whole deck;
20
- never modify the user's deck source directory.
21
- - **Target users**: consultants / trainers / internal communicators / content creators who already use
22
- DSH or are willing to install it.
23
-
24
- ## 2. Current state (available / gaps)
25
-
26
- | Capability | Current state | Productization gap |
27
- |---|---|---|
28
- | DSH plugin form | ✅ bundle + preset + client panel | Package metadata is clean (MIT / direct name / no personal paths, see §10) |
29
- | Installation | ✅ three ways: npm direct name / git subdirectory / local clone (`docs/install.md`) | npm not published yet (owner action) |
30
- | Configuration | ✅ profile patch + `cordis.example.yml` + env conventions (personal paths all moved to the local patch) | Team-shared config templates (Phase 3) |
31
- | Image key | ✅ `apiKeyEnv` / `apiKeyFile`; UI only shows whether a key is configured | Docs + clearer guidance in the Settings panel |
32
- | Styles | ✅ add style (paste/upload/drag) + 8 presets | Presets ship with the plugin + one-click import (done) |
33
- | Data | ✅ `.deck/` fully gitignored, source decks read-only | "where is data / how to back up / how to migrate" docs |
34
- | Verification | ✅ smoke 46 + harness gate + 16-scenario agent eval + real-machine Playwright checks | CI / public fixtures (Phase 1) |
35
- | Docs | ✅ README (zh/en) + install / styles / sample-deck docs (zh/en) + compatibility matrix | productization English, contributing guide, screenshots (Phase 2) |
36
-
37
- ## 3. Phased plan (recommended cadence)
38
-
39
- ### Phase 0 · Installable (v0.1, 1–2 days)
40
-
41
- - [x] Plugin package metadata: `repository` / `homepage` / `keywords` / `peerDependencies`;
42
- - [x] Default config template: `plugin/deck-studio/cordis.example.yml` (decks / projectsRoot / render nodes);
43
- - [x] Install docs `docs/install.md` (npm / git / local, key config, troubleshooting);
44
- - [x] README made public-facing (intro / 5-minute install / config / adding a design / styles);
45
- - [x] Preset styles ship with the plugin: `seeds/styles/*.json` + `seeds/samples/*.jpg` + "+ Import preset styles";
46
- - [x] Adding a design supports paste / drag / file picker; existing styles can take more samples;
47
- - [x] Image key: Settings panel shows nodes + "key configured" + missing-key hints;
48
- - [x] Moved personal config from the **plugin default patch** into the profile patch (distributable defaults) — migrated and verified via `--dump-config`.
49
-
50
- **Acceptance**: a clean machine (macOS/Linux, Node `^22.19` or `≥24` + pnpm + DSH) following the README
51
- can: start → create a deck → create a style (paste an image) → see key status in Settings → run one render
52
- in fake mode.
53
-
54
- ### Phase 1 · Distributable (v0.1.0, ready; release awaits the owner)
55
-
56
- - [x] **License: MIT** (owner decision, 2026-10-21) — `LICENSE` in the repo, package metadata aligned;
57
- - [x] **Direct package name**: `dsh-plugin-deck-studio`, no org / scope; `private: false`;
58
- - [x] **Public scope** (owner decision): plugin + public docs + preset designs (seeds) + 1 sample deck + 1 sample logo;
59
- → `scripts/build-public-bundle.mjs` produces a **whitelisted clean directory** (no internal ledgers / client
60
- names / personal paths / git history);
61
- - [x] Sample logo: `assets/logo/deckline-logo.svg|png` + mark; referenced at the top of README;
62
- - [x] Sample deck: `samples/deckline-intro/` (10-page Deckline product handbook source + paste-ready
63
- walkthrough in `docs/sample-deck.md`);
64
- - [x] Privacy scan: plugin code sanitized (all client names / personal paths / key names replaced), the
65
- bundler has a built-in forbidden-word scan;
66
- - [x] `CHANGELOG.md` + v0.1.0;
67
- - [ ] **Publish the repository**: `gh repo create yuna78/dsh-deck-studio --public --source dist/public --remote origin --push`
68
- (after owner confirmation; do not push the development repo — its history contains internal process docs and client information);
69
- - [ ] `npm publish dsh-plugin-deck-studio` (npm account + 2FA required);
70
- - [ ] Add 3 real screenshots/GIF at the top of README (before going public);
71
- - [ ] GitHub Actions:
72
- - `ci.yml`: `node --check` + smoke (`plugin/deck-studio/verify/smoke.mjs`) + commit message lint;
73
- - `release.yml`: `npm publish` on tag (via `NPM_TOKEN` secret);
74
- - [x] Version compatibility: `peerDependencies` / `engines.dsh` declare `0.1.7–0.2.x`; `docs/compat.md`
75
- records two regression batches (0.1.7-rc.2 / 0.2.1-rc.2).
76
-
77
- ### Phase 2 · Easy onboarding (v0.3, 1 week)
78
-
79
- - [ ] First-run wizard: when there are no decks, guide through "new deck / import preset styles / configure key";
80
- - [ ] "New Deck" form can pick a preset style (currently free-text only);
81
- - [ ] Style library page: preview / search / tags (business, fancy, Japanese…), apply to a new deck in one click;
82
- - [ ] Data management: visualize the `.deck` location, backup/export button (zip);
83
- - [ ] i18n: Chinese + English (DSH itself supports multiple languages) — public docs are bilingual; UI strings pending;
84
- - [ ] Error reporting: actionable render failure reasons (key / quota / model name / size).
85
-
86
- ### Phase 3 · Collaborative (v0.4+)
87
-
88
- - [ ] Multi-user / team-shared profile config templates (keys never shared);
89
- - [ ] Style-pack marketplace (community contributes `seeds/styles`, PRs validated for schema + samples);
90
- - [ ] Render cost estimation / usage stats (per node);
91
- - [ ] Deck source adapters for more directory conventions (currently scans `outputs/full/final`, `outputs/final`, `pages`, `images`, `exports`).
92
-
93
- ### Phase 4 · Optional (on demand)
94
-
95
- - Desktop build (DSH desktop already exists), Docker image, SaaS hosting;
96
- - Object storage / image CDN integration (renders go straight to a CDN);
97
- - HTML deck renderer (separate project, not in this repository).
98
-
99
- ## 4. How the Git repo goes public (concrete steps)
100
-
101
- > Owner decision: **public scope = plugin + public docs + preset designs + 1 sample deck + 1 sample logo**.
102
- > The current development repo's git history contains internal ledgers (feedback / tasks / client names /
103
- > personal paths) and **must not be pushed directly**. Generate a clean whitelisted directory, then create
104
- > a new public repository from it.
105
-
106
- ```sh
107
- # 1) Generate the clean directory (whitelist + privacy scan; output in dist/public, gitignored)
108
- node scripts/build-public-bundle.mjs
109
- # ✔ whitelist + privacy scan pass (see script output), no /Users/ paths, no client names, no keys
110
-
111
- # 2) Create the public repo (after owner confirmation; account yuna78)
112
- gh repo create yuna78/dsh-deck-studio --public --source dist/public --remote origin --push
113
-
114
- # 3) Fill in repo metadata: About = "DSH image deck workbench plugin",
115
- # Topics = deepseek-harness, dsh-plugin, image-deck, presentation, deck
116
- # 4) Tag / release
117
- cd dist/public && git tag v0.1.0 && git push origin v0.1.0
118
- gh release create v0.1.0 --notes "First public release: install via dsh plugin add dsh-plugin-deck-studio"
119
-
120
- # 5) npm publish (Phase 1)
121
- cd plugin/deck-studio
122
- npm publish --access public # npm account + 2FA required
123
- ```
124
-
125
- **Public bundle contents (whitelist)**:
126
-
127
- - `README.md` / `LICENSE` / `CHANGELOG.md`;
128
- - `docs/`: `productization.md` + `productization.en.md`, `install.md` + `install.en.md`, `styles.md` + `styles.en.md`,
129
- `sample-deck.md` + `sample-deck.en.md`, `compat.md`;
130
- - `plugin/deck-studio/`: the plugin itself (host + client + ui + lib + seeds preset styles/samples + verify smoke);
131
- - `samples/deckline-intro/`: sample deck source (README / intent / script);
132
- - `assets/logo/`: sample logo (svg + png).
133
-
134
- **Not public**: `CURRENT.md` / `HANDOVER.md` / `tasks.md` / `docs/feedback.md` / `docs/architecture.md` /
135
- `docs/adr/` / `docs/research/` / `fixtures/` (contain local references to client material) / `.deck/` (runtime
136
- data) / git history.
137
-
138
- **Cleanup already done**:
139
-
140
- 1. Personal paths in `cordis.patch.yml` → profile patch; the repo carries a generic bundle patch + `cordis.example.yml`;
141
- 2. Plugin `package.json`: `private: false`, MIT, direct package name, repository / homepage / peerDependencies;
142
- 3. Plugin code sanitized: client names, personal paths and env var names in `client.js` / `lib/config.js` /
143
- `lib/deck-workspace.js` / `lib/render-nodes.js` / `lib/source-adapter.js` / `lib/tools.js` / `verify/smoke.mjs`
144
- replaced with generic examples;
145
- 4. The bundler has a built-in forbidden-word scan (client names / `/Users/` / `sk-` / private key names); a hit
146
- aborts the build.
147
-
148
- ## 5. Install and distribution matrix
149
-
150
- | Method | Command | For |
151
- |---|---|---|
152
- | npm | `dsh plugin --profile deckline add dsh-plugin-deck-studio` | Regular users (after publish) |
153
- | Git | `dsh plugin --profile deckline add "github:yuna78/dsh-deck-studio#path:/plugin/deck-studio"` | People tracking main |
154
- | Local clone | `dsh plugin --profile deckline add "$PWD/plugin/deck-studio"` | Developers / customization |
155
-
156
- All three land in `~/.dsh/profiles/deckline/`; after installing, users only edit the **profile patch**
157
- (decks / projectsRoot / render nodes) and never the plugin package — that boundary is what makes
158
- "anyone can install it" true.
159
-
160
- ## 6. Configuration conventions (image key)
161
-
162
- - Configuration exists in exactly two places: the **profile patch** (structure) and **env vars / key files** (credentials);
163
- - The structure only holds `apiKeyEnv` (variable name) or `apiKeyFile` (local path), **never a plaintext key**;
164
- - The workbench Settings panel only shows the `keyConfigured` boolean + source name;
165
- - Multiple nodes supported (primary / backup / different models), each with its own key;
166
- - Missing key: fake mode still runs the full flow; real mode reports where to configure it.
167
-
168
- ## 7. Style ecosystem (the productization differentiator)
169
-
170
- - **Preset bundle ships with the plugin**: `plugin/deck-studio/seeds/styles/<ABBR>.json` (definitions with
171
- `styleBrief` + `baselineBriefs`) + `seeds/samples/<ABBR>-0{1,2,3}.jpg` (3 baseline samples);
172
- - **One-click import**: Designs page "+ Import preset styles", existing abbreviations are skipped (idempotent);
173
- - **Add your own**: paste/upload/drag in the UI; or drop images into `designs/<ABBR>/`;
174
- - **Contribute**: a PR adds one JSON + 3 baseline samples (schema: abbr / name / description / constraints /
175
- reference / styleBrief / baselineBriefs);
176
- - Known limitation: samples illustrate a style, not guarantee verbatim reproduction of their text (a general
177
- image-model limitation); for exact text, overlay HTML/Mermaid text afterwards.
178
-
179
- ## 8. Risks and boundaries
180
-
181
- | Risk | Description | Mitigation |
182
- |---|---|---|
183
- | DSH plugin API changes | The plugin depends on the DSH version | Declared peer range + CI smoke + compatibility docs |
184
- | Credential leakage | Users paste a key into the patch | Docs stress apiKeyEnv / apiKeyFile; Settings never echoes keys |
185
- | Runaway render cost | Users batch-render everything | Named batches / whole-deck renders are allowed (per-page revisions, single-page retries); no one-click button, to avoid accidents |
186
- | Client data | Deck sources may be sensitive | Sources read-only, output local under `.deck`, repo carries no data; export packages sanitized |
187
- | Model quality | Garbled text, broken layouts | Style prompt constraints + "little text, English" advice + post-edit overlays |
188
-
189
- ## 9. Milestones (suggested)
190
-
191
- - **M1 (this week)**: Phase 0 complete + 8 preset styles imported and usable locally;
192
- - **M2 (next week)**: repository public + v0.1.0 release + 2 external users install successfully;
193
- - **M3 (within two weeks)**: npm publish + CI + first-run wizard;
194
- - **M4 (one month)**: style library page + bilingual UI + community style packs.
195
-
196
- ## 10. Decisions made (2026-10-21)
197
-
198
- | # | Decision | Outcome | Implementation |
199
- |---|---|---|---|
200
- | 1 | License | **MIT** | `LICENSE` + package.json `license: MIT` |
201
- | 2 | Package name | **Direct, no org**: `dsh-plugin-deck-studio` | package.json `name` / `private: false` |
202
- | 3 | Public scope | Plugin + docs + preset designs + **1 sample deck** + **1 sample logo** | `scripts/build-public-bundle.mjs` whitelist; no internal ledgers or history |
203
- | 4 | Brand | **Deckline** confirmed (repo name stays `dsh-deck-studio`) | README / UI / logo |
204
- | 5 | Public account | GitHub `yuna78`; repo `yuna78/dsh-deck-studio` (public) | `gh repo create` + push after owner confirmation |
205
-
206
- **Two actions still waiting on the owner**:
207
-
208
- 1. Confirm going public: run `node scripts/build-public-bundle.mjs`, then
209
- `gh repo create yuna78/dsh-deck-studio --public --source dist/public --remote origin --push`;
210
- 2. npm publish: `cd plugin/deck-studio && npm publish --access public` (needs npm login + 2FA).
211
-
212
- CI / release / screenshots come in the iterations after this round.
@@ -1,193 +0,0 @@
1
- # 产品化计划(Deckline · DSH image deck 插件)
2
-
3
- 简体中文 · [English](productization.en.md)
4
-
5
- > 目标:把现在这个「在 Yuna 机器上跑通的 DSH 插件」变成**任何人在自己机器上 5 分钟内装好、
6
- > 配好自己的 deck 和生图 key、开始用**的产品。
7
- >
8
- > 本文是完整计划 + 现状盘点 + 分阶段验收。执行登记见 `docs/feedback.md` F-54…F-57、`tasks.md` P27。
9
-
10
- ## 1. 产品形态与定位
11
-
12
- - **形态**:DSH(DeepSeek Harness)的 **bundle 插件** + 一个专用 profile(`deckline`)。
13
- 不是独立 App;DSH 负责 chat / 模型 / 权限,Deckline 负责「整页图片 deck」的工作台。
14
- - **一句话**:把「讲给谁、讲什么、怎么出图」变成一条可追踪的产线——
15
- Chat 里讨论 → script / intent / 治理文档 → 逐页 image prompt → 单页出图 + 版本 → 导出 / 交接。
16
- - **不做什么**:不做 HTML deck 渲染器(另立项目);不默认批量出整套图;不改用户 deck 源目录。
17
- - **目标用户**:咨询 / 培训 / 内部汇报 / 内容创作者,已经有 DSH 或愿意装 DSH 的人。
18
-
19
- ## 2. 现状盘点(已具备 / 缺口)
20
-
21
- | 能力 | 现状 | 产品化缺口 |
22
- |---|---|---|
23
- | DSH 插件形态 | ✅ bundle + preset + 客户端面板 | 包元数据已整理(MIT / 直发名 / 无个人路径,见 §10) |
24
- | 安装 | ✅ npm 直发包名 / git 子目录 / 本地 clone 三种方式(`docs/install.md`) | npm 尚未发布(负责人执行) |
25
- | 配置 | ✅ profile patch + `cordis.example.yml` + env 口径(个人路径已全部下沉到本机 patch) | 团队共享配置模板(Phase 3) |
26
- | 生图 key | ✅ `apiKeyEnv` / `apiKeyFile`,界面只显示是否配置 | 缺文档 + 设置面板更明确的引导 |
27
- | 风格 | ✅ 添加风格(贴图/上传/拖拽/粘贴)+ 8 个预设 | 预设需要随插件分发 + 一键导入(已做) |
28
- | 数据 | ✅ `.deck/` 全部 gitignore,源 deck 只读 | 缺「数据在哪 / 怎么备份 / 怎么迁移」说明 |
29
- | 验证 | ✅ smoke 46 项 + harness 门禁 + Agent 评估 16 场景 + 真机 Playwright 验证 | CI / 公开 fixture(Phase 1) |
30
- | 文档 | ✅ README(中/英)+ 安装 / 风格目录 / 示例 deck 文档(中/英)+ 兼容矩阵 | productization 英译、贡献指南、截图(Phase 2) |
31
-
32
- ## 3. 分阶段计划(建议按此节奏)
33
-
34
- ### Phase 0 · 可安装(v0.1,1–2 天)
35
-
36
- - [x] 插件包元数据:加 `repository` / `homepage` / `keywords` / `peerDependencies`(`private: true` + `UNLICENSED` 暂留防误发,license 拍板后改);
37
- - [x] 默认配置模板:`plugin/deck-studio/cordis.example.yml`(decks / projectsRoot / render 节点);
38
- - [x] 安装文档 `docs/install.md`(npm / git / 本地 三种方式 + key 配置 + 排障);
39
- - [x] README 对外化(产品介绍 / 5 分钟安装 / 配置 / 加 design / 风格);
40
- - [x] 预设风格随插件分发:`seeds/styles/*.json` + `seeds/samples/*.jpg` + 「+ 导入预设风格」;
41
- - [x] 添加 design 支持贴图 / 拖拽 / 选文件;已有风格可继续加样张;
42
- - [x] 生图 key:工作台设置面板显示节点 + 「key 是否配置」+ 缺 key 提示;
43
- - [x] 把当前机器上的个人配置从**插件默认 patch** 挪到 profile patch(保证默认包可分发)——已迁移并用 `--dump-config` 验证。
44
-
45
- **验收**:一台干净机器(macOS/Linux,Node `^22.19` 或 `≥24` + pnpm + DSH)按 README 走完能:启动 → 建 deck →
46
- 建风格(贴图)→ 设置里看到 key 状态 →(fake 模式)走完一次出图。
47
-
48
- ### Phase 1 · 可分发(v0.1.0,已就绪,待负责人执行发布)
49
-
50
- - [x] **许可证:MIT**(负责人 2026-10-21 拍板)——`LICENSE` 已落仓库,包元数据同步;
51
- - [x] **包名直发**:`dsh-plugin-deck-studio`,无 org / scope;`private: false`;
52
- - [x] **公开范围**(负责人拍板):插件 + 对外 docs + 预设 design(seeds)+ 1 个 sample deck + 1 个 sample logo;
53
- → 用 `scripts/build-public-bundle.mjs` 生成**白名单干净目录**(不含内部台账 / 客户名 / 个人路径 / git 历史);
54
- - [x] sample logo:`assets/logo/deckline-logo.svg|png` + mark;README 顶部已引用;
55
- - [x] sample deck:`samples/deckline-intro/`(Deckline 产品说明手册 10 页源 + 可粘贴跑通流程文案 `docs/sample-deck.md`);
56
- - [x] 隐私扫描:插件代码已脱敏(客户名 / 个人路径 / key 名全部换掉),打包脚本内置禁用词扫描;
57
- - [x] `CHANGELOG.md` + v0.1.0;
58
- - [ ] **仓库公开**:`gh repo create yuna78/dsh-deck-studio --public --source dist/public --remote origin --push`
59
- (需负责人确认后执行;不要推当前开发仓库——它的 git 历史含内部过程文档与客户信息);
60
- - [ ] `npm publish dsh-plugin-deck-studio`(需 npm 账号 + 2FA);
61
- - [ ] README 顶部补 3 张真机截图/GIF(公开前);
62
- - [ ] GitHub Actions:
63
- - `ci.yml`:`node --check` + smoke(`plugin/deck-studio/verify/smoke.mjs`)+ 提交信息规范;
64
- - `release.yml`:tag 时 `npm publish`(用 `NPM_TOKEN` secret);
65
- - [x] 版本兼容:`peerDependencies` / `engines.dsh` 声明 `0.1.7–0.2.x`;`docs/compat.md` 记录 0.1.7-rc.2 / 0.2.1-rc.2 两批回归。
66
-
67
- ### Phase 2 · 好上手(v0.3,1 周)
68
-
69
- - [ ] 首次启动向导:无 deck 时引导「新建 deck / 导入预设风格 / 配 key」三步;
70
- - [ ] 「新建 Deck」表单支持从预设风格里选(现在只能手填风格);
71
- - [ ] 风格库页面:预览 / 搜索 / 标签(商务、fancy、日系…),一键套用到新 deck;
72
- - [ ] 数据管理:`.deck` 位置可视化、备份/导出按钮(zip);
73
- - [ ] i18n:中文 + English(DSH 本身支持多语言)——公开文档已中英双语,UI 文案待做;
74
- - [ ] 错误上报:出图失败给出可操作原因(key / 配额 / 模型名 / 尺寸)。
75
-
76
- ### Phase 3 · 可协作(v0.4+)
77
-
78
- - [ ] 多用户 / 团队共享 profile 配置模板(不共享 key);
79
- - [ ] 风格包 marketplace(社区贡献 `seeds/styles`,PR 校验 schema + 样张);
80
- - [ ] 出图成本估算 / 用量统计(按节点);
81
- - [ ] Deck 源适配更多目录约定(现在扫描 `outputs/full/final`、`outputs/final`、`pages`、`images`、`exports`)。
82
-
83
- ### Phase 4 · 可选(视需求)
84
-
85
- - 桌面版(DSH desktop 已有)、Docker 镜像、SaaS 托管;
86
- - 与对象存储 / 图床集成(出图直接进 CDN);
87
- - HTML deck 渲染器(独立项目,不在本仓库)。
88
-
89
- ## 4. Git 如何公开(具体步骤)
90
-
91
- > 负责人已拍板:**公开范围 = 插件 + 对外 docs + 预设 design + 1 个 sample deck + 1 个 sample logo**。
92
- > 当前开发仓库的 git 历史含内部台账(feedback / tasks / 客户名 / 个人路径),**不能直接 push**。
93
- > 用白名单脚本生成干净目录,再创建一个新的公开仓库。
94
-
95
- ```sh
96
- # 1) 生成干净目录(白名单 + 隐私扫描;产物在 dist/public,已 gitignore)
97
- node scripts/build-public-bundle.mjs
98
- # ✔ 白名单 + 隐私扫描通过(以脚本输出为准),无 /Users/ 个人路径、无客户名、无 key
99
-
100
- # 2) 建公开仓库(负责人确认后执行;账号 yuna78)
101
- gh repo create yuna78/dsh-deck-studio --public --source dist/public --remote origin --push
102
-
103
- # 3) 补仓库元信息:About = "DSH image deck workbench plugin",
104
- # Topics = deepseek-harness, dsh-plugin, image-deck, presentation, deck
105
- # 4) 打 tag / 发 Release
106
- cd dist/public && git tag v0.1.0 && git push origin v0.1.0
107
- gh release create v0.1.0 --notes "First public release: install via dsh plugin add dsh-plugin-deck-studio"
108
-
109
- # 5) npm 发布(Phase 1)
110
- cd plugin/deck-studio
111
- npm publish --access public # 需要 npm 账号 + 2FA
112
- ```
113
-
114
- **公开包内容(白名单)**:
115
-
116
- - `README.md` / `LICENSE` / `CHANGELOG.md`;
117
- - `docs/`:`productization.md`、`install.md` + `install.en.md`、`styles.md` + `styles.en.md`、`sample-deck.md` + `sample-deck.en.md`、`compat.md`;
118
- - `plugin/deck-studio/`:插件本体(宿主 + 客户端 + ui + lib + seeds 预设风格/样张 + verify smoke);
119
- - `samples/deckline-intro/`:示例 deck 源(README / intent / script);
120
- - `assets/logo/`:sample logo(svg + png)。
121
-
122
- **不公开**:`CURRENT.md` / `HANDOVER.md` / `tasks.md` / `docs/feedback.md` / `docs/architecture.md` /
123
- `docs/adr/` / `docs/research/` / `fixtures/`(含客户材料的本地源引用)/ `.deck/`(运行数据)/ git 历史。
124
-
125
- **已完成的清理**:
126
-
127
- 1. `cordis.patch.yml` 个人路径 → profile patch;仓库里是通用 bundle patch + `cordis.example.yml`;
128
- 2. 插件 `package.json`:`private: false`、MIT、直发包名、repository / homepage / peerDependencies;
129
- 3. 插件代码脱敏:`client.js` / `lib/config.js` / `lib/deck-workspace.js` / `lib/render-nodes.js` /
130
- `lib/source-adapter.js` / `lib/tools.js` / `verify/smoke.mjs` 里的客户名、个人路径、环境变量名全部替换为通用示例;
131
- 4. 白名单脚本内置禁用词扫描(客户名 / `/Users/` / `sk-` / 私有 key 名),命中即中止打包。
132
-
133
- ## 5. 安装与分发矩阵
134
-
135
- | 方式 | 命令 | 适合 |
136
- |---|---|---|
137
- | npm | `dsh plugin --profile deckline add dsh-plugin-deck-studio` | 普通用户(发布后) |
138
- | Git | `dsh plugin --profile deckline add "github:yuna78/dsh-deck-studio#path:/plugin/deck-studio"` | 想跟 main 的人 |
139
- | 本地 clone | `dsh plugin --profile deckline add "$PWD/plugin/deck-studio"` | 开发者 / 定制 |
140
-
141
- 三种方式都落在 `~/.dsh/profiles/deckline/`;安装后用户只需要改 **profile patch**(decks / projectsRoot / render 节点),
142
- 不需要动插件包本身——这是「任何人可安装」的关键边界。
143
-
144
- ## 6. 配置口径(生图 key)
145
-
146
- - 配置只存在于两处:**profile patch**(结构)与 **环境变量 / key 文件**(凭证);
147
- - 结构里只写 `apiKeyEnv`(变量名)或 `apiKeyFile`(本机路径),**永远不写 key 明文**;
148
- - 工作台设置面板只显示 `keyConfigured` 布尔 + 来源名;
149
- - 支持多节点(主 / 备用 / 不同模型),每节点独立 key;
150
- - 缺 key 时:fake 模式仍可走通流程;real 模式报错信息直接给出配置位置。
151
-
152
- ## 7. 风格生态(本产品化差异点)
153
-
154
- - **预设包随插件分发**:`plugin/deck-studio/seeds/styles/<ABBR>.json`(定义,含 `styleBrief` + `baselineBriefs`)
155
- + `seeds/samples/<ABBR>-0{1,2,3}.jpg`(3 张基线样张);
156
- - **一键导入**:Designs 页「+ 导入预设风格」,已存在的缩写自动跳过(幂等);
157
- - **自己加**:界面里贴图/上传/拖拽;或把图片放到 `designs/<ABBR>/`;
158
- - **贡献**:PR 新增一个 JSON + 3 张基线样张即可(schema:abbr / name / description / constraints / reference / styleBrief / baselineBriefs);
159
- - 已知限制:样张是风格示意,不保证能逐字复刻其中的文字(图像模型通病);要精确文字用 HTML/Mermaid 叠字。
160
-
161
- ## 8. 风险与边界
162
-
163
- | 风险 | 说明 | 对策 |
164
- |---|---|---|
165
- | DSH 插件 API 变动 | 插件依赖 DSH 版本 | 声明 peer 范围 + CI 跑 smoke + 版本豁免文档 |
166
- | 凭证泄漏 | 用户误把 key 写进 patch | 文档强调 apiKeyEnv / apiKeyFile;设置面板不回显 |
167
- | 出图成本失控 | 用户批量出图 | 允许点名批量 / 整套(逐页留痕、失败单页重出);不做一键按钮,避免误触 |
168
- | 客户数据 | deck 源可能含敏感内容 | 源只读、产物本地 `.deck`、仓库不含数据;导出交接包脱敏 |
169
- | 模型质量 | 文字糊、版式跑 | 风格 prompt 约束 + 「文字少而英文」建议 + 后期叠字 |
170
-
171
- ## 9. 里程碑(建议时间)
172
-
173
- - **M1(本周)**:Phase 0 完成 + 本机导入 8 个预设风格可用;
174
- - **M2(下周)**:仓库公开 + v0.1.0 Release + 2 位外部用户装通;
175
- - **M3(两周内)**:npm 发布 + CI + 首次启动向导;
176
- - **M4(一个月)**:风格库页面 + UI 双语 + 社区风格包。
177
-
178
- ## 10. 已拍板(2026-10-21)
179
-
180
- | # | 决策 | 结论 | 落地 |
181
- |---|---|---|---|
182
- | 1 | License | **MIT** | `LICENSE` + package.json `license: MIT` |
183
- | 2 | 包名 | **直发,无 org**:`dsh-plugin-deck-studio` | package.json `name` / `private: false` |
184
- | 3 | 公开范围 | 插件 + docs + 预设 design + **1 个 sample deck** + **1 个 sample logo** | `scripts/build-public-bundle.mjs` 白名单;不含内部台账与历史 |
185
- | 4 | 品牌 | **Deckline** 最终确认(仓库名仍 `dsh-deck-studio`) | README / UI / logo |
186
- | 5 | 公开账号 | GitHub `yuna78`;仓库 `yuna78/dsh-deck-studio`(public) | 待负责人确认后 `gh repo create` + push |
187
-
188
- **还等负责人执行的 2 个动作**:
189
-
190
- 1. 确认公开:跑 `node scripts/build-public-bundle.mjs` 后 `gh repo create yuna78/dsh-deck-studio --public --source dist/public --remote origin --push`;
191
- 2. npm 发布:`cd plugin/deck-studio && npm publish --access public`(需登录 npm + 2FA)。
192
-
193
- 之后 CI / Release / 截图由本轮之后的迭代补。