dsh-archify-projects 0.1.0 → 0.2.0
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.md +42 -2
- package/dist/client.js +276 -15
- package/dist/client.js.map +1 -1
- package/docs/DESIGN.md +81 -0
- package/docs/HANDOVER.md +117 -4
- package/lib/host/context.js.map +1 -1
- package/lib/host/index.js +4 -2
- package/lib/host/index.js.map +1 -1
- package/lib/host/models.js +137 -0
- package/lib/host/models.js.map +1 -0
- package/lib/host/routes.js +43 -0
- package/lib/host/routes.js.map +1 -1
- package/lib/host/runner.js +140 -16
- package/lib/host/runner.js.map +1 -1
- package/lib/host/settings.js +83 -0
- package/lib/host/settings.js.map +1 -0
- package/lib/shared/model-key.js +12 -0
- package/lib/shared/model-key.js.map +1 -0
- package/lib/shared/protocol.js +2 -0
- package/lib/shared/protocol.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,31 @@
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
+
## 效果
|
|
31
|
+
|
|
32
|
+
> 截图来自真实运行——被测仓库就是这个插件自己,数据都是跑出来的,不是设计稿。
|
|
33
|
+
|
|
34
|
+
**总览**:每个仓库一张卡片,直接看出分支、工作区改动、概念数、哪些图已经就绪、有没有人在跑。
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
**工作台**:图名 tab 在图片上方(只有文字,不占高度),左边是图,右边是检查器——
|
|
39
|
+
prompt、概念范围、分支、进度、token 用量、运行记录都在这一栏。
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
**提问**:对着某张图问细节,agent 在**只读会话**里读代码回答,回答可以直接「补进图里」
|
|
44
|
+
或变成一张子图。
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
**新建图**:图型 tag 直接对应 archify 的 5 种渲染器,点一下告诉你「适合看什么 / 会展示
|
|
49
|
+
代码库的哪些信息 / 能让你掌握什么」;不选也行,由 agent 判断。
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
30
55
|
## 安装
|
|
31
56
|
|
|
32
57
|
装的是**已发布的包**,一条命令,装在哪个 profile 就写哪个名字(默认 `web`):
|
|
@@ -37,10 +62,12 @@ dsh plugin --profile web add dsh-archify-projects
|
|
|
37
62
|
|
|
38
63
|
然后重启 dsh(宿主侧代码,必须重启)。重启后左栏出现「架构建模」。
|
|
39
64
|
|
|
40
|
-
|
|
65
|
+
也在 npm 上(`dsh-archify-projects@0.1.1`)。
|
|
66
|
+
|
|
67
|
+
**备用路径**:GitHub Release 上的预构建 tarball(和 npm 包内容一致):
|
|
41
68
|
|
|
42
69
|
```bash
|
|
43
|
-
dsh plugin --profile web add https://github.com/guicaiyue/dsh-archify-projects/releases/download/v0.1.
|
|
70
|
+
dsh plugin --profile web add https://github.com/guicaiyue/dsh-archify-projects/releases/download/v0.1.1/dsh-archify-projects-0.1.1.tgz
|
|
44
71
|
```
|
|
45
72
|
|
|
46
73
|
> ⚠️ **不要用 `add github:guicaiyue/dsh-archify-projects` 从源码装**:
|
|
@@ -71,6 +98,8 @@ pnpm add link:/absolute/path/to/dsh-archify-projects # 或在 package.json 里
|
|
|
71
98
|
3. 首次添加会自动跑一次 `create`:结构图 + 领域图各一张,通常几分钟。
|
|
72
99
|
4. 之后随时「更新」:切回记录的分支 → 快进到最新 → 重画两张图。
|
|
73
100
|
5. 图是可以直接看的 HTML(archify 产物:可搜索、可缩放、可导出),不是截图。
|
|
101
|
+
6. 右上角**齿轮**是设置:只有一个「分析模型」下拉,选项就是你在 DSH 里已经配好、
|
|
102
|
+
当前真能路由的模型;不选=跟随宿主默认。改完对下一次运行生效。
|
|
74
103
|
|
|
75
104
|
---
|
|
76
105
|
|
|
@@ -99,6 +128,16 @@ pnpm add link:/absolute/path/to/dsh-archify-projects # 或在 package.json 里
|
|
|
99
128
|
`restrict` 只能装在 scoped ctx(`agent.ctx`)上,写在 preset 行里会直接抛 ——
|
|
100
129
|
所以白名单与守卫在 `src/host/policy.ts` 里,运行开始时对当次会话安装。
|
|
101
130
|
|
|
131
|
+
### 分析跑在子代理会话里,不占你的会话列表
|
|
132
|
+
|
|
133
|
+
每次出图 / 提问都用一个 **`origin: 'subagent'` 的会话**(`ctx.agents.create` +
|
|
134
|
+
`agentPresets.mount`),所以:
|
|
135
|
+
|
|
136
|
+
- DSH 左侧会话列表按 origin 过滤子代理会话 —— 分析不会往你的对话列表里塞新条目;
|
|
137
|
+
- 会话的模型走 `agentOptions`,**只影响这一次运行**,不会改掉 DSH 里你的默认模型;
|
|
138
|
+
- 宿主版本没有这条路(缺 `agents.create` / `agentPresets`)时安静退回普通会话,
|
|
139
|
+
工具白名单、preset、产物回收都不变。
|
|
140
|
+
|
|
102
141
|
### 深浅色
|
|
103
142
|
|
|
104
143
|
页面只用 DSH 的主题令牌(`--dsw-alias-*` / `--dsw-specific-*` / `--dsw-radius-*` /
|
|
@@ -190,6 +229,7 @@ scripts/ artifact 门禁
|
|
|
190
229
|
| 位置 | 内容 |
|
|
191
230
|
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
192
231
|
| `~/.dsh/archify-projects/projects.json`(可用 `dataDir` 改) | 台账:项目、Git 快照、两张图的状态、运行历史(保留最近 20 条) |
|
|
232
|
+
| 同目录 `settings.json` | 插件设置:分析用哪个模型(`{ provider, model }`,null = 跟随宿主默认) |
|
|
193
233
|
| `~/.dsh/archify-projects/workspace/<projectId>/` | 每个项目的工作目录;`.archify/<kind>-<slug>-<stamp>/` 里是 `candidate.json` 与成品 HTML |
|
|
194
234
|
| `<插件目录>/skills/archify/` | 内嵌的 archify skill(不进 git:见 `.gitignore`;npm 包用 `files` 带上) |
|
|
195
235
|
|
package/dist/client.js
CHANGED
|
@@ -28,6 +28,8 @@ const ROUTES = {
|
|
|
28
28
|
mergeQa: (id, qaId) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/qa/${encodeURIComponent(qaId)}/merge`,
|
|
29
29
|
childQa: (id, qaId) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/qa/${encodeURIComponent(qaId)}/child`,
|
|
30
30
|
runs: (id) => `${ROUTE_PREFIX}/projects/${encodeURIComponent(id)}/runs`,
|
|
31
|
+
/** 插件级设置(目前只有「分析用哪个模型」)。 */
|
|
32
|
+
settings: `${ROUTE_PREFIX}/settings`,
|
|
31
33
|
artifact: (relativePath) => `${ROUTE_PREFIX}/artifact/${relativePath.split(/[\\/]+/).map((part) => encodeURIComponent(part)).join("/")}`
|
|
32
34
|
};
|
|
33
35
|
|
|
@@ -163,7 +165,7 @@ async function api(path, init) {
|
|
|
163
165
|
const message = result.error?.message ?? "未知错误";
|
|
164
166
|
throw new ApiError(code, message);
|
|
165
167
|
}
|
|
166
|
-
/** POST/PATCH 的便捷封装。 */
|
|
168
|
+
/** POST/PUT/PATCH/DELETE 的便捷封装。 */
|
|
167
169
|
function send(path, method, body) {
|
|
168
170
|
return api(path, {
|
|
169
171
|
method,
|
|
@@ -171,6 +173,12 @@ function send(path, method, body) {
|
|
|
171
173
|
});
|
|
172
174
|
}
|
|
173
175
|
|
|
176
|
+
// src/shared/model-key.ts
|
|
177
|
+
/** 生成路由键。 */
|
|
178
|
+
function modelKey(ref) {
|
|
179
|
+
return `${ref.provider}::${ref.model}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
174
182
|
// src/client/components.tsx
|
|
175
183
|
/**
|
|
176
184
|
* 页面里可复用的小组件:标签、状态点、两个对话框。
|
|
@@ -531,6 +539,136 @@ function NewDiagramDialog({ t, project, selectedId, busy, onClose, onSubmit }) {
|
|
|
531
539
|
})
|
|
532
540
|
});
|
|
533
541
|
}
|
|
542
|
+
/** 齿轮图标(设置入口)。尺寸跟随字号,颜色跟随按钮。 */
|
|
543
|
+
function GearIcon({ size = 13 }) {
|
|
544
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
545
|
+
width: size,
|
|
546
|
+
height: size,
|
|
547
|
+
viewBox: "0 0 24 24",
|
|
548
|
+
fill: "none",
|
|
549
|
+
stroke: "currentColor",
|
|
550
|
+
strokeWidth: 1.7,
|
|
551
|
+
strokeLinecap: "round",
|
|
552
|
+
strokeLinejoin: "round",
|
|
553
|
+
"aria-hidden": "true",
|
|
554
|
+
focusable: "false",
|
|
555
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
556
|
+
cx: "12",
|
|
557
|
+
cy: "12",
|
|
558
|
+
r: "3.1"
|
|
559
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19.4 15a1.6 1.6 0 0 0 .32 1.77l.06.06a1.9 1.9 0 1 1-2.7 2.7l-.06-.06a1.6 1.6 0 0 0-1.77-.32 1.6 1.6 0 0 0-.97 1.47V21a1.9 1.9 0 1 1-3.8 0v-.1a1.6 1.6 0 0 0-1.05-1.47 1.6 1.6 0 0 0-1.77.32l-.06.06a1.9 1.9 0 1 1-2.7-2.7l.06-.06A1.6 1.6 0 0 0 4.6 15a1.6 1.6 0 0 0-1.47-.97H3a1.9 1.9 0 1 1 0-3.8h.1a1.6 1.6 0 0 0 1.47-1.05 1.6 1.6 0 0 0-.32-1.77l-.06-.06a1.9 1.9 0 1 1 2.7-2.7l.06.06A1.6 1.6 0 0 0 9 4.6a1.6 1.6 0 0 0 .97-1.47V3a1.9 1.9 0 1 1 3.8 0v.1a1.6 1.6 0 0 0 .97 1.47 1.6 1.6 0 0 0 1.77-.32l.06-.06a1.9 1.9 0 1 1 2.7 2.7l-.06.06a1.6 1.6 0 0 0-.32 1.77V9a1.6 1.6 0 0 0 1.47.97H21a1.9 1.9 0 1 1 0 3.8h-.1a1.6 1.6 0 0 0-1.47.97z" })]
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* 设置对话框:**只有一个字段**——分析用哪个模型。
|
|
564
|
+
*
|
|
565
|
+
* 模型选项不是插件编的,是宿主 `modelCatalog()` 列出来的(用户在 DSH 里
|
|
566
|
+
* 配好、当前真的能路由到的那些)。目录拿不到时只留「跟随宿主默认」一项,
|
|
567
|
+
* 并且把原因写在提示里,不假装能选。
|
|
568
|
+
*/
|
|
569
|
+
function SettingsDialog({ t, settings, busy, onClose, onSubmit }) {
|
|
570
|
+
const catalog = settings?.catalog ?? null;
|
|
571
|
+
const current = settings?.model ?? null;
|
|
572
|
+
const [chosen, setChosen] = (0, react.useState)(current === null ? "" : modelKey(current));
|
|
573
|
+
const options = catalog?.options ?? [];
|
|
574
|
+
const selectedOption = options.find((option) => option.key === chosen) ?? null;
|
|
575
|
+
const submit = () => {
|
|
576
|
+
onSubmit(selectedOption === null ? null : {
|
|
577
|
+
provider: selectedOption.provider,
|
|
578
|
+
model: selectedOption.model
|
|
579
|
+
});
|
|
580
|
+
};
|
|
581
|
+
const fallbackText = catalog !== null && catalog.fallback !== null ? `${catalog.fallback.provider} / ${catalog.fallback.model}` : null;
|
|
582
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
583
|
+
className: "dap-mask",
|
|
584
|
+
role: "presentation",
|
|
585
|
+
onClick: (e) => e.target === e.currentTarget && onClose(),
|
|
586
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
587
|
+
className: "dap-dialog",
|
|
588
|
+
role: "dialog",
|
|
589
|
+
"aria-modal": "true",
|
|
590
|
+
"aria-label": t("dialog.settings"),
|
|
591
|
+
children: [
|
|
592
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
593
|
+
className: "dap-dialog__title",
|
|
594
|
+
children: t("dialog.settings")
|
|
595
|
+
}),
|
|
596
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
597
|
+
className: "dap-dialog__sub",
|
|
598
|
+
children: t("dialog.settingsSub")
|
|
599
|
+
}),
|
|
600
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
601
|
+
className: "dap-dialog__row",
|
|
602
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
603
|
+
className: "dap-field__label",
|
|
604
|
+
children: t("field.model")
|
|
605
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
606
|
+
className: "dap-select",
|
|
607
|
+
value: chosen,
|
|
608
|
+
onChange: (e) => setChosen(e.target.value),
|
|
609
|
+
"aria-label": t("field.model"),
|
|
610
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
611
|
+
value: "",
|
|
612
|
+
children: fallbackText === null ? t("model.followDefault") : t("model.followDefaultNamed", { model: fallbackText })
|
|
613
|
+
}), options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
614
|
+
value: option.key,
|
|
615
|
+
children: [
|
|
616
|
+
option.providerName,
|
|
617
|
+
" · ",
|
|
618
|
+
option.name
|
|
619
|
+
]
|
|
620
|
+
}, option.key))]
|
|
621
|
+
})]
|
|
622
|
+
}),
|
|
623
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
624
|
+
className: "dap-hint",
|
|
625
|
+
children: catalog === null || catalog.source === "unavailable" ? t("settings.unavailable") : options.length === 0 ? t("settings.noModels") : t("field.modelHint")
|
|
626
|
+
}),
|
|
627
|
+
selectedOption?.description != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
628
|
+
className: "dap-hint",
|
|
629
|
+
children: selectedOption.description
|
|
630
|
+
}) : null,
|
|
631
|
+
catalog !== null && catalog.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
632
|
+
className: "dap-hint dap-hint--warn",
|
|
633
|
+
children: t("settings.providerFailed", { list: catalog.failures.map((f) => f.providerName).join("、") })
|
|
634
|
+
}) : null,
|
|
635
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
636
|
+
className: "dap-dialog__row",
|
|
637
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
638
|
+
className: "dap-field__label",
|
|
639
|
+
children: t("field.sessionMode")
|
|
640
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
641
|
+
className: "dap-hint",
|
|
642
|
+
children: settings?.subagentSessions === true ? t("settings.sessionSubagent") : t("settings.sessionPlain")
|
|
643
|
+
})]
|
|
644
|
+
}),
|
|
645
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
646
|
+
className: "dap-dialog__foot",
|
|
647
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
648
|
+
className: "dap-hint",
|
|
649
|
+
children: t("settings.scopeHint")
|
|
650
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
651
|
+
style: {
|
|
652
|
+
display: "flex",
|
|
653
|
+
gap: 6
|
|
654
|
+
},
|
|
655
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
656
|
+
className: "dap-btn dap-btn--sm",
|
|
657
|
+
onClick: onClose,
|
|
658
|
+
disabled: busy,
|
|
659
|
+
children: t("action.cancel")
|
|
660
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
661
|
+
className: "dap-btn dap-btn--sm dap-btn--primary",
|
|
662
|
+
onClick: submit,
|
|
663
|
+
disabled: busy,
|
|
664
|
+
children: t("action.save")
|
|
665
|
+
})]
|
|
666
|
+
})]
|
|
667
|
+
})
|
|
668
|
+
]
|
|
669
|
+
})
|
|
670
|
+
});
|
|
671
|
+
}
|
|
534
672
|
|
|
535
673
|
// src/client/page.tsx
|
|
536
674
|
/**
|
|
@@ -557,6 +695,7 @@ function StudioPage({ t }) {
|
|
|
557
695
|
const [diagramId, setDiagramId] = (0, react.useState)(null);
|
|
558
696
|
const [tab, setTab] = (0, react.useState)("info");
|
|
559
697
|
const [dialog, setDialog] = (0, react.useState)(null);
|
|
698
|
+
const [settings, setSettings] = (0, react.useState)(null);
|
|
560
699
|
const [busy, setBusy] = (0, react.useState)(null);
|
|
561
700
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
562
701
|
const [question, setQuestion] = (0, react.useState)("");
|
|
@@ -590,6 +729,18 @@ function StudioPage({ t }) {
|
|
|
590
729
|
(0, react.useEffect)(() => {
|
|
591
730
|
load();
|
|
592
731
|
}, [load]);
|
|
732
|
+
(0, react.useEffect)(() => {
|
|
733
|
+
let alive = true;
|
|
734
|
+
(async () => {
|
|
735
|
+
try {
|
|
736
|
+
const value = await api(ROUTES.settings);
|
|
737
|
+
if (alive) setSettings(value);
|
|
738
|
+
} catch {}
|
|
739
|
+
})();
|
|
740
|
+
return () => {
|
|
741
|
+
alive = false;
|
|
742
|
+
};
|
|
743
|
+
}, []);
|
|
593
744
|
const running = state.projects.some((project) => isRunning(project));
|
|
594
745
|
(0, react.useEffect)(() => {
|
|
595
746
|
if (!running) return void 0;
|
|
@@ -634,6 +785,19 @@ function StudioPage({ t }) {
|
|
|
634
785
|
setBusy(null);
|
|
635
786
|
}
|
|
636
787
|
};
|
|
788
|
+
const saveSettings = async (model) => {
|
|
789
|
+
setBusy("settings");
|
|
790
|
+
setNotice(null);
|
|
791
|
+
try {
|
|
792
|
+
const value = await send(ROUTES.settings, "PUT", { model });
|
|
793
|
+
if (mounted.current) setSettings(value);
|
|
794
|
+
setDialog(null);
|
|
795
|
+
} catch (err) {
|
|
796
|
+
fail(err);
|
|
797
|
+
} finally {
|
|
798
|
+
setBusy(null);
|
|
799
|
+
}
|
|
800
|
+
};
|
|
637
801
|
const updateAll = async () => {
|
|
638
802
|
for (const item of state.projects) {
|
|
639
803
|
if (isRunning(item)) continue;
|
|
@@ -783,14 +947,20 @@ function StudioPage({ t }) {
|
|
|
783
947
|
className: "dap-head__sub",
|
|
784
948
|
children: project.repoPath
|
|
785
949
|
})
|
|
786
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
950
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
787
951
|
className: "dap-head__actions",
|
|
788
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
952
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
953
|
+
className: "dap-iconbtn",
|
|
954
|
+
onClick: () => setDialog("settings"),
|
|
955
|
+
title: t("action.settings"),
|
|
956
|
+
"aria-label": t("action.settings"),
|
|
957
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {})
|
|
958
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
789
959
|
className: "dap-btn dap-btn--sm dap-btn--primary",
|
|
790
960
|
onClick: () => void runUpdate(project),
|
|
791
961
|
disabled: busy !== null || isRunning(project),
|
|
792
962
|
children: t("action.update")
|
|
793
|
-
})
|
|
963
|
+
})]
|
|
794
964
|
})]
|
|
795
965
|
}),
|
|
796
966
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -818,6 +988,13 @@ function StudioPage({ t }) {
|
|
|
818
988
|
onNewDiagram: () => setDialog("diagram")
|
|
819
989
|
})]
|
|
820
990
|
}),
|
|
991
|
+
dialog === "settings" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsDialog, {
|
|
992
|
+
t,
|
|
993
|
+
settings,
|
|
994
|
+
busy: busy !== null,
|
|
995
|
+
onClose: () => setDialog(null),
|
|
996
|
+
onSubmit: (model) => void saveSettings(model)
|
|
997
|
+
}) : null,
|
|
821
998
|
dialog === "diagram" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewDiagramDialog, {
|
|
822
999
|
t,
|
|
823
1000
|
project,
|
|
@@ -841,16 +1018,26 @@ function StudioPage({ t }) {
|
|
|
841
1018
|
children: t("page.subtitle")
|
|
842
1019
|
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
843
1020
|
className: "dap-head__actions",
|
|
844
|
-
children: [
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1021
|
+
children: [
|
|
1022
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1023
|
+
className: "dap-iconbtn",
|
|
1024
|
+
onClick: () => setDialog("settings"),
|
|
1025
|
+
title: t("action.settings"),
|
|
1026
|
+
"aria-label": t("action.settings"),
|
|
1027
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {})
|
|
1028
|
+
}),
|
|
1029
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1030
|
+
className: "dap-btn dap-btn--sm",
|
|
1031
|
+
onClick: () => void updateAll(),
|
|
1032
|
+
disabled: busy !== null || state.projects.length === 0,
|
|
1033
|
+
children: t("action.updateAll")
|
|
1034
|
+
}),
|
|
1035
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1036
|
+
className: "dap-btn dap-btn--sm dap-btn--primary",
|
|
1037
|
+
onClick: () => setDialog("project"),
|
|
1038
|
+
children: ["+ ", t("action.add")]
|
|
1039
|
+
})
|
|
1040
|
+
]
|
|
854
1041
|
})]
|
|
855
1042
|
}),
|
|
856
1043
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -938,6 +1125,13 @@ function StudioPage({ t }) {
|
|
|
938
1125
|
})] })
|
|
939
1126
|
]
|
|
940
1127
|
}),
|
|
1128
|
+
dialog === "settings" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsDialog, {
|
|
1129
|
+
t,
|
|
1130
|
+
settings,
|
|
1131
|
+
busy: busy !== null,
|
|
1132
|
+
onClose: () => setDialog(null),
|
|
1133
|
+
onSubmit: (model) => void saveSettings(model)
|
|
1134
|
+
}) : null,
|
|
941
1135
|
dialog === "project" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewProjectDialog, {
|
|
942
1136
|
t,
|
|
943
1137
|
busy: busy !== null,
|
|
@@ -1126,7 +1320,8 @@ function Workbench({ t, project, diagram, tab, busy, question, pricing, ago, onT
|
|
|
1126
1320
|
className: "dap-iframe",
|
|
1127
1321
|
title: diagram.title,
|
|
1128
1322
|
src: ROUTES.artifact(diagram.htmlPath),
|
|
1129
|
-
sandbox: "allow-scripts allow-same-origin"
|
|
1323
|
+
sandbox: "allow-scripts allow-same-origin",
|
|
1324
|
+
onLoad: (event) => hideArtifactHeader(event.currentTarget)
|
|
1130
1325
|
})]
|
|
1131
1326
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1132
1327
|
className: "dap-frame",
|
|
@@ -1416,6 +1611,25 @@ function ThumbStrip({ t, project, selectedId, onSelect, onNewDiagram }) {
|
|
|
1416
1611
|
});
|
|
1417
1612
|
}
|
|
1418
1613
|
/**
|
|
1614
|
+
* 内嵌查看器里去掉成品自带的页头(大标题 + 状态圆点)。
|
|
1615
|
+
*
|
|
1616
|
+
* 成品 HTML 顶部有一行「<项目名> · 图名」的大标题和一个 pulse-dot,而插件自己的
|
|
1617
|
+
* 查看器标题栏已经写了图名、帧头也写了分支与时间——重复一遍只是白吃画布高度。
|
|
1618
|
+
* 只在 iframe 里注入,点「新窗口」单独打开时仍是成品原样(那里没有外层标题)。
|
|
1619
|
+
* 同源才改得动;被沙箱或跨域挡住就静默跳过,不影响看图。
|
|
1620
|
+
*/
|
|
1621
|
+
function hideArtifactHeader(frame) {
|
|
1622
|
+
try {
|
|
1623
|
+
const doc = frame.contentDocument;
|
|
1624
|
+
if (doc === null) return;
|
|
1625
|
+
if (doc.head.querySelector("style[data-embedded=\"compact\"]") !== null) return;
|
|
1626
|
+
const style = doc.createElement("style");
|
|
1627
|
+
style.dataset.embedded = "compact";
|
|
1628
|
+
style.textContent = ".header{display:none!important}";
|
|
1629
|
+
doc.head.appendChild(style);
|
|
1630
|
+
} catch {}
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1419
1633
|
* 一个图名 tab:只有文字,没有缩略图。
|
|
1420
1634
|
*
|
|
1421
1635
|
* 之前用 0.25 倍 iframe 做真预览,一条占 91px,把图片本身压下去;改成纯文字后
|
|
@@ -1618,6 +1832,20 @@ const zh = {
|
|
|
1618
1832
|
"action.delete": "删除",
|
|
1619
1833
|
"action.openSession": "查看会话",
|
|
1620
1834
|
"action.cancel": "取消",
|
|
1835
|
+
"action.settings": "设置",
|
|
1836
|
+
"dialog.settings": "设置",
|
|
1837
|
+
"dialog.settingsSub": "只影响这个插件的分析会话,不动 DSH 里你自己的默认模型。",
|
|
1838
|
+
"field.model": "分析模型",
|
|
1839
|
+
"field.modelHint": "选项来自你在 DSH 里已经配好、当前能路由的模型。",
|
|
1840
|
+
"field.sessionMode": "会话形态",
|
|
1841
|
+
"model.followDefault": "跟随宿主默认",
|
|
1842
|
+
"model.followDefaultNamed": "跟随宿主默认({model})",
|
|
1843
|
+
"settings.noModels": "宿主的模型目录是空的:去 DSH 设置里配一个模型,这里才有得选。",
|
|
1844
|
+
"settings.unavailable": "这个宿主版本没有提供模型目录,只能跟随宿主默认。",
|
|
1845
|
+
"settings.providerFailed": "{list} 的模型列表取不到(网络或凭据),暂时不能选。",
|
|
1846
|
+
"settings.sessionSubagent": "子代理会话(不占用左侧会话列表)",
|
|
1847
|
+
"settings.sessionPlain": "普通会话(宿主不支持子代理会话)",
|
|
1848
|
+
"settings.scopeHint": "保存后对下一次运行生效",
|
|
1621
1849
|
"action.submit": "创建并分析",
|
|
1622
1850
|
"action.close": "关闭",
|
|
1623
1851
|
"action.back": "返回图册",
|
|
@@ -1779,6 +2007,20 @@ const en = {
|
|
|
1779
2007
|
"action.delete": "Delete",
|
|
1780
2008
|
"action.openSession": "Open session",
|
|
1781
2009
|
"action.cancel": "Cancel",
|
|
2010
|
+
"action.settings": "Settings",
|
|
2011
|
+
"dialog.settings": "Settings",
|
|
2012
|
+
"dialog.settingsSub": "Only affects this plugin's analysis sessions; your own default model in DSH stays untouched.",
|
|
2013
|
+
"field.model": "Analysis model",
|
|
2014
|
+
"field.modelHint": "Options are the models already configured in DSH and currently routable.",
|
|
2015
|
+
"field.sessionMode": "Session",
|
|
2016
|
+
"model.followDefault": "Follow the host default",
|
|
2017
|
+
"model.followDefaultNamed": "Follow the host default ({model})",
|
|
2018
|
+
"settings.noModels": "The host catalog is empty. Configure a model in DSH settings to pick one here.",
|
|
2019
|
+
"settings.unavailable": "This host version exposes no model catalog; the host default is used.",
|
|
2020
|
+
"settings.providerFailed": "Could not list models for {list} (network or credentials).",
|
|
2021
|
+
"settings.sessionSubagent": "Subagent session (kept out of the sidebar list)",
|
|
2022
|
+
"settings.sessionPlain": "Plain session (this host lacks subagent sessions)",
|
|
2023
|
+
"settings.scopeHint": "Applies to the next run",
|
|
1782
2024
|
"action.submit": "Create and analyse",
|
|
1783
2025
|
"action.close": "Close",
|
|
1784
2026
|
"action.back": "Back to atlas",
|
|
@@ -2170,6 +2412,25 @@ const PAGE_CSS = `
|
|
|
2170
2412
|
|
|
2171
2413
|
|
|
2172
2414
|
.dap-dialog--wide { width: 680px; }
|
|
2415
|
+
|
|
2416
|
+
/* 头部图标按钮(设置齿轮):与 dap-btn--sm 同高,窄一点 */
|
|
2417
|
+
.dap-iconbtn {
|
|
2418
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
2419
|
+
width: 26px; height: 26px; border-radius: var(--dsw-radius-md);
|
|
2420
|
+
border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2);
|
|
2421
|
+
color: var(--dsw-alias-label-secondary); cursor: pointer;
|
|
2422
|
+
}
|
|
2423
|
+
.dap-iconbtn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l2); }
|
|
2424
|
+
|
|
2425
|
+
/* 设置弹窗的模型下拉:原生 select 最省事,长列表也能滚动 */
|
|
2426
|
+
.dap-select {
|
|
2427
|
+
min-width: 240px; max-width: 380px; height: 28px; padding: 0 8px;
|
|
2428
|
+
border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md);
|
|
2429
|
+
background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary);
|
|
2430
|
+
font: inherit; font-size: 12px; outline: none; cursor: pointer;
|
|
2431
|
+
}
|
|
2432
|
+
.dap-select:focus { border-color: var(--dsw-alias-border-l3); }
|
|
2433
|
+
.dap-hint--warn { color: var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-tertiary)); }
|
|
2173
2434
|
.dap-typebar { display: flex; flex-wrap: wrap; gap: 5px; margin: 6px 0 8px; }
|
|
2174
2435
|
.dap-type-tag {
|
|
2175
2436
|
display: inline-flex; align-items: center; gap: 5px; height: 27px; padding: 0 9px;
|