dsh-archify-projects 0.1.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/client.js +301 -25
- package/dist/client.js.map +1 -1
- package/docs/DESIGN.md +81 -0
- package/docs/HANDOVER.md +226 -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 +220 -26
- 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/lib/shared/types.js +25 -0
- package/lib/shared/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,8 @@ pnpm add link:/absolute/path/to/dsh-archify-projects # 或在 package.json 里
|
|
|
98
98
|
3. 首次添加会自动跑一次 `create`:结构图 + 领域图各一张,通常几分钟。
|
|
99
99
|
4. 之后随时「更新」:切回记录的分支 → 快进到最新 → 重画两张图。
|
|
100
100
|
5. 图是可以直接看的 HTML(archify 产物:可搜索、可缩放、可导出),不是截图。
|
|
101
|
+
6. 右上角**齿轮**是设置:只有一个「分析模型」下拉,选项就是你在 DSH 里已经配好、
|
|
102
|
+
当前真能路由的模型;不选=跟随宿主默认。改完对下一次运行生效。
|
|
101
103
|
|
|
102
104
|
---
|
|
103
105
|
|
|
@@ -126,6 +128,17 @@ pnpm add link:/absolute/path/to/dsh-archify-projects # 或在 package.json 里
|
|
|
126
128
|
`restrict` 只能装在 scoped ctx(`agent.ctx`)上,写在 preset 行里会直接抛 ——
|
|
127
129
|
所以白名单与守卫在 `src/host/policy.ts` 里,运行开始时对当次会话安装。
|
|
128
130
|
|
|
131
|
+
### 分析跑在子代理会话里,不占你的会话列表
|
|
132
|
+
|
|
133
|
+
每次出图 / 提问都用一个 **`origin: 'subagent'` 的会话**(`ctx.agents.create` +
|
|
134
|
+
`agentPresets.mount`),所以:
|
|
135
|
+
|
|
136
|
+
- DSH 左侧会话列表按 origin 过滤子代理会话 —— 分析不会往你的对话列表里塞新条目;
|
|
137
|
+
- 会话的模型走 `agentOptions`:设置里没选就用 **DSH 里配的默认模型**
|
|
138
|
+
(`agentDefaultModel`),**只影响这一次运行**,不会改掉 DSH 里你的默认模型;
|
|
139
|
+
- 宿主版本没有这条路(缺 `agents.create` / `agentPresets`)时安静退回普通会话,
|
|
140
|
+
工具白名单、preset、产物回收都不变。
|
|
141
|
+
|
|
129
142
|
### 深浅色
|
|
130
143
|
|
|
131
144
|
页面只用 DSH 的主题令牌(`--dsw-alias-*` / `--dsw-specific-*` / `--dsw-radius-*` /
|
|
@@ -217,6 +230,7 @@ scripts/ artifact 门禁
|
|
|
217
230
|
| 位置 | 内容 |
|
|
218
231
|
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
219
232
|
| `~/.dsh/archify-projects/projects.json`(可用 `dataDir` 改) | 台账:项目、Git 快照、两张图的状态、运行历史(保留最近 20 条) |
|
|
233
|
+
| 同目录 `settings.json` | 插件设置:分析用哪个模型(`{ provider, model }`,null = 跟随宿主默认) |
|
|
220
234
|
| `~/.dsh/archify-projects/workspace/<projectId>/` | 每个项目的工作目录;`.archify/<kind>-<slug>-<stamp>/` 里是 `candidate.json` 与成品 HTML |
|
|
221
235
|
| `<插件目录>/skills/archify/` | 内嵌的 archify skill(不进 git:见 `.gitignore`;npm 包用 `files` 带上) |
|
|
222
236
|
|
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
|
|
|
@@ -85,6 +87,32 @@ function isRunAlive(run, now = Date.now()) {
|
|
|
85
87
|
function isRunning(project, now = Date.now()) {
|
|
86
88
|
return project.runs.some((run) => isRunAlive(run, now)) || allDiagrams(project).some((d) => d.status === "queued" || d.status === "running");
|
|
87
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* 把 todo 收敛到「已经没人在跑」的状态。
|
|
92
|
+
*
|
|
93
|
+
* agent 经常在最后一步(回报产物标记)没来得及再写一次 `todo_write` 就被判 idle,
|
|
94
|
+
* 台账里于是留下一条永远 `in_progress` 的记录:页面会一直转圈、卡片一直显示
|
|
95
|
+
* 「正在:回报产物标记与统计」,用户以为跑了几天还没结束。
|
|
96
|
+
*
|
|
97
|
+
* 规则:运行结束就不再有任何「正在进行」。
|
|
98
|
+
* - `succeeded` → 剩下的 in_progress 记成 completed(整轮跑完了,最后一步确实发生);
|
|
99
|
+
* - `failed` → 退回 pending(做到哪一步不知道,别假装完成);
|
|
100
|
+
* - `running` → 原样返回(还在跑,本来就有当前步骤)。
|
|
101
|
+
*/
|
|
102
|
+
function settleTodos(todos, status) {
|
|
103
|
+
if (status === "running") return todos;
|
|
104
|
+
const next = status === "succeeded" ? "completed" : "pending";
|
|
105
|
+
let changed = false;
|
|
106
|
+
const settled = todos.map((todo) => {
|
|
107
|
+
if (todo.status !== "in_progress") return todo;
|
|
108
|
+
changed = true;
|
|
109
|
+
return {
|
|
110
|
+
...todo,
|
|
111
|
+
status: next
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
return changed ? settled : todos;
|
|
115
|
+
}
|
|
88
116
|
|
|
89
117
|
// src/shared/format.ts
|
|
90
118
|
/**
|
|
@@ -163,7 +191,7 @@ async function api(path, init) {
|
|
|
163
191
|
const message = result.error?.message ?? "未知错误";
|
|
164
192
|
throw new ApiError(code, message);
|
|
165
193
|
}
|
|
166
|
-
/** POST/PATCH 的便捷封装。 */
|
|
194
|
+
/** POST/PUT/PATCH/DELETE 的便捷封装。 */
|
|
167
195
|
function send(path, method, body) {
|
|
168
196
|
return api(path, {
|
|
169
197
|
method,
|
|
@@ -171,6 +199,12 @@ function send(path, method, body) {
|
|
|
171
199
|
});
|
|
172
200
|
}
|
|
173
201
|
|
|
202
|
+
// src/shared/model-key.ts
|
|
203
|
+
/** 生成路由键。 */
|
|
204
|
+
function modelKey(ref) {
|
|
205
|
+
return `${ref.provider}::${ref.model}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
174
208
|
// src/client/components.tsx
|
|
175
209
|
/**
|
|
176
210
|
* 页面里可复用的小组件:标签、状态点、两个对话框。
|
|
@@ -531,6 +565,136 @@ function NewDiagramDialog({ t, project, selectedId, busy, onClose, onSubmit }) {
|
|
|
531
565
|
})
|
|
532
566
|
});
|
|
533
567
|
}
|
|
568
|
+
/** 齿轮图标(设置入口)。尺寸跟随字号,颜色跟随按钮。 */
|
|
569
|
+
function GearIcon({ size = 13 }) {
|
|
570
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
571
|
+
width: size,
|
|
572
|
+
height: size,
|
|
573
|
+
viewBox: "0 0 24 24",
|
|
574
|
+
fill: "none",
|
|
575
|
+
stroke: "currentColor",
|
|
576
|
+
strokeWidth: 1.7,
|
|
577
|
+
strokeLinecap: "round",
|
|
578
|
+
strokeLinejoin: "round",
|
|
579
|
+
"aria-hidden": "true",
|
|
580
|
+
focusable: "false",
|
|
581
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
582
|
+
cx: "12",
|
|
583
|
+
cy: "12",
|
|
584
|
+
r: "3.1"
|
|
585
|
+
}), /* @__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" })]
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* 设置对话框:**只有一个字段**——分析用哪个模型。
|
|
590
|
+
*
|
|
591
|
+
* 模型选项不是插件编的,是宿主 `modelCatalog()` 列出来的(用户在 DSH 里
|
|
592
|
+
* 配好、当前真的能路由到的那些)。目录拿不到时只留「跟随宿主默认」一项,
|
|
593
|
+
* 并且把原因写在提示里,不假装能选。
|
|
594
|
+
*/
|
|
595
|
+
function SettingsDialog({ t, settings, busy, onClose, onSubmit }) {
|
|
596
|
+
const catalog = settings?.catalog ?? null;
|
|
597
|
+
const current = settings?.model ?? null;
|
|
598
|
+
const [chosen, setChosen] = (0, react.useState)(current === null ? "" : modelKey(current));
|
|
599
|
+
const options = catalog?.options ?? [];
|
|
600
|
+
const selectedOption = options.find((option) => option.key === chosen) ?? null;
|
|
601
|
+
const submit = () => {
|
|
602
|
+
onSubmit(selectedOption === null ? null : {
|
|
603
|
+
provider: selectedOption.provider,
|
|
604
|
+
model: selectedOption.model
|
|
605
|
+
});
|
|
606
|
+
};
|
|
607
|
+
const fallbackText = catalog !== null && catalog.fallback !== null ? `${catalog.fallback.provider} / ${catalog.fallback.model}` : null;
|
|
608
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
609
|
+
className: "dap-mask",
|
|
610
|
+
role: "presentation",
|
|
611
|
+
onClick: (e) => e.target === e.currentTarget && onClose(),
|
|
612
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
613
|
+
className: "dap-dialog",
|
|
614
|
+
role: "dialog",
|
|
615
|
+
"aria-modal": "true",
|
|
616
|
+
"aria-label": t("dialog.settings"),
|
|
617
|
+
children: [
|
|
618
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
619
|
+
className: "dap-dialog__title",
|
|
620
|
+
children: t("dialog.settings")
|
|
621
|
+
}),
|
|
622
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
623
|
+
className: "dap-dialog__sub",
|
|
624
|
+
children: t("dialog.settingsSub")
|
|
625
|
+
}),
|
|
626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
627
|
+
className: "dap-dialog__row",
|
|
628
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
629
|
+
className: "dap-field__label",
|
|
630
|
+
children: t("field.model")
|
|
631
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
632
|
+
className: "dap-select",
|
|
633
|
+
value: chosen,
|
|
634
|
+
onChange: (e) => setChosen(e.target.value),
|
|
635
|
+
"aria-label": t("field.model"),
|
|
636
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
637
|
+
value: "",
|
|
638
|
+
children: fallbackText === null ? t("model.followDefault") : t("model.followDefaultNamed", { model: fallbackText })
|
|
639
|
+
}), options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
640
|
+
value: option.key,
|
|
641
|
+
children: [
|
|
642
|
+
option.providerName,
|
|
643
|
+
" · ",
|
|
644
|
+
option.name
|
|
645
|
+
]
|
|
646
|
+
}, option.key))]
|
|
647
|
+
})]
|
|
648
|
+
}),
|
|
649
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
650
|
+
className: "dap-hint",
|
|
651
|
+
children: catalog === null || catalog.source === "unavailable" ? t("settings.unavailable") : options.length === 0 ? t("settings.noModels") : t("field.modelHint")
|
|
652
|
+
}),
|
|
653
|
+
selectedOption?.description != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
654
|
+
className: "dap-hint",
|
|
655
|
+
children: selectedOption.description
|
|
656
|
+
}) : null,
|
|
657
|
+
catalog !== null && catalog.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
658
|
+
className: "dap-hint dap-hint--warn",
|
|
659
|
+
children: t("settings.providerFailed", { list: catalog.failures.map((f) => f.providerName).join("、") })
|
|
660
|
+
}) : null,
|
|
661
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
662
|
+
className: "dap-dialog__row",
|
|
663
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
664
|
+
className: "dap-field__label",
|
|
665
|
+
children: t("field.sessionMode")
|
|
666
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
667
|
+
className: "dap-hint",
|
|
668
|
+
children: settings?.subagentSessions === true ? t("settings.sessionSubagent") : t("settings.sessionPlain")
|
|
669
|
+
})]
|
|
670
|
+
}),
|
|
671
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
672
|
+
className: "dap-dialog__foot",
|
|
673
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
674
|
+
className: "dap-hint",
|
|
675
|
+
children: t("settings.scopeHint")
|
|
676
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
677
|
+
style: {
|
|
678
|
+
display: "flex",
|
|
679
|
+
gap: 6
|
|
680
|
+
},
|
|
681
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
682
|
+
className: "dap-btn dap-btn--sm",
|
|
683
|
+
onClick: onClose,
|
|
684
|
+
disabled: busy,
|
|
685
|
+
children: t("action.cancel")
|
|
686
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
687
|
+
className: "dap-btn dap-btn--sm dap-btn--primary",
|
|
688
|
+
onClick: submit,
|
|
689
|
+
disabled: busy,
|
|
690
|
+
children: t("action.save")
|
|
691
|
+
})]
|
|
692
|
+
})]
|
|
693
|
+
})
|
|
694
|
+
]
|
|
695
|
+
})
|
|
696
|
+
});
|
|
697
|
+
}
|
|
534
698
|
|
|
535
699
|
// src/client/page.tsx
|
|
536
700
|
/**
|
|
@@ -557,6 +721,7 @@ function StudioPage({ t }) {
|
|
|
557
721
|
const [diagramId, setDiagramId] = (0, react.useState)(null);
|
|
558
722
|
const [tab, setTab] = (0, react.useState)("info");
|
|
559
723
|
const [dialog, setDialog] = (0, react.useState)(null);
|
|
724
|
+
const [settings, setSettings] = (0, react.useState)(null);
|
|
560
725
|
const [busy, setBusy] = (0, react.useState)(null);
|
|
561
726
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
562
727
|
const [question, setQuestion] = (0, react.useState)("");
|
|
@@ -590,6 +755,18 @@ function StudioPage({ t }) {
|
|
|
590
755
|
(0, react.useEffect)(() => {
|
|
591
756
|
load();
|
|
592
757
|
}, [load]);
|
|
758
|
+
(0, react.useEffect)(() => {
|
|
759
|
+
let alive = true;
|
|
760
|
+
(async () => {
|
|
761
|
+
try {
|
|
762
|
+
const value = await api(ROUTES.settings);
|
|
763
|
+
if (alive) setSettings(value);
|
|
764
|
+
} catch {}
|
|
765
|
+
})();
|
|
766
|
+
return () => {
|
|
767
|
+
alive = false;
|
|
768
|
+
};
|
|
769
|
+
}, []);
|
|
593
770
|
const running = state.projects.some((project) => isRunning(project));
|
|
594
771
|
(0, react.useEffect)(() => {
|
|
595
772
|
if (!running) return void 0;
|
|
@@ -634,6 +811,19 @@ function StudioPage({ t }) {
|
|
|
634
811
|
setBusy(null);
|
|
635
812
|
}
|
|
636
813
|
};
|
|
814
|
+
const saveSettings = async (model) => {
|
|
815
|
+
setBusy("settings");
|
|
816
|
+
setNotice(null);
|
|
817
|
+
try {
|
|
818
|
+
const value = await send(ROUTES.settings, "PUT", { model });
|
|
819
|
+
if (mounted.current) setSettings(value);
|
|
820
|
+
setDialog(null);
|
|
821
|
+
} catch (err) {
|
|
822
|
+
fail(err);
|
|
823
|
+
} finally {
|
|
824
|
+
setBusy(null);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
637
827
|
const updateAll = async () => {
|
|
638
828
|
for (const item of state.projects) {
|
|
639
829
|
if (isRunning(item)) continue;
|
|
@@ -783,14 +973,20 @@ function StudioPage({ t }) {
|
|
|
783
973
|
className: "dap-head__sub",
|
|
784
974
|
children: project.repoPath
|
|
785
975
|
})
|
|
786
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
976
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
787
977
|
className: "dap-head__actions",
|
|
788
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
978
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
979
|
+
className: "dap-iconbtn",
|
|
980
|
+
onClick: () => setDialog("settings"),
|
|
981
|
+
title: t("action.settings"),
|
|
982
|
+
"aria-label": t("action.settings"),
|
|
983
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {})
|
|
984
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
789
985
|
className: "dap-btn dap-btn--sm dap-btn--primary",
|
|
790
986
|
onClick: () => void runUpdate(project),
|
|
791
987
|
disabled: busy !== null || isRunning(project),
|
|
792
988
|
children: t("action.update")
|
|
793
|
-
})
|
|
989
|
+
})]
|
|
794
990
|
})]
|
|
795
991
|
}),
|
|
796
992
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -818,6 +1014,13 @@ function StudioPage({ t }) {
|
|
|
818
1014
|
onNewDiagram: () => setDialog("diagram")
|
|
819
1015
|
})]
|
|
820
1016
|
}),
|
|
1017
|
+
dialog === "settings" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsDialog, {
|
|
1018
|
+
t,
|
|
1019
|
+
settings,
|
|
1020
|
+
busy: busy !== null,
|
|
1021
|
+
onClose: () => setDialog(null),
|
|
1022
|
+
onSubmit: (model) => void saveSettings(model)
|
|
1023
|
+
}) : null,
|
|
821
1024
|
dialog === "diagram" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewDiagramDialog, {
|
|
822
1025
|
t,
|
|
823
1026
|
project,
|
|
@@ -841,16 +1044,26 @@ function StudioPage({ t }) {
|
|
|
841
1044
|
children: t("page.subtitle")
|
|
842
1045
|
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
843
1046
|
className: "dap-head__actions",
|
|
844
|
-
children: [
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1047
|
+
children: [
|
|
1048
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1049
|
+
className: "dap-iconbtn",
|
|
1050
|
+
onClick: () => setDialog("settings"),
|
|
1051
|
+
title: t("action.settings"),
|
|
1052
|
+
"aria-label": t("action.settings"),
|
|
1053
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {})
|
|
1054
|
+
}),
|
|
1055
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1056
|
+
className: "dap-btn dap-btn--sm",
|
|
1057
|
+
onClick: () => void updateAll(),
|
|
1058
|
+
disabled: busy !== null || state.projects.length === 0,
|
|
1059
|
+
children: t("action.updateAll")
|
|
1060
|
+
}),
|
|
1061
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1062
|
+
className: "dap-btn dap-btn--sm dap-btn--primary",
|
|
1063
|
+
onClick: () => setDialog("project"),
|
|
1064
|
+
children: ["+ ", t("action.add")]
|
|
1065
|
+
})
|
|
1066
|
+
]
|
|
854
1067
|
})]
|
|
855
1068
|
}),
|
|
856
1069
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -938,6 +1151,13 @@ function StudioPage({ t }) {
|
|
|
938
1151
|
})] })
|
|
939
1152
|
]
|
|
940
1153
|
}),
|
|
1154
|
+
dialog === "settings" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsDialog, {
|
|
1155
|
+
t,
|
|
1156
|
+
settings,
|
|
1157
|
+
busy: busy !== null,
|
|
1158
|
+
onClose: () => setDialog(null),
|
|
1159
|
+
onSubmit: (model) => void saveSettings(model)
|
|
1160
|
+
}) : null,
|
|
941
1161
|
dialog === "project" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewProjectDialog, {
|
|
942
1162
|
t,
|
|
943
1163
|
busy: busy !== null,
|
|
@@ -1011,7 +1231,7 @@ function AtlasCard({ t, project, busy, ago, onOpen, onUpdate }) {
|
|
|
1011
1231
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1012
1232
|
className: "dap-thumbs",
|
|
1013
1233
|
children: diagrams.slice(0, 3).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1014
|
-
className:
|
|
1234
|
+
className: "dap-thumb",
|
|
1015
1235
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1016
1236
|
className: "dap-thumb__title",
|
|
1017
1237
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: item.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1268,7 +1488,8 @@ function Workbench({ t, project, diagram, tab, busy, question, pricing, ago, onT
|
|
|
1268
1488
|
children: t("progress.title")
|
|
1269
1489
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TodoList, {
|
|
1270
1490
|
t,
|
|
1271
|
-
todos: project.lastRun?.todos ?? []
|
|
1491
|
+
todos: project.lastRun?.todos ?? [],
|
|
1492
|
+
status: project.lastRun?.status ?? null
|
|
1272
1493
|
})]
|
|
1273
1494
|
}),
|
|
1274
1495
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1512,14 +1733,15 @@ function RunBar({ t, project }) {
|
|
|
1512
1733
|
});
|
|
1513
1734
|
}
|
|
1514
1735
|
/** todo 列表:✓ / spinner / ○。 */
|
|
1515
|
-
function TodoList({ t, todos }) {
|
|
1736
|
+
function TodoList({ t, todos, status = null }) {
|
|
1516
1737
|
if (todos.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1517
1738
|
className: "dap-hint",
|
|
1518
1739
|
children: t("progress.none")
|
|
1519
1740
|
});
|
|
1741
|
+
const shown = status === null ? todos : settleTodos(todos, status);
|
|
1520
1742
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1521
1743
|
className: "dap-todos",
|
|
1522
|
-
children:
|
|
1744
|
+
children: shown.map((todo) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1523
1745
|
className: todo.status === "completed" ? "dap-todo dap-todo--done" : todo.status === "in_progress" ? "dap-todo dap-todo--now" : "dap-todo",
|
|
1524
1746
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1525
1747
|
className: "dap-todo__mark",
|
|
@@ -1638,6 +1860,20 @@ const zh = {
|
|
|
1638
1860
|
"action.delete": "删除",
|
|
1639
1861
|
"action.openSession": "查看会话",
|
|
1640
1862
|
"action.cancel": "取消",
|
|
1863
|
+
"action.settings": "设置",
|
|
1864
|
+
"dialog.settings": "设置",
|
|
1865
|
+
"dialog.settingsSub": "只影响这个插件的分析会话,不动 DSH 里你自己的默认模型。",
|
|
1866
|
+
"field.model": "分析模型",
|
|
1867
|
+
"field.modelHint": "选项来自你在 DSH 里已经配好、当前能路由的模型。",
|
|
1868
|
+
"field.sessionMode": "会话形态",
|
|
1869
|
+
"model.followDefault": "跟随宿主默认",
|
|
1870
|
+
"model.followDefaultNamed": "跟随宿主默认({model})",
|
|
1871
|
+
"settings.noModels": "宿主的模型目录是空的:去 DSH 设置里配一个模型,这里才有得选。",
|
|
1872
|
+
"settings.unavailable": "这个宿主版本没有提供模型目录,只能跟随宿主默认。",
|
|
1873
|
+
"settings.providerFailed": "{list} 的模型列表取不到(网络或凭据),暂时不能选。",
|
|
1874
|
+
"settings.sessionSubagent": "子代理会话(不占用左侧会话列表)",
|
|
1875
|
+
"settings.sessionPlain": "普通会话(宿主不支持子代理会话)",
|
|
1876
|
+
"settings.scopeHint": "保存后对下一次运行生效",
|
|
1641
1877
|
"action.submit": "创建并分析",
|
|
1642
1878
|
"action.close": "关闭",
|
|
1643
1879
|
"action.back": "返回图册",
|
|
@@ -1799,6 +2035,20 @@ const en = {
|
|
|
1799
2035
|
"action.delete": "Delete",
|
|
1800
2036
|
"action.openSession": "Open session",
|
|
1801
2037
|
"action.cancel": "Cancel",
|
|
2038
|
+
"action.settings": "Settings",
|
|
2039
|
+
"dialog.settings": "Settings",
|
|
2040
|
+
"dialog.settingsSub": "Only affects this plugin's analysis sessions; your own default model in DSH stays untouched.",
|
|
2041
|
+
"field.model": "Analysis model",
|
|
2042
|
+
"field.modelHint": "Options are the models already configured in DSH and currently routable.",
|
|
2043
|
+
"field.sessionMode": "Session",
|
|
2044
|
+
"model.followDefault": "Follow the host default",
|
|
2045
|
+
"model.followDefaultNamed": "Follow the host default ({model})",
|
|
2046
|
+
"settings.noModels": "The host catalog is empty. Configure a model in DSH settings to pick one here.",
|
|
2047
|
+
"settings.unavailable": "This host version exposes no model catalog; the host default is used.",
|
|
2048
|
+
"settings.providerFailed": "Could not list models for {list} (network or credentials).",
|
|
2049
|
+
"settings.sessionSubagent": "Subagent session (kept out of the sidebar list)",
|
|
2050
|
+
"settings.sessionPlain": "Plain session (this host lacks subagent sessions)",
|
|
2051
|
+
"settings.scopeHint": "Applies to the next run",
|
|
1802
2052
|
"action.submit": "Create and analyse",
|
|
1803
2053
|
"action.close": "Close",
|
|
1804
2054
|
"action.back": "Back to atlas",
|
|
@@ -2022,7 +2272,9 @@ const PAGE_CSS = `
|
|
|
2022
2272
|
.dap-card {
|
|
2023
2273
|
border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg);
|
|
2024
2274
|
background: var(--dsw-alias-bg-layer-1); padding: 14px; display: flex; flex-direction: column; gap: 10px;
|
|
2025
|
-
cursor: pointer; text-align: left; width: 100%; transition: border-color .15s, box-shadow .15s;
|
|
2275
|
+
cursor: pointer; text-align: left; width: 100%; min-width: 0; transition: border-color .15s, box-shadow .15s;
|
|
2276
|
+
/* 宿主没有全局 border-box:不写死就会让 100% 宽 + 14px padding 溢出栅格列。 */
|
|
2277
|
+
box-sizing: border-box;
|
|
2026
2278
|
}
|
|
2027
2279
|
.dap-card:hover { border-color: var(--dsw-alias-border-l3); box-shadow: var(--dsw-shadow-lv2); }
|
|
2028
2280
|
.dap-card__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
|
|
@@ -2030,16 +2282,21 @@ const PAGE_CSS = `
|
|
|
2030
2282
|
.dap-repo__name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2031
2283
|
.dap-repo__path { font-size: 11px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: ui-monospace, monospace; }
|
|
2032
2284
|
.dap-chips { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
2033
|
-
|
|
2285
|
+
/* 图条:等宽分栏,行宽永远等于卡片内容宽。
|
|
2286
|
+
旧的 1fr 1fr 会被子项的 min-content 撑宽(撑出卡片边框外),必须 minmax(0, 1fr);
|
|
2287
|
+
auto-fit 让「只有一张图」时自然占满整行,不再靠 grid-column: 1 / -1 单独拉长。 */
|
|
2288
|
+
.dap-thumbs { display: grid; grid-template-columns: repeat(auto-fit, minmax(165px, 1fr)); gap: 6px; }
|
|
2034
2289
|
.dap-thumb {
|
|
2035
2290
|
border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-md);
|
|
2036
|
-
background: var(--dsw-alias-bg-layer-2); padding: 6px 8px;
|
|
2291
|
+
background: var(--dsw-alias-bg-layer-2); padding: 6px 8px;
|
|
2292
|
+
min-width: 0; box-sizing: border-box;
|
|
2037
2293
|
}
|
|
2038
|
-
.dap-
|
|
2039
|
-
.dap-thumb__title {
|
|
2040
|
-
.dap-thumb__title
|
|
2294
|
+
.dap-thumb__title { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); min-width: 0; }
|
|
2295
|
+
.dap-thumb__title b { flex: 1 1 auto; min-width: 0; font-weight: 550; color: var(--dsw-alias-label-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2296
|
+
.dap-thumb__title .dap-thumb__count { flex: none; }
|
|
2041
2297
|
.dap-thumb__count { font-size: 10.5px; color: var(--dsw-alias-label-tertiary); }
|
|
2042
|
-
.dap-card__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 11.5px; color: var(--dsw-alias-label-tertiary); }
|
|
2298
|
+
.dap-card__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 11.5px; color: var(--dsw-alias-label-tertiary); min-width: 0; }
|
|
2299
|
+
.dap-card__foot > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2043
2300
|
|
|
2044
2301
|
/* ---------------------------------------------------------------- 工作台 */
|
|
2045
2302
|
.dap-wb { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 12px; align-items: stretch; flex: 1; min-height: 0; }
|
|
@@ -2190,6 +2447,25 @@ const PAGE_CSS = `
|
|
|
2190
2447
|
|
|
2191
2448
|
|
|
2192
2449
|
.dap-dialog--wide { width: 680px; }
|
|
2450
|
+
|
|
2451
|
+
/* 头部图标按钮(设置齿轮):与 dap-btn--sm 同高,窄一点 */
|
|
2452
|
+
.dap-iconbtn {
|
|
2453
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
2454
|
+
width: 26px; height: 26px; border-radius: var(--dsw-radius-md);
|
|
2455
|
+
border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2);
|
|
2456
|
+
color: var(--dsw-alias-label-secondary); cursor: pointer;
|
|
2457
|
+
}
|
|
2458
|
+
.dap-iconbtn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l2); }
|
|
2459
|
+
|
|
2460
|
+
/* 设置弹窗的模型下拉:原生 select 最省事,长列表也能滚动 */
|
|
2461
|
+
.dap-select {
|
|
2462
|
+
min-width: 240px; max-width: 380px; height: 28px; padding: 0 8px;
|
|
2463
|
+
border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md);
|
|
2464
|
+
background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary);
|
|
2465
|
+
font: inherit; font-size: 12px; outline: none; cursor: pointer;
|
|
2466
|
+
}
|
|
2467
|
+
.dap-select:focus { border-color: var(--dsw-alias-border-l3); }
|
|
2468
|
+
.dap-hint--warn { color: var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-tertiary)); }
|
|
2193
2469
|
.dap-typebar { display: flex; flex-wrap: wrap; gap: 5px; margin: 6px 0 8px; }
|
|
2194
2470
|
.dap-type-tag {
|
|
2195
2471
|
display: inline-flex; align-items: center; gap: 5px; height: 27px; padding: 0 9px;
|