dsh-plugin-deck-studio 0.2.5 → 0.2.8
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/cordis.example.yml +88 -30
- package/cordis.patch.yml +81 -30
- package/index.js +187 -28
- package/lib/capabilities.js +234 -0
- package/lib/citation-check.js +93 -0
- package/lib/config.js +30 -7
- package/lib/deck-brief.js +35 -1
- package/lib/deck-docs.js +45 -7
- package/lib/deck-templates.js +8 -1
- package/lib/deck-workspace.js +27 -1
- package/lib/designs.js +15 -0
- package/lib/fact-labels.js +81 -0
- package/lib/first-draft.js +119 -0
- package/lib/image-review.js +523 -0
- package/lib/page-contract.js +112 -0
- package/lib/proposal-decision.js +249 -75
- package/lib/review-agent.js +168 -0
- package/lib/review-personas.js +59 -3
- package/lib/review-protocol.js +411 -0
- package/lib/review-readers.js +300 -0
- package/lib/review-score.js +198 -0
- package/lib/title-chain.js +122 -0
- package/lib/tools.js +983 -85
- package/package.json +4 -3
- package/seeds/docs/assets/screenshots/07-new-deck.png +0 -0
- package/seeds/docs/assets/screenshots/08-presentation.png +0 -0
- package/seeds/docs/assets/screenshots/09-style-library.png +0 -0
- package/seeds/docs/assets/screenshots/10-style-management.png +0 -0
- package/seeds/docs/assets/screenshots/f186-01-agent-style-create.png +0 -0
- package/seeds/docs/assets/screenshots/f186-02-designs-card.png +0 -0
- package/seeds/docs/assets/screenshots/f187-01-review-readers.png +0 -0
- package/seeds/docs/assets/screenshots/f187-02-gap-report.png +0 -0
- package/seeds/docs/assets/screenshots/i14-01-create.png +0 -0
- package/seeds/docs/assets/screenshots/i14-02-first-draft.png +0 -0
- package/seeds/docs/assets/screenshots/i14-03-rendered.png +0 -0
- package/seeds/docs/assets/screenshots/i14-04-image-review.png +0 -0
- package/seeds/docs/assets/screenshots/i14-05-p01-page.png +0 -0
- package/seeds/docs/assets/screenshots/manual/07-new-deck.png +0 -0
- package/seeds/docs/compat.md +3 -3
- package/seeds/docs/install.en.md +29 -14
- package/seeds/docs/install.md +29 -15
- package/seeds/docs/sample-deck.en.md +1 -1
- package/seeds/docs/sample-deck.md +1 -1
- package/seeds/docs/styles.en.md +5 -4
- package/seeds/docs/styles.md +17 -7
- package/seeds/samples/DSA-01.jpg +0 -0
- package/seeds/samples/DSA-02.jpg +0 -0
- package/seeds/samples/DSA-03.jpg +0 -0
- package/seeds/styles/DSA.json +22 -0
- package/ui/workbench.css +5 -0
- package/ui/workbench.html +111 -7
package/ui/workbench.html
CHANGED
|
@@ -30,6 +30,18 @@
|
|
|
30
30
|
.brief-style-badge.is-unknown{border-color:#cdd6d4;background:#f4f7f6;color:#6b7b7a}
|
|
31
31
|
.brief-cover{margin-left:6px;color:#4a5c5a}
|
|
32
32
|
.brief-banner{border-color:#e0b96a;background:#fdf7e8}
|
|
33
|
+
.review-panel{margin:10px 0 12px;padding:10px 12px;border:1px solid #cfd9e8;background:#f6f9fd;border-radius:10px}
|
|
34
|
+
.review-row{display:grid;grid-template-columns:18px 132px 1fr 130px 1.2fr;gap:8px;align-items:center;padding:5px 0;border-top:1px dashed #dde5ef;font-size:12px}
|
|
35
|
+
.review-row:first-of-type{border-top:none}
|
|
36
|
+
.review-row-off{opacity:.55}
|
|
37
|
+
.review-tag{color:#25507f;font-weight:600;font-size:11px}
|
|
38
|
+
.review-why{color:#4a5464}
|
|
39
|
+
.review-page{color:#7a8595;font-size:11px}
|
|
40
|
+
.review-diff{display:flex;gap:6px;align-items:center;overflow:hidden}
|
|
41
|
+
.review-diff .proposal-before{max-width:45%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#8a8a8a}
|
|
42
|
+
.review-diff .proposal-value{max-width:55%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#1d5c2e}
|
|
43
|
+
.review-actions{display:flex;gap:10px;align-items:center;justify-content:flex-end;margin-top:8px}
|
|
44
|
+
.review-hint{color:#7a8595;font-size:11px}
|
|
33
45
|
.assoc-switch-panel{margin-top:10px;padding:12px 14px;border:1px dashed #d8b46a;border-radius:10px;background:#fffdf6}
|
|
34
46
|
.assoc-switch-panel label{display:block;margin:6px 0;font-size:13px;color:#3c4a4c}
|
|
35
47
|
.assoc-switch-panel .panel-actions{display:flex;gap:8px;margin-top:10px}
|
|
@@ -130,7 +142,7 @@
|
|
|
130
142
|
<button class="modal-close" id="ndClose" title="关闭">✕</button>
|
|
131
143
|
</div>
|
|
132
144
|
<form class="modal-body newdeck-body" id="newDeckForm">
|
|
133
|
-
<p class="nd-tip">先定「讲给谁、讲什么、想带走什么」+ 视觉风格。创建后会开出源目录与 9
|
|
145
|
+
<p class="nd-tip">先定「讲给谁、讲什么、想带走什么」+ 视觉风格。创建后会开出源目录与 9 份文档骨架,然后在对话里继续补资料、改 script,直到可以出图。<br>新项目保存位置:<code id="ndStoragePath">正在读取配置…</code>。<span id="ndStorageNote"></span></p>
|
|
134
146
|
<label class="field"><span>主题 / 标题</span><input id="ndTitle" required placeholder="例:XX 业务场景的 AI 落地分享"></label>
|
|
135
147
|
<label class="field"><span>项目目录名(可选,英文小写中划线;留空自动生成)</span><input id="ndDirName" placeholder="例:talk-ai-transformation-6-stages"></label>
|
|
136
148
|
<label class="field"><span>页数(占位页 1–40;创建后还能在对话里追加)</span><input id="ndPages" type="number" min="1" max="40" inputmode="numeric" value="6"></label>
|
|
@@ -1399,10 +1411,21 @@ function docHeader(doc, extra = "") {
|
|
|
1399
1411
|
<div><div class="eyebrow">Deck Document</div><h2>${esc(doc.label)}</h2></div>
|
|
1400
1412
|
${docBadge(doc.status)}
|
|
1401
1413
|
<span class="small doc-file">${esc(doc.primary || "无文件")}</span>
|
|
1414
|
+
${(doc.files || []).length > 1 ? `<div class="doc-files">${doc.files.map((file) => `<button type="button" class="doc-file-chip${file.name === doc.primary ? " active" : ""}" data-doc-file="${esc(file.name)}" title="${esc(file.name)}">${esc(file.name.replace(/^docs\//, ""))}</button>`).join("")}</div>` : ""}
|
|
1402
1415
|
${extra}
|
|
1403
1416
|
</div>`;
|
|
1404
1417
|
}
|
|
1405
1418
|
|
|
1419
|
+
/** F-187:审稿页有读者本 / 差距本 / 工程本,点 chip 切换正文。 */
|
|
1420
|
+
async function openDocFile(docId, file) {
|
|
1421
|
+
const deckId = STATE.data?.active?.deckId ?? "";
|
|
1422
|
+
const res = await fetch(`/api/deck-studio.doc?` + new URLSearchParams({ deckId, docId, file }));
|
|
1423
|
+
const doc = await res.json().catch(() => null);
|
|
1424
|
+
if (!res.ok || !doc || doc.ok === false) return;
|
|
1425
|
+
STATE.docCache[docId] = doc;
|
|
1426
|
+
renderDocTab(docId);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1406
1429
|
function docFooter(doc, content) {
|
|
1407
1430
|
return `<details class="doc-raw"><summary>查看原始 markdown</summary><div class="md"><pre>${esc(content || "(空)")}</pre></div></details>`;
|
|
1408
1431
|
}
|
|
@@ -1557,7 +1580,25 @@ function renderDoc(doc, index) {
|
|
|
1557
1580
|
const name = kind === "body" ? "正文" : "画面要求";
|
|
1558
1581
|
return `<span class="density-count${off ? " is-off" : ""}" title="当前密度「${esc(densityTarget.label)}」:${name}目标 ${min}–${max} 字">${name} ${chars} 字${off ? ` · 目标 ${min}–${max}` : " · 达标"}</span>`;
|
|
1559
1582
|
};
|
|
1560
|
-
|
|
1583
|
+
// F-176 / I-18:评审意见清单——带 reviewRef / severity / confidence 的提案合成一个可勾选清单,
|
|
1584
|
+
// 勾完点「接受并更新」→ 一条 revision(不是一条提案一条)。C 类(原文找不到)根本不进提案。
|
|
1585
|
+
const reviewBatch = actionablePending.filter((proposal) => proposal.reviewRef || proposal.severity || proposal.confidence);
|
|
1586
|
+
const reviewRow = (proposal) => {
|
|
1587
|
+
const confidence = String(proposal.confidence || "").toUpperCase();
|
|
1588
|
+
const selectable = confidence !== "C";
|
|
1589
|
+
const tag = [proposal.reviewRef, proposal.severity, confidence ? `证据 ${confidence}` : ""].filter(Boolean).join(" · ");
|
|
1590
|
+
return `<label class="review-row${selectable ? "" : " review-row-off"}">
|
|
1591
|
+
<input type="checkbox" data-review-pick="${esc(proposal.proposalId)}"${selectable ? (confidence === "D" ? "" : " checked") : " disabled"}>
|
|
1592
|
+
<span class="review-tag">${esc(tag || "评审意见")}</span>
|
|
1593
|
+
<span class="review-why">${esc(String(proposal.reason || proposal.evidence || "按评审意见修改").slice(0, 80))}</span>
|
|
1594
|
+
<span class="review-page">${esc(proposal.pageId)} · ${esc(proposalFieldLabel(proposal.field))}</span>
|
|
1595
|
+
<span class="review-diff"><span class="proposal-before">${esc(proposalCurrent(proposal) || "(空)")}</span><span class="proposal-arrow">→</span><span class="proposal-value">${esc(proposalPreview(proposal) || "(空值)")}</span></span>
|
|
1596
|
+
</label>`;
|
|
1597
|
+
};
|
|
1598
|
+
const reviewPanel = reviewBatch.length
|
|
1599
|
+
? `<div class="review-panel"><p class="proposal-strip-note">评审意见 <b>${reviewBatch.length}</b> 条 · 勾选后点「接受并更新」合成<b>一条</b> revision(旧版保留,可回滚);不点就什么都不改</p>${reviewBatch.map(reviewRow).join("")}<div class="review-actions"><button class="btn solid" data-accept-review="1">接受并更新所选</button><span class="review-hint">A=原文可定位;B=转述或支撑偏弱;D=原文在但推不出结论(默认不勾,只作参考);C=无法核实,不进清单</span></div></div>`
|
|
1600
|
+
: "";
|
|
1601
|
+
return head + styleNote + densityNote + briefBanner + docIssues(doc) + storyOverview + banner + reviewPanel + `<div class="page-cards">${pages.map((page) => {
|
|
1561
1602
|
const pageProposals = pendingByPage.get(Number(page.page)) || [];
|
|
1562
1603
|
const slide = slideById.get(page.id) || {};
|
|
1563
1604
|
// 已落版值(manifest)优先:确认过的 title/question/body 必须立刻显示,不能被源文档快照盖住。
|
|
@@ -1678,6 +1719,12 @@ function renderDoc(doc, index) {
|
|
|
1678
1719
|
if (doc.id === "changelog") {
|
|
1679
1720
|
const feed = doc.parsed.feed ?? [];
|
|
1680
1721
|
const fmtWhen = (value) => String(value || "").replace("T", " ").slice(0, 16).replace(/-/g, "/");
|
|
1722
|
+
// F-173:最新一版是首稿自动落版时,给出「这版没经过确认」+ 一键回滚。
|
|
1723
|
+
const latestRevision = feed.map((row) => row.revision).filter(Boolean).sort().pop() || "";
|
|
1724
|
+
const autoRow = feed.find((row) => row.autoland && row.autoland.revision === latestRevision && !row.autoland.rolledBack);
|
|
1725
|
+
const rollbackBar = autoRow
|
|
1726
|
+
? `<div class="proposal-banner"><div><b>${esc(autoRow.autoland.badge || "首稿(自动落版,未经确认)")}</b><span>Agent 自改 ${esc(String(autoRow.autoland.selfReview?.rounds ?? 0))} 轮后直接落版,没有逐条等你确认。${autoRow.autoland.selfReview?.summary ? `主要改了:${esc(autoRow.autoland.selfReview.summary)}。` : ""}不满意可以回到落版前。</span></div><div class="proposal-banner-actions"><button class="btn alt" data-rollback-draft="${esc(autoRow.autoland.revision)}">回滚首稿</button></div></div>`
|
|
1727
|
+
: "";
|
|
1681
1728
|
const list = feed.length
|
|
1682
1729
|
? `<div class="change-feed">${feed.map((row) => `
|
|
1683
1730
|
<div class="change-item">
|
|
@@ -1691,7 +1738,7 @@ function renderDoc(doc, index) {
|
|
|
1691
1738
|
<div class="change-note">${row.note ? mdInline(row.note) : `<span class="small">系统记录,未写说明</span>`}</div>
|
|
1692
1739
|
</div>`).join("")}</div>`
|
|
1693
1740
|
: `<p class="engine-note">还没有变更记录:改一次 script / 出一次图就会自动出现在这里。</p>`;
|
|
1694
|
-
return head + docIssues(doc) + list + `<p class="engine-note">这张日志由系统从版本与事件自动生成;Agent 每次改完 deck 只用补一句「为什么」(工具 <code>deck_note_change</code>),不需要手抄历史。</p>` + docFooter(doc, content);
|
|
1741
|
+
return head + docIssues(doc) + rollbackBar + list + `<p class="engine-note">这张日志由系统从版本与事件自动生成;Agent 每次改完 deck 只用补一句「为什么」(工具 <code>deck_note_change</code>),不需要手抄历史。</p>` + docFooter(doc, content);
|
|
1695
1742
|
}
|
|
1696
1743
|
if (doc.id === "feedback") {
|
|
1697
1744
|
const rows = doc.parsed.rows ?? [];
|
|
@@ -1822,7 +1869,7 @@ function flash(text, kind = "ok") {
|
|
|
1822
1869
|
}
|
|
1823
1870
|
|
|
1824
1871
|
/** F-108:在工作台直接落版 / 驳回提案(与 Agent 的 deck_revision_confirm 同一条写入路径)。 */
|
|
1825
|
-
async function decideProposals(proposalIds, confirmed) {
|
|
1872
|
+
async function decideProposals(proposalIds, confirmed, batch = false) {
|
|
1826
1873
|
const ids = (proposalIds || []).filter(Boolean);
|
|
1827
1874
|
if (!ids.length) return;
|
|
1828
1875
|
const res = await fetch("/api/deck-studio.proposal-decide", {
|
|
@@ -1831,7 +1878,8 @@ async function decideProposals(proposalIds, confirmed) {
|
|
|
1831
1878
|
body: JSON.stringify({
|
|
1832
1879
|
proposalIds: ids,
|
|
1833
1880
|
confirmed,
|
|
1834
|
-
|
|
1881
|
+
...(batch ? { batch: true } : {}),
|
|
1882
|
+
quote: confirmed ? (batch ? "(工作台:用户点了「接受并更新」)" : "(工作台:用户点了「确认落版」)") : "(工作台:用户点了「驳回」)",
|
|
1835
1883
|
}),
|
|
1836
1884
|
});
|
|
1837
1885
|
let payload = {};
|
|
@@ -1840,6 +1888,13 @@ async function decideProposals(proposalIds, confirmed) {
|
|
|
1840
1888
|
flash(`操作失败:${payload?.error?.message || `HTTP ${res.status}`}`, "error");
|
|
1841
1889
|
return;
|
|
1842
1890
|
}
|
|
1891
|
+
if (payload.batch) {
|
|
1892
|
+
flash(`已接受并更新:${payload.decided} 条意见合成 revision ${payload.revision}(旧版保留,可回滚)`, "ok");
|
|
1893
|
+
STATE.docCache = {};
|
|
1894
|
+
await loadWorkbench(STATE.deckId);
|
|
1895
|
+
if (["script", "design", "intent", "changelog"].includes(STATE.tab)) await renderDocTab(STATE.tab);
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1843
1898
|
const failed = (payload.results || []).filter((item) => !item.ok);
|
|
1844
1899
|
const expired = failed.filter((item) => /已过期|工作副本已漂移/.test(String(item.error || "")));
|
|
1845
1900
|
let message;
|
|
@@ -1853,7 +1908,32 @@ async function decideProposals(proposalIds, confirmed) {
|
|
|
1853
1908
|
if (["script", "design", "intent", "changelog"].includes(STATE.tab)) await renderDocTab(STATE.tab);
|
|
1854
1909
|
}
|
|
1855
1910
|
|
|
1911
|
+
/** F-173:回滚首稿(回到自动落版前的内容,追加新 revision)。 */
|
|
1912
|
+
async function rollbackFirstDraft(revision) {
|
|
1913
|
+
if (!STATE.deckId) return;
|
|
1914
|
+
const res = await fetch("/api/deck-studio.draft-rollback", {
|
|
1915
|
+
method: "POST",
|
|
1916
|
+
headers: { "content-type": "application/json" },
|
|
1917
|
+
body: JSON.stringify({ deckId: STATE.deckId, note: "(工作台:用户点了「回滚首稿」)" }),
|
|
1918
|
+
});
|
|
1919
|
+
let payload = {};
|
|
1920
|
+
try { payload = await res.json(); } catch { /* 非 JSON 走兜底 */ }
|
|
1921
|
+
if (!res.ok || !payload.ok) {
|
|
1922
|
+
flash(`回滚失败:${payload?.error?.message || `HTTP ${res.status}`}`, "error");
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
flash(`已回滚首稿:${payload.beforeRevision || revision} → ${payload.revision}(旧版保留)`, "ok");
|
|
1926
|
+
STATE.docCache = {};
|
|
1927
|
+
await loadWorkbench(STATE.deckId);
|
|
1928
|
+
if (["script", "changelog"].includes(STATE.tab)) await renderDocTab(STATE.tab);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1856
1931
|
function bindDocActions(root) {
|
|
1932
|
+
root.querySelectorAll("[data-doc-file]").forEach((btn) => btn.addEventListener("click", () => {
|
|
1933
|
+
const file = btn.getAttribute("data-doc-file") || "";
|
|
1934
|
+
if (file) openDocFile(STATE.tab, file);
|
|
1935
|
+
}));
|
|
1936
|
+
root.querySelectorAll("[data-rollback-draft]").forEach((btn) => btn.addEventListener("click", () => rollbackFirstDraft(btn.getAttribute("data-rollback-draft") || "")));
|
|
1857
1937
|
root.querySelectorAll("[data-assoc-bind]").forEach((btn) => btn.addEventListener("click", () => bindDeckDesign(getEl("assocSelect")?.value || "")));
|
|
1858
1938
|
root.querySelectorAll("[data-assoc-switch]").forEach((btn) => btn.addEventListener("click", openAssocSwitchPanel));
|
|
1859
1939
|
root.querySelectorAll("[data-assoc-switch-confirm]").forEach((btn) => btn.addEventListener("click", confirmAssocSwitch));
|
|
@@ -1879,11 +1959,12 @@ function bindDocActions(root) {
|
|
|
1879
1959
|
}));
|
|
1880
1960
|
root.querySelectorAll("[data-assoc-unbind]").forEach((btn) => btn.addEventListener("click", () => bindDeckDesign("")));
|
|
1881
1961
|
root.querySelectorAll("[data-doc-fill]").forEach((btn) => btn.addEventListener("click", () => askAgentForDoc(btn.dataset.docFill)));
|
|
1882
|
-
// F-159
|
|
1962
|
+
// F-159 / F-172:听众评审入口(脚本页)——两段式(盲审 → 冻结 → 对照)。
|
|
1963
|
+
// 评审只出意见、不改稿;用户点这里是要「审完给结论再决定」,所以列完问题必须先问是否接受并更新。
|
|
1883
1964
|
root.querySelectorAll("[data-script-review]").forEach((btn) =>
|
|
1884
1965
|
btn.addEventListener("click", () =>
|
|
1885
1966
|
sendPrompt(
|
|
1886
|
-
`请调用 deck_script_review
|
|
1967
|
+
`请调用 deck_script_review(phase=full)评审 deck ${STATE.data.active.deckId} 的整份 script:按 intent 里的听众先生成审稿人做盲审(不给意图、逐页四问、结论冻结),再对照意图做三方差分;不要直接改稿、不要出图。\n评审完先告诉我:盲审覆盖率、判定「否 / 部分」的页(带页号 + 原文证据 + 改法)、可删可合并的页。然后按稿子状态分开处理:\n① 如果这份 deck 还一次都没落过版(首稿):按「否 / 部分」自己改,最多 2 轮,然后用 deck_script_propose_edits 带 autoland: true + selfReviewRounds + selfReviewSummary 直接落版,落版后告我自改摘要和「可回滚」;\n② 如果已经有已落版 revision:停下来问我「是否接受并更新稿件」;我同意后按可采纳清单逐条提案(每条带 reviewRef/severity/confidence/evidence,C 类不提),变成脚本页的「评审意见」清单,我勾选后点「接受并更新」合成一条 revision。`,
|
|
1887
1968
|
),
|
|
1888
1969
|
),
|
|
1889
1970
|
);
|
|
@@ -1910,6 +1991,17 @@ function bindDocActions(root) {
|
|
|
1910
1991
|
decideProposals(ids, true);
|
|
1911
1992
|
}));
|
|
1912
1993
|
root.querySelectorAll("[data-confirm-all]").forEach((btn) => btn.addEventListener("click", () => decideProposals(freshProposalIds(), true)));
|
|
1994
|
+
// F-176 / I-18:勾选的评审意见一次接受并更新(一条 revision)。
|
|
1995
|
+
root.querySelectorAll("[data-accept-review]").forEach((btn) =>
|
|
1996
|
+
btn.addEventListener("click", () => {
|
|
1997
|
+
const picked = [...root.querySelectorAll("[data-review-pick]:checked")].map((input) => input.dataset.reviewPick).filter(Boolean);
|
|
1998
|
+
if (!picked.length) {
|
|
1999
|
+
flash("先勾选要采纳的意见(C 类无法核实,不能选)", "error");
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
decideProposals(picked, true, true);
|
|
2003
|
+
}),
|
|
2004
|
+
);
|
|
1913
2005
|
root.querySelectorAll("[data-reject-all]").forEach((btn) => btn.addEventListener("click", () => decideProposals(allPendingProposalIds(), false)));
|
|
1914
2006
|
root.querySelectorAll("[data-reject-stale]").forEach((btn) => btn.addEventListener("click", () => decideProposals(String(btn.dataset.rejectStale || "").split(",").filter(Boolean), false)));
|
|
1915
2007
|
root.querySelectorAll("[data-reject-expired-all]").forEach((btn) => btn.addEventListener("click", () => decideProposals(deckPendingProposals().filter((proposal) => proposal.stale).map((proposal) => proposal.proposalId), false)));
|
|
@@ -3468,11 +3560,23 @@ function renderDeckStyleChoices() {
|
|
|
3468
3560
|
}
|
|
3469
3561
|
}
|
|
3470
3562
|
|
|
3563
|
+
function newDeckStorageInfo(config = {}) {
|
|
3564
|
+
const root = String(config?.projectsRoot || "").trim();
|
|
3565
|
+
const dataRoot = String(config?.dataRoot || "").trim();
|
|
3566
|
+
const append = (base, suffix) => `${base.replace(/[\\/]+$/, "")}/${suffix}`;
|
|
3567
|
+
if (root) return { path: append(root, "<目录名或自动生成的 deck-id>/"), note: "由 profile 的 projectsRoot 配置;与会话工作区独立。" };
|
|
3568
|
+
if (dataRoot) return { path: append(dataRoot, "projects/<deck-id>/source/"), note: "未配置 projectsRoot,使用插件数据目录;不会自动保存在当前工作区。" };
|
|
3569
|
+
return { path: "暂时无法确定", note: "请等待工作台配置加载后重试;未配置 projectsRoot 时使用 <dataRoot>/projects/<deck-id>/source/。" };
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3471
3572
|
function openNewDeckModal() {
|
|
3472
3573
|
// F-112:profile 里配了 defaultStyleAbbr 就默认预选这个风格(用户仍可改/取消)。
|
|
3473
3574
|
STATE.ndStyleAbbr = String(STATE.data?.config?.defaultStyleAbbr || "");
|
|
3474
3575
|
getEl("newDeckForm").reset();
|
|
3475
3576
|
getEl("ndState").textContent = "";
|
|
3577
|
+
const storage = newDeckStorageInfo(STATE.data?.config);
|
|
3578
|
+
getEl("ndStoragePath").textContent = storage.path;
|
|
3579
|
+
getEl("ndStorageNote").textContent = storage.note;
|
|
3476
3580
|
getEl("newDeckModal").hidden = false;
|
|
3477
3581
|
renderDeckStyleChoices();
|
|
3478
3582
|
setTimeout(() => getEl("ndTitle").focus(), 30);
|