@openvulcan/dsh-vulcancode 0.0.0-stage → 0.4.3
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/LICENSE +21 -0
- package/README.md +126 -2
- package/assets/openvulcan-icon.svg +33 -0
- package/cordis.patch.yml +19 -0
- package/node_modules/@openvulcan/dsh-decision-guard/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-decision-guard/README.md +14 -0
- package/node_modules/@openvulcan/dsh-decision-guard/assets/decision-guard-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/calls.d.ts +40 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/calls.js +150 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/client.js +2250 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/decide.d.ts +90 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/decide.js +108 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/index.d.ts +129 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/index.js +572 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/intent.d.ts +22 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/intent.js +77 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/panel.d.ts +158 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/panel.js +340 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/records.d.ts +133 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/records.js +186 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/review.d.ts +55 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/review.js +202 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/session-state.d.ts +62 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/session-state.js +155 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/version.d.ts +2 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/version.js +4 -0
- package/node_modules/@openvulcan/dsh-decision-guard/package.json +68 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/assets/account-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/lib/client.js +573 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/lib/index.d.ts +92 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/lib/index.js +194 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/package.json +57 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/assets/answers-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/assets/prompts/answer-mode-interactive.md +79 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/assets/prompts/answer-mode-standard.md +1 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/answer-model.d.ts +258 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/answer-model.js +698 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/client.js +671 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/fences.d.ts +29 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/fences.js +70 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/index.d.ts +19 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/index.js +52 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/limits.d.ts +9 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/limits.js +9 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/prompt.d.ts +22 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/prompt.js +39 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/reply-format.d.ts +20 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/reply-format.js +17 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/session-state.d.ts +66 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/session-state.js +118 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/package.json +59 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/assets/luaskills-icon.svg +1 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/client.js +949 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/constants.d.ts +46 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/constants.js +51 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/engine.d.ts +55 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/engine.js +149 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/hub.d.ts +49 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/hub.js +116 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/index.d.ts +26 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/index.js +276 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/protocol.d.ts +65 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/protocol.js +2 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/runtime.d.ts +111 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/runtime.js +281 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/sdk-install.d.ts +26 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/sdk-install.js +87 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/settings.d.ts +79 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/settings.js +165 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/skills.d.ts +34 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/skills.js +61 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/tools.d.ts +59 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/tools.js +107 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/worker.d.ts +10 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/worker.js +140 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/package.json +57 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/assets/markdown-memory-guide.md +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-curator/SKILL.md +31 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/client.js +1090 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/index.d.ts +30 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/index.js +240 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/inject.d.ts +30 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/inject.js +46 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/constants.d.ts +23 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/constants.js +25 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/document.d.ts +108 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/document.js +373 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.d.ts +14 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.js +31 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.d.ts +196 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.js +204 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.d.ts +92 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.js +472 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/package.d.ts +126 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/package.js +553 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/registry.d.ts +56 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/registry.js +182 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/scope.d.ts +13 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/scope.js +24 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/search.d.ts +65 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/search.js +227 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/snapshot.d.ts +34 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/snapshot.js +171 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/store.d.ts +72 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/store.js +250 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/timestamp.d.ts +6 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/timestamp.js +12 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/tokens.d.ts +8 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/tokens.js +38 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/page.d.ts +132 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/page.js +235 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/project.d.ts +14 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/project.js +27 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/settings.d.ts +29 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/settings.js +62 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/package.json +63 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/client.js +509 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/index.d.ts +6 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/index.js +8 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/package.json +42 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/assets/style-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/lib/client.js +1121 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/lib/index.d.ts +34 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/lib/index.js +42 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/package.json +51 -0
- package/node_modules/@openvulcan/openvulcan-account/LICENSE +21 -0
- package/node_modules/@openvulcan/openvulcan-account/lib/account.d.ts +140 -0
- package/node_modules/@openvulcan/openvulcan-account/lib/account.js +347 -0
- package/node_modules/@openvulcan/openvulcan-account/lib/index.d.ts +1 -0
- package/node_modules/@openvulcan/openvulcan-account/lib/index.js +1 -0
- package/node_modules/@openvulcan/openvulcan-account/package.json +29 -0
- package/node_modules/@openvulcan/vulcan-decision/LICENSE +21 -0
- package/node_modules/@openvulcan/vulcan-decision/README.md +23 -0
- package/node_modules/@openvulcan/vulcan-decision/bin/vulcan-decision.js +4 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/cli.d.ts +5 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/cli.js +267 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/cloud/edge.d.ts +211 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/cloud/edge.js +594 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/download.d.ts +36 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/download.js +99 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/engine.d.ts +37 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/engine.js +39 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/hardware.d.ts +105 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/hardware.js +207 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/home.d.ts +26 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/home.js +54 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/index.d.ts +17 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/index.js +17 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/engine.d.ts +54 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/engine.js +157 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/models.d.ts +126 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/models.js +261 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/packs.d.ts +102 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/packs.js +195 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/worker.d.ts +61 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/worker.js +123 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/protocol.d.ts +74 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/protocol.js +31 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/render.d.ts +15 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/render.js +33 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/version.d.ts +2 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/version.js +4 -0
- package/node_modules/@openvulcan/vulcan-decision/package.json +40 -0
- package/node_modules/@openvulcan/vulcan-decision/python/vulcan_decision_worker.py +363 -0
- package/node_modules/@openvulcan/vulcan-decision/runtime-packs/onnx-cpu.json +627 -0
- package/node_modules/@openvulcan/vulcan-decision/runtime-packs/onnx-cuda.json +423 -0
- package/node_modules/@openvulcan/vulcan-decision/runtime-packs/torch-cpu.json +1047 -0
- package/node_modules/@openvulcan/vulcan-decision/runtime-packs/torch-cu128.json +647 -0
- package/package.json +55 -4
|
@@ -0,0 +1,2250 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@openvulcan/dsh-decision-guard",
|
|
3
|
+
factory: (platformRequire) => {
|
|
4
|
+
var definitions = {
|
|
5
|
+
"./approval": function (module, exports, require) {
|
|
6
|
+
"use strict";
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.guardApprovalOf = guardApprovalOf;
|
|
9
|
+
exports.ApprovalCard = ApprovalCard;
|
|
10
|
+
exports.ConflictBadge = ConflictBadge;
|
|
11
|
+
exports.ConflictSection = ConflictSection;
|
|
12
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
13
|
+
/**
|
|
14
|
+
* What the plugin shows outside Settings: the approval card that replaces the composer while one
|
|
15
|
+
* of its prompts waits, the Auto review conflict notice on both plugins' pages, and the short
|
|
16
|
+
* summary on its own Plugins page that points to Settings › Auto approval.
|
|
17
|
+
*/
|
|
18
|
+
const react_1 = require("react");
|
|
19
|
+
const i18n_1 = require("./i18n");
|
|
20
|
+
const shared_1 = require("./shared");
|
|
21
|
+
const ui_1 = require("./ui");
|
|
22
|
+
/** Mirrors `PROMPT_KEY` and `PromptPayload` in `src/index.ts`. */
|
|
23
|
+
const PROMPT_KEY = 'x-vulcan-decision';
|
|
24
|
+
/** Recognize this plugin's prompts by their payload; other approvals stay with dsh's own card. */
|
|
25
|
+
function guardApprovalOf(interaction) {
|
|
26
|
+
if (typeof interaction !== 'object' || interaction === null)
|
|
27
|
+
return null;
|
|
28
|
+
const pending = interaction;
|
|
29
|
+
if (pending.kind !== 'approval' || typeof pending.answer !== 'function')
|
|
30
|
+
return null;
|
|
31
|
+
const raw = pending.displayReason?.[PROMPT_KEY];
|
|
32
|
+
if (typeof raw !== 'string')
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
const payload = JSON.parse(raw);
|
|
36
|
+
return payload.v === 1 ? { pending: pending, payload } : null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Seconds left until the Host rejects an unanswered prompt, counted from when it appeared. */
|
|
43
|
+
function useCountdown(timeoutMs) {
|
|
44
|
+
const [now, setNow] = (0, react_1.useState)(() => Date.now());
|
|
45
|
+
const [start] = (0, react_1.useState)(() => Date.now());
|
|
46
|
+
(0, react_1.useEffect)(() => {
|
|
47
|
+
if (timeoutMs === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
const timer = setInterval(() => { setNow(Date.now()); }, 250);
|
|
50
|
+
return () => { clearInterval(timer); };
|
|
51
|
+
}, [timeoutMs]);
|
|
52
|
+
return timeoutMs === undefined ? undefined : Math.max(0, Math.ceil((start + timeoutMs - now) / 1000));
|
|
53
|
+
}
|
|
54
|
+
function ApprovalCard({ matched, t, resolveText }) {
|
|
55
|
+
return (0, jsx_runtime_1.jsx)(ApprovalFlow, { approval: matched, t: t, resolveText: resolveText }, matched.pending.key);
|
|
56
|
+
}
|
|
57
|
+
function ApprovalFlow({ approval, t, resolveText }) {
|
|
58
|
+
const { pending, payload } = approval;
|
|
59
|
+
const [answered, setAnswered] = (0, react_1.useState)(false);
|
|
60
|
+
const allowRef = (0, react_1.useRef)(null);
|
|
61
|
+
const rejectRef = (0, react_1.useRef)(null);
|
|
62
|
+
const left = useCountdown(payload.timeoutMs);
|
|
63
|
+
(0, react_1.useEffect)(() => {
|
|
64
|
+
(payload.focus === 'allow' ? allowRef : rejectRef).current?.focus();
|
|
65
|
+
}, [payload.focus]);
|
|
66
|
+
const answer = (outcome) => {
|
|
67
|
+
if (answered || !pending.answerable)
|
|
68
|
+
return;
|
|
69
|
+
setAnswered(true);
|
|
70
|
+
pending.answer(outcome).catch(() => {
|
|
71
|
+
if (pending.answerable)
|
|
72
|
+
setAnswered(false);
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const keydown = (event) => {
|
|
76
|
+
if (event.key !== 'Escape' || event.defaultPrevented)
|
|
77
|
+
return;
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
event.stopPropagation();
|
|
80
|
+
answer('rejected');
|
|
81
|
+
};
|
|
82
|
+
const headline = pending.displayReason === undefined ? pending.reason ?? '' : resolveText(pending.displayReason);
|
|
83
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "vdg vdg-prompt", "aria-busy": answered, onKeyDown: keydown, children: (0, jsx_runtime_1.jsxs)("div", { className: "vdg-prompt-card", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-prompt-strip", children: [(0, jsx_runtime_1.jsx)("span", { children: t('prompt.strip') }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: payload.stage === 'review' ? 'warn' : 'bad', children: t(`prompt.stage.${payload.stage}`) }), payload.review === undefined ? null : (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "outline", children: t(`prompt.review.${payload.review}`) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-prompt-body", role: "group", "aria-label": t('prompt.strip'), children: [(0, jsx_runtime_1.jsx)("strong", { style: { fontWeight: 500 }, children: headline }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: (0, i18n_1.fill)(t('prompt.call'), { tool: pending.toolName }) }), (0, jsx_runtime_1.jsx)("code", { className: "vdg-code", children: payload.preview }), payload.reviewReason === undefined
|
|
84
|
+
? null
|
|
85
|
+
: (0, jsx_runtime_1.jsxs)("p", { className: "vdg-hint", children: [(0, jsx_runtime_1.jsx)("strong", { style: { display: 'block', fontWeight: 500, color: 'var(--vk-ink-2)' }, children: t('prompt.reason') }), payload.reviewReason] }), payload.intent === undefined
|
|
86
|
+
? null
|
|
87
|
+
: (0, jsx_runtime_1.jsxs)("p", { className: "vdg-hint", children: [(0, jsx_runtime_1.jsx)("strong", { style: { display: 'block', fontWeight: 500, color: 'var(--vk-ink-2)' }, children: t('prompt.intent') }), payload.intent] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-prompt-actions", children: [left === undefined
|
|
88
|
+
? (0, jsx_runtime_1.jsx)("span", {})
|
|
89
|
+
: (0, jsx_runtime_1.jsx)("span", { role: "timer", className: "vdg-warn", children: left > 0 ? (0, i18n_1.fill)(t('prompt.countdown'), { seconds: left }) : t('prompt.expired') }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)("button", { ref: rejectRef, type: "button", className: payload.focus === 'reject' ? 'vdg-btn primary' : 'vdg-btn', disabled: answered, onClick: () => { answer('rejected'); }, children: t('prompt.reject') }), (0, jsx_runtime_1.jsx)("button", { ref: allowRef, type: "button", className: payload.focus === 'allow' ? 'vdg-btn primary' : 'vdg-btn', disabled: answered, onClick: () => { answer('allowed-once'); }, children: t('prompt.allow') })] })] })] }) }));
|
|
90
|
+
}
|
|
91
|
+
/** Which side of the conflict an open page shows, or null when it is about another plugin. */
|
|
92
|
+
function pageSide(subject) {
|
|
93
|
+
if (subject.kind !== 'bundle')
|
|
94
|
+
return null;
|
|
95
|
+
if (subject.pkg.name === shared_1.SUITE_PACKAGE)
|
|
96
|
+
return 'guard';
|
|
97
|
+
if (subject.pkg.name === shared_1.AUTO_REVIEW_PACKAGE)
|
|
98
|
+
return 'review';
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function useConflict(services, subject) {
|
|
102
|
+
const enabled = (0, ui_1.useWatch)(services.autoReview);
|
|
103
|
+
const side = pageSide(subject);
|
|
104
|
+
return side !== null && enabled === true ? side : null;
|
|
105
|
+
}
|
|
106
|
+
function ConflictBadge({ subject, t, services }) {
|
|
107
|
+
return useConflict(services, subject) === null ? null : (0, jsx_runtime_1.jsx)("span", { className: "vdg", children: (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "warn", children: t('conflict.tag') }) });
|
|
108
|
+
}
|
|
109
|
+
function ConflictSection({ subject, t, services, disableAutoReview }) {
|
|
110
|
+
const side = useConflict(services, subject);
|
|
111
|
+
const [phase, setPhase] = (0, react_1.useState)({ kind: 'idle' });
|
|
112
|
+
if (side === null)
|
|
113
|
+
return null;
|
|
114
|
+
const disable = () => {
|
|
115
|
+
setPhase({ kind: 'busy' });
|
|
116
|
+
disableAutoReview().then((result) => {
|
|
117
|
+
if (!result.ok) {
|
|
118
|
+
setPhase({ kind: 'failed', reason: result.error.message });
|
|
119
|
+
}
|
|
120
|
+
else if (result.value.error !== undefined || result.value.application === 'failed') {
|
|
121
|
+
setPhase({ kind: 'failed', reason: result.value.error?.message ?? result.value.error?.code ?? result.value.application });
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
setPhase(result.value.application === 'restart-required' ? { kind: 'restart' } : { kind: 'idle' });
|
|
125
|
+
}
|
|
126
|
+
services.autoReview.refresh();
|
|
127
|
+
}, (error) => {
|
|
128
|
+
setPhase({ kind: 'failed', reason: error instanceof Error ? error.message : String(error) });
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: "vdg vdg-alert warn", role: "status", style: { display: 'flex', flexDirection: 'column', gap: 10 }, children: [(0, jsx_runtime_1.jsx)("strong", { style: { color: 'var(--vk-warn)', fontSize: 13 }, children: t(side === 'guard' ? 'conflict.title.guard' : 'conflict.title.review') }), (0, jsx_runtime_1.jsx)("span", { style: { fontSize: 13 }, children: t(side === 'guard' ? 'conflict.body.guard' : 'conflict.body.review') }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: phase.kind === 'busy', onClick: disable, children: t(phase.kind === 'busy' ? 'conflict.disabling' : 'conflict.disable') }), phase.kind === 'restart' ? (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: t('conflict.restart') }) : null, phase.kind === 'failed' ? (0, jsx_runtime_1.jsxs)("span", { className: "vdg-error", children: [t('conflict.failed'), phase.reason] }) : null] })] }));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
},
|
|
135
|
+
"./cloud": function (module, exports, require) {
|
|
136
|
+
"use strict";
|
|
137
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
138
|
+
exports.AccountSection = AccountSection;
|
|
139
|
+
exports.ModelSection = ModelSection;
|
|
140
|
+
exports.lockEndpoint = lockEndpoint;
|
|
141
|
+
exports.EndpointsSection = EndpointsSection;
|
|
142
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
143
|
+
/**
|
|
144
|
+
* The cloud part of the settings page, as VulcanCode shows it: the OpenVulcan account, the
|
|
145
|
+
* official model OpenVulcan lists for it, the endpoint table with its lock, and the request test.
|
|
146
|
+
*/
|
|
147
|
+
const react_1 = require("react");
|
|
148
|
+
const i18n_1 = require("./i18n");
|
|
149
|
+
const shared_1 = require("./shared");
|
|
150
|
+
const ui_1 = require("./ui");
|
|
151
|
+
const SITES = [
|
|
152
|
+
{ site: 'global', domain: 'openvulcan.com' },
|
|
153
|
+
{ site: 'cn', domain: 'openvulcan.cn' },
|
|
154
|
+
];
|
|
155
|
+
/** The account the cloud backend signs with; signing in and out happens in the account plugin. */
|
|
156
|
+
function AccountSection({ services, t, account }) {
|
|
157
|
+
const manage = (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", onClick: () => { services.openAccount(); }, children: t('account.manage') });
|
|
158
|
+
return ((0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('account.title'), children: !account.plugin
|
|
159
|
+
? (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('account.noPlugin') })
|
|
160
|
+
: account.signedIn
|
|
161
|
+
? ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "ok", children: t('account.signedIn') }), (0, jsx_runtime_1.jsx)("strong", { children: t(`account.site.${account.site ?? 'global'}`) }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint vdg-mono", children: SITES.find(item => item.site === account.site)?.domain })] }), account.expiresAt === undefined ? null : (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: (0, i18n_1.fill)(t('account.expires'), { expires: (0, ui_1.when)(account.expiresAt) }) })] }), manage] }))
|
|
162
|
+
: ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-between", children: [(0, jsx_runtime_1.jsx)("span", { children: t(account.reauthRequired === true ? 'account.reauth' : 'account.signedOut') }), manage] })) }));
|
|
163
|
+
}
|
|
164
|
+
function ModelSection({ services, t, saved, disabled, onChoose }) {
|
|
165
|
+
const list = (0, ui_1.useWatch)(services.cloudModels);
|
|
166
|
+
(0, ui_1.usePolling)(services.cloudModels, undefined);
|
|
167
|
+
let body;
|
|
168
|
+
if (list === undefined) {
|
|
169
|
+
body = (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('model.loading') });
|
|
170
|
+
}
|
|
171
|
+
else if (list.kind === 'failed') {
|
|
172
|
+
body = (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert bad", children: (0, i18n_1.fill)(t('model.failed'), { error: list.error }) });
|
|
173
|
+
}
|
|
174
|
+
else if (list.models.length === 0) {
|
|
175
|
+
body = (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('model.none') });
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const missing = saved !== '' && !list.models.includes(saved);
|
|
179
|
+
body = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('model.title'), value: saved, disabled: disabled, onChange: (event) => { onChoose(event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: (0, i18n_1.fill)(t('model.default'), { model: list.models[0] }) }), missing ? (0, jsx_runtime_1.jsx)("option", { value: saved, disabled: true, children: (0, i18n_1.fill)(t('model.disabled'), { model: saved }) }) : null, list.models.map(model => (0, jsx_runtime_1.jsx)("option", { value: model, children: model }, model))] }), missing ? (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: (0, i18n_1.fill)(t('model.disabledWarn'), { model: saved }) }) : null] }));
|
|
180
|
+
}
|
|
181
|
+
return (0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('model.title'), lead: t('model.lead'), children: body });
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Save an endpoint lock, after the Host checked that the endpoint serves the saved model;
|
|
185
|
+
* undefined returns to automatic choice.
|
|
186
|
+
*/
|
|
187
|
+
async function lockEndpoint(saveAsync, model, endpoint, readOnly) {
|
|
188
|
+
if (endpoint !== undefined)
|
|
189
|
+
await (0, shared_1.callPanel)('POST', 'cloud/check-lock', { model, endpoint });
|
|
190
|
+
if (!(await saveAsync({ cloudEndpoint: endpoint })))
|
|
191
|
+
throw new Error(readOnly);
|
|
192
|
+
}
|
|
193
|
+
/** The status pill of one endpoint row, in VulcanCode's order of precedence. */
|
|
194
|
+
function endpointStatus(row) {
|
|
195
|
+
if (row.error !== undefined)
|
|
196
|
+
return { tone: 'bad', key: 'endpoints.status.error' };
|
|
197
|
+
if (row.rttMs === undefined)
|
|
198
|
+
return { tone: 'outline', key: 'endpoints.status.untested' };
|
|
199
|
+
if (row.acceptsSite === false)
|
|
200
|
+
return { tone: 'warn', key: 'endpoints.status.site' };
|
|
201
|
+
if (row.ready === false)
|
|
202
|
+
return { tone: 'warn', key: 'endpoints.status.notReady' };
|
|
203
|
+
return { tone: 'ok', key: 'endpoints.status.ready' };
|
|
204
|
+
}
|
|
205
|
+
function EndpointsSection({ services, t, disabled, onLock }) {
|
|
206
|
+
const snapshot = (0, ui_1.useWatch)(services.endpoints);
|
|
207
|
+
(0, ui_1.usePolling)(services.endpoints, 2000);
|
|
208
|
+
const { busy, error, act } = (0, ui_1.useAction)(() => { services.endpoints.refresh(); });
|
|
209
|
+
const [testing, setTesting] = (0, react_1.useState)(undefined);
|
|
210
|
+
const close = (0, react_1.useCallback)(() => { setTesting(undefined); }, []);
|
|
211
|
+
const rows = snapshot?.endpoints ?? [];
|
|
212
|
+
const locked = snapshot?.lockedEndpointId;
|
|
213
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('endpoints.title'), lead: t('endpoints.lead'), flush: true, actions: ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: busy !== undefined || snapshot?.modelId === undefined, onClick: () => { act('probe', () => (0, shared_1.callPanel)('POST', 'cloud/probe')); }, children: t(busy === 'probe' ? 'endpoints.testing' : 'endpoints.retest') })), children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-between", style: { padding: '0 16px 12px' }, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "vdg-label", children: t('endpoints.choice') }), (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: (0, i18n_1.fill)(t('endpoints.times'), {
|
|
214
|
+
probed: snapshot?.probedAtMs === undefined ? t('endpoints.never') : (0, ui_1.when)(snapshot.probedAtMs),
|
|
215
|
+
next: snapshot?.nextProbeAtMs === undefined ? t('endpoints.never') : (0, ui_1.when)(snapshot.nextProbeAtMs),
|
|
216
|
+
}) })] }), (0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('endpoints.choice'), value: locked ?? '', disabled: disabled || busy !== undefined || rows.length === 0, onChange: (event) => {
|
|
217
|
+
const next = event.target.value === '' ? undefined : event.target.value;
|
|
218
|
+
act('lock', () => onLock(next));
|
|
219
|
+
}, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: t('endpoints.auto') }), locked !== undefined && !rows.some(row => row.endpointId === locked) ? (0, jsx_runtime_1.jsx)("option", { value: locked, children: (0, i18n_1.fill)(t('endpoints.always'), { id: locked }) }) : null, rows.map(row => (0, jsx_runtime_1.jsx)("option", { value: row.endpointId, children: (0, i18n_1.fill)(t('endpoints.always'), { id: row.endpointId }) }, row.endpointId))] })] }), snapshot?.error === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert bad", style: { margin: '0 16px 12px' }, children: snapshot.error }), error === undefined ? null : (0, jsx_runtime_1.jsx)("div", { style: { padding: '0 16px 12px' }, children: (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: error }) }), rows.length === 0
|
|
220
|
+
? (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", style: { padding: '0 16px 16px' }, children: t('endpoints.empty') })
|
|
221
|
+
: ((0, jsx_runtime_1.jsx)("div", { className: "vdg-table-wrap", children: (0, jsx_runtime_1.jsxs)("table", { className: "vdg-table", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: t('endpoints.col.endpoint') }), (0, jsx_runtime_1.jsx)("th", { children: t('endpoints.col.region') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('endpoints.col.network') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('endpoints.col.compute') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('endpoints.col.total') }), (0, jsx_runtime_1.jsx)("th", { children: t('endpoints.col.status') }), (0, jsx_runtime_1.jsx)("th", { children: t('endpoints.col.test') })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: rows.map((row) => {
|
|
222
|
+
const status = endpointStatus(row);
|
|
223
|
+
const inUse = locked === undefined ? snapshot?.selectedEndpointId === row.endpointId : locked === row.endpointId;
|
|
224
|
+
return ((0, jsx_runtime_1.jsxs)("tr", { "aria-selected": inUse, children: [(0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsxs)("span", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-mono", children: row.endpointId }), locked === row.endpointId ? (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "outline", children: t('endpoints.locked') }) : inUse ? (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "outline", children: t('endpoints.inUse') }) : null] }) }), (0, jsx_runtime_1.jsx)("td", { children: row.region ?? '—' }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, row.rttMs) }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, row.computeP50Ms) }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, row.scoreMs) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("span", { title: row.error, children: (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: status.tone, children: t(status.key) }) }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "vdg-btn ghost sm", onClick: () => { setTesting(row.endpointId); }, children: ["\u2197 ", t('endpoints.requestTest')] }) })] }, row.endpointId));
|
|
225
|
+
}) })] }) })), testing === undefined ? null : (0, jsx_runtime_1.jsx)(RequestTestDialog, { t: t, endpoint: testing, onClose: close })] }));
|
|
226
|
+
}
|
|
227
|
+
const PRESETS = ['ordinary', 'destructive', 'database_reset', 'oversized'];
|
|
228
|
+
function RequestTestDialog({ t, endpoint, onClose }) {
|
|
229
|
+
const [choice, setChoice] = (0, react_1.useState)('ordinary');
|
|
230
|
+
const [command, setCommand] = (0, react_1.useState)('');
|
|
231
|
+
const [result, setResult] = (0, react_1.useState)(undefined);
|
|
232
|
+
const [error, setError] = (0, react_1.useState)(undefined);
|
|
233
|
+
const [sending, setSending] = (0, react_1.useState)(false);
|
|
234
|
+
const send = () => {
|
|
235
|
+
setSending(true);
|
|
236
|
+
setError(undefined);
|
|
237
|
+
const input = choice === 'custom' ? { kind: 'custom', command } : { kind: 'preset', preset: choice };
|
|
238
|
+
(0, shared_1.callPanel)('POST', 'cloud/test', { endpoint, input }).then(setResult, (failure) => { setError((0, shared_1.messageOf)(failure)); }).finally(() => { setSending(false); });
|
|
239
|
+
};
|
|
240
|
+
(0, react_1.useEffect)(() => { setResult(undefined); }, [choice]);
|
|
241
|
+
const answered = result?.attempts.filter(attempt => attempt.answer !== undefined).at(-1)?.answer;
|
|
242
|
+
const scores = answered?.results[0]?.logits;
|
|
243
|
+
const failures = result?.attempts.filter(attempt => attempt.error !== undefined) ?? [];
|
|
244
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Dialog, { title: (0, i18n_1.fill)(t('test.title'), { id: endpoint }), onClose: onClose, footer: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn primary", disabled: sending || (choice === 'custom' && command.trim() === ''), onClick: send, children: t(sending ? 'test.sending' : 'test.send') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", onClick: onClose, children: t('test.close') })] })), children: [(0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('test.lead') }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: t('test.input') }), (0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('test.input'), value: choice, disabled: sending, onChange: (event) => { setChoice(event.target.value); }, children: [PRESETS.map(preset => (0, jsx_runtime_1.jsx)("option", { value: preset, children: t(`test.preset.${preset}`) }, preset)), (0, jsx_runtime_1.jsx)("option", { value: "custom", children: t('test.custom') })] })] }), choice === 'custom'
|
|
245
|
+
? (0, jsx_runtime_1.jsx)("textarea", { className: "vdg-textarea vdg-mono", rows: 2, value: command, placeholder: t('test.custom.placeholder'), onChange: (event) => { setCommand(event.target.value); } })
|
|
246
|
+
: null, (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: error }), result === undefined
|
|
247
|
+
? null
|
|
248
|
+
: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: (0, i18n_1.fill)(t('test.summary'), {
|
|
249
|
+
total: result.attempts.length, succeeded: result.succeeded,
|
|
250
|
+
min: (0, ui_1.ms)(t, result.minMs), median: (0, ui_1.ms)(t, result.medianMs), max: (0, ui_1.ms)(t, result.maxMs),
|
|
251
|
+
}) }), (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: (0, i18n_1.fill)(t('test.target'), { endpoint: result.endpoint, site: result.site, chars: result.renderedChars }) }), (0, jsx_runtime_1.jsx)("div", { className: "vdg-table-wrap", style: { border: '1px solid var(--vk-line)' }, children: (0, jsx_runtime_1.jsxs)("table", { className: "vdg-table", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "#" }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('test.col.total') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('test.col.network') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('test.col.endpoint') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('test.col.node') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('test.col.queue') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('test.col.compute') }), (0, jsx_runtime_1.jsx)("th", { children: t('test.col.result') })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: result.attempts.map((attempt, index) => {
|
|
252
|
+
const first = attempt.answer?.results[0];
|
|
253
|
+
const tone = first?.decision === 'allow' ? 'ok' : first?.decision === 'review' ? 'warn' : 'bad';
|
|
254
|
+
return ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: index + 1 }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, attempt.totalMs) }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, attempt.networkMs) }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, attempt.endpointMs) }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, attempt.nodeMs) }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, attempt.queueMs) }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, ui_1.ms)(t, attempt.computeMs) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsxs)("span", { className: "vdg-inline", style: { flexWrap: 'nowrap' }, children: [first === undefined ? null : (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: tone, children: t(`decision.${first.decision}`) }), (0, jsx_runtime_1.jsx)("span", { children: attempt.httpStatus === undefined ? t('test.unreached') : `HTTP ${attempt.httpStatus}` }), first?.input_tokens === undefined ? null : (0, jsx_runtime_1.jsx)("span", { children: (0, i18n_1.fill)(t('test.tokens'), { count: first.input_tokens }) }), first?.reason_code === undefined ? null : (0, jsx_runtime_1.jsx)("span", { children: (0, i18n_1.fill)(t('test.unscored'), { reason: first.reason_code }) }), attempt.errorCode === undefined ? null : (0, jsx_runtime_1.jsx)("code", { className: "vdg-mono", children: attempt.errorCode }), attempt.tokenRenewed ? (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "info", children: t('test.renewed') }) : null] }) })] }, attempt.requestId));
|
|
255
|
+
}) })] }) }), answered === undefined
|
|
256
|
+
? null
|
|
257
|
+
: ((0, jsx_runtime_1.jsxs)("dl", { className: "vdg-facts", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("dt", { children: t('test.facts.region') }), (0, jsx_runtime_1.jsx)("dd", { children: answered.region ?? '—' })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("dt", { children: t('test.facts.model') }), (0, jsx_runtime_1.jsx)("dd", { className: "vdg-mono", children: answered.model ?? result.modelId })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("dt", { children: t('test.facts.policy') }), (0, jsx_runtime_1.jsx)("dd", { className: "vdg-mono", children: answered.policyVersion })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("dt", { children: t('test.facts.scores') }), (0, jsx_runtime_1.jsx)("dd", { children: scores === undefined ? '—' : (0, i18n_1.fill)(t('test.scores'), { allow: scores[0].toFixed(3), review: scores[1].toFixed(3), block: scores[2].toFixed(3) }) })] })] })), (0, jsx_runtime_1.jsxs)("details", { className: "vdg-details", children: [(0, jsx_runtime_1.jsx)("summary", { children: t('test.details') }), (0, jsx_runtime_1.jsxs)("dl", { className: "vdg-facts", style: { marginTop: 8 }, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("dt", { children: t('test.checkpoint') }), (0, jsx_runtime_1.jsx)("dd", { className: "vdg-mono", children: answered?.checkpointSha256 ?? '—' })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("dt", { children: t('test.requestIds') }), (0, jsx_runtime_1.jsx)("dd", { className: "vdg-mono", children: result.attempts.map(attempt => attempt.requestId).join('\n') })] })] })] }), failures.length === 0
|
|
258
|
+
? null
|
|
259
|
+
: ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-alert bad", children: [(0, i18n_1.fill)(t('test.failures'), { count: failures.length }), failures.map(attempt => (0, jsx_runtime_1.jsx)("div", { children: attempt.error }, attempt.requestId))] }))] }))] }));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
},
|
|
263
|
+
"./dsh-client": function (module, exports, require) {
|
|
264
|
+
"use strict";
|
|
265
|
+
/**
|
|
266
|
+
* The parts of dsh's browser runtime this client half uses.
|
|
267
|
+
*
|
|
268
|
+
* dsh serves these modules from its page module table; their packages are not published at the
|
|
269
|
+
* Host's version, so the shapes are restated here from dsh 0.2.1-rc.2 source and must be checked
|
|
270
|
+
* again when the supported dsh range moves:
|
|
271
|
+
* - `packages/client/ui-plugin-manager/src/client/slot-contract.ts` (detail slots and subjects)
|
|
272
|
+
* - `packages/boot/plugin-manager/src/types.ts` (`BundleInfo`, `ChangeResult`)
|
|
273
|
+
* - `packages/client/ui-primitives/src/{Tag,Button}.tsx`, `settings-form/SettingsForm.tsx`
|
|
274
|
+
* - `packages/client/ui-settings/src/client/config-form-types.ts` (`ConfigForm`)
|
|
275
|
+
* - `packages/interaction/permission-presets/src/types.ts` (`PermissionCatalog`)
|
|
276
|
+
* - `packages/client/ui-approval/src/client/contract/slots.ts` (`PendingApproval`)
|
|
277
|
+
* - `packages/client/ui-conversation/src/client/contract/slots.ts` (composer chain, input slots)
|
|
278
|
+
* - `packages/client/ui-settings/src/client/contract/slots.ts` (`settings.section`)
|
|
279
|
+
* - `packages/client/ui-settings-general/src/client/{index,shell-store}.ts` (`sidebar.settings` store)
|
|
280
|
+
* - `packages/client/locale/src/client/index.ts` (`bind`)
|
|
281
|
+
* - `packages/client/ui-session/src/client/index.ts` (`sessionId`, `useProjection` props)
|
|
282
|
+
* - `packages/api/session-controller/src/client/contract/{sessions,session}.ts` (`binding`, `command`)
|
|
283
|
+
* - `packages/api/session-controller/src/types.ts` (`ModelCatalog`)
|
|
284
|
+
*/
|
|
285
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
286
|
+
|
|
287
|
+
},
|
|
288
|
+
"./i18n": function (module, exports, require) {
|
|
289
|
+
"use strict";
|
|
290
|
+
/** The plugin's interface text; dsh picks the dictionary that matches its interface language. */
|
|
291
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
292
|
+
exports.en = exports.zh = exports.NAMESPACE = void 0;
|
|
293
|
+
exports.fill = fill;
|
|
294
|
+
exports.NAMESPACE = 'vulcan-decision-guard';
|
|
295
|
+
exports.zh = {
|
|
296
|
+
'nav': '自动审批',
|
|
297
|
+
'conflict.tag': '冲突',
|
|
298
|
+
'conflict.title.guard': '与「自动授权审查」冲突',
|
|
299
|
+
'conflict.title.review': '与 Vulcan Decision 冲突',
|
|
300
|
+
'conflict.body.guard': '「自动授权审查」也已启用。两者都在每次工具调用前审查,功能重复;权限选择器中的「自动审查」只能由其中一个提供,另一个不会生效。请停用「自动授权审查」,由 Vulcan Decision 接管。',
|
|
301
|
+
'conflict.body.review': 'Vulcan Decision 已启用。两者都在每次工具调用前审查,功能重复;权限选择器中的「自动审查」只能由其中一个提供,另一个不会生效。请停用本插件,由 Vulcan Decision 接管。',
|
|
302
|
+
'conflict.disable': '停用自动授权审查',
|
|
303
|
+
'conflict.disabling': '正在停用…',
|
|
304
|
+
'conflict.restart': '已停用,重启 dsh 后生效。',
|
|
305
|
+
'conflict.failed': '停用失败:',
|
|
306
|
+
'page.title': '自动审批',
|
|
307
|
+
'page.lead': 'AI 每次调用工具前,先由 Vulcan Decision 判定模型判断:安全的直接放行,拿不准的交给复核 LLM 或由你确认,危险的拦截。任何错误都不会被当作放行。',
|
|
308
|
+
'page.sections': '自动审批设置分区',
|
|
309
|
+
'page.section.engine': '判定引擎',
|
|
310
|
+
'page.section.policy': '复核与规则',
|
|
311
|
+
'page.section.records': '判定记录',
|
|
312
|
+
'loading': '读取中…',
|
|
313
|
+
'saveFailed': '保存失败:{error}',
|
|
314
|
+
'readOnly': '当前配置为只读,无法修改。',
|
|
315
|
+
'unavailable': 'dsh 未提供本插件的设置。',
|
|
316
|
+
'panel.unavailable': '无法读取本机状态:dsh 没有向本页面提供插件接口。',
|
|
317
|
+
'status.title': '状态',
|
|
318
|
+
'status.decision': '判定',
|
|
319
|
+
'status.auto': '自动审查预设',
|
|
320
|
+
'status.auto.provided': '已提供,在权限选择器中选择「自动审查」即可启用',
|
|
321
|
+
'status.auto.held': '被「自动授权审查」占用',
|
|
322
|
+
'status.auto.pending': '正在注册',
|
|
323
|
+
'status.auto.standalone': '独立开关方式不使用该预设,所有会话都受判定',
|
|
324
|
+
'status.decision.cloud': 'OpenVulcan 云端 · {model}',
|
|
325
|
+
'status.decision.cloudNoModel': 'OpenVulcan 云端 · 没有可用的模型',
|
|
326
|
+
'status.decision.signedOut': 'OpenVulcan 云端 · 未登录,受管控的调用会询问你',
|
|
327
|
+
'status.decision.local': '本机 · {pack} 运行 {model}',
|
|
328
|
+
'status.decision.localMissing': '本机 · 尚未选择运行包与模型,受管控的调用会询问你',
|
|
329
|
+
'tone.ok': '正常',
|
|
330
|
+
'tone.warn': '注意',
|
|
331
|
+
'tone.off': '未使用',
|
|
332
|
+
'mode.title': '接入方式',
|
|
333
|
+
'mode.auto-preset': '自动审查预设',
|
|
334
|
+
'mode.auto-preset.hint': '在权限选择器中提供「自动审查」,只判定选择它的会话。该预设为完全权限,不使用文件沙箱。',
|
|
335
|
+
'mode.standalone': '独立开关',
|
|
336
|
+
'mode.standalone.hint': '判定所有会话,叠加在会话所选的权限预设之上,保留其文件沙箱。',
|
|
337
|
+
'backend.title': '判定模型',
|
|
338
|
+
'backend.cloud': 'OpenVulcan 云端',
|
|
339
|
+
'backend.cloud.hint': '默认方式,本机不安装任何运行库。工具调用的内容发送到 OpenVulcan 判定,需要登录 OpenVulcan 账号。',
|
|
340
|
+
'backend.local': '本机模型',
|
|
341
|
+
'backend.local.hint': '在本机运行 vulcan-decision-shell 模型,工具调用的内容不出本机。需要先安装运行包与模型。',
|
|
342
|
+
'account.title': 'OpenVulcan 账号',
|
|
343
|
+
'account.site.global': '国际站',
|
|
344
|
+
'account.site.cn': '中国站',
|
|
345
|
+
'account.signedIn': '已登录',
|
|
346
|
+
'account.expires': '凭据有效期至 {expires}',
|
|
347
|
+
'account.reauth': '凭据轮换中断,需要重新登录。',
|
|
348
|
+
'account.signedOut': '未登录。云端判定使用 OpenVulcan 账号,在账号设置中登录。',
|
|
349
|
+
'account.noPlugin': 'OpenVulcan 账号插件未运行,云端判定无法使用。请在插件页启用 VulcanCode 套件中的账号插件。',
|
|
350
|
+
'account.manage': '账号设置',
|
|
351
|
+
'model.title': '官方模型',
|
|
352
|
+
'model.lead': '可用的判定模型由 OpenVulcan 按账号提供。未选择时使用列表中的第一个。',
|
|
353
|
+
'model.loading': '正在读取模型列表…',
|
|
354
|
+
'model.none': 'OpenVulcan 当前没有为此账号提供判定模型。',
|
|
355
|
+
'model.failed': '无法读取模型列表:{error}',
|
|
356
|
+
'model.default': '默认({model})',
|
|
357
|
+
'model.disabled': '{model}(未启用)',
|
|
358
|
+
'model.disabledWarn': '已保存的模型 {model} 当前未启用,判定会失败。请改选列表中的模型。',
|
|
359
|
+
'model.signInFirst': '登录后列出可用的模型。',
|
|
360
|
+
'endpoints.title': '端点',
|
|
361
|
+
'endpoints.lead': '判定请求发往预计延迟(网络加计算)最低的端点。每 10 分钟在后台重新测试各端点,只有另一个端点明显更快时才切换。',
|
|
362
|
+
'endpoints.retest': '立即重新测试',
|
|
363
|
+
'endpoints.testing': '测试中…',
|
|
364
|
+
'endpoints.choice': '端点选择',
|
|
365
|
+
'endpoints.auto': '自动(延迟最低)',
|
|
366
|
+
'endpoints.always': '始终使用 {id}',
|
|
367
|
+
'endpoints.times': '上次测试 {probed} · 下次测试 {next}',
|
|
368
|
+
'endpoints.never': '尚未进行',
|
|
369
|
+
'endpoints.empty': '尚未取得端点。',
|
|
370
|
+
'endpoints.col.endpoint': '端点',
|
|
371
|
+
'endpoints.col.region': '地区',
|
|
372
|
+
'endpoints.col.network': '网络',
|
|
373
|
+
'endpoints.col.compute': '计算',
|
|
374
|
+
'endpoints.col.total': '合计',
|
|
375
|
+
'endpoints.col.status': '状态',
|
|
376
|
+
'endpoints.col.test': '请求测试',
|
|
377
|
+
'endpoints.locked': '已锁定',
|
|
378
|
+
'endpoints.inUse': '使用中',
|
|
379
|
+
'endpoints.requestTest': '请求测试',
|
|
380
|
+
'endpoints.status.error': '无法连接',
|
|
381
|
+
'endpoints.status.untested': '未测试',
|
|
382
|
+
'endpoints.status.site': '不服务此账号',
|
|
383
|
+
'endpoints.status.notReady': '未就绪',
|
|
384
|
+
'endpoints.status.ready': '可用',
|
|
385
|
+
'ms': '{value} 毫秒',
|
|
386
|
+
'test.title': '请求测试:{id}',
|
|
387
|
+
'test.lead': '按真实判定的方式向该端点依次发送 5 次判定请求,显示每个请求的各阶段耗时与回答。这些请求会计入你的官方直连用量。不会改变端点选择,结果只保留在此窗口中。',
|
|
388
|
+
'test.input': '发送的调用',
|
|
389
|
+
'test.preset.ordinary': '普通命令(git status)',
|
|
390
|
+
'test.preset.destructive': '危险命令(rm -rf ~)',
|
|
391
|
+
'test.preset.database_reset': '数据库重置(DROP SCHEMA)',
|
|
392
|
+
'test.preset.oversized': '超长输入(超过 token 上限)',
|
|
393
|
+
'test.custom': '自定义命令',
|
|
394
|
+
'test.custom.placeholder': '输入要判定的 shell 命令',
|
|
395
|
+
'test.send': '发送请求',
|
|
396
|
+
'test.sending': '发送中…',
|
|
397
|
+
'test.close': '关闭',
|
|
398
|
+
'test.summary': '{total} 个请求中 {succeeded} 个成功 · 最快 {min} · 中位 {median} · 最慢 {max}',
|
|
399
|
+
'test.target': '{endpoint} · 来源 {site} · 每个请求 {chars} 个字符。第一个请求可能包含建立连接的时间。',
|
|
400
|
+
'test.col.total': '合计',
|
|
401
|
+
'test.col.network': '网络',
|
|
402
|
+
'test.col.endpoint': '端点',
|
|
403
|
+
'test.col.node': '节点往返',
|
|
404
|
+
'test.col.queue': '排队',
|
|
405
|
+
'test.col.compute': '计算',
|
|
406
|
+
'test.col.result': '结果',
|
|
407
|
+
'test.unreached': '未到达',
|
|
408
|
+
'test.tokens': '{count} 个 token',
|
|
409
|
+
'test.unscored': '未评分:{reason}',
|
|
410
|
+
'test.renewed': '已续期令牌',
|
|
411
|
+
'test.facts.region': '区域',
|
|
412
|
+
'test.facts.model': '模型',
|
|
413
|
+
'test.facts.policy': '策略',
|
|
414
|
+
'test.facts.scores': '分数',
|
|
415
|
+
'test.scores': '放行 {allow} · 需确认 {review} · 拦截 {block}',
|
|
416
|
+
'test.details': '技术详情',
|
|
417
|
+
'test.checkpoint': '检查点',
|
|
418
|
+
'test.requestIds': '请求 ID',
|
|
419
|
+
'test.failures': '{count} 个请求失败。',
|
|
420
|
+
'decision.allow': '放行',
|
|
421
|
+
'decision.review': '需确认',
|
|
422
|
+
'decision.block': '拦下',
|
|
423
|
+
'hw.title': '本机硬件与建议',
|
|
424
|
+
'hw.gpu': '{name} · {arch} · 计算能力 {cc} · 驱动 {driver}',
|
|
425
|
+
'hw.fp16.tensor-cores': 'FP16 由张量核心加速',
|
|
426
|
+
'hw.fp16.native': 'FP16 原生支持,较慢',
|
|
427
|
+
'hw.fp16.slow': 'FP16 很慢,只适合 FP32',
|
|
428
|
+
'hw.noGpu': '未发现可用的 NVIDIA 显卡。',
|
|
429
|
+
'hw.npu': '发现 NPU:{name}。NPU 运行包等待对应的 ONNX 模型发布,目前还不能安装。',
|
|
430
|
+
'hw.rec.cuda': '建议本机运行:PyTorch 模型安装 {torch},ONNX 模型安装 {onnx}。',
|
|
431
|
+
'hw.rec.cpu_only': '建议使用云端。也可以安装 {torch} 在 CPU 上运行,判定较慢。',
|
|
432
|
+
'hw.rec.driver_update': '显卡支持 CUDA,但驱动低于运行包所需的版本。请自行从 NVIDIA 更新驱动(本插件不安装驱动),在此之前建议使用云端。',
|
|
433
|
+
'hw.rec.gpu_too_old': '显卡计算能力低于 7.5(RTX 20 系列之前),现成的 CUDA 运行库不支持它,建议使用云端。',
|
|
434
|
+
'packs.title': '运行包',
|
|
435
|
+
'packs.lead': '运行包是预编译的 Python 运行时与官方 wheel,逐个按锁定的 SHA-256 校验后安装到 {home}。不编译任何东西,也不安装驱动。',
|
|
436
|
+
'packs.source': '下载源',
|
|
437
|
+
'packs.source.official': '官方(python-build-standalone、PyPI、PyTorch)',
|
|
438
|
+
'packs.source.cn': '国内镜像(npmmirror、阿里云)',
|
|
439
|
+
'packs.col.pack': '运行包',
|
|
440
|
+
'packs.col.size': '下载量',
|
|
441
|
+
'packs.col.state': '状态',
|
|
442
|
+
'packs.install': '安装',
|
|
443
|
+
'packs.update': '更新',
|
|
444
|
+
'packs.remove': '删除',
|
|
445
|
+
'packs.installed': '已安装',
|
|
446
|
+
'packs.outdated': '有更新',
|
|
447
|
+
'packs.notInstalled': '未安装',
|
|
448
|
+
'packs.fit.ok': '可用',
|
|
449
|
+
'packs.fit.no_build': '本平台无构建',
|
|
450
|
+
'packs.fit.no_nvidia_gpu': '需要 NVIDIA 显卡',
|
|
451
|
+
'packs.fit.gpu_too_old': '显卡过旧',
|
|
452
|
+
'packs.fit.driver_too_old': '驱动需 ≥ {driver}',
|
|
453
|
+
'packs.planned': '规划中:{id}({accelerator})',
|
|
454
|
+
'job.download': '下载中 {percent}%',
|
|
455
|
+
'job.unpack': '解压中…',
|
|
456
|
+
'job.install': '安装中…',
|
|
457
|
+
'job.probe': '检查运行时…',
|
|
458
|
+
'job.verify': '校验中 {percent}%',
|
|
459
|
+
'job.done': '已完成',
|
|
460
|
+
'job.failed': '失败:{error}',
|
|
461
|
+
'job.cancelled': '已取消',
|
|
462
|
+
'job.cancel': '取消',
|
|
463
|
+
'models.title': '本机模型',
|
|
464
|
+
'models.none': '尚未安装模型。',
|
|
465
|
+
'models.remove': '删除',
|
|
466
|
+
'models.download': '下载',
|
|
467
|
+
'models.notPublished': '下载地址尚未发布',
|
|
468
|
+
'models.source': '模型来源',
|
|
469
|
+
'models.source.huggingface': 'Hugging Face',
|
|
470
|
+
'models.source.modelscope-intl': '魔搭国际站',
|
|
471
|
+
'models.source.modelscope-cn': '魔搭国内站',
|
|
472
|
+
'models.dir': '从本地目录安装',
|
|
473
|
+
'models.dir.placeholder': '模型包目录:PyTorch 发布仓库或 ONNX 交付目录',
|
|
474
|
+
'models.dir.install': '安装',
|
|
475
|
+
'local.title': '本机判定使用',
|
|
476
|
+
'local.pack': '运行包',
|
|
477
|
+
'local.model': '模型',
|
|
478
|
+
'local.pick': '请选择',
|
|
479
|
+
'local.mismatch': '所选模型需要 {backend} 运行包。',
|
|
480
|
+
'options.title': '授权选项',
|
|
481
|
+
'options.lead': '判定模型按这三项设置判断,是模型训练时使用的固定选项。',
|
|
482
|
+
'cross.title': '项目外的普通写入',
|
|
483
|
+
'cross.allow': '允许',
|
|
484
|
+
'cross.review': '先确认',
|
|
485
|
+
'database.title': '数据库重置',
|
|
486
|
+
'database.confirm': '先确认',
|
|
487
|
+
'database.allow': '允许',
|
|
488
|
+
'project.title': '重置当前项目',
|
|
489
|
+
'project.confirm': '先确认',
|
|
490
|
+
'project.allow': '允许',
|
|
491
|
+
'review.title': '复核 LLM',
|
|
492
|
+
'review.lead': '判定模型未放行的调用先交复核 LLM,按下方规则判断。判定模型放行的调用直接执行,不经过 LLM。',
|
|
493
|
+
'review.session': '会话模型',
|
|
494
|
+
'review.custom': '指定模型',
|
|
495
|
+
'review.off': '关闭',
|
|
496
|
+
'review.session.hint': '由会话正在使用的模型复核,会消耗该模型的用量。',
|
|
497
|
+
'review.custom.hint': '由下方选择的模型复核,与会话所用模型无关。',
|
|
498
|
+
'review.off.hint': '不做复核:有人在场时询问你,无人值守时一律拒绝。',
|
|
499
|
+
'review.provider': '提供方',
|
|
500
|
+
'review.model': '模型',
|
|
501
|
+
'review.pick': '请选择',
|
|
502
|
+
'review.catalogFailed': '无法读取模型列表。',
|
|
503
|
+
'review.missing': '尚未选择复核模型,复核会失败并按失败处理。',
|
|
504
|
+
'override.title': '允许 LLM 推翻拦截',
|
|
505
|
+
'override.hint': '判定模型拦截的调用也交复核 LLM,复核放行即执行。关闭时被拦截的调用有人时询问你,无人值守时拒绝。',
|
|
506
|
+
'rules.title': '复核规则',
|
|
507
|
+
'rules.lead': '用自然语言描述,原样交给复核 LLM。优先级:绝对禁止 > 禁止 > 允许 > LLM 自行判断。',
|
|
508
|
+
'rules.forbid': '绝对禁止',
|
|
509
|
+
'rules.forbid.placeholder': '例如:删除或改写生产数据库。直接拒绝,不询问你。',
|
|
510
|
+
'rules.deny': '禁止',
|
|
511
|
+
'rules.deny.placeholder': '例如:推送到远程仓库。有人时询问你并默认选中拒绝。',
|
|
512
|
+
'rules.allow': '允许',
|
|
513
|
+
'rules.allow.placeholder': '例如:运行项目内的测试与构建命令。',
|
|
514
|
+
'rules.save': '保存规则',
|
|
515
|
+
'rules.discard': '放弃修改',
|
|
516
|
+
'ask.title': '询问',
|
|
517
|
+
'ask.timeout': '倒计时自动拒绝',
|
|
518
|
+
'ask.timeout.hint': '倒计时结束仍未回答就拒绝此次调用,避免人不在时会话一直等待。关闭时一直等你回答。',
|
|
519
|
+
'ask.seconds': '秒',
|
|
520
|
+
'try.title': '试判定',
|
|
521
|
+
'try.lead': '用当前设置判定一个示例调用。',
|
|
522
|
+
'try.safe': '查看仓库状态(git status)',
|
|
523
|
+
'try.risky': '强制推送到远程(git push --force)',
|
|
524
|
+
'try.run': '判定',
|
|
525
|
+
'try.running': '判定中…',
|
|
526
|
+
'try.result': '{decision} · {ms} 毫秒 · {backend} · {via}',
|
|
527
|
+
'try.failed': '判定失败:{error}',
|
|
528
|
+
'quick.label': '自动审批',
|
|
529
|
+
'quick.off': '未启用',
|
|
530
|
+
'quick.unattended': '无人值守',
|
|
531
|
+
'quick.title': '自动审批',
|
|
532
|
+
'quick.notGoverned': '此会话未选择「自动审查」权限预设,调用不经判定。',
|
|
533
|
+
'quick.governed': '此会话的工具调用由 Vulcan Decision 判定。',
|
|
534
|
+
'quick.backend': '判定模型',
|
|
535
|
+
'quick.endpoint': '端点',
|
|
536
|
+
'quick.review': '复核 LLM',
|
|
537
|
+
'quick.override': '允许 LLM 推翻拦截',
|
|
538
|
+
'quick.timeout': '询问倒计时',
|
|
539
|
+
'quick.timeout.off': '不限时',
|
|
540
|
+
'quick.unattendedHint': '不再询问;原本要你批准的调用由复核 LLM 决定,未通过即拒绝',
|
|
541
|
+
'quick.more': '全部设置',
|
|
542
|
+
'quick.signIn': '未登录 OpenVulcan,去登录',
|
|
543
|
+
'prompt.strip': 'Vulcan Decision 等待你确认',
|
|
544
|
+
'prompt.stage.review': '需确认',
|
|
545
|
+
'prompt.stage.block': '已拦截',
|
|
546
|
+
'prompt.stage.unjudged': '无法判定',
|
|
547
|
+
'prompt.review.review': 'LLM:交由你决定',
|
|
548
|
+
'prompt.review.deny': 'LLM:未放行',
|
|
549
|
+
'prompt.review.error': 'LLM 复核失败',
|
|
550
|
+
'prompt.reason': 'LLM 理由',
|
|
551
|
+
'prompt.intent': 'AI 说明的目的',
|
|
552
|
+
'prompt.call': '调用 {tool}',
|
|
553
|
+
'prompt.countdown': '{seconds} 秒后自动拒绝',
|
|
554
|
+
'prompt.expired': '正在拒绝…',
|
|
555
|
+
'prompt.allow': '允许一次',
|
|
556
|
+
'prompt.reject': '拒绝',
|
|
557
|
+
'unattended.failed': '无法保存无人值守模式:{error}',
|
|
558
|
+
'quick.unattendedOn': '无人值守:已开启',
|
|
559
|
+
'quick.unattendedOff': '无人值守:关闭',
|
|
560
|
+
'quick.draftHint': '发送第一条消息后即可设置。',
|
|
561
|
+
'quick.categories': '类别授权',
|
|
562
|
+
'quick.categoriesHint': '勾选的类别可由判定模型直接放行,作用于所有会话',
|
|
563
|
+
'quick.category.crossProjectWrites': '项目外的写入',
|
|
564
|
+
'quick.category.databaseReset': '删除或清空数据库',
|
|
565
|
+
'quick.category.projectReset': '重置整个项目',
|
|
566
|
+
'quick.reviewPrompt': '复核附加提示词',
|
|
567
|
+
'quick.reviewPrompt.hint': '你在场时复核 LLM 额外遵循的要求',
|
|
568
|
+
'quick.reviewOff': '复核 LLM 已关闭,提示词不会被读取。',
|
|
569
|
+
'dialog.cancel': '取消',
|
|
570
|
+
'dialog.counter': '{count} / {max}',
|
|
571
|
+
'reviewPrompt.title': '常规复核附加提示词',
|
|
572
|
+
'reviewPrompt.body': '你在场时,复核 LLM 在复核规则与类别授权之后读取这段文本,再决定放行还是询问你。它可以让复核更严格、或说明你在意的事项,但不能放宽规则或类别授权。无人值守会话改用其拒绝策略。',
|
|
573
|
+
'reviewPrompt.label': '提示词',
|
|
574
|
+
'reviewPrompt.placeholder': '例如:凡涉及发布分支的操作都先问我;运行测试命令可以放行',
|
|
575
|
+
'reviewPrompt.sourceSystem': '跟随系统默认值',
|
|
576
|
+
'reviewPrompt.sourceSession': '本会话自定义',
|
|
577
|
+
'reviewPrompt.followSystem': '跟随系统默认值',
|
|
578
|
+
'reviewPrompt.save': '保存为本会话提示词',
|
|
579
|
+
'reviewPrompt.saveFailed': '无法保存复核提示词:{error}',
|
|
580
|
+
'unattended.enableTitle': '开启无人值守模式',
|
|
581
|
+
'unattended.editTitle': '无人值守模式',
|
|
582
|
+
'unattended.body': '无人值守只替代你的批准:原本要询问你的调用改由复核 LLM 依拒绝策略决定,未通过即拒绝;复核 LLM 关闭时一律拒绝。判定模型已放行的调用照常执行,不经过拒绝策略;要收紧某类操作,请取消勾选对应的类别授权。',
|
|
583
|
+
'unattended.policyLabel': '拒绝策略',
|
|
584
|
+
'unattended.placeholder': '例如:需要批准的操作中,凡推送到远程、删除或重置数据库的一律拒绝',
|
|
585
|
+
'unattended.sourceSystem': '系统默认值',
|
|
586
|
+
'unattended.enable': '开启',
|
|
587
|
+
'unattended.update': '更新策略',
|
|
588
|
+
'unattended.disable': '关闭',
|
|
589
|
+
'prompts.title': '附加提示词',
|
|
590
|
+
'prompts.lead': '复核 LLM 在复核规则之后读取,且不能放宽规则或类别授权。会话可以在输入框旁的自动审批卡片中改用自己的文本。',
|
|
591
|
+
'prompts.review': '默认复核附加提示词',
|
|
592
|
+
'prompts.review.hint': '你在场时使用。所有未自定义提示词的会话都使用它;下一次复核即读取已保存的文本。',
|
|
593
|
+
'prompts.unattended': '系统默认拒绝策略',
|
|
594
|
+
'prompts.save': '保存提示词',
|
|
595
|
+
'prompts.unattended.hint': '会话开启无人值守时预填的拒绝策略,也用于以「从不询问」批准策略运行的会话。修改不影响已开启无人值守的会话。',
|
|
596
|
+
'records.title': '判定记录',
|
|
597
|
+
'records.enabled': '记录判定',
|
|
598
|
+
'records.enabledHint': '保存判定模型或复核参与的每次决定,含原始命令与路径,只保存在这台电脑上。关闭后不再写入新记录,已有记录保留到你清除为止。',
|
|
599
|
+
'records.entryHint': '查看判定模型的决定,标记判错的结果并导出为训练样本。',
|
|
600
|
+
'records.paused': '记录判定已关闭,不会写入新记录。',
|
|
601
|
+
'records.hint': '保留最近 {records} 条、最长 {days} 天的记录。记录包含原始命令与路径,只保存在这台电脑上。',
|
|
602
|
+
'records.count': '{count} 条记录',
|
|
603
|
+
'records.filterResult': '判定结果',
|
|
604
|
+
'records.all': '全部',
|
|
605
|
+
'records.label.allow': '放行',
|
|
606
|
+
'records.label.review': '需确认',
|
|
607
|
+
'records.label.block': '拦下',
|
|
608
|
+
'records.label.failed': '失败',
|
|
609
|
+
'records.allWorkspaces': '全部工作区',
|
|
610
|
+
'records.markedOnly': '只看已标记',
|
|
611
|
+
'records.empty': '还没有记录。',
|
|
612
|
+
'records.loadMore': '加载更多',
|
|
613
|
+
'records.loadFailed': '无法读取判定记录:{error}',
|
|
614
|
+
'records.export': '导出已标记({count})',
|
|
615
|
+
'records.exported': '已导出 {count} 条样本',
|
|
616
|
+
'records.exportFailed': '导出失败:{error}',
|
|
617
|
+
'records.clear': '清除',
|
|
618
|
+
'records.clearTitle': '清除判定记录?',
|
|
619
|
+
'records.clearDescription': '全部 {count} 条记录及其标记都会被删除,且无法恢复。',
|
|
620
|
+
'records.clearFailed': '无法清除判定记录:{error}',
|
|
621
|
+
'records.markAs': '应为:',
|
|
622
|
+
'records.marked': '已标记:{label}',
|
|
623
|
+
'records.markFailed': '无法保存标记:{error}',
|
|
624
|
+
'records.noTrace': '这条记录没有判定模型的输入,无法标记。',
|
|
625
|
+
'records.details': '详细信息',
|
|
626
|
+
'records.detail.tool': '工具',
|
|
627
|
+
'records.detail.workspace': '工作区',
|
|
628
|
+
'records.detail.model': '模型',
|
|
629
|
+
'records.detail.scores': '分数',
|
|
630
|
+
'records.detail.rule': '规则',
|
|
631
|
+
'records.detail.judgeTime': '判定耗时',
|
|
632
|
+
'records.detail.error': '错误',
|
|
633
|
+
'records.detail.review': '复核',
|
|
634
|
+
'records.detail.reviewTime': '复核耗时',
|
|
635
|
+
'records.detail.attendance': '会话',
|
|
636
|
+
'records.attended': '有人在场',
|
|
637
|
+
'records.unattended': '无人值守',
|
|
638
|
+
'records.verdict.allow': '放行',
|
|
639
|
+
'records.verdict.review': '交给你决定',
|
|
640
|
+
'records.verdict.deny': '拒绝',
|
|
641
|
+
'records.verdict.forbid': '禁止',
|
|
642
|
+
'records.outcome.judge_allowed': '判定放行',
|
|
643
|
+
'records.outcome.review_allowed': '复核放行',
|
|
644
|
+
'records.outcome.review_denied': '复核拒绝',
|
|
645
|
+
'records.outcome.unattended_denied': '已拒绝(无人值守)',
|
|
646
|
+
'records.outcome.user_allowed': '你已放行',
|
|
647
|
+
'records.outcome.user_denied': '你已拒绝',
|
|
648
|
+
'records.outcome.approval_expired': '已拒绝(未及时回答)',
|
|
649
|
+
'records.outcome.not_asked': '已拒绝(无法询问你)',
|
|
650
|
+
'records.outcome.host_denied': '已拒绝(其他插件)',
|
|
651
|
+
};
|
|
652
|
+
exports.en = {
|
|
653
|
+
'nav': 'Auto approval',
|
|
654
|
+
'conflict.tag': 'Conflict',
|
|
655
|
+
'conflict.title.guard': 'Conflicts with Auto review',
|
|
656
|
+
'conflict.title.review': 'Conflicts with Vulcan Decision',
|
|
657
|
+
'conflict.body.guard': 'Auto review is also enabled. Both review every tool call before it runs, so they duplicate each other, and only one of them can provide Auto in the permission picker; the other has no effect. Disable Auto review to let Vulcan Decision take over.',
|
|
658
|
+
'conflict.body.review': 'Vulcan Decision is enabled. Both review every tool call before it runs, so they duplicate each other, and only one of them can provide Auto in the permission picker; the other has no effect. Disable this plugin to let Vulcan Decision take over.',
|
|
659
|
+
'conflict.disable': 'Disable Auto review',
|
|
660
|
+
'conflict.disabling': 'Disabling…',
|
|
661
|
+
'conflict.restart': 'Disabled; takes effect after dsh restarts.',
|
|
662
|
+
'conflict.failed': 'Could not disable: ',
|
|
663
|
+
'page.title': 'Auto approval',
|
|
664
|
+
'page.lead': 'Before the AI runs a tool, the Vulcan Decision model judges the call: safe calls run, uncertain ones go to the review LLM or to you, dangerous ones are blocked. No error ever counts as an allow.',
|
|
665
|
+
'page.sections': 'Auto approval settings sections',
|
|
666
|
+
'page.section.engine': 'Decision engine',
|
|
667
|
+
'page.section.policy': 'Review and rules',
|
|
668
|
+
'page.section.records': 'Decision records',
|
|
669
|
+
'loading': 'Loading…',
|
|
670
|
+
'saveFailed': 'Could not save: {error}',
|
|
671
|
+
'readOnly': 'The configuration is read-only.',
|
|
672
|
+
'unavailable': 'dsh offers no settings for this plugin.',
|
|
673
|
+
'panel.unavailable': 'Cannot read this machine\'s state: dsh offers this page no plugin routes.',
|
|
674
|
+
'status.title': 'Status',
|
|
675
|
+
'status.decision': 'Decisions',
|
|
676
|
+
'status.auto': 'Auto preset',
|
|
677
|
+
'status.auto.provided': 'Provided; pick Auto in the permission picker to use it',
|
|
678
|
+
'status.auto.held': 'Held by Auto review',
|
|
679
|
+
'status.auto.pending': 'Registering',
|
|
680
|
+
'status.auto.standalone': 'The standalone switch does not use the preset; every session is judged',
|
|
681
|
+
'status.decision.cloud': 'OpenVulcan cloud · {model}',
|
|
682
|
+
'status.decision.cloudNoModel': 'OpenVulcan cloud · no model available',
|
|
683
|
+
'status.decision.signedOut': 'OpenVulcan cloud · not signed in; governed calls ask you',
|
|
684
|
+
'status.decision.local': 'This machine · {pack} runs {model}',
|
|
685
|
+
'status.decision.localMissing': 'This machine · no runtime pack and model chosen; governed calls ask you',
|
|
686
|
+
'tone.ok': 'OK',
|
|
687
|
+
'tone.warn': 'Attention',
|
|
688
|
+
'tone.off': 'Not used',
|
|
689
|
+
'mode.title': 'Integration',
|
|
690
|
+
'mode.auto-preset': 'Auto preset',
|
|
691
|
+
'mode.auto-preset.hint': 'Offers Auto in the permission picker and judges only the sessions that choose it. The preset has full permissions and no file sandbox.',
|
|
692
|
+
'mode.standalone': 'Standalone switch',
|
|
693
|
+
'mode.standalone.hint': 'Judges every session on top of its chosen permission preset, keeping its file sandbox.',
|
|
694
|
+
'backend.title': 'Decision model',
|
|
695
|
+
'backend.cloud': 'OpenVulcan cloud',
|
|
696
|
+
'backend.cloud.hint': 'The default; nothing is installed here. Tool calls are sent to OpenVulcan to be judged, which needs an OpenVulcan sign-in.',
|
|
697
|
+
'backend.local': 'This machine',
|
|
698
|
+
'backend.local.hint': 'Runs the vulcan-decision-shell model here; tool calls never leave this machine. Install a runtime pack and a model first.',
|
|
699
|
+
'account.title': 'OpenVulcan account',
|
|
700
|
+
'account.site.global': 'Global',
|
|
701
|
+
'account.site.cn': 'China',
|
|
702
|
+
'account.signedIn': 'Signed in',
|
|
703
|
+
'account.expires': 'Credential valid until {expires}',
|
|
704
|
+
'account.reauth': 'A credential rotation was interrupted; sign in again.',
|
|
705
|
+
'account.signedOut': 'Not signed in. Cloud judging uses your OpenVulcan account; sign in under account settings.',
|
|
706
|
+
'account.noPlugin': 'The OpenVulcan account plugin is not running, so cloud judging cannot work. Enable it in the VulcanCode suite on the Plugins page.',
|
|
707
|
+
'account.manage': 'Account settings',
|
|
708
|
+
'model.title': 'Official model',
|
|
709
|
+
'model.lead': 'OpenVulcan offers decision models per account. Without a choice, the first listed model is used.',
|
|
710
|
+
'model.loading': 'Reading the model list…',
|
|
711
|
+
'model.none': 'OpenVulcan currently offers this account no decision model.',
|
|
712
|
+
'model.failed': 'Cannot read the model list: {error}',
|
|
713
|
+
'model.default': 'Default ({model})',
|
|
714
|
+
'model.disabled': '{model} (not enabled)',
|
|
715
|
+
'model.disabledWarn': 'The saved model {model} is not enabled now, so decisions fail. Choose a listed model.',
|
|
716
|
+
'model.signInFirst': 'Sign in to list the available models.',
|
|
717
|
+
'endpoints.title': 'Endpoints',
|
|
718
|
+
'endpoints.lead': 'Decisions go to the endpoint with the lowest expected latency (network plus compute). Endpoints are re-tested in the background every 10 minutes, and another one is chosen only when it is clearly faster.',
|
|
719
|
+
'endpoints.retest': 'Test again now',
|
|
720
|
+
'endpoints.testing': 'Testing…',
|
|
721
|
+
'endpoints.choice': 'Endpoint choice',
|
|
722
|
+
'endpoints.auto': 'Automatic (lowest latency)',
|
|
723
|
+
'endpoints.always': 'Always use {id}',
|
|
724
|
+
'endpoints.times': 'Last test {probed} · next test {next}',
|
|
725
|
+
'endpoints.never': 'not yet',
|
|
726
|
+
'endpoints.empty': 'No endpoints yet.',
|
|
727
|
+
'endpoints.col.endpoint': 'Endpoint',
|
|
728
|
+
'endpoints.col.region': 'Region',
|
|
729
|
+
'endpoints.col.network': 'Network',
|
|
730
|
+
'endpoints.col.compute': 'Compute',
|
|
731
|
+
'endpoints.col.total': 'Total',
|
|
732
|
+
'endpoints.col.status': 'Status',
|
|
733
|
+
'endpoints.col.test': 'Request test',
|
|
734
|
+
'endpoints.locked': 'Locked',
|
|
735
|
+
'endpoints.inUse': 'In use',
|
|
736
|
+
'endpoints.requestTest': 'Request test',
|
|
737
|
+
'endpoints.status.error': 'Unreachable',
|
|
738
|
+
'endpoints.status.untested': 'Untested',
|
|
739
|
+
'endpoints.status.site': 'Not for this account',
|
|
740
|
+
'endpoints.status.notReady': 'Not ready',
|
|
741
|
+
'endpoints.status.ready': 'Available',
|
|
742
|
+
'ms': '{value} ms',
|
|
743
|
+
'test.title': 'Request test: {id}',
|
|
744
|
+
'test.lead': 'Sends 5 real decision requests to this endpoint one after another and shows each request\'s timings and answer. They count toward your official direct usage. The endpoint choice does not change, and results stay in this window.',
|
|
745
|
+
'test.input': 'Call to send',
|
|
746
|
+
'test.preset.ordinary': 'Ordinary command (git status)',
|
|
747
|
+
'test.preset.destructive': 'Destructive command (rm -rf ~)',
|
|
748
|
+
'test.preset.database_reset': 'Database reset (DROP SCHEMA)',
|
|
749
|
+
'test.preset.oversized': 'Oversized input (over the token limit)',
|
|
750
|
+
'test.custom': 'Custom command',
|
|
751
|
+
'test.custom.placeholder': 'A shell command to judge',
|
|
752
|
+
'test.send': 'Send requests',
|
|
753
|
+
'test.sending': 'Sending…',
|
|
754
|
+
'test.close': 'Close',
|
|
755
|
+
'test.summary': '{succeeded} of {total} requests succeeded · fastest {min} · median {median} · slowest {max}',
|
|
756
|
+
'test.target': '{endpoint} · source {site} · {chars} characters per request. The first request may include connection setup.',
|
|
757
|
+
'test.col.total': 'Total',
|
|
758
|
+
'test.col.network': 'Network',
|
|
759
|
+
'test.col.endpoint': 'Endpoint',
|
|
760
|
+
'test.col.node': 'Node trip',
|
|
761
|
+
'test.col.queue': 'Queue',
|
|
762
|
+
'test.col.compute': 'Compute',
|
|
763
|
+
'test.col.result': 'Result',
|
|
764
|
+
'test.unreached': 'Not reached',
|
|
765
|
+
'test.tokens': '{count} tokens',
|
|
766
|
+
'test.unscored': 'Not scored: {reason}',
|
|
767
|
+
'test.renewed': 'Token renewed',
|
|
768
|
+
'test.facts.region': 'Region',
|
|
769
|
+
'test.facts.model': 'Model',
|
|
770
|
+
'test.facts.policy': 'Policy',
|
|
771
|
+
'test.facts.scores': 'Scores',
|
|
772
|
+
'test.scores': 'allow {allow} · review {review} · block {block}',
|
|
773
|
+
'test.details': 'Technical details',
|
|
774
|
+
'test.checkpoint': 'Checkpoint',
|
|
775
|
+
'test.requestIds': 'Request IDs',
|
|
776
|
+
'test.failures': '{count} requests failed.',
|
|
777
|
+
'decision.allow': 'Allow',
|
|
778
|
+
'decision.review': 'Review',
|
|
779
|
+
'decision.block': 'Block',
|
|
780
|
+
'hw.title': 'Hardware and recommendation',
|
|
781
|
+
'hw.gpu': '{name} · {arch} · compute capability {cc} · driver {driver}',
|
|
782
|
+
'hw.fp16.tensor-cores': 'FP16 on tensor cores',
|
|
783
|
+
'hw.fp16.native': 'native FP16, slower',
|
|
784
|
+
'hw.fp16.slow': 'slow FP16, FP32 only',
|
|
785
|
+
'hw.noGpu': 'No usable NVIDIA graphics card was found.',
|
|
786
|
+
'hw.npu': 'NPU found: {name}. Its runtime pack waits for an NPU model release and cannot be installed yet.',
|
|
787
|
+
'hw.rec.cuda': 'Recommended here: {torch} for PyTorch models, {onnx} for ONNX models.',
|
|
788
|
+
'hw.rec.cpu_only': 'The cloud is recommended. {torch} also runs on the CPU, more slowly.',
|
|
789
|
+
'hw.rec.driver_update': 'The card supports CUDA, but its driver is older than the packs need. Update the driver from NVIDIA yourself (this plugin installs no drivers); until then the cloud is recommended.',
|
|
790
|
+
'hw.rec.gpu_too_old': 'The card\'s compute capability is below 7.5 (before RTX 20), which the prebuilt CUDA runtimes do not support; the cloud is recommended.',
|
|
791
|
+
'packs.title': 'Runtime packs',
|
|
792
|
+
'packs.lead': 'A runtime pack is a prebuilt Python runtime with official wheels, each checked against its locked SHA-256 and installed to {home}. Nothing is compiled and no driver is installed.',
|
|
793
|
+
'packs.source': 'Download source',
|
|
794
|
+
'packs.source.official': 'Official (python-build-standalone, PyPI, PyTorch)',
|
|
795
|
+
'packs.source.cn': 'Mirrors in China (npmmirror, Aliyun)',
|
|
796
|
+
'packs.col.pack': 'Pack',
|
|
797
|
+
'packs.col.size': 'Download',
|
|
798
|
+
'packs.col.state': 'State',
|
|
799
|
+
'packs.install': 'Install',
|
|
800
|
+
'packs.update': 'Update',
|
|
801
|
+
'packs.remove': 'Remove',
|
|
802
|
+
'packs.installed': 'Installed',
|
|
803
|
+
'packs.outdated': 'Update available',
|
|
804
|
+
'packs.notInstalled': 'Not installed',
|
|
805
|
+
'packs.fit.ok': 'Usable',
|
|
806
|
+
'packs.fit.no_build': 'No build for this platform',
|
|
807
|
+
'packs.fit.no_nvidia_gpu': 'Needs an NVIDIA card',
|
|
808
|
+
'packs.fit.gpu_too_old': 'Card too old',
|
|
809
|
+
'packs.fit.driver_too_old': 'Driver ≥ {driver} needed',
|
|
810
|
+
'packs.planned': 'Planned: {id} ({accelerator})',
|
|
811
|
+
'job.download': 'Downloading {percent}%',
|
|
812
|
+
'job.unpack': 'Unpacking…',
|
|
813
|
+
'job.install': 'Installing…',
|
|
814
|
+
'job.probe': 'Checking the runtime…',
|
|
815
|
+
'job.verify': 'Verifying {percent}%',
|
|
816
|
+
'job.done': 'Done',
|
|
817
|
+
'job.failed': 'Failed: {error}',
|
|
818
|
+
'job.cancelled': 'Cancelled',
|
|
819
|
+
'job.cancel': 'Cancel',
|
|
820
|
+
'models.title': 'Local models',
|
|
821
|
+
'models.none': 'No model is installed.',
|
|
822
|
+
'models.remove': 'Remove',
|
|
823
|
+
'models.download': 'Download',
|
|
824
|
+
'models.notPublished': 'Not published yet',
|
|
825
|
+
'models.source': 'Model source',
|
|
826
|
+
'models.source.huggingface': 'Hugging Face',
|
|
827
|
+
'models.source.modelscope-intl': 'ModelScope (international)',
|
|
828
|
+
'models.source.modelscope-cn': 'ModelScope (China)',
|
|
829
|
+
'models.dir': 'Install from a local directory',
|
|
830
|
+
'models.dir.placeholder': 'Model package directory: a PyTorch release or an ONNX delivery',
|
|
831
|
+
'models.dir.install': 'Install',
|
|
832
|
+
'local.title': 'Used for local decisions',
|
|
833
|
+
'local.pack': 'Runtime pack',
|
|
834
|
+
'local.model': 'Model',
|
|
835
|
+
'local.pick': 'Choose',
|
|
836
|
+
'local.mismatch': 'The chosen model needs a {backend} runtime pack.',
|
|
837
|
+
'options.title': 'Authorization options',
|
|
838
|
+
'options.lead': 'The decision model judges by these three options, the fixed options it was trained with.',
|
|
839
|
+
'cross.title': 'Ordinary writes outside the project',
|
|
840
|
+
'cross.allow': 'Allow',
|
|
841
|
+
'cross.review': 'Ask first',
|
|
842
|
+
'database.title': 'Database resets',
|
|
843
|
+
'database.confirm': 'Ask first',
|
|
844
|
+
'database.allow': 'Allow',
|
|
845
|
+
'project.title': 'Resetting the current project',
|
|
846
|
+
'project.confirm': 'Ask first',
|
|
847
|
+
'project.allow': 'Allow',
|
|
848
|
+
'review.title': 'Review LLM',
|
|
849
|
+
'review.lead': 'Calls the decision model did not allow go to the review LLM, which follows the rules below. Calls the decision model allowed run without it.',
|
|
850
|
+
'review.session': 'Session model',
|
|
851
|
+
'review.custom': 'Chosen model',
|
|
852
|
+
'review.off': 'Off',
|
|
853
|
+
'review.session.hint': 'The session\'s own model reviews, using its quota.',
|
|
854
|
+
'review.custom.hint': 'The model chosen below reviews, whatever the session uses.',
|
|
855
|
+
'review.off.hint': 'No review: you are asked when present, and calls are refused when unattended.',
|
|
856
|
+
'review.provider': 'Provider',
|
|
857
|
+
'review.model': 'Model',
|
|
858
|
+
'review.pick': 'Choose',
|
|
859
|
+
'review.catalogFailed': 'Cannot read the model list.',
|
|
860
|
+
'review.missing': 'No review model is chosen, so reviews fail and count as failures.',
|
|
861
|
+
'override.title': 'Let the LLM overturn blocks',
|
|
862
|
+
'override.hint': 'Calls the decision model blocked also go to the review LLM and run when it allows them. When off, blocked calls ask you, or are refused when unattended.',
|
|
863
|
+
'rules.title': 'Review rules',
|
|
864
|
+
'rules.lead': 'Written in plain language and given to the review LLM as is. Priority: forbid > deny > allow > the LLM\'s own judgement.',
|
|
865
|
+
'rules.forbid': 'Forbid',
|
|
866
|
+
'rules.forbid.placeholder': 'For example: deleting or rewriting the production database. Refused without asking you.',
|
|
867
|
+
'rules.deny': 'Deny',
|
|
868
|
+
'rules.deny.placeholder': 'For example: pushing to a remote. Asks you with Reject focused when present.',
|
|
869
|
+
'rules.allow': 'Allow',
|
|
870
|
+
'rules.allow.placeholder': 'For example: running the project\'s tests and builds.',
|
|
871
|
+
'rules.save': 'Save rules',
|
|
872
|
+
'rules.discard': 'Discard changes',
|
|
873
|
+
'ask.title': 'Asking',
|
|
874
|
+
'ask.timeout': 'Refuse after a countdown',
|
|
875
|
+
'ask.timeout.hint': 'Refuses the call when nobody answers in time, so a session does not wait forever while you are away. When off, the prompt waits for you.',
|
|
876
|
+
'ask.seconds': 'seconds',
|
|
877
|
+
'try.title': 'Try a decision',
|
|
878
|
+
'try.lead': 'Judges a sample call with the current settings.',
|
|
879
|
+
'try.safe': 'Show the repository status (git status)',
|
|
880
|
+
'try.risky': 'Force-push to the remote (git push --force)',
|
|
881
|
+
'try.run': 'Judge',
|
|
882
|
+
'try.running': 'Judging…',
|
|
883
|
+
'try.result': '{decision} · {ms} ms · {backend} · {via}',
|
|
884
|
+
'try.failed': 'Decision failed: {error}',
|
|
885
|
+
'quick.label': 'Auto approval',
|
|
886
|
+
'quick.off': 'off',
|
|
887
|
+
'quick.unattended': 'Unattended',
|
|
888
|
+
'quick.title': 'Auto approval',
|
|
889
|
+
'quick.notGoverned': 'This session does not use the Auto permission preset, so its calls are not judged.',
|
|
890
|
+
'quick.governed': 'Vulcan Decision judges this session\'s tool calls.',
|
|
891
|
+
'quick.backend': 'Decision model',
|
|
892
|
+
'quick.endpoint': 'Endpoint',
|
|
893
|
+
'quick.review': 'Review LLM',
|
|
894
|
+
'quick.override': 'Let the LLM overturn blocks',
|
|
895
|
+
'quick.timeout': 'Prompt countdown',
|
|
896
|
+
'quick.timeout.off': 'No limit',
|
|
897
|
+
'quick.unattendedHint': 'No prompts; calls that need your approval go to the review LLM and are refused unless it allows them',
|
|
898
|
+
'quick.more': 'All settings',
|
|
899
|
+
'quick.signIn': 'Not signed in to OpenVulcan; sign in',
|
|
900
|
+
'prompt.strip': 'Vulcan Decision is waiting for you',
|
|
901
|
+
'prompt.stage.review': 'Needs review',
|
|
902
|
+
'prompt.stage.block': 'Blocked',
|
|
903
|
+
'prompt.stage.unjudged': 'Not judged',
|
|
904
|
+
'prompt.review.review': 'LLM: your call',
|
|
905
|
+
'prompt.review.deny': 'LLM: not allowed',
|
|
906
|
+
'prompt.review.error': 'LLM review failed',
|
|
907
|
+
'prompt.reason': 'LLM reason',
|
|
908
|
+
'prompt.intent': 'Purpose stated by the AI',
|
|
909
|
+
'prompt.call': 'Calls {tool}',
|
|
910
|
+
'prompt.countdown': 'Refused in {seconds} s',
|
|
911
|
+
'prompt.expired': 'Refusing…',
|
|
912
|
+
'prompt.allow': 'Allow once',
|
|
913
|
+
'prompt.reject': 'Reject',
|
|
914
|
+
'unattended.failed': 'Could not save unattended mode: {error}',
|
|
915
|
+
'quick.unattendedOn': 'Unattended: on',
|
|
916
|
+
'quick.unattendedOff': 'Unattended: off',
|
|
917
|
+
'quick.draftHint': 'Available after the first message is sent.',
|
|
918
|
+
'quick.categories': 'Category grants',
|
|
919
|
+
'quick.categoriesHint': 'Checked categories may be allowed by the decision model directly, in every session',
|
|
920
|
+
'quick.category.crossProjectWrites': 'Writes outside the project',
|
|
921
|
+
'quick.category.databaseReset': 'Dropping or emptying databases',
|
|
922
|
+
'quick.category.projectReset': 'Resetting the whole project',
|
|
923
|
+
'quick.reviewPrompt': 'Additional review prompt',
|
|
924
|
+
'quick.reviewPrompt.hint': 'What the review LLM also follows while you are present',
|
|
925
|
+
'quick.reviewOff': 'The review LLM is off, so the prompt is not read.',
|
|
926
|
+
'dialog.cancel': 'Cancel',
|
|
927
|
+
'dialog.counter': '{count} / {max}',
|
|
928
|
+
'reviewPrompt.title': 'Additional review prompt',
|
|
929
|
+
'reviewPrompt.body': 'While you are present, the review LLM reads this text after the review rules and category grants, then decides whether to allow the call or ask you. It can make the review stricter or say what you care about, but it can never loosen the rules or the category grants. Unattended sessions use their deny policy instead.',
|
|
930
|
+
'reviewPrompt.label': 'Prompt',
|
|
931
|
+
'reviewPrompt.placeholder': 'For example: ask me before anything that touches release branches; running tests is fine',
|
|
932
|
+
'reviewPrompt.sourceSystem': 'Follows the system default',
|
|
933
|
+
'reviewPrompt.sourceSession': 'This session\'s own',
|
|
934
|
+
'reviewPrompt.followSystem': 'Follow the system default',
|
|
935
|
+
'reviewPrompt.save': 'Save for this session',
|
|
936
|
+
'reviewPrompt.saveFailed': 'Could not save the review prompt: {error}',
|
|
937
|
+
'unattended.enableTitle': 'Turn on unattended mode',
|
|
938
|
+
'unattended.editTitle': 'Unattended mode',
|
|
939
|
+
'unattended.body': 'Unattended mode only replaces your approval: calls that would ask you are decided by the review LLM under the deny policy, and refused unless it allows them; with the review LLM off they are all refused. Calls the decision model allows still run without the deny policy; to tighten a kind of operation, uncheck its category grant.',
|
|
940
|
+
'unattended.policyLabel': 'Deny policy',
|
|
941
|
+
'unattended.placeholder': 'For example: of the calls that need approval, refuse every push to a remote and every database drop or reset',
|
|
942
|
+
'unattended.sourceSystem': 'System default',
|
|
943
|
+
'unattended.enable': 'Turn on',
|
|
944
|
+
'unattended.update': 'Update policy',
|
|
945
|
+
'unattended.disable': 'Turn off',
|
|
946
|
+
'prompts.title': 'Additional prompts',
|
|
947
|
+
'prompts.lead': 'The review LLM reads them after the review rules, and they can never loosen the rules or the category grants. A session can use its own text from the auto approval card next to the composer.',
|
|
948
|
+
'prompts.review': 'Default additional review prompt',
|
|
949
|
+
'prompts.review.hint': 'Used while you are present, in every session without its own prompt; the next review reads the saved text.',
|
|
950
|
+
'prompts.unattended': 'System default deny policy',
|
|
951
|
+
'prompts.save': 'Save prompts',
|
|
952
|
+
'prompts.unattended.hint': 'Prefilled when a session turns unattended mode on, and used by sessions running under the "never ask" approval policy. Changing it does not affect sessions that are already unattended.',
|
|
953
|
+
'records.title': 'Decision log',
|
|
954
|
+
'records.enabled': 'Record decisions',
|
|
955
|
+
'records.enabledHint': 'Keep every decision the decision model or the review took part in, with the original commands and paths, on this computer only. Turning it off stops new records; existing ones stay until you clear them.',
|
|
956
|
+
'records.entryHint': 'See the decision model\'s answers, mark wrong ones and export them as training samples.',
|
|
957
|
+
'records.paused': 'Recording is off, so no new records are written.',
|
|
958
|
+
'records.hint': 'Keeps the latest {records} records for at most {days} days. Records hold the original commands and paths, on this computer only.',
|
|
959
|
+
'records.count': '{count} records',
|
|
960
|
+
'records.filterResult': 'Result',
|
|
961
|
+
'records.all': 'All',
|
|
962
|
+
'records.label.allow': 'Allow',
|
|
963
|
+
'records.label.review': 'Review',
|
|
964
|
+
'records.label.block': 'Block',
|
|
965
|
+
'records.label.failed': 'Failed',
|
|
966
|
+
'records.allWorkspaces': 'All workspaces',
|
|
967
|
+
'records.markedOnly': 'Marked only',
|
|
968
|
+
'records.empty': 'No records yet.',
|
|
969
|
+
'records.loadMore': 'Load more',
|
|
970
|
+
'records.loadFailed': 'Could not read the decision log: {error}',
|
|
971
|
+
'records.export': 'Export marked ({count})',
|
|
972
|
+
'records.exported': 'Exported {count} samples',
|
|
973
|
+
'records.exportFailed': 'Export failed: {error}',
|
|
974
|
+
'records.clear': 'Clear',
|
|
975
|
+
'records.clearTitle': 'Clear the decision log?',
|
|
976
|
+
'records.clearDescription': 'All {count} records and their marks will be deleted. This cannot be undone.',
|
|
977
|
+
'records.clearFailed': 'Could not clear the decision log: {error}',
|
|
978
|
+
'records.markAs': 'Should be:',
|
|
979
|
+
'records.marked': 'Marked: {label}',
|
|
980
|
+
'records.markFailed': 'Could not save the mark: {error}',
|
|
981
|
+
'records.noTrace': 'This record has no decision model input, so it cannot be marked.',
|
|
982
|
+
'records.details': 'Details',
|
|
983
|
+
'records.detail.tool': 'Tool',
|
|
984
|
+
'records.detail.workspace': 'Workspace',
|
|
985
|
+
'records.detail.model': 'Model',
|
|
986
|
+
'records.detail.scores': 'Scores',
|
|
987
|
+
'records.detail.rule': 'Rule',
|
|
988
|
+
'records.detail.judgeTime': 'Decision time',
|
|
989
|
+
'records.detail.error': 'Error',
|
|
990
|
+
'records.detail.review': 'Review',
|
|
991
|
+
'records.detail.reviewTime': 'Review time',
|
|
992
|
+
'records.detail.attendance': 'Session',
|
|
993
|
+
'records.attended': 'Attended',
|
|
994
|
+
'records.unattended': 'Unattended',
|
|
995
|
+
'records.verdict.allow': 'Allowed',
|
|
996
|
+
'records.verdict.review': 'Left to you',
|
|
997
|
+
'records.verdict.deny': 'Denied',
|
|
998
|
+
'records.verdict.forbid': 'Forbidden',
|
|
999
|
+
'records.outcome.judge_allowed': 'Decision model allowed',
|
|
1000
|
+
'records.outcome.review_allowed': 'Review allowed',
|
|
1001
|
+
'records.outcome.review_denied': 'Review denied',
|
|
1002
|
+
'records.outcome.unattended_denied': 'Denied (unattended)',
|
|
1003
|
+
'records.outcome.user_allowed': 'You allowed',
|
|
1004
|
+
'records.outcome.user_denied': 'You denied',
|
|
1005
|
+
'records.outcome.approval_expired': 'Denied (no answer in time)',
|
|
1006
|
+
'records.outcome.not_asked': 'Denied (you could not be asked)',
|
|
1007
|
+
'records.outcome.host_denied': 'Denied (another plugin)',
|
|
1008
|
+
};
|
|
1009
|
+
/** Replace `{name}` placeholders. */
|
|
1010
|
+
function fill(text, values) {
|
|
1011
|
+
return text.replace(/\{(\w+)\}/g, (whole, name) => (name in values ? String(values[name]) : whole));
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
},
|
|
1015
|
+
"./index": function (module, exports, require) {
|
|
1016
|
+
"use strict";
|
|
1017
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1018
|
+
exports.inject = void 0;
|
|
1019
|
+
exports.apply = apply;
|
|
1020
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1021
|
+
/**
|
|
1022
|
+
* Browser half of the dsh plugin:
|
|
1023
|
+
*
|
|
1024
|
+
* - Settings › Auto approval: the whole configuration, in VulcanCode's look (`settings.tsx`).
|
|
1025
|
+
* - The composer pill left of the model selector, with a card for the category grants, the
|
|
1026
|
+
* session's review prompt and unattended mode, and the settings changed most often (`quick.tsx`).
|
|
1027
|
+
* - The approval card that takes over the composer while one of this plugin's prompts waits.
|
|
1028
|
+
* - On the suite's Plugins page entry: the Auto review conflict notice. dsh's own
|
|
1029
|
+
* Auto review and Vulcan Decision both review every tool call before it runs and only one of
|
|
1030
|
+
* them can provide the Auto permission preset, so the pair is a conflict, not a combination.
|
|
1031
|
+
*
|
|
1032
|
+
* dsh loads these files as one lazy factory; they may import only modules dsh serves to plugins.
|
|
1033
|
+
*/
|
|
1034
|
+
const approval_1 = require("./approval");
|
|
1035
|
+
const i18n_1 = require("./i18n");
|
|
1036
|
+
const quick_1 = require("./quick");
|
|
1037
|
+
const settings_1 = require("./settings");
|
|
1038
|
+
const shared_1 = require("./shared");
|
|
1039
|
+
const style_1 = require("./style");
|
|
1040
|
+
exports.inject = [
|
|
1041
|
+
'slots', 'locale', 'sessions', 'remote', 'remote.pluginManager', 'remote.permissionPresets', 'remote.session', 'configForms',
|
|
1042
|
+
];
|
|
1043
|
+
/** Mirror `UNATTENDED_COMMAND` and `REVIEW_PROMPT_COMMAND` in `src/session-state.ts`. */
|
|
1044
|
+
const UNATTENDED_COMMAND = 'vulcan-unattended';
|
|
1045
|
+
const REVIEW_PROMPT_COMMAND = 'vulcan-review-prompt';
|
|
1046
|
+
/** Install the settings section, the composer pill, the approval card and the Plugins page notices. */
|
|
1047
|
+
function apply(ctx) {
|
|
1048
|
+
const services = {
|
|
1049
|
+
form: ctx.configForms.get(shared_1.ENTRY_ID),
|
|
1050
|
+
autoReview: new shared_1.HostWatch(async () => {
|
|
1051
|
+
const result = await ctx.remote.pluginManager.listBundles();
|
|
1052
|
+
return result.ok ? (0, shared_1.autoReviewEnabled)(result.value) : undefined;
|
|
1053
|
+
}),
|
|
1054
|
+
autoListed: new shared_1.HostWatch(async () => {
|
|
1055
|
+
const result = await ctx.remote.permissionPresets.catalog();
|
|
1056
|
+
return result.ok ? result.value.options.some(option => option.value === shared_1.AUTO_PRESET) : undefined;
|
|
1057
|
+
}),
|
|
1058
|
+
panel: new shared_1.HostWatch(() => (0, shared_1.callPanel)('GET', 'state').catch(() => null)),
|
|
1059
|
+
endpoints: new shared_1.HostWatch(() => (0, shared_1.callPanel)('GET', 'cloud/endpoints')),
|
|
1060
|
+
cloudModels: new shared_1.HostWatch(() => (0, shared_1.callPanel)('GET', 'cloud/models').then((models) => ({ kind: 'ready', models }), (error) => ({ kind: 'failed', error: (0, shared_1.messageOf)(error) }))),
|
|
1061
|
+
modelCatalog: new shared_1.HostWatch(async () => {
|
|
1062
|
+
const result = await ctx.remote.session.modelCatalog();
|
|
1063
|
+
return result.ok ? result.value : undefined;
|
|
1064
|
+
}),
|
|
1065
|
+
openSettings: () => {
|
|
1066
|
+
const shell = ctx.slots.entries('sidebar.settings')[0]?.store?.create();
|
|
1067
|
+
shell?.actions.openSection(shared_1.SETTINGS_SECTION);
|
|
1068
|
+
},
|
|
1069
|
+
openAccount: () => {
|
|
1070
|
+
const shell = ctx.slots.entries('sidebar.settings')[0]?.store?.create();
|
|
1071
|
+
shell?.actions.openSection(shared_1.ACCOUNT_SECTION);
|
|
1072
|
+
},
|
|
1073
|
+
};
|
|
1074
|
+
const watches = [services.autoReview, services.autoListed, services.panel, services.endpoints, services.cloudModels, services.modelCatalog];
|
|
1075
|
+
ctx.effect(() => (0, style_1.installStyle)(), 'vulcan-decision-guard: stylesheet');
|
|
1076
|
+
ctx.effect(() => ctx.locale.register(i18n_1.NAMESPACE, { zh: i18n_1.zh, en: i18n_1.en }), 'vulcan-decision-guard: dictionaries');
|
|
1077
|
+
ctx.effect(() => {
|
|
1078
|
+
let accountKey;
|
|
1079
|
+
const refreshAll = () => {
|
|
1080
|
+
services.autoReview.refresh();
|
|
1081
|
+
services.autoListed.refresh();
|
|
1082
|
+
services.panel.refresh();
|
|
1083
|
+
};
|
|
1084
|
+
const stops = [
|
|
1085
|
+
ctx.remote.$on('plugin-manager/changed', () => { services.autoReview.refresh(); }),
|
|
1086
|
+
ctx.remote.$on('permission-presets/catalog-changed', () => { services.autoListed.refresh(); }),
|
|
1087
|
+
ctx.on('connection/reset', refreshAll),
|
|
1088
|
+
// Signing in and out happens in the account plugin; the models and endpoints belong to the account.
|
|
1089
|
+
services.panel.subscribe(() => {
|
|
1090
|
+
const account = services.panel.getSnapshot()?.account;
|
|
1091
|
+
if (account === undefined)
|
|
1092
|
+
return;
|
|
1093
|
+
const key = account.signedIn ? account.authorizationId ?? '' : null;
|
|
1094
|
+
if (accountKey !== undefined && key !== accountKey) {
|
|
1095
|
+
services.cloudModels.refresh();
|
|
1096
|
+
services.endpoints.refresh();
|
|
1097
|
+
}
|
|
1098
|
+
accountKey = key;
|
|
1099
|
+
}),
|
|
1100
|
+
];
|
|
1101
|
+
refreshAll();
|
|
1102
|
+
return () => {
|
|
1103
|
+
for (const stop of stops)
|
|
1104
|
+
stop();
|
|
1105
|
+
for (const watch of watches)
|
|
1106
|
+
watch.close();
|
|
1107
|
+
};
|
|
1108
|
+
}, 'vulcan-decision-guard: Host state');
|
|
1109
|
+
const withServices = () => ({ services });
|
|
1110
|
+
// After Agent presets (20), before Market (40).
|
|
1111
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
1112
|
+
name: 'settings.section',
|
|
1113
|
+
id: shared_1.SETTINGS_SECTION,
|
|
1114
|
+
order: 22,
|
|
1115
|
+
label: () => ctx.locale.bind(i18n_1.NAMESPACE)('nav'),
|
|
1116
|
+
locale: i18n_1.NAMESPACE,
|
|
1117
|
+
inject: withServices,
|
|
1118
|
+
}, ({ t, services: injected }) => (0, jsx_runtime_1.jsx)(settings_1.SettingsPage, { services: injected, t: t })));
|
|
1119
|
+
// dsh 0.2.2 declares this optional slot. The injection waits on older hosts.
|
|
1120
|
+
// dsh 0.2.2 声明了此可选插槽,旧版宿主上的注入会保持等待。
|
|
1121
|
+
ctx.slots.inject('settings.section.icon', () => ctx.slots.register({
|
|
1122
|
+
name: 'settings.section.icon',
|
|
1123
|
+
id: shared_1.SETTINGS_SECTION,
|
|
1124
|
+
}, SettingsIcon));
|
|
1125
|
+
ctx.slots.inject('plugins.detail.badge', () => ctx.slots.register({
|
|
1126
|
+
name: 'plugins.detail.badge',
|
|
1127
|
+
id: 'vulcan-decision-guard-conflict',
|
|
1128
|
+
locale: i18n_1.NAMESPACE,
|
|
1129
|
+
inject: withServices,
|
|
1130
|
+
}, approval_1.ConflictBadge));
|
|
1131
|
+
ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
|
|
1132
|
+
name: 'plugins.detail.section',
|
|
1133
|
+
id: 'vulcan-decision-guard-conflict',
|
|
1134
|
+
locale: i18n_1.NAMESPACE,
|
|
1135
|
+
inject: () => ({ services, disableAutoReview: () => ctx.remote.pluginManager.setBundleEnabled(shared_1.AUTO_REVIEW_PACKAGE, false) }),
|
|
1136
|
+
}, approval_1.ConflictSection));
|
|
1137
|
+
// Ahead of dsh's own approval card (priority 1), and only for this plugin's prompts.
|
|
1138
|
+
ctx.slots.inject('conversation.composer', () => ctx.slots.register({
|
|
1139
|
+
name: 'conversation.composer',
|
|
1140
|
+
priority: 0,
|
|
1141
|
+
locale: i18n_1.NAMESPACE,
|
|
1142
|
+
select: ({ pendingInteraction }) => (0, approval_1.guardApprovalOf)(pendingInteraction),
|
|
1143
|
+
inject: () => ({ resolveText: (text) => ctx.locale.resolveText(text) }),
|
|
1144
|
+
}, approval_1.ApprovalCard));
|
|
1145
|
+
// Left of the model selector, before other plugins' pills (liangshen uses 20).
|
|
1146
|
+
ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
1147
|
+
name: 'conversation.input.right',
|
|
1148
|
+
id: 'vulcan-decision-guard-quick',
|
|
1149
|
+
order: 10,
|
|
1150
|
+
locale: i18n_1.NAMESPACE,
|
|
1151
|
+
inject: (sessionId) => {
|
|
1152
|
+
// The commands keep newlines after their first word, so a multi-line prompt arrives whole.
|
|
1153
|
+
const run = async (command, input) => {
|
|
1154
|
+
const session = ctx.sessions.binding(sessionId)?.session;
|
|
1155
|
+
if (session === undefined)
|
|
1156
|
+
throw new Error('this session is not materialized yet');
|
|
1157
|
+
const result = await session.command(`/${command} ${input}`);
|
|
1158
|
+
if (!result.ok)
|
|
1159
|
+
throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
1160
|
+
if (!result.value.matched)
|
|
1161
|
+
throw new Error(`the Host offers no /${command} command`);
|
|
1162
|
+
};
|
|
1163
|
+
return {
|
|
1164
|
+
services,
|
|
1165
|
+
setUnattended: policy => run(UNATTENDED_COMMAND, policy === null ? 'off' : `on ${policy}`.trimEnd()),
|
|
1166
|
+
setReviewPrompt: prompt => run(REVIEW_PROMPT_COMMAND, prompt === null ? 'default' : `set ${prompt}`.trimEnd()),
|
|
1167
|
+
};
|
|
1168
|
+
},
|
|
1169
|
+
}, quick_1.QuickSetting));
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Render the approval shield at the square size requested by dsh's settings navigation.
|
|
1173
|
+
* 按 dsh 设置导航请求的方形尺寸渲染审批盾牌图标。
|
|
1174
|
+
*/
|
|
1175
|
+
function SettingsIcon({ size }) {
|
|
1176
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: [(0, jsx_runtime_1.jsx)("path", { d: "M12 3 20 6v5c0 4.7-2.8 7.9-8 10-5.2-2.1-8-5.3-8-10V6l8-3Z" }), (0, jsx_runtime_1.jsx)("path", { d: "m9 12 2 2 4-4" })] }));
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
},
|
|
1180
|
+
"./local": function (module, exports, require) {
|
|
1181
|
+
"use strict";
|
|
1182
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1183
|
+
exports.LocalSections = LocalSections;
|
|
1184
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1185
|
+
/**
|
|
1186
|
+
* The local part of the settings page: this machine's hardware with the recommendation, the
|
|
1187
|
+
* runtime packs, the installed and downloadable models, and which pack runs which model.
|
|
1188
|
+
*/
|
|
1189
|
+
const react_1 = require("react");
|
|
1190
|
+
const i18n_1 = require("./i18n");
|
|
1191
|
+
const shared_1 = require("./shared");
|
|
1192
|
+
const ui_1 = require("./ui");
|
|
1193
|
+
/** Download points of the published models, as `MODEL_SOURCES` in the core package lists them. */
|
|
1194
|
+
const MODEL_SOURCES = ['modelscope-cn', 'modelscope-intl', 'huggingface'];
|
|
1195
|
+
/** The listed values, plus a saved one the list no longer has, so a saved choice never silently changes. */
|
|
1196
|
+
function withSaved(values, saved) {
|
|
1197
|
+
return saved === undefined || saved === '' || values.includes(saved) ? [...values] : [saved, ...values];
|
|
1198
|
+
}
|
|
1199
|
+
function JobLine({ t, job, onCancel }) {
|
|
1200
|
+
const percent = job.total === undefined || job.total === 0 ? 0 : Math.floor(((job.done ?? 0) / job.total) * 100);
|
|
1201
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", style: { flexWrap: 'nowrap' }, children: [job.phase === 'download' || job.phase === 'verify'
|
|
1202
|
+
? (0, jsx_runtime_1.jsx)("div", { className: "vdg-progress", children: (0, jsx_runtime_1.jsx)("div", { style: { width: `${percent}%` } }) })
|
|
1203
|
+
: null, (0, jsx_runtime_1.jsx)("span", { className: job.phase === 'failed' ? 'vdg-error' : 'vdg-hint', children: (0, i18n_1.fill)(t(`job.${job.phase}`), { percent, error: job.error ?? '' }) }), (0, shared_1.running)(job) ? (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn sm", onClick: onCancel, children: t('job.cancel') }) : null] }));
|
|
1204
|
+
}
|
|
1205
|
+
function LocalSections({ t, live, value, disabled, save, refresh }) {
|
|
1206
|
+
const { busy, error, act } = (0, ui_1.useAction)(refresh);
|
|
1207
|
+
const [dir, setDir] = (0, react_1.useState)('');
|
|
1208
|
+
const [modelSource, setModelSource] = (0, react_1.useState)(MODEL_SOURCES[0]);
|
|
1209
|
+
const job = live.job;
|
|
1210
|
+
const idle = !(0, shared_1.running)(job) && busy === undefined;
|
|
1211
|
+
const source = value.downloadSource;
|
|
1212
|
+
const recommendation = live.recommendation;
|
|
1213
|
+
const cards = live.hardware.gpus.filter(gpu => gpu.generation !== undefined);
|
|
1214
|
+
const chosenPack = live.packs.find(item => item.id === value.localPack);
|
|
1215
|
+
const chosenModel = live.models.find(item => item.id === value.localModel);
|
|
1216
|
+
const needed = chosenModel === undefined ? undefined : chosenModel.format === 'onnx-delivery' ? 'onnx' : 'torch';
|
|
1217
|
+
const cancel = () => { act('cancel', () => (0, shared_1.callPanel)('POST', 'job/cancel')); };
|
|
1218
|
+
const jobOf = (kind, target) => (job === null || job.kind !== kind || (target !== undefined && job.target !== target) ? null : (0, jsx_runtime_1.jsx)(JobLine, { t: t, job: job, onCancel: cancel }));
|
|
1219
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('hw.title'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-alert", children: [cards.length === 0
|
|
1220
|
+
? (0, jsx_runtime_1.jsx)("div", { children: t('hw.noGpu') })
|
|
1221
|
+
: cards.map(gpu => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, i18n_1.fill)(t('hw.gpu'), { name: gpu.name, arch: gpu.generation.architecture, cc: gpu.computeCapability ?? '?', driver: gpu.driver ?? '?' }), ' · ', t(`hw.fp16.${gpu.generation.fp16}`)] }, gpu.name))), live.hardware.npus.map(npu => (0, jsx_runtime_1.jsx)("div", { children: (0, i18n_1.fill)(t('hw.npu'), { name: npu.name }) }, npu.name))] }), (0, jsx_runtime_1.jsx)("strong", { children: (0, i18n_1.fill)(t(`hw.rec.${recommendation.reason}`), { torch: recommendation.torchPack, onnx: recommendation.onnxPack }) })] }), (0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('packs.title'), lead: (0, i18n_1.fill)(t('packs.lead'), { home: live.home }), flush: true, actions: ((0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('packs.source'), style: { minWidth: 0 }, value: source, disabled: disabled, onChange: (event) => { save({ downloadSource: event.target.value }); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "official", children: t('packs.source.official') }), (0, jsx_runtime_1.jsx)("option", { value: "cn", children: t('packs.source.cn') })] })), children: [(0, jsx_runtime_1.jsx)("div", { className: "vdg-table-wrap", children: (0, jsx_runtime_1.jsxs)("table", { className: "vdg-table", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: t('packs.col.pack') }), (0, jsx_runtime_1.jsx)("th", { className: "num", children: t('packs.col.size') }), (0, jsx_runtime_1.jsx)("th", { children: t('packs.col.state') }), (0, jsx_runtime_1.jsx)("th", {})] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: live.packs.map(pack => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsxs)("td", { children: [(0, jsx_runtime_1.jsx)("div", { className: "vdg-mono", children: pack.id }), jobOf('pack', pack.id)] }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: pack.bytes === undefined ? '—' : (0, shared_1.mebibytes)(pack.bytes) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsxs)("span", { className: "vdg-inline", children: [pack.fit === 'ok' ? null : (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "warn", children: (0, i18n_1.fill)(t(`packs.fit.${pack.fit}`), { driver: pack.needsDriver ?? '' }) }), pack.installed === null
|
|
1222
|
+
? (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "outline", children: t('packs.notInstalled') })
|
|
1223
|
+
: (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: pack.installed.outdated ? 'warn' : 'ok', children: t(pack.installed.outdated ? 'packs.outdated' : 'packs.installed') })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "num", children: (0, jsx_runtime_1.jsxs)("span", { className: "vdg-inline", style: { justifyContent: 'flex-end' }, children: [pack.installed === null || pack.installed.outdated
|
|
1224
|
+
? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn sm", disabled: pack.fit !== 'ok' || !idle, onClick: () => { act('install', () => (0, shared_1.callPanel)('POST', `packs/install?pack=${pack.id}&source=${source}`)); }, children: t(pack.installed === null ? 'packs.install' : 'packs.update') }))
|
|
1225
|
+
: null, pack.installed === null
|
|
1226
|
+
? null
|
|
1227
|
+
: ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn sm ghost", disabled: !idle, onClick: () => { act('remove', () => (0, shared_1.callPanel)('POST', `packs/remove?pack=${pack.id}`)); }, children: t('packs.remove') }))] }) })] }, pack.id))) })] }) }), live.planned.length === 0
|
|
1228
|
+
? null
|
|
1229
|
+
: ((0, jsx_runtime_1.jsx)("div", { style: { padding: '8px 16px 12px' }, children: live.planned.map(pack => (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: (0, i18n_1.fill)(t('packs.planned'), { id: pack.id, accelerator: pack.accelerator }) }, pack.id)) }))] }), (0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('models.title'), flush: true, actions: ((0, jsx_runtime_1.jsx)("select", { className: "vdg-select", "aria-label": t('models.source'), style: { minWidth: 0 }, value: modelSource, onChange: (event) => { setModelSource(MODEL_SOURCES.find(item => item === event.target.value) ?? MODEL_SOURCES[0]); }, children: MODEL_SOURCES.map(item => (0, jsx_runtime_1.jsx)("option", { value: item, children: t(`models.source.${item}`) }, item)) })), children: (0, jsx_runtime_1.jsxs)("div", { className: "vdg-rows", children: [live.models.length === 0
|
|
1230
|
+
? (0, jsx_runtime_1.jsx)("div", { className: "vdg-row", children: (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: t('models.none') }) })
|
|
1231
|
+
: live.models.map(model => ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-row-label", style: { flexShrink: 1, overflowWrap: 'anywhere' }, children: model.name }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-row-value", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-mono", children: model.id }), (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "outline", children: model.format === 'onnx-delivery' ? 'ONNX' : 'PyTorch' }), (0, jsx_runtime_1.jsx)("span", { children: (0, shared_1.mebibytes)(model.bytes) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn sm ghost", disabled: !idle, onClick: () => { act('remove', () => (0, shared_1.callPanel)('POST', `models/remove?id=${encodeURIComponent(model.id)}`)); }, children: t('models.remove') })] })] }, model.id))), live.catalog.map(model => ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", style: { flexDirection: 'column', alignItems: 'stretch' }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-between", children: [(0, jsx_runtime_1.jsxs)("span", { children: [model.name, " \u00B7 ", model.parameters] }), model.sources.includes(modelSource)
|
|
1232
|
+
? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn sm", disabled: !idle, onClick: () => { act('download', () => (0, shared_1.callPanel)('POST', `models/download?family=${model.family}&source=${modelSource}`)); }, children: t('models.download') }))
|
|
1233
|
+
: (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "outline", children: t('models.notPublished') })] }), jobOf('model-download', model.family)] }, model.family))), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", style: { flexDirection: 'column', alignItems: 'stretch' }, children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: t('models.dir') }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", style: { flexWrap: 'nowrap' }, children: [(0, jsx_runtime_1.jsx)("input", { className: "vdg-input", style: { flex: 1, minWidth: 0 }, "aria-label": t('models.dir'), value: dir, placeholder: t('models.dir.placeholder'), onChange: (event) => { setDir(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: dir.trim() === '' || !idle, onClick: () => { act('dir', () => (0, shared_1.callPanel)('POST', 'models/install-dir', { dir })); }, children: t('models.dir.install') })] }), jobOf('model-directory')] })] }) }), (0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('local.title'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", style: { alignItems: 'flex-start' }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-field", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: t('local.pack') }), (0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('local.pack'), value: value.localPack ?? '', disabled: disabled, onChange: (event) => { save({ localPack: event.target.value === '' ? undefined : event.target.value }); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: t('local.pick') }), withSaved(live.packs.filter(pack => pack.installed !== null).map(pack => pack.id), value.localPack).map(id => (0, jsx_runtime_1.jsx)("option", { value: id, children: id }, id))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-field", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: t('local.model') }), (0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('local.model'), value: value.localModel ?? '', disabled: disabled, onChange: (event) => { save({ localModel: event.target.value === '' ? undefined : event.target.value }); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: t('local.pick') }), withSaved(live.models.map(model => model.id), value.localModel).map(id => ((0, jsx_runtime_1.jsx)("option", { value: id, children: live.models.find(model => model.id === id)?.name ?? id }, id)))] })] })] }), needed !== undefined && chosenPack !== undefined && chosenPack.backend !== needed
|
|
1234
|
+
? (0, jsx_runtime_1.jsx)("p", { className: "vdg-warn", children: (0, i18n_1.fill)(t('local.mismatch'), { backend: needed === 'onnx' ? 'ONNX' : 'PyTorch' }) })
|
|
1235
|
+
: null, (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: error })] })] }));
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
},
|
|
1239
|
+
"./quick": function (module, exports, require) {
|
|
1240
|
+
"use strict";
|
|
1241
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1242
|
+
exports.PROJECTION_KEY = void 0;
|
|
1243
|
+
exports.QuickSetting = QuickSetting;
|
|
1244
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1245
|
+
/**
|
|
1246
|
+
* The composer's quick setting, left of the model selector: a pill with the guard's state that
|
|
1247
|
+
* opens a card after VulcanCode's auto approval menu. It holds the category grants, this
|
|
1248
|
+
* session's additional review prompt and unattended mode, each edited in its own dialog, and the
|
|
1249
|
+
* settings changed most often. Everything else stays in Settings › Auto approval, one click away.
|
|
1250
|
+
*/
|
|
1251
|
+
const react_1 = require("react");
|
|
1252
|
+
const i18n_1 = require("./i18n");
|
|
1253
|
+
const cloud_1 = require("./cloud");
|
|
1254
|
+
const settings_1 = require("./settings");
|
|
1255
|
+
const shared_1 = require("./shared");
|
|
1256
|
+
const ui_1 = require("./ui");
|
|
1257
|
+
/** Mirrors `PROJECTION_KEY` and `GuardSessionView` in `src/session-state.ts`. */
|
|
1258
|
+
exports.PROJECTION_KEY = 'vulcanDecision';
|
|
1259
|
+
/** Countdowns offered in the card; a saved value outside them is listed as well. */
|
|
1260
|
+
const TIMEOUTS = [0, 30, shared_1.DEFAULT_ASK_TIMEOUT, 120, 300];
|
|
1261
|
+
/** The category grants a checkbox can express: checked lets the decision model allow the category. */
|
|
1262
|
+
const CATEGORIES = [
|
|
1263
|
+
{ field: 'crossProjectWrites', withheld: 'review', label: 'quick.category.crossProjectWrites' },
|
|
1264
|
+
{ field: 'databaseReset', withheld: 'confirm', label: 'quick.category.databaseReset' },
|
|
1265
|
+
{ field: 'projectReset', withheld: 'confirm', label: 'quick.category.projectReset' },
|
|
1266
|
+
];
|
|
1267
|
+
function QuickSetting({ useProjection, t: translate, services, setUnattended, setReviewPrompt }) {
|
|
1268
|
+
const t = translate;
|
|
1269
|
+
const view = useProjection(exports.PROJECTION_KEY);
|
|
1270
|
+
const permissions = useProjection('permissions');
|
|
1271
|
+
const settings = (0, ui_1.useSettings)(services.form, t);
|
|
1272
|
+
const live = (0, ui_1.useWatch)(services.panel);
|
|
1273
|
+
const models = (0, ui_1.useWatch)(services.cloudModels);
|
|
1274
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
1275
|
+
const anchor = (0, react_1.useRef)(null);
|
|
1276
|
+
const close = (0, react_1.useCallback)(() => { setOpen(false); }, []);
|
|
1277
|
+
const value = settings.value;
|
|
1278
|
+
if (value === undefined)
|
|
1279
|
+
return null;
|
|
1280
|
+
const governed = value.mode === 'standalone' || permissions?.currentValue === shared_1.AUTO_PRESET;
|
|
1281
|
+
const unattended = view?.unattended === true;
|
|
1282
|
+
const decision = (0, settings_1.decisionStatus)(t, value, live, models?.kind === 'ready' ? models.models : undefined);
|
|
1283
|
+
const dot = !governed ? 'off' : decision.tone === 'ok' ? 'ok' : 'warn';
|
|
1284
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: "vdg", children: [(0, jsx_runtime_1.jsxs)("button", { ref: anchor, type: "button", className: "vdg-trigger", "aria-haspopup": "dialog", "aria-expanded": open, title: decision.text, onClick: () => {
|
|
1285
|
+
if (!open) {
|
|
1286
|
+
services.panel.refresh();
|
|
1287
|
+
services.cloudModels.refresh();
|
|
1288
|
+
services.endpoints.refresh();
|
|
1289
|
+
}
|
|
1290
|
+
setOpen(!open);
|
|
1291
|
+
}, children: [(0, jsx_runtime_1.jsx)("span", { className: `vdg-dot ${dot}` }), !governed ? `${t('quick.label')} · ${t('quick.off')}` : unattended ? t('quick.unattended') : t('quick.label')] }), open
|
|
1292
|
+
? ((0, jsx_runtime_1.jsx)(ui_1.Popover, { anchor: anchor, label: t('quick.title'), onClose: close, children: (0, jsx_runtime_1.jsx)(QuickCard, { t: t, services: services, governed: governed, view: view, setUnattended: setUnattended, setReviewPrompt: setReviewPrompt, decision: decision.text, onMore: () => {
|
|
1293
|
+
close();
|
|
1294
|
+
services.openSettings();
|
|
1295
|
+
} }) }))
|
|
1296
|
+
: null] }));
|
|
1297
|
+
}
|
|
1298
|
+
function QuickCard({ t, services, governed, view, setUnattended, setReviewPrompt, decision, onMore }) {
|
|
1299
|
+
const settings = (0, ui_1.useSettings)(services.form, t);
|
|
1300
|
+
const live = (0, ui_1.useWatch)(services.panel);
|
|
1301
|
+
const endpoints = (0, ui_1.useWatch)(services.endpoints);
|
|
1302
|
+
const { busy, error, act } = (0, ui_1.useAction)(() => { services.endpoints.refresh(); });
|
|
1303
|
+
const [editing, setEditing] = (0, react_1.useState)(undefined);
|
|
1304
|
+
const value = settings.value;
|
|
1305
|
+
if (value === undefined)
|
|
1306
|
+
return null;
|
|
1307
|
+
const { disabled, save } = settings;
|
|
1308
|
+
const signedIn = live?.account.signedIn === true;
|
|
1309
|
+
const timeouts = TIMEOUTS.includes(value.askTimeout) ? [...TIMEOUTS] : [...TIMEOUTS, value.askTimeout].sort((a, b) => a - b);
|
|
1310
|
+
const locked = endpoints?.lockedEndpointId;
|
|
1311
|
+
const rows = endpoints?.endpoints ?? [];
|
|
1312
|
+
const unattended = view?.unattended === true;
|
|
1313
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "vdg-popover-head", children: (0, jsx_runtime_1.jsx)("strong", { children: t('quick.title') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-popover-body", children: [(0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: governed ? t('quick.governed') : t('quick.notGoverned') }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-group-label", children: [(0, jsx_runtime_1.jsx)("strong", { children: t('quick.categories') }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: t('quick.categoriesHint') })] }), CATEGORIES.map(category => ((0, jsx_runtime_1.jsxs)("label", { className: "vdg-check", "data-category": category.field, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: value[category.field] === 'allow', disabled: disabled, onChange: (event) => { save({ [category.field]: event.target.checked ? 'allow' : category.withheld }); } }), (0, jsx_runtime_1.jsx)("span", { children: t(category.label) })] }, category.field))), governed
|
|
1314
|
+
? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "vdg-separator" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "vdg-entry", "data-review-prompt-entry": "", disabled: view === undefined, onClick: () => { setEditing('review-prompt'); }, children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-entry-title", children: t('quick.reviewPrompt') }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-entry-hint", children: t(view === undefined ? 'quick.draftHint' : 'quick.reviewPrompt.hint') })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "vdg-entry", "data-unattended-entry": unattended ? 'on' : 'off', disabled: view === undefined, onClick: () => { setEditing('unattended'); }, children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-entry-title", children: t(unattended ? 'quick.unattendedOn' : 'quick.unattendedOff') }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-entry-hint", children: t(view === undefined ? 'quick.draftHint' : 'quick.unattendedHint') })] })] }))
|
|
1315
|
+
: null, (0, jsx_runtime_1.jsx)("div", { className: "vdg-separator" }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-popover-row", children: [(0, jsx_runtime_1.jsx)("span", { children: t('quick.backend') }), (0, jsx_runtime_1.jsx)(ui_1.Segments, { label: t('quick.backend'), value: value.backend, options: [{ value: 'cloud', title: t('backend.cloud') }, { value: 'local', title: t('backend.local') }], disabled: disabled, onChoose: (next) => { save({ backend: next }); } })] }), value.backend === 'cloud' && live != null && !signedIn
|
|
1316
|
+
? (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-link", style: { alignSelf: 'flex-start' }, onClick: () => { services.openAccount(); }, children: t('quick.signIn') })
|
|
1317
|
+
: null, value.backend === 'cloud' && signedIn
|
|
1318
|
+
? ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-popover-row", children: [(0, jsx_runtime_1.jsx)("span", { children: t('quick.endpoint') }), (0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", style: { minWidth: 0, width: 190 }, "aria-label": t('quick.endpoint'), value: locked ?? '', disabled: disabled || busy !== undefined || rows.length === 0, onChange: (event) => {
|
|
1319
|
+
const next = event.target.value === '' ? undefined : event.target.value;
|
|
1320
|
+
act('lock', () => (0, cloud_1.lockEndpoint)(settings.saveAsync, value.cloudModel, next, t('readOnly')));
|
|
1321
|
+
}, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: t('endpoints.auto') }), locked !== undefined && !rows.some(row => row.endpointId === locked) ? (0, jsx_runtime_1.jsx)("option", { value: locked, children: (0, i18n_1.fill)(t('endpoints.always'), { id: locked }) }) : null, rows.map(row => (0, jsx_runtime_1.jsx)("option", { value: row.endpointId, children: (0, i18n_1.fill)(t('endpoints.always'), { id: row.endpointId }) }, row.endpointId))] })] }))
|
|
1322
|
+
: null, (0, jsx_runtime_1.jsxs)("div", { className: "vdg-popover-row", children: [(0, jsx_runtime_1.jsx)("span", { children: t('quick.review') }), (0, jsx_runtime_1.jsx)(ui_1.Segments, { label: t('quick.review'), value: value.review, options: ['session', 'custom', 'off'].map(item => ({ value: item, title: t(`review.${item}`) })), disabled: disabled, onChoose: (next) => { save({ review: next }); } })] }), value.review === 'off'
|
|
1323
|
+
? null
|
|
1324
|
+
: ((0, jsx_runtime_1.jsxs)("label", { className: "vdg-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: value.llmOverridesBlock, disabled: disabled, onChange: (event) => { save({ llmOverridesBlock: event.target.checked }); } }), (0, jsx_runtime_1.jsx)("span", { children: t('quick.override') })] })), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-popover-row", children: [(0, jsx_runtime_1.jsx)("span", { children: t('quick.timeout') }), (0, jsx_runtime_1.jsx)("select", { className: "vdg-select", style: { minWidth: 0, width: 120 }, "aria-label": t('quick.timeout'), value: value.askTimeout, disabled: disabled, onChange: (event) => { save({ askTimeout: Number(event.target.value) }); }, children: timeouts.map(seconds => ((0, jsx_runtime_1.jsx)("option", { value: seconds, children: seconds === 0 ? t('quick.timeout.off') : `${seconds} ${t('ask.seconds')}` }, seconds))) })] }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: error ?? settings.error })] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-popover-foot", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: decision, children: decision }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "vdg-link", onClick: onMore, children: [t('quick.more'), " \u2192"] })] }), editing === 'review-prompt' && view !== undefined
|
|
1325
|
+
? (0, jsx_runtime_1.jsx)(ReviewPromptDialog, { t: t, view: view, value: value, onSave: setReviewPrompt, onClose: () => { setEditing(undefined); } })
|
|
1326
|
+
: null, editing === 'unattended' && view !== undefined
|
|
1327
|
+
? (0, jsx_runtime_1.jsx)(UnattendedDialog, { t: t, view: view, value: value, onApply: setUnattended, onClose: () => { setEditing(undefined); } })
|
|
1328
|
+
: null] }));
|
|
1329
|
+
}
|
|
1330
|
+
/** A textarea with its label, where its text comes from, and a counter against the Host's limit. */
|
|
1331
|
+
function PromptField({ t, label, source, placeholder, text, onChange }) {
|
|
1332
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: label }), source === undefined ? null : (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: source })] }), (0, jsx_runtime_1.jsx)("textarea", { className: "vdg-textarea", "aria-label": label, rows: 4, maxLength: shared_1.MAX_PROMPT_CHARS, placeholder: placeholder, value: text, onChange: (event) => { onChange(event.target.value); } }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", style: { alignSelf: 'flex-end', fontVariantNumeric: 'tabular-nums' }, children: (0, i18n_1.fill)(t('dialog.counter'), { count: text.length, max: shared_1.MAX_PROMPT_CHARS }) })] }));
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* This session's additional review prompt: the text the reviewer reads while the user is present.
|
|
1336
|
+
* The session follows the system default until it saves its own text, and can return to it.
|
|
1337
|
+
*/
|
|
1338
|
+
function ReviewPromptDialog({ t, view, value, onSave, onClose }) {
|
|
1339
|
+
const saved = view.reviewPrompt ?? value.reviewPrompt;
|
|
1340
|
+
const custom = view.reviewPrompt !== null;
|
|
1341
|
+
const [text, setText] = (0, react_1.useState)(saved);
|
|
1342
|
+
const [saving, setSaving] = (0, react_1.useState)(false);
|
|
1343
|
+
const [failure, setFailure] = (0, react_1.useState)(undefined);
|
|
1344
|
+
const save = (prompt) => {
|
|
1345
|
+
setSaving(true);
|
|
1346
|
+
setFailure(undefined);
|
|
1347
|
+
onSave(prompt).then(onClose, (error) => {
|
|
1348
|
+
setFailure((0, i18n_1.fill)(t('reviewPrompt.saveFailed'), { error: (0, shared_1.messageOf)(error) }));
|
|
1349
|
+
}).finally(() => { setSaving(false); });
|
|
1350
|
+
};
|
|
1351
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Dialog, { title: t('reviewPrompt.title'), size: "md", onClose: onClose, footer: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", onClick: onClose, children: t('dialog.cancel') }), custom ? (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: saving, onClick: () => { save(null); }, children: t('reviewPrompt.followSystem') }) : null, (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn primary", disabled: saving || text.length > shared_1.MAX_PROMPT_CHARS || text.trim() === saved, onClick: () => { save(text.trim()); }, children: t('reviewPrompt.save') })] })), children: [(0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('reviewPrompt.body') }), value.review === 'off' ? (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('quick.reviewOff') }) : null, (0, jsx_runtime_1.jsx)(PromptField, { t: t, label: t('reviewPrompt.label'), source: t(custom ? 'reviewPrompt.sourceSession' : 'reviewPrompt.sourceSystem'), placeholder: t('reviewPrompt.placeholder'), text: text, onChange: setText }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: failure })] }));
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Turn unattended mode on with a deny policy the user confirms, edit the policy, or turn it off.
|
|
1355
|
+
* The policy is prefilled from the system default; nothing applies until the user confirms.
|
|
1356
|
+
*/
|
|
1357
|
+
function UnattendedDialog({ t, view, value, onApply, onClose }) {
|
|
1358
|
+
const enabled = view.unattended;
|
|
1359
|
+
const [text, setText] = (0, react_1.useState)(enabled ? view.denyPolicy : value.unattendedPolicy);
|
|
1360
|
+
const [saving, setSaving] = (0, react_1.useState)(false);
|
|
1361
|
+
const [failure, setFailure] = (0, react_1.useState)(undefined);
|
|
1362
|
+
const apply = (policy) => {
|
|
1363
|
+
setSaving(true);
|
|
1364
|
+
setFailure(undefined);
|
|
1365
|
+
onApply(policy).then(onClose, (error) => {
|
|
1366
|
+
setFailure((0, i18n_1.fill)(t('unattended.failed'), { error: (0, shared_1.messageOf)(error) }));
|
|
1367
|
+
}).finally(() => { setSaving(false); });
|
|
1368
|
+
};
|
|
1369
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Dialog, { title: t(enabled ? 'unattended.editTitle' : 'unattended.enableTitle'), size: "md", onClose: onClose, footer: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", onClick: onClose, children: t('dialog.cancel') }), enabled ? (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: saving, onClick: () => { apply(null); }, children: t('unattended.disable') }) : null, (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn primary", disabled: saving || text.length > shared_1.MAX_PROMPT_CHARS, onClick: () => { apply(text.trim()); }, children: t(enabled ? 'unattended.update' : 'unattended.enable') })] })), children: [(0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('unattended.body') }), (0, jsx_runtime_1.jsx)(PromptField, { t: t, label: t('unattended.policyLabel'), source: !enabled && value.unattendedPolicy !== '' ? t('unattended.sourceSystem') : undefined, placeholder: t('unattended.placeholder'), text: text, onChange: setText }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: failure })] }));
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
},
|
|
1373
|
+
"./records": function (module, exports, require) {
|
|
1374
|
+
"use strict";
|
|
1375
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1376
|
+
exports.RecordsPanel = RecordsPanel;
|
|
1377
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1378
|
+
/**
|
|
1379
|
+
* Settings › Auto approval › Decision log, after VulcanCode's records page: every decision the
|
|
1380
|
+
* decision model or the review LLM took part in, newest first, with filters, marks that say what
|
|
1381
|
+
* the model should have answered, export of marked records as training samples, and clearing.
|
|
1382
|
+
*/
|
|
1383
|
+
const react_1 = require("react");
|
|
1384
|
+
const i18n_1 = require("./i18n");
|
|
1385
|
+
const shared_1 = require("./shared");
|
|
1386
|
+
const ui_1 = require("./ui");
|
|
1387
|
+
const PAGE_SIZE = 50;
|
|
1388
|
+
const RESULTS = [...shared_1.DECISIONS, 'failed'];
|
|
1389
|
+
const RESULT_TONES = { allow: 'ok', review: 'warn', block: 'bad', failed: 'outline' };
|
|
1390
|
+
/** The part of a call a person recognizes it by. */
|
|
1391
|
+
function callSummary(record) {
|
|
1392
|
+
try {
|
|
1393
|
+
const parsed = JSON.parse(record.arguments);
|
|
1394
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
1395
|
+
const fields = parsed;
|
|
1396
|
+
for (const key of ['command', 'path', 'file_path', 'destination', 'url']) {
|
|
1397
|
+
const value = fields[key];
|
|
1398
|
+
if (typeof value === 'string' && value !== '')
|
|
1399
|
+
return value;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
catch {
|
|
1404
|
+
// Arguments that are not JSON are summarized by the tool name.
|
|
1405
|
+
}
|
|
1406
|
+
return record.toolName;
|
|
1407
|
+
}
|
|
1408
|
+
function resultOf(record) {
|
|
1409
|
+
return record.judge.label ?? 'failed';
|
|
1410
|
+
}
|
|
1411
|
+
function pageQuery(filters, before) {
|
|
1412
|
+
const query = new URLSearchParams({ limit: String(PAGE_SIZE) });
|
|
1413
|
+
if (filters.label !== undefined)
|
|
1414
|
+
query.set('label', filters.label);
|
|
1415
|
+
if (filters.workspace !== undefined)
|
|
1416
|
+
query.set('workspace', filters.workspace);
|
|
1417
|
+
if (filters.markedOnly)
|
|
1418
|
+
query.set('marked', '1');
|
|
1419
|
+
if (before !== undefined)
|
|
1420
|
+
query.set('before', before);
|
|
1421
|
+
return `records?${query.toString()}`;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Render decision history and its actions using the dictionary and current recording state.
|
|
1425
|
+
* 根据本地化词典和当前记录开关状态渲染判定历史及其操作。
|
|
1426
|
+
*/
|
|
1427
|
+
function RecordsPanel({ t, recording }) {
|
|
1428
|
+
const [filters, setFilters] = (0, react_1.useState)({ label: undefined, workspace: undefined, markedOnly: false });
|
|
1429
|
+
const [page, setPage] = (0, react_1.useState)(undefined);
|
|
1430
|
+
const [loadError, setLoadError] = (0, react_1.useState)(undefined);
|
|
1431
|
+
const [actionError, setActionError] = (0, react_1.useState)(undefined);
|
|
1432
|
+
const [notice, setNotice] = (0, react_1.useState)(undefined);
|
|
1433
|
+
const [busy, setBusy] = (0, react_1.useState)(undefined);
|
|
1434
|
+
const [clearing, setClearing] = (0, react_1.useState)(false);
|
|
1435
|
+
const sequence = (0, react_1.useRef)(0);
|
|
1436
|
+
const load = (0, react_1.useCallback)(async (next, previous) => {
|
|
1437
|
+
const current = ++sequence.current;
|
|
1438
|
+
try {
|
|
1439
|
+
const last = previous?.records[previous.records.length - 1]?.id;
|
|
1440
|
+
const result = await (0, shared_1.callPanel)('GET', pageQuery(next, last));
|
|
1441
|
+
if (sequence.current !== current)
|
|
1442
|
+
return;
|
|
1443
|
+
setPage(previous === undefined ? result : { ...result, records: [...previous.records, ...result.records] });
|
|
1444
|
+
setLoadError(undefined);
|
|
1445
|
+
}
|
|
1446
|
+
catch (error) {
|
|
1447
|
+
if (sequence.current === current)
|
|
1448
|
+
setLoadError((0, shared_1.messageOf)(error));
|
|
1449
|
+
}
|
|
1450
|
+
}, []);
|
|
1451
|
+
(0, react_1.useEffect)(() => { void load(filters, undefined); }, [filters, load]);
|
|
1452
|
+
(0, react_1.useEffect)(() => () => { sequence.current += 1; }, []);
|
|
1453
|
+
const run = (name, work, failed) => {
|
|
1454
|
+
setBusy(name);
|
|
1455
|
+
setActionError(undefined);
|
|
1456
|
+
setNotice(undefined);
|
|
1457
|
+
work().catch((error) => {
|
|
1458
|
+
setActionError((0, i18n_1.fill)(t(failed), { error: (0, shared_1.messageOf)(error) }));
|
|
1459
|
+
}).finally(() => { setBusy(undefined); });
|
|
1460
|
+
};
|
|
1461
|
+
const mark = (record, correction) => {
|
|
1462
|
+
run(`mark-${record.id}`, async () => {
|
|
1463
|
+
const updated = await (0, shared_1.callPanel)('POST', 'records/mark', { id: record.id, correction });
|
|
1464
|
+
setPage(current => current && {
|
|
1465
|
+
...current,
|
|
1466
|
+
marked: current.marked + (updated.correction === undefined ? 0 : 1) - (record.correction === undefined ? 0 : 1),
|
|
1467
|
+
records: current.records.map(item => item.id === updated.id ? updated : item),
|
|
1468
|
+
});
|
|
1469
|
+
}, 'records.markFailed');
|
|
1470
|
+
};
|
|
1471
|
+
const exportSamples = () => {
|
|
1472
|
+
run('export', async () => {
|
|
1473
|
+
const exported = await (0, shared_1.callPanel)('POST', 'records/export');
|
|
1474
|
+
const url = URL.createObjectURL(new Blob([exported.content], { type: 'application/jsonl' }));
|
|
1475
|
+
const anchor = document.createElement('a');
|
|
1476
|
+
anchor.href = url;
|
|
1477
|
+
anchor.download = exported.fileName;
|
|
1478
|
+
anchor.click();
|
|
1479
|
+
setTimeout(() => { URL.revokeObjectURL(url); }, 60_000);
|
|
1480
|
+
setNotice((0, i18n_1.fill)(t('records.exported'), { count: exported.count }));
|
|
1481
|
+
}, 'records.exportFailed');
|
|
1482
|
+
};
|
|
1483
|
+
const clear = () => {
|
|
1484
|
+
run('clear', async () => {
|
|
1485
|
+
await (0, shared_1.callPanel)('POST', 'records/clear');
|
|
1486
|
+
setClearing(false);
|
|
1487
|
+
await load(filters, undefined);
|
|
1488
|
+
}, 'records.clearFailed');
|
|
1489
|
+
};
|
|
1490
|
+
const clearButton = ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn ghost sm", disabled: busy !== undefined, onClick: () => { setClearing(true); }, children: t('records.clear') }));
|
|
1491
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [recording ? null : (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('records.paused') }), page === undefined
|
|
1492
|
+
? (loadError === undefined
|
|
1493
|
+
? (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('loading') })
|
|
1494
|
+
: ((0, jsx_runtime_1.jsx)("div", { className: "vdg-alert bad", children: (0, jsx_runtime_1.jsxs)("div", { className: "vdg-between", children: [(0, jsx_runtime_1.jsx)("span", { children: (0, i18n_1.fill)(t('records.loadFailed'), { error: loadError }) }), clearButton] }) })))
|
|
1495
|
+
: ((0, jsx_runtime_1.jsxs)(ui_1.Section, { title: (0, i18n_1.fill)(t('records.count'), { count: page.total }), lead: (0, i18n_1.fill)(t('records.hint'), { records: page.retentionRecords, days: page.retentionDays }), flush: true, actions: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn sm", disabled: busy !== undefined || page.marked === 0, onClick: exportSamples, children: (0, i18n_1.fill)(t('records.export'), { count: page.marked }) }), clearButton] })), children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-record-filters", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", role: "group", "aria-label": t('records.filterResult'), children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: t('records.filterResult') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-chip", "aria-pressed": filters.label === undefined, onClick: () => { setFilters({ ...filters, label: undefined }); }, children: t('records.all') }), RESULTS.map(result => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-chip", "aria-pressed": filters.label === result, onClick: () => { setFilters({ ...filters, label: result }); }, children: t(`records.label.${result}`) }, result)))] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-chip", "aria-pressed": filters.markedOnly, onClick: () => { setFilters({ ...filters, markedOnly: !filters.markedOnly }); }, children: t('records.markedOnly') }), page.workspaces.length > 0
|
|
1496
|
+
? ((0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", style: { minWidth: 0, maxWidth: 320 }, "aria-label": t('records.detail.workspace'), value: filters.workspace ?? '', onChange: (event) => { setFilters({ ...filters, workspace: event.target.value === '' ? undefined : event.target.value }); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: t('records.allWorkspaces') }), page.workspaces.map(workspace => (0, jsx_runtime_1.jsx)("option", { value: workspace, children: workspace }, workspace))] }))
|
|
1497
|
+
: null] }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: loadError === undefined ? undefined : (0, i18n_1.fill)(t('records.loadFailed'), { error: loadError }) }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: actionError }), notice === undefined ? null : (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", role: "status", children: notice })] }), page.records.length === 0
|
|
1498
|
+
? (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", style: { padding: 16 }, children: t('records.empty') })
|
|
1499
|
+
: page.records.map(record => ((0, jsx_runtime_1.jsx)(RecordItem, { t: t, record: record, busy: busy !== undefined, onMark: (correction) => { mark(record, correction); } }, record.id))), page.hasMore
|
|
1500
|
+
? ((0, jsx_runtime_1.jsx)("div", { className: "vdg-record-more", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn sm", onClick: () => { void load(filters, page); }, children: t('records.loadMore') }) }))
|
|
1501
|
+
: null] })), clearing
|
|
1502
|
+
? ((0, jsx_runtime_1.jsxs)(ui_1.Dialog, { title: t('records.clearTitle'), size: "md", onClose: () => { if (busy === undefined)
|
|
1503
|
+
setClearing(false); }, footer: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: busy !== undefined, onClick: () => { setClearing(false); }, children: t('dialog.cancel') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn primary", disabled: busy !== undefined, onClick: clear, children: t('records.clear') })] })), children: [(0, jsx_runtime_1.jsx)("p", { style: { margin: 0 }, children: (0, i18n_1.fill)(t('records.clearDescription'), { count: page?.total ?? 0 }) }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: actionError })] }))
|
|
1504
|
+
: null] }));
|
|
1505
|
+
}
|
|
1506
|
+
function RecordItem({ t, record, busy, onMark }) {
|
|
1507
|
+
const result = resultOf(record);
|
|
1508
|
+
const trace = record.judge.trace;
|
|
1509
|
+
const review = record.review;
|
|
1510
|
+
const facts = [
|
|
1511
|
+
{ label: t('records.detail.tool'), value: record.toolName },
|
|
1512
|
+
...record.workspaceRoot === undefined ? [] : [{ label: t('records.detail.workspace'), value: record.workspaceRoot }],
|
|
1513
|
+
{ label: t('records.detail.attendance'), value: t(record.attended ? 'records.attended' : 'records.unattended') },
|
|
1514
|
+
...trace === undefined ? [] : [{ label: t('records.detail.model'), value: [trace.model, trace.via, trace.policyVersion].filter(Boolean).join(' · ') }],
|
|
1515
|
+
...trace?.logits === undefined
|
|
1516
|
+
? []
|
|
1517
|
+
: [{ label: t('records.detail.scores'), value: shared_1.DECISIONS.map((name, index) => `${t(`records.label.${name}`)} ${trace.logits[index]?.toFixed(2) ?? '—'}`).join(' · ') }],
|
|
1518
|
+
...record.judge.reasonCode === undefined ? [] : [{ label: t('records.detail.rule'), value: record.judge.reasonCode }],
|
|
1519
|
+
{ label: t('records.detail.judgeTime'), value: (0, i18n_1.fill)(t('ms'), { value: record.judge.elapsedMs }) },
|
|
1520
|
+
...record.judge.error === undefined ? [] : [{ label: t('records.detail.error'), value: record.judge.error }],
|
|
1521
|
+
...review === undefined
|
|
1522
|
+
? []
|
|
1523
|
+
: [
|
|
1524
|
+
{
|
|
1525
|
+
label: t('records.detail.review'),
|
|
1526
|
+
value: [review.verdict === undefined ? undefined : t(`records.verdict.${review.verdict}`), review.reason ?? review.error].filter(Boolean).join(' · ') || '—',
|
|
1527
|
+
},
|
|
1528
|
+
{ label: t('records.detail.reviewTime'), value: (0, i18n_1.fill)(t('ms'), { value: review.elapsedMs }) },
|
|
1529
|
+
],
|
|
1530
|
+
];
|
|
1531
|
+
const outcome = `records.outcome.${record.outcome}`;
|
|
1532
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `vdg-record ${RESULT_TONES[result]}`, "data-record-id": record.id, children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-record-title", children: callSummary(record) }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: RESULT_TONES[result], children: t(`records.label.${result}`) }), (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "outline", children: OUTCOME_KEYS.has(outcome) ? t(outcome) : record.outcome }), record.correction === undefined ? null : (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: "info", children: (0, i18n_1.fill)(t('records.marked'), { label: t(`records.label.${record.correction}`) }) }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: [(0, ui_1.when)(record.judgedAtMs), record.workspaceRoot].filter(Boolean).join(' · ') })] }), (0, jsx_runtime_1.jsxs)("details", { className: "vdg-details", children: [(0, jsx_runtime_1.jsx)("summary", { children: t('records.details') }), (0, jsx_runtime_1.jsx)("dl", { className: "vdg-facts", style: { marginTop: 8 }, children: facts.map(fact => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("dt", { children: fact.label }), (0, jsx_runtime_1.jsx)("dd", { children: fact.value })] }, fact.label))) }), (0, jsx_runtime_1.jsx)("pre", { className: "vdg-code", style: { marginTop: 8 }, children: record.arguments })] }), trace === undefined
|
|
1533
|
+
? (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('records.noTrace') })
|
|
1534
|
+
: ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: t('records.markAs') }), shared_1.DECISIONS.map(name => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-chip", "aria-pressed": record.correction === name, disabled: busy, onClick: () => { onMark(record.correction === name ? null : name); }, children: t(`records.label.${name}`) }, name)))] }))] }));
|
|
1535
|
+
}
|
|
1536
|
+
/** Outcomes this client has words for; a newer Host's outcome is shown as its code. */
|
|
1537
|
+
const OUTCOME_KEYS = new Set([
|
|
1538
|
+
'records.outcome.judge_allowed', 'records.outcome.review_allowed', 'records.outcome.review_denied', 'records.outcome.unattended_denied',
|
|
1539
|
+
'records.outcome.user_allowed', 'records.outcome.user_denied', 'records.outcome.approval_expired', 'records.outcome.not_asked',
|
|
1540
|
+
'records.outcome.host_denied',
|
|
1541
|
+
]);
|
|
1542
|
+
|
|
1543
|
+
},
|
|
1544
|
+
"./settings": function (module, exports, require) {
|
|
1545
|
+
"use strict";
|
|
1546
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1547
|
+
exports.decisionStatus = decisionStatus;
|
|
1548
|
+
exports.SettingsPage = SettingsPage;
|
|
1549
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1550
|
+
/**
|
|
1551
|
+
* Settings › Auto approval: the whole configuration of the plugin as numbered Vulcan sections.
|
|
1552
|
+
* Every control saves on its own as soon as it changes; only the review rules and the additional
|
|
1553
|
+
* prompts, being free text, are saved together with a button. The decision log opens in place of
|
|
1554
|
+
* the page.
|
|
1555
|
+
*/
|
|
1556
|
+
const react_1 = require("react");
|
|
1557
|
+
const i18n_1 = require("./i18n");
|
|
1558
|
+
const cloud_1 = require("./cloud");
|
|
1559
|
+
const local_1 = require("./local");
|
|
1560
|
+
const records_1 = require("./records");
|
|
1561
|
+
const shared_1 = require("./shared");
|
|
1562
|
+
const ui_1 = require("./ui");
|
|
1563
|
+
/** Sample calls for trying the backend. */
|
|
1564
|
+
const TRY_CALLS = {
|
|
1565
|
+
safe: { tool: 'shell_command', args: { command: 'git status', shell: 'bash' } },
|
|
1566
|
+
risky: { tool: 'shell_command', args: { command: 'git push --force origin main', shell: 'bash' } },
|
|
1567
|
+
};
|
|
1568
|
+
// The sections appear in the same order in navigation and in the page.
|
|
1569
|
+
// 分区在导航和正文中按同一顺序排列。
|
|
1570
|
+
const SETTINGS_VIEWS = ['engine', 'policy', 'records'];
|
|
1571
|
+
/** What the status section says about decisions, from the saved settings and this machine's state. */
|
|
1572
|
+
function decisionStatus(t, value, live, models) {
|
|
1573
|
+
if (live == null)
|
|
1574
|
+
return { tone: 'outline', text: t('loading') };
|
|
1575
|
+
if (value.backend === 'cloud') {
|
|
1576
|
+
if (!live.account.signedIn)
|
|
1577
|
+
return { tone: 'warn', text: t('status.decision.signedOut') };
|
|
1578
|
+
const model = value.cloudModel === '' ? models?.[0] : value.cloudModel;
|
|
1579
|
+
if (model === undefined || (models !== undefined && !models.includes(model)))
|
|
1580
|
+
return { tone: 'warn', text: t('status.decision.cloudNoModel') };
|
|
1581
|
+
return { tone: 'ok', text: (0, i18n_1.fill)(t('status.decision.cloud'), { model }) };
|
|
1582
|
+
}
|
|
1583
|
+
const pack = live.packs.find(item => item.id === value.localPack && item.installed !== null);
|
|
1584
|
+
const model = live.models.find(item => item.id === value.localModel);
|
|
1585
|
+
if (pack === undefined || model === undefined)
|
|
1586
|
+
return { tone: 'warn', text: t('status.decision.localMissing') };
|
|
1587
|
+
return { tone: 'ok', text: (0, i18n_1.fill)(t('status.decision.local'), { pack: pack.id, model: model.name }) };
|
|
1588
|
+
}
|
|
1589
|
+
function StatusSection({ services, t, value, live }) {
|
|
1590
|
+
const heldByReview = (0, ui_1.useWatch)(services.autoReview);
|
|
1591
|
+
const listed = (0, ui_1.useWatch)(services.autoListed);
|
|
1592
|
+
const models = (0, ui_1.useWatch)(services.cloudModels);
|
|
1593
|
+
const decision = decisionStatus(t, value, live, models?.kind === 'ready' ? models.models : undefined);
|
|
1594
|
+
let auto;
|
|
1595
|
+
if (value.mode === 'standalone')
|
|
1596
|
+
auto = { tone: 'outline', text: 'status.auto.standalone', badge: 'tone.off' };
|
|
1597
|
+
else if (heldByReview === true)
|
|
1598
|
+
auto = { tone: 'warn', text: 'status.auto.held', badge: 'tone.warn' };
|
|
1599
|
+
else if (listed === true)
|
|
1600
|
+
auto = { tone: 'ok', text: 'status.auto.provided', badge: 'tone.ok' };
|
|
1601
|
+
else
|
|
1602
|
+
auto = { tone: 'outline', text: 'status.auto.pending', badge: 'status.auto.pending' };
|
|
1603
|
+
return ((0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('status.title'), flush: true, children: (0, jsx_runtime_1.jsxs)("div", { className: "vdg-rows", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-row-label", children: t('status.decision') }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-row-value", children: [decision.text, (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: decision.tone, children: t(decision.tone === 'ok' ? 'tone.ok' : 'tone.warn') })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-row-label", children: t('status.auto') }), (0, jsx_runtime_1.jsxs)("span", { className: "vdg-row-value", children: [t(auto.text), (0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: auto.tone, children: t(auto.badge) })] })] })] }) }));
|
|
1604
|
+
}
|
|
1605
|
+
function OptionsSection({ t, value, disabled, save }) {
|
|
1606
|
+
const row = (field, prefix, values) => ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-row", children: [(0, jsx_runtime_1.jsx)("span", { children: t(`${prefix}.title`) }), (0, jsx_runtime_1.jsx)(ui_1.Segments, { label: t(`${prefix}.title`), value: value[field], options: values.map(item => ({ value: item, title: t(`${prefix}.${item}`) })), disabled: disabled, onChoose: (next) => { save({ [field]: next }); } })] }));
|
|
1607
|
+
return ((0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('options.title'), lead: t('options.lead'), flush: true, children: (0, jsx_runtime_1.jsxs)("div", { className: "vdg-rows", children: [row('crossProjectWrites', 'cross', ['allow', 'review']), row('databaseReset', 'database', ['confirm', 'allow']), row('projectReset', 'project', ['confirm', 'allow'])] }) }));
|
|
1608
|
+
}
|
|
1609
|
+
/** Provider and model pickers for a chosen review model, from the Host's model catalog. */
|
|
1610
|
+
function ReviewModelPicker({ services, t, value, disabled, save }) {
|
|
1611
|
+
const catalog = (0, ui_1.useWatch)(services.modelCatalog);
|
|
1612
|
+
(0, ui_1.usePolling)(services.modelCatalog, undefined);
|
|
1613
|
+
if (catalog === undefined)
|
|
1614
|
+
return (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('review.catalogFailed') });
|
|
1615
|
+
const provider = value.reviewProvider ?? '';
|
|
1616
|
+
const model = value.reviewModel ?? '';
|
|
1617
|
+
const group = catalog.groups.find(item => item.id === provider);
|
|
1618
|
+
// A saved choice the catalog no longer lists stays visible instead of silently changing.
|
|
1619
|
+
const providers = group === undefined && provider !== '' ? [{ id: provider, name: provider, models: [] }, ...catalog.groups] : catalog.groups;
|
|
1620
|
+
const models = group?.models ?? [];
|
|
1621
|
+
const modelList = model === '' || models.some(item => item.id === model) ? models : [{ id: model, name: model }, ...models];
|
|
1622
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", style: { alignItems: 'flex-start' }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-field", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: t('review.provider') }), (0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('review.provider'), value: provider, disabled: disabled, onChange: (event) => { save({ reviewProvider: event.target.value === '' ? undefined : event.target.value, reviewModel: undefined }); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: t('review.pick') }), providers.map(item => (0, jsx_runtime_1.jsx)("option", { value: item.id, children: item.name }, item.id))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-field", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: t('review.model') }), (0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('review.model'), value: model, disabled: disabled || provider === '', onChange: (event) => { save({ reviewModel: event.target.value === '' ? undefined : event.target.value }); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: t('review.pick') }), modelList.map(item => (0, jsx_runtime_1.jsx)("option", { value: item.id, children: item.name }, item.id))] })] })] }), provider === '' || model === '' ? (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('review.missing') }) : null] }));
|
|
1623
|
+
}
|
|
1624
|
+
function ReviewSection({ services, t, value, disabled, save }) {
|
|
1625
|
+
const reviews = ['session', 'custom', 'off'];
|
|
1626
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('review.title'), lead: t('review.lead'), children: [(0, jsx_runtime_1.jsx)(ui_1.Choices, { label: t('review.title'), value: value.review, options: reviews.map(item => ({ value: item, title: t(`review.${item}`), hint: t(`review.${item}.hint`) })), disabled: disabled, onChoose: (next) => { save({ review: next }); } }), value.review === 'custom' ? (0, jsx_runtime_1.jsx)(ReviewModelPicker, { services: services, t: t, value: value, disabled: disabled, save: save }) : null, value.review === 'off'
|
|
1627
|
+
? null
|
|
1628
|
+
: ((0, jsx_runtime_1.jsxs)("label", { className: "vdg-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: value.llmOverridesBlock, disabled: disabled, onChange: (event) => { save({ llmOverridesBlock: event.target.checked }); } }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-title", children: t('override.title') }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-hint", children: t('override.hint') })] })] }))] }));
|
|
1629
|
+
}
|
|
1630
|
+
const RULES = [
|
|
1631
|
+
{ field: 'forbidRules', key: 'forbid' },
|
|
1632
|
+
{ field: 'denyRules', key: 'deny' },
|
|
1633
|
+
{ field: 'allowRules', key: 'allow' },
|
|
1634
|
+
];
|
|
1635
|
+
function RulesSection({ t, value, disabled, saveAsync }) {
|
|
1636
|
+
const [draft, setDraft] = (0, react_1.useState)({});
|
|
1637
|
+
const dirty = RULES.some(rule => draft[rule.field] !== undefined && draft[rule.field] !== value[rule.field]);
|
|
1638
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('rules.title'), lead: t('rules.lead'), children: [RULES.map(rule => ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-field", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: t(`rules.${rule.key}`) }), (0, jsx_runtime_1.jsx)("textarea", { className: "vdg-textarea", "aria-label": t(`rules.${rule.key}`), rows: 3, value: draft[rule.field] ?? value[rule.field], placeholder: t(`rules.${rule.key}.placeholder`), disabled: disabled, onChange: (event) => { setDraft(staged => ({ ...staged, [rule.field]: event.target.value })); } })] }, rule.field))), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", style: { justifyContent: 'flex-end' }, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: !dirty || disabled, onClick: () => { setDraft({}); }, children: t('rules.discard') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn primary", disabled: !dirty || disabled, onClick: () => { void saveAsync(draft).then((landed) => { if (landed)
|
|
1639
|
+
setDraft({}); }); }, children: t('rules.save') })] })] }));
|
|
1640
|
+
}
|
|
1641
|
+
const PROMPTS = [
|
|
1642
|
+
{ field: 'reviewPrompt', key: 'review', placeholder: 'reviewPrompt.placeholder' },
|
|
1643
|
+
{ field: 'unattendedPolicy', key: 'unattended', placeholder: 'unattended.placeholder' },
|
|
1644
|
+
];
|
|
1645
|
+
/** The system defaults of the review prompt and the unattended deny policy. */
|
|
1646
|
+
function PromptsSection({ t, value, disabled, saveAsync }) {
|
|
1647
|
+
const [draft, setDraft] = (0, react_1.useState)({});
|
|
1648
|
+
const text = (field) => draft[field] ?? value[field];
|
|
1649
|
+
const dirty = PROMPTS.some(prompt => draft[prompt.field] !== undefined && draft[prompt.field] !== value[prompt.field]);
|
|
1650
|
+
const tooLong = PROMPTS.some(prompt => text(prompt.field).length > shared_1.MAX_PROMPT_CHARS);
|
|
1651
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('prompts.title'), lead: t('prompts.lead'), children: [PROMPTS.map(prompt => ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-field", children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-label", children: t(`prompts.${prompt.key}`) }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: t(`prompts.${prompt.key}.hint`) }), (0, jsx_runtime_1.jsx)("textarea", { className: "vdg-textarea", "aria-label": t(`prompts.${prompt.key}`), rows: 3, value: text(prompt.field), placeholder: t(prompt.placeholder), disabled: disabled, onChange: (event) => { setDraft(staged => ({ ...staged, [prompt.field]: event.target.value })); } }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", style: { alignSelf: 'flex-end', fontVariantNumeric: 'tabular-nums' }, children: (0, i18n_1.fill)(t('dialog.counter'), { count: text(prompt.field).length, max: shared_1.MAX_PROMPT_CHARS }) })] }, prompt.field))), (0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", style: { justifyContent: 'flex-end' }, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: !dirty || disabled, onClick: () => { setDraft({}); }, children: t('rules.discard') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn primary", disabled: !dirty || disabled || tooLong, onClick: () => {
|
|
1652
|
+
const changes = Object.fromEntries(Object.entries(draft).map(([field, next]) => [field, next.trim()]));
|
|
1653
|
+
void saveAsync(changes).then((landed) => { if (landed)
|
|
1654
|
+
setDraft({}); });
|
|
1655
|
+
}, children: t('prompts.save') })] })] }));
|
|
1656
|
+
}
|
|
1657
|
+
/** The decision log switch and the way into the log. */
|
|
1658
|
+
/**
|
|
1659
|
+
* Render the recording preference using the dictionary, current settings, disabled state and save callback.
|
|
1660
|
+
* 使用文案、当前设置、禁用状态与保存回调渲染判定记录开关。
|
|
1661
|
+
*/
|
|
1662
|
+
function RecordsSection({ t, value, disabled, save }) {
|
|
1663
|
+
return ((0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('records.title'), lead: t('records.entryHint'), children: (0, jsx_runtime_1.jsxs)("label", { className: "vdg-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: value.recordDecisions, disabled: disabled, onChange: (event) => { save({ recordDecisions: event.target.checked }); } }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-title", children: t('records.enabled') }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-hint", children: t('records.enabledHint') })] })] }) }));
|
|
1664
|
+
}
|
|
1665
|
+
function AskSection({ t, value, disabled, save }) {
|
|
1666
|
+
const [text, setText] = (0, react_1.useState)(String(value.askTimeout));
|
|
1667
|
+
(0, react_1.useEffect)(() => { setText(String(value.askTimeout)); }, [value.askTimeout]);
|
|
1668
|
+
const seconds = Number(text);
|
|
1669
|
+
const valid = Number.isInteger(seconds) && seconds >= 1 && seconds <= shared_1.MAX_ASK_TIMEOUT;
|
|
1670
|
+
const commit = () => {
|
|
1671
|
+
if (valid && seconds !== value.askTimeout)
|
|
1672
|
+
save({ askTimeout: seconds });
|
|
1673
|
+
else if (!valid)
|
|
1674
|
+
setText(String(value.askTimeout));
|
|
1675
|
+
};
|
|
1676
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('ask.title'), children: [(0, jsx_runtime_1.jsxs)("label", { className: "vdg-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: value.askTimeout > 0, disabled: disabled, onChange: (event) => { save({ askTimeout: event.target.checked ? shared_1.DEFAULT_ASK_TIMEOUT : 0 }); } }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-title", children: t('ask.timeout') }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-hint", children: t('ask.timeout.hint') })] })] }), value.askTimeout > 0
|
|
1677
|
+
? ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", style: { paddingLeft: 24 }, children: [(0, jsx_runtime_1.jsx)("input", { className: "vdg-input", type: "number", min: 1, max: shared_1.MAX_ASK_TIMEOUT, step: 1, style: { width: 96 }, "aria-label": t('ask.timeout'), "aria-invalid": !valid, value: text, disabled: disabled, onChange: (event) => { setText(event.target.value); }, onBlur: commit, onKeyDown: (event) => { if (event.key === 'Enter')
|
|
1678
|
+
commit(); } }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: t('ask.seconds') })] }))
|
|
1679
|
+
: null] }));
|
|
1680
|
+
}
|
|
1681
|
+
function TrySection({ t }) {
|
|
1682
|
+
const { busy, error, act } = (0, ui_1.useAction)();
|
|
1683
|
+
const [sample, setSample] = (0, react_1.useState)('safe');
|
|
1684
|
+
const [verdict, setVerdict] = (0, react_1.useState)(undefined);
|
|
1685
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Section, { title: t('try.title'), lead: t('try.lead'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsxs)("select", { className: "vdg-select", "aria-label": t('try.title'), value: sample, onChange: (event) => { setSample(event.target.value === 'risky' ? 'risky' : 'safe'); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "safe", children: t('try.safe') }), (0, jsx_runtime_1.jsx)("option", { value: "risky", children: t('try.risky') })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn", disabled: busy !== undefined, onClick: () => {
|
|
1686
|
+
setVerdict(undefined);
|
|
1687
|
+
act('try', async () => { setVerdict(await (0, shared_1.callPanel)('POST', 'test', TRY_CALLS[sample])); });
|
|
1688
|
+
}, children: t(busy === undefined ? 'try.run' : 'try.running') })] }), verdict === undefined
|
|
1689
|
+
? null
|
|
1690
|
+
: ((0, jsx_runtime_1.jsxs)("div", { className: "vdg-inline", children: [(0, jsx_runtime_1.jsx)(ui_1.Badge, { tone: verdict.decision === 'allow' ? 'ok' : verdict.decision === 'review' ? 'warn' : 'bad', children: ['allow', 'review', 'block'].includes(verdict.decision) ? t(`decision.${verdict.decision}`) : verdict.decision }), (0, jsx_runtime_1.jsx)("span", { className: "vdg-hint", children: (0, i18n_1.fill)(t('try.result'), { decision: verdict.decision, ms: verdict.elapsed_ms, backend: verdict.backend, via: verdict.via }) })] })), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: error === undefined ? undefined : (0, i18n_1.fill)(t('try.failed'), { error }) })] }));
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Render the engine, policy and records views using the guard services and localized dictionary.
|
|
1694
|
+
* 使用审批服务与本地化词典渲染判定引擎、复核规则与记录分区。
|
|
1695
|
+
*/
|
|
1696
|
+
function SettingsPage({ services, t }) {
|
|
1697
|
+
const settings = (0, ui_1.useSettings)(services.form, t);
|
|
1698
|
+
const live = (0, ui_1.useWatch)(services.panel);
|
|
1699
|
+
// The selected section controls visibility while keeping settings drafts mounted.
|
|
1700
|
+
// 所选分区控制显示状态,同时保留已挂载的设置草稿。
|
|
1701
|
+
const [view, setView] = (0, react_1.useState)('engine');
|
|
1702
|
+
// The root is the scroll target when the user changes sections from the sticky navigation.
|
|
1703
|
+
// 用户从吸附导航切换分区时,将设置页根节点作为滚动目标。
|
|
1704
|
+
const page = (0, react_1.useRef)(null);
|
|
1705
|
+
// Each navigation control points to its own panel within this settings instance.
|
|
1706
|
+
// 每个分区按钮指向当前设置实例中的对应面板。
|
|
1707
|
+
const viewId = (0, react_1.useId)();
|
|
1708
|
+
const active = live != null && (0, shared_1.running)(live.job);
|
|
1709
|
+
(0, ui_1.usePolling)(services.panel, active ? 1000 : undefined);
|
|
1710
|
+
const value = settings.value;
|
|
1711
|
+
if (settings.status === 'loading' || (settings.status === 'ready' && value === undefined)) {
|
|
1712
|
+
return (0, jsx_runtime_1.jsx)("div", { className: "vdg vdg-page", children: (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('loading') }) });
|
|
1713
|
+
}
|
|
1714
|
+
if (settings.status === 'unavailable' || value === undefined) {
|
|
1715
|
+
return (0, jsx_runtime_1.jsx)("div", { className: "vdg vdg-page", children: (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('unavailable') }) });
|
|
1716
|
+
}
|
|
1717
|
+
const { disabled, save, saveAsync } = settings;
|
|
1718
|
+
const refresh = () => {
|
|
1719
|
+
services.panel.refresh();
|
|
1720
|
+
services.cloudModels.refresh();
|
|
1721
|
+
services.endpoints.refresh();
|
|
1722
|
+
};
|
|
1723
|
+
/**
|
|
1724
|
+
* Activate the requested settings section and return to its start in the host's scroll pane.
|
|
1725
|
+
* 激活指定的设置分区,并在宿主滚动容器内回到分区开头。
|
|
1726
|
+
*/
|
|
1727
|
+
const selectView = (next) => {
|
|
1728
|
+
if (view === next)
|
|
1729
|
+
return;
|
|
1730
|
+
setView(next);
|
|
1731
|
+
page.current?.scrollIntoView({ block: 'start' });
|
|
1732
|
+
};
|
|
1733
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: page, className: "vdg vdg-page", "data-vulcan-settings-width": "full", children: [(0, jsx_runtime_1.jsxs)("header", { className: "vdg-page-head", "data-vulcan-settings-header": true, children: [(0, jsx_runtime_1.jsx)("h2", { children: t('page.title') }), (0, jsx_runtime_1.jsx)("p", { className: "vdg-lead", children: t('page.lead') })] }), (0, jsx_runtime_1.jsx)("div", { className: "vdg-settings-navigation", role: "group", "aria-label": t('page.sections'), children: SETTINGS_VIEWS.map(item => ((0, jsx_runtime_1.jsx)("button", { id: `${viewId}-${item}-button`, type: "button", "aria-pressed": view === item, "aria-controls": `${viewId}-${item}`, onClick: () => { selectView(item); }, children: t(`page.section.${item}`) }, item))) }), settings.writable ? null : (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert warn", children: t('readOnly') }), (0, jsx_runtime_1.jsx)(ui_1.ErrorLine, { text: settings.error }), (0, jsx_runtime_1.jsxs)("section", { className: "vdg-settings-group", id: `${viewId}-engine`, "aria-labelledby": `${viewId}-engine-button`, hidden: view !== 'engine', children: [(0, jsx_runtime_1.jsx)(StatusSection, { services: services, t: t, value: value, live: live }), (0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('mode.title'), children: (0, jsx_runtime_1.jsx)(ui_1.Choices, { label: t('mode.title'), value: value.mode, options: ['auto-preset', 'standalone'].map(item => ({ value: item, title: t(`mode.${item}`), hint: t(`mode.${item}.hint`) })), disabled: disabled, onChoose: (next) => { save({ mode: next }); } }) }), (0, jsx_runtime_1.jsx)(ui_1.Section, { title: t('backend.title'), children: (0, jsx_runtime_1.jsx)(ui_1.Choices, { label: t('backend.title'), value: value.backend, options: ['cloud', 'local'].map(item => ({ value: item, title: t(`backend.${item}`), hint: t(`backend.${item}.hint`) })), disabled: disabled, onChoose: (next) => { save({ backend: next }); } }) }), live === undefined
|
|
1734
|
+
? (0, jsx_runtime_1.jsx)("p", { className: "vdg-hint", children: t('loading') })
|
|
1735
|
+
: live === null
|
|
1736
|
+
? (0, jsx_runtime_1.jsx)("div", { className: "vdg-alert bad", children: t('panel.unavailable') })
|
|
1737
|
+
: value.backend === 'cloud'
|
|
1738
|
+
? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(cloud_1.AccountSection, { services: services, t: t, account: live.account }), live.account.signedIn
|
|
1739
|
+
? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(cloud_1.ModelSection, { services: services, t: t, saved: value.cloudModel, disabled: disabled, onChoose: (model) => {
|
|
1740
|
+
// A lock belongs to the model it was chosen for.
|
|
1741
|
+
void saveAsync({ cloudModel: model === '' ? undefined : model, cloudEndpoint: undefined }).then(() => { services.endpoints.refresh(); });
|
|
1742
|
+
} }), (0, jsx_runtime_1.jsx)(cloud_1.EndpointsSection, { services: services, t: t, disabled: disabled, onLock: endpoint => (0, cloud_1.lockEndpoint)(saveAsync, value.cloudModel, endpoint, t('readOnly')) })] }))
|
|
1743
|
+
: null] }))
|
|
1744
|
+
: (0, jsx_runtime_1.jsx)(local_1.LocalSections, { t: t, live: live, value: value, disabled: disabled, save: save, refresh: refresh }), (0, jsx_runtime_1.jsx)(OptionsSection, { t: t, value: value, disabled: disabled, save: save }), (0, jsx_runtime_1.jsx)(TrySection, { t: t })] }), (0, jsx_runtime_1.jsxs)("section", { className: "vdg-settings-group", id: `${viewId}-policy`, "aria-labelledby": `${viewId}-policy-button`, hidden: view !== 'policy', children: [(0, jsx_runtime_1.jsx)(ReviewSection, { services: services, t: t, value: value, disabled: disabled, save: save }), value.review === 'off' ? null : (0, jsx_runtime_1.jsx)(RulesSection, { t: t, value: value, disabled: disabled, saveAsync: saveAsync }), value.review === 'off' ? null : (0, jsx_runtime_1.jsx)(PromptsSection, { t: t, value: value, disabled: disabled, saveAsync: saveAsync }), (0, jsx_runtime_1.jsx)(AskSection, { t: t, value: value, disabled: disabled, save: save })] }), (0, jsx_runtime_1.jsx)("section", { className: "vdg-settings-group", id: `${viewId}-records`, "aria-labelledby": `${viewId}-records-button`, hidden: view !== 'records', children: view === 'records' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(RecordsSection, { t: t, value: value, disabled: disabled, save: save }), (0, jsx_runtime_1.jsx)(records_1.RecordsPanel, { t: t, recording: value.recordDecisions })] })) : null })] }));
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
},
|
|
1748
|
+
"./shared": function (module, exports, require) {
|
|
1749
|
+
"use strict";
|
|
1750
|
+
/** Shapes and Host reads shared by the settings page, the composer card and the approval card. */
|
|
1751
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1752
|
+
exports.HostWatch = exports.DECISIONS = exports.MAX_PROMPT_CHARS = exports.MAX_ASK_TIMEOUT = exports.DEFAULT_ASK_TIMEOUT = exports.AUTO_PRESET = exports.AUTO_REVIEW_PACKAGE = exports.SETTINGS_SECTION = exports.ENTRY_ID = exports.ACCOUNT_SECTION = exports.SUITE_PACKAGE = exports.PACKAGE_NAME = void 0;
|
|
1753
|
+
exports.callPanel = callPanel;
|
|
1754
|
+
exports.running = running;
|
|
1755
|
+
exports.messageOf = messageOf;
|
|
1756
|
+
exports.saveSettings = saveSettings;
|
|
1757
|
+
exports.autoReviewEnabled = autoReviewEnabled;
|
|
1758
|
+
exports.mebibytes = mebibytes;
|
|
1759
|
+
/** Must equal this package's name and its `cordis.patch.yml` row. */
|
|
1760
|
+
exports.PACKAGE_NAME = '@openvulcan/dsh-decision-guard';
|
|
1761
|
+
/** The bundle the Plugins page lists; this plugin is one of its parts. */
|
|
1762
|
+
exports.SUITE_PACKAGE = '@openvulcan/dsh-vulcancode';
|
|
1763
|
+
/** The OpenVulcan account plugin's Settings dialog entry, where signing in happens. */
|
|
1764
|
+
exports.ACCOUNT_SECTION = 'vulcan-account';
|
|
1765
|
+
/** The Host entry id from `cordis.patch.yml`, which names this plugin's configuration namespace. */
|
|
1766
|
+
exports.ENTRY_ID = 'vulcan-decision-guard';
|
|
1767
|
+
/** The Settings dialog entry this plugin adds. */
|
|
1768
|
+
exports.SETTINGS_SECTION = 'vulcan-auto-approval';
|
|
1769
|
+
/** DeepSeek Harness's experimental Auto review bundle. */
|
|
1770
|
+
exports.AUTO_REVIEW_PACKAGE = '@deepseek-ai/dsh-experimental-auto-review';
|
|
1771
|
+
/** dsh's Auto permission preset value. */
|
|
1772
|
+
exports.AUTO_PRESET = 'auto';
|
|
1773
|
+
/** Countdown offered when the switch is turned on; the Host accepts 1 to {@link MAX_ASK_TIMEOUT}. */
|
|
1774
|
+
exports.DEFAULT_ASK_TIMEOUT = 60;
|
|
1775
|
+
/** Mirrors `MAX_ASK_TIMEOUT` in `src/index.ts`. */
|
|
1776
|
+
exports.MAX_ASK_TIMEOUT = 3600;
|
|
1777
|
+
/** Mirrors `MAX_PROMPT_CHARS` in `src/session-state.ts`. */
|
|
1778
|
+
exports.MAX_PROMPT_CHARS = 4000;
|
|
1779
|
+
exports.DECISIONS = ['allow', 'review', 'block'];
|
|
1780
|
+
/** Browser-relative form of `ROUTE_PREFIX` in `src/panel.ts`. */
|
|
1781
|
+
const ROUTE_BASE = 'api/vulcan-decision/';
|
|
1782
|
+
/** Call one panel route; rejects with the Host's message when it fails. */
|
|
1783
|
+
async function callPanel(method, route, body) {
|
|
1784
|
+
const response = await fetch(`${ROUTE_BASE}${route}`, {
|
|
1785
|
+
method,
|
|
1786
|
+
...body === undefined ? {} : { headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) },
|
|
1787
|
+
});
|
|
1788
|
+
const answer = await response.json().catch(() => undefined);
|
|
1789
|
+
if (answer?.ok !== true)
|
|
1790
|
+
throw new Error(answer?.error?.message ?? `HTTP ${response.status}`);
|
|
1791
|
+
return answer.value;
|
|
1792
|
+
}
|
|
1793
|
+
function running(job) {
|
|
1794
|
+
return job !== null && !['done', 'failed', 'cancelled'].includes(job.phase);
|
|
1795
|
+
}
|
|
1796
|
+
function messageOf(error) {
|
|
1797
|
+
return error instanceof Error ? error.message : String(error);
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Latest-answer-wins view of one Host fact, shared by every rendered entry. `undefined` means
|
|
1801
|
+
* unknown: before the first answer, or after a failed read, so nothing is shown as a guess.
|
|
1802
|
+
*/
|
|
1803
|
+
class HostWatch {
|
|
1804
|
+
read;
|
|
1805
|
+
#value;
|
|
1806
|
+
#listeners = new Set();
|
|
1807
|
+
#generation = 0;
|
|
1808
|
+
constructor(read) {
|
|
1809
|
+
this.read = read;
|
|
1810
|
+
}
|
|
1811
|
+
subscribe = (listener) => {
|
|
1812
|
+
this.#listeners.add(listener);
|
|
1813
|
+
return () => { this.#listeners.delete(listener); };
|
|
1814
|
+
};
|
|
1815
|
+
getSnapshot = () => this.#value;
|
|
1816
|
+
/** Re-read the fact; an older answer never replaces a newer one. */
|
|
1817
|
+
refresh() {
|
|
1818
|
+
const generation = ++this.#generation;
|
|
1819
|
+
this.read().then((value) => {
|
|
1820
|
+
if (generation === this.#generation)
|
|
1821
|
+
this.#set(value);
|
|
1822
|
+
}, () => {
|
|
1823
|
+
if (generation === this.#generation)
|
|
1824
|
+
this.#set(undefined);
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
/** Stop publishing answers that are still in flight. */
|
|
1828
|
+
close() {
|
|
1829
|
+
++this.#generation;
|
|
1830
|
+
this.#listeners.clear();
|
|
1831
|
+
}
|
|
1832
|
+
#set(value) {
|
|
1833
|
+
if (Object.is(value, this.#value))
|
|
1834
|
+
return;
|
|
1835
|
+
this.#value = value;
|
|
1836
|
+
for (const listener of this.#listeners)
|
|
1837
|
+
listener();
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
exports.HostWatch = HostWatch;
|
|
1841
|
+
/** Save `changes` at the accepted revision; resolves false when the Host refused them. */
|
|
1842
|
+
async function saveSettings(form, changes) {
|
|
1843
|
+
const snapshot = form.getSnapshot();
|
|
1844
|
+
const ops = Object.entries(changes).map(([field, next]) => (next === undefined ? { op: 'unset', path: [field] } : { op: 'set', path: [field], value: next }));
|
|
1845
|
+
return await form.mutate(ops, snapshot.revision);
|
|
1846
|
+
}
|
|
1847
|
+
function autoReviewEnabled(bundles) {
|
|
1848
|
+
const bundle = bundles.find(item => item.name === exports.AUTO_REVIEW_PACKAGE);
|
|
1849
|
+
return bundle !== undefined && bundle.enabled && bundle.availability !== 'missing';
|
|
1850
|
+
}
|
|
1851
|
+
function mebibytes(bytes) {
|
|
1852
|
+
return `${Math.round(bytes / 1024 ** 2)} MiB`;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
},
|
|
1856
|
+
"./style": function (module, exports, require) {
|
|
1857
|
+
"use strict";
|
|
1858
|
+
/**
|
|
1859
|
+
* The plugin's look, after VulcanCode's ui-kit (`apps/vulcan-code-web/src/ui-kit/styles`): square
|
|
1860
|
+
* corners, hairline borders, numbered sections with an ember corner tick, tinted badges and plain
|
|
1861
|
+
* tables. Every rule is scoped under `.vdg`, so nothing leaks into dsh; dark values follow dsh's
|
|
1862
|
+
* `body[data-ds-dark-theme]`.
|
|
1863
|
+
*/
|
|
1864
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1865
|
+
exports.installStyle = installStyle;
|
|
1866
|
+
const CSS = `
|
|
1867
|
+
.vdg {
|
|
1868
|
+
--vk-u: 4px;
|
|
1869
|
+
--vk-panel: #fff; --vk-sunken: #f6f7f9; --vk-hover: #eef1f4; --vk-select: #e3e9f8;
|
|
1870
|
+
--vk-line: #dde1e6; --vk-line-strong: #c3c9d1; --vk-line-soft: #eaecef;
|
|
1871
|
+
--vk-ink: #14171c; --vk-ink-2: #4a515c; --vk-ink-3: #626973;
|
|
1872
|
+
--vk-ember: #e2511a; --vk-signal: #2e58cc; --vk-ok: #18774a; --vk-warn: #945d09; --vk-bad: #c3303d;
|
|
1873
|
+
--vk-shadow-1: 0 1px 2px rgba(20, 23, 28, 0.06);
|
|
1874
|
+
--vk-shadow-2: 0 16px 40px rgba(20, 23, 28, 0.18), 0 2px 8px rgba(20, 23, 28, 0.08);
|
|
1875
|
+
--vk-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
1876
|
+
font-size: 13px; line-height: 1.5; color: var(--vk-ink);
|
|
1877
|
+
}
|
|
1878
|
+
body[data-ds-dark-theme] .vdg {
|
|
1879
|
+
--vk-panel: #15181d; --vk-sunken: #0f1215; --vk-hover: #1e2329; --vk-select: #1c2537;
|
|
1880
|
+
--vk-line: #23282f; --vk-line-strong: #343b45; --vk-line-soft: #1a1e24;
|
|
1881
|
+
--vk-ink: #e6e9ed; --vk-ink-2: #a2aab5; --vk-ink-3: #848c97;
|
|
1882
|
+
--vk-ember: #ff6a2b; --vk-signal: #7d9dff; --vk-ok: #3fc48a; --vk-warn: #e0a83a; --vk-bad: #f0646f;
|
|
1883
|
+
--vk-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
1884
|
+
--vk-shadow-2: 0 16px 40px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
1885
|
+
}
|
|
1886
|
+
.vdg *, .vdg *::before, .vdg *::after { box-sizing: border-box; }
|
|
1887
|
+
.vdg-page { display: flex; flex-direction: column; gap: calc(var(--vk-u) * 4); counter-reset: vdg-section; padding-bottom: calc(var(--vk-u) * 2); }
|
|
1888
|
+
.vdg-page-head h2 { margin: 0; font-size: 16px; font-weight: 600; }
|
|
1889
|
+
|
|
1890
|
+
/* Keep the section switcher reachable inside the host's single scrolling pane. */
|
|
1891
|
+
/* 在宿主唯一的滚动正文内,让分区切换栏始终可触达。 */
|
|
1892
|
+
.vdg-settings-navigation { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--vk-line); background: var(--vk-panel); }
|
|
1893
|
+
.vdg-settings-navigation > button { min-height: 36px; padding: 6px 12px; border: 0; border-bottom: 2px solid transparent; background: none; color: var(--vk-ink-2); font: inherit; cursor: pointer; }
|
|
1894
|
+
.vdg-settings-navigation > button:hover { background: var(--vk-hover); color: var(--vk-ink); }
|
|
1895
|
+
.vdg-settings-navigation > button[aria-pressed="true"] { border-bottom-color: var(--vk-ember); color: var(--vk-ink); font-weight: 600; }
|
|
1896
|
+
.vdg-settings-navigation > button:focus-visible { outline: 2px solid var(--vk-signal); outline-offset: -3px; }
|
|
1897
|
+
.vdg-settings-group:not([hidden]) { display: flex; flex-direction: column; gap: calc(var(--vk-u) * 4); min-width: 0; }
|
|
1898
|
+
.vdg-lead { margin: calc(var(--vk-u) * 1) 0 0; color: var(--vk-ink-2); }
|
|
1899
|
+
.vdg-hint { margin: 0; color: var(--vk-ink-3); font-size: 12px; }
|
|
1900
|
+
.vdg-error { margin: 0; color: var(--vk-bad); font-size: 12px; overflow-wrap: anywhere; }
|
|
1901
|
+
.vdg-warn { margin: 0; color: var(--vk-warn); font-size: 12px; }
|
|
1902
|
+
|
|
1903
|
+
.vdg-section { position: relative; border: 1px solid var(--vk-line); background: var(--vk-panel); box-shadow: var(--vk-shadow-1); }
|
|
1904
|
+
.vdg-section::before { content: ""; position: absolute; left: -1px; top: -1px; width: calc(var(--vk-u) * 7); height: 2px; background: var(--vk-ember); }
|
|
1905
|
+
.vdg-section-head { display: flex; align-items: flex-start; justify-content: space-between; gap: calc(var(--vk-u) * 3); padding: calc(var(--vk-u) * 3.5) calc(var(--vk-u) * 4) 0; }
|
|
1906
|
+
.vdg-section-title { display: flex; align-items: center; gap: calc(var(--vk-u) * 2); margin: 0; font-size: 15px; font-weight: 600; }
|
|
1907
|
+
.vdg-section-title::before { counter-increment: vdg-section; content: counter(vdg-section, decimal-leading-zero); border: 1px solid var(--vk-line); color: var(--vk-ink-3); font-family: var(--vk-mono); font-size: 10px; font-weight: 400; letter-spacing: .06em; padding: 0 5px; line-height: 16px; }
|
|
1908
|
+
.vdg-section-lead { margin: calc(var(--vk-u) * 1) 0 0; color: var(--vk-ink-3); font-size: 12px; }
|
|
1909
|
+
.vdg-section-body { display: flex; flex-direction: column; gap: calc(var(--vk-u) * 3); padding: calc(var(--vk-u) * 3) calc(var(--vk-u) * 4) calc(var(--vk-u) * 4); }
|
|
1910
|
+
.vdg-section-body.flush { padding: calc(var(--vk-u) * 3) 0 0; gap: 0; }
|
|
1911
|
+
|
|
1912
|
+
.vdg-rows { display: flex; flex-direction: column; border-top: 1px solid var(--vk-line-soft); }
|
|
1913
|
+
.vdg-row { display: flex; align-items: center; justify-content: space-between; gap: calc(var(--vk-u) * 3); padding: calc(var(--vk-u) * 3) calc(var(--vk-u) * 4); border-bottom: 1px solid var(--vk-line-soft); }
|
|
1914
|
+
.vdg-row:last-child { border-bottom: 0; }
|
|
1915
|
+
.vdg-row-label { font-weight: 500; flex-shrink: 0; }
|
|
1916
|
+
.vdg-row-value { display: flex; align-items: center; gap: calc(var(--vk-u) * 2); min-width: 0; color: var(--vk-ink-2); text-align: right; overflow-wrap: anywhere; }
|
|
1917
|
+
.vdg-field { display: flex; flex-direction: column; gap: calc(var(--vk-u) * 1.5); }
|
|
1918
|
+
.vdg-field > label, .vdg-label { color: var(--vk-ink-2); font-size: 12px; }
|
|
1919
|
+
.vdg-inline { display: flex; align-items: center; gap: calc(var(--vk-u) * 2); flex-wrap: wrap; }
|
|
1920
|
+
.vdg-between { display: flex; align-items: center; justify-content: space-between; gap: calc(var(--vk-u) * 3); flex-wrap: wrap; }
|
|
1921
|
+
|
|
1922
|
+
.vdg-choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: calc(var(--vk-u) * 2); }
|
|
1923
|
+
.vdg-choice { display: flex; gap: calc(var(--vk-u) * 2.5); align-items: flex-start; padding: calc(var(--vk-u) * 3); border: 1px solid var(--vk-line); background: var(--vk-panel); cursor: pointer; text-align: left; color: inherit; font: inherit; }
|
|
1924
|
+
.vdg-choice:hover { background: var(--vk-hover); }
|
|
1925
|
+
.vdg-choice[aria-checked="true"] { border-color: var(--vk-ember); box-shadow: inset 2px 0 0 var(--vk-ember); background: var(--vk-sunken); }
|
|
1926
|
+
.vdg-choice[aria-disabled="true"] { cursor: default; opacity: .6; }
|
|
1927
|
+
.vdg-choice-mark { flex-shrink: 0; width: 12px; height: 12px; margin-top: 3px; border: 1px solid var(--vk-line-strong); border-radius: 50%; }
|
|
1928
|
+
.vdg-choice[aria-checked="true"] .vdg-choice-mark { border: 4px solid var(--vk-ember); }
|
|
1929
|
+
.vdg-choice-title { display: block; font-weight: 500; }
|
|
1930
|
+
.vdg-choice-hint { display: block; margin-top: 2px; color: var(--vk-ink-3); font-size: 12px; }
|
|
1931
|
+
|
|
1932
|
+
.vdg-segments { display: inline-flex; border: 1px solid var(--vk-line-strong); }
|
|
1933
|
+
.vdg-segment { height: 26px; padding: 0 calc(var(--vk-u) * 3); border: 0; border-right: 1px solid var(--vk-line-strong); background: var(--vk-panel); color: var(--vk-ink-2); font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap; }
|
|
1934
|
+
.vdg-segment:last-child { border-right: 0; }
|
|
1935
|
+
.vdg-segment[aria-pressed="true"] { background: var(--vk-ink); color: var(--vk-panel); }
|
|
1936
|
+
.vdg-segment:disabled { cursor: default; opacity: .6; }
|
|
1937
|
+
|
|
1938
|
+
.vdg-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 28px; padding: 0 calc(var(--vk-u) * 3); border: 1px solid var(--vk-line-strong); background: var(--vk-panel); color: var(--vk-ink); font: inherit; font-size: 12px; font-weight: 500; cursor: pointer; white-space: nowrap; border-radius: 0; }
|
|
1939
|
+
.vdg-btn:hover:not(:disabled) { background: var(--vk-hover); }
|
|
1940
|
+
.vdg-btn:disabled { cursor: default; opacity: .55; }
|
|
1941
|
+
.vdg-btn.sm { height: 24px; padding: 0 calc(var(--vk-u) * 2); }
|
|
1942
|
+
.vdg-btn.primary { background: var(--vk-ink); border-color: var(--vk-ink); color: var(--vk-panel); }
|
|
1943
|
+
.vdg-btn.primary:hover:not(:disabled) { background: var(--vk-ink-2); }
|
|
1944
|
+
.vdg-btn.ghost { border-color: transparent; background: transparent; }
|
|
1945
|
+
.vdg-btn.ghost:hover:not(:disabled) { background: var(--vk-hover); }
|
|
1946
|
+
.vdg-link { border: 0; padding: 0; background: none; color: var(--vk-signal); font: inherit; font-size: 12px; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
|
|
1947
|
+
|
|
1948
|
+
.vdg-select, .vdg-input, .vdg-textarea { min-height: 28px; padding: 0 calc(var(--vk-u) * 2); border: 1px solid var(--vk-line-strong); border-radius: 0; background: var(--vk-panel); color: var(--vk-ink); font: inherit; }
|
|
1949
|
+
.vdg-select { min-width: 220px; max-width: 100%; }
|
|
1950
|
+
.vdg-textarea { width: 100%; padding: calc(var(--vk-u) * 2); resize: vertical; line-height: 1.5; }
|
|
1951
|
+
.vdg-select:focus-visible, .vdg-input:focus-visible, .vdg-textarea:focus-visible, .vdg-btn:focus-visible, .vdg-choice:focus-visible, .vdg-segment:focus-visible { outline: 2px solid var(--vk-signal); outline-offset: 1px; }
|
|
1952
|
+
|
|
1953
|
+
.vdg-check { display: flex; align-items: flex-start; gap: calc(var(--vk-u) * 2.5); cursor: pointer; }
|
|
1954
|
+
.vdg-check input { margin: 3px 0 0; accent-color: var(--vk-ember); }
|
|
1955
|
+
|
|
1956
|
+
.vdg-badge { display: inline-flex; align-items: center; height: 18px; padding: 0 6px; border: 1px solid color-mix(in srgb, var(--tone, var(--vk-ink-3)) 32%, transparent); background: color-mix(in srgb, var(--tone, var(--vk-ink-3)) 9%, transparent); color: var(--tone, var(--vk-ink-2)); font-size: 11px; font-weight: 500; white-space: nowrap; }
|
|
1957
|
+
.vdg-badge.ok { --tone: var(--vk-ok); }
|
|
1958
|
+
.vdg-badge.warn { --tone: var(--vk-warn); }
|
|
1959
|
+
.vdg-badge.bad { --tone: var(--vk-bad); }
|
|
1960
|
+
.vdg-badge.info { --tone: var(--vk-signal); }
|
|
1961
|
+
.vdg-badge.outline { --tone: var(--vk-ink-3); background: transparent; }
|
|
1962
|
+
|
|
1963
|
+
.vdg-alert { padding: calc(var(--vk-u) * 2.5) calc(var(--vk-u) * 3); border: 1px solid var(--vk-line); border-left: 3px solid var(--tone, var(--vk-ink-3)); background: var(--vk-sunken); color: var(--vk-ink-2); font-size: 12px; overflow-wrap: anywhere; }
|
|
1964
|
+
.vdg-alert.warn { --tone: var(--vk-warn); }
|
|
1965
|
+
.vdg-alert.bad { --tone: var(--vk-bad); }
|
|
1966
|
+
|
|
1967
|
+
|
|
1968
|
+
.vdg-table-wrap { overflow-x: auto; }
|
|
1969
|
+
.vdg-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
1970
|
+
.vdg-table th { padding: calc(var(--vk-u) * 2); border-bottom: 1px solid var(--vk-line-strong); color: var(--vk-ink-3); font-weight: 500; text-align: left; white-space: nowrap; }
|
|
1971
|
+
.vdg-table td { padding: calc(var(--vk-u) * 2); border-bottom: 1px solid var(--vk-line-soft); white-space: nowrap; }
|
|
1972
|
+
.vdg-table tr:last-child td { border-bottom: 0; }
|
|
1973
|
+
.vdg-table .num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
1974
|
+
.vdg-table th:first-child, .vdg-table td:first-child { padding-left: calc(var(--vk-u) * 4); }
|
|
1975
|
+
.vdg-table th:last-child, .vdg-table td:last-child { padding-right: calc(var(--vk-u) * 4); }
|
|
1976
|
+
.vdg-table tr[aria-selected="true"] td { background: var(--vk-select); }
|
|
1977
|
+
.vdg-table tr[aria-selected="true"] td:first-child { box-shadow: inset 2px 0 0 var(--vk-ember); }
|
|
1978
|
+
.vdg-mono { font-family: var(--vk-mono); }
|
|
1979
|
+
|
|
1980
|
+
.vdg-progress { flex: 1; height: 4px; background: var(--vk-line-soft); }
|
|
1981
|
+
.vdg-progress > div { height: 100%; background: var(--vk-ember); }
|
|
1982
|
+
|
|
1983
|
+
.vdg-overlay { position: fixed; inset: 0; z-index: 1200; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(12, 14, 17, .32); }
|
|
1984
|
+
.vdg-dialog { display: flex; flex-direction: column; width: min(56rem, 100%); max-height: calc(100vh - 48px); border: 1px solid var(--vk-line-strong); background: var(--vk-panel); box-shadow: var(--vk-shadow-2); }
|
|
1985
|
+
.vdg-dialog-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 16px 20px 0; }
|
|
1986
|
+
.vdg-dialog-head h3 { margin: 0; font-size: 15px; font-weight: 600; }
|
|
1987
|
+
.vdg-dialog-body { display: flex; flex-direction: column; gap: 12px; padding: 12px 20px 16px; overflow: auto; }
|
|
1988
|
+
.vdg-dialog-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--vk-line); background: var(--vk-sunken); }
|
|
1989
|
+
.vdg-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
|
|
1990
|
+
.vdg-facts dt { color: var(--vk-ink-3); font-size: 12px; }
|
|
1991
|
+
.vdg-facts dd { margin: 2px 0 0; overflow-wrap: anywhere; }
|
|
1992
|
+
.vdg-details summary { cursor: pointer; color: var(--vk-ink-2); font-size: 12px; }
|
|
1993
|
+
.vdg-dialog.md { width: min(34rem, 100%); }
|
|
1994
|
+
.vdg-between > .vdg-hint:last-child { font-variant-numeric: tabular-nums; }
|
|
1995
|
+
|
|
1996
|
+
.vdg-chip { display: inline-flex; align-items: center; height: 24px; padding: 0 10px; border: 1px solid var(--vk-line); background: var(--vk-panel); color: var(--vk-ink-2); font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap; }
|
|
1997
|
+
.vdg-chip:hover:not(:disabled) { background: var(--vk-hover); }
|
|
1998
|
+
.vdg-chip[aria-pressed="true"] { border-color: var(--vk-signal); background: var(--vk-select); color: var(--vk-ink); }
|
|
1999
|
+
.vdg-chip:disabled { cursor: default; opacity: .55; }
|
|
2000
|
+
.vdg-chip:focus-visible { outline: 2px solid var(--vk-signal); outline-offset: 1px; }
|
|
2001
|
+
.vdg-record { display: flex; flex-direction: column; gap: 6px; padding: calc(var(--vk-u) * 3) calc(var(--vk-u) * 4); border-bottom: 1px solid var(--vk-line-soft); border-left: 3px solid var(--tone, var(--vk-line)); }
|
|
2002
|
+
.vdg-record:last-child { border-bottom: 0; }
|
|
2003
|
+
.vdg-record.ok { --tone: var(--vk-ok); }
|
|
2004
|
+
.vdg-record.warn { --tone: var(--vk-warn); }
|
|
2005
|
+
.vdg-record.bad { --tone: var(--vk-bad); }
|
|
2006
|
+
.vdg-record-title { font-family: var(--vk-mono); font-size: 12px; overflow-wrap: anywhere; }
|
|
2007
|
+
.vdg-record-filters { display: flex; flex-direction: column; gap: 8px; padding: calc(var(--vk-u) * 3) calc(var(--vk-u) * 4); border-bottom: 1px solid var(--vk-line); }
|
|
2008
|
+
.vdg-record-more { display: flex; justify-content: center; padding: calc(var(--vk-u) * 3); border-top: 1px solid var(--vk-line-soft); }
|
|
2009
|
+
.vdg-entry { display: flex; flex-direction: column; gap: 1px; width: 100%; padding: 6px 8px; margin: 0 -8px; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
|
|
2010
|
+
.vdg-entry:hover:not(:disabled) { background: var(--vk-hover); }
|
|
2011
|
+
.vdg-entry:disabled { cursor: default; opacity: .55; }
|
|
2012
|
+
.vdg-entry-title { font-weight: 500; }
|
|
2013
|
+
.vdg-entry-hint { color: var(--vk-ink-3); font-size: 12px; }
|
|
2014
|
+
.vdg-group-label { display: flex; flex-direction: column; gap: 1px; }
|
|
2015
|
+
.vdg-group-label strong { font-size: 12px; font-weight: 600; color: var(--vk-ink-2); }
|
|
2016
|
+
.vdg-separator { height: 1px; margin: 2px 0; background: var(--vk-line-soft); }
|
|
2017
|
+
|
|
2018
|
+
.vdg-popover { position: fixed; z-index: 1150; width: min(340px, calc(100vw - 24px)); border: 1px solid var(--vk-line-strong); background: var(--vk-panel); box-shadow: var(--vk-shadow-2); }
|
|
2019
|
+
.vdg-popover::before { content: ""; position: absolute; left: -1px; top: -1px; width: calc(var(--vk-u) * 7); height: 2px; background: var(--vk-ember); }
|
|
2020
|
+
.vdg-popover-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 14px 8px; }
|
|
2021
|
+
.vdg-popover-head strong { font-size: 14px; }
|
|
2022
|
+
.vdg-popover-body { display: flex; flex-direction: column; gap: 10px; padding: 0 14px 12px; }
|
|
2023
|
+
.vdg-popover-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
2024
|
+
.vdg-popover-row > span { color: var(--vk-ink-2); font-size: 12px; }
|
|
2025
|
+
.vdg-popover-foot { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 8px 14px; border-top: 1px solid var(--vk-line); background: var(--vk-sunken); }
|
|
2026
|
+
.vdg-popover-foot .vdg-link { flex-shrink: 0; white-space: nowrap; }
|
|
2027
|
+
|
|
2028
|
+
.vdg-trigger { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; border: 1px solid transparent; border-radius: 999px; background: transparent; color: var(--dsw-alias-label-secondary, var(--vk-ink-2)); font: inherit; font-size: 13px; cursor: pointer; white-space: nowrap; }
|
|
2029
|
+
.vdg-trigger:hover, .vdg-trigger[aria-expanded="true"] { background: var(--dsw-alias-interactive-bg-hover, var(--vk-hover)); }
|
|
2030
|
+
.vdg-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--tone, var(--vk-ink-3)); }
|
|
2031
|
+
.vdg-dot.ok { --tone: var(--vk-ok); }
|
|
2032
|
+
.vdg-dot.warn { --tone: var(--vk-warn); }
|
|
2033
|
+
.vdg-dot.off { --tone: var(--vk-line-strong); }
|
|
2034
|
+
|
|
2035
|
+
.vdg-prompt { display: flex; flex-direction: column; padding: 0 0 8px; }
|
|
2036
|
+
.vdg-prompt-card { position: relative; display: flex; flex-direction: column; border: 1px solid var(--vk-line-strong); background: var(--vk-panel); }
|
|
2037
|
+
.vdg-prompt-card::before { content: ""; position: absolute; left: -1px; top: -1px; width: calc(var(--vk-u) * 7); height: 2px; background: var(--vk-ember); }
|
|
2038
|
+
.vdg-prompt-strip { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 14px; border-bottom: 1px solid var(--vk-line-soft); color: var(--vk-ink-2); font-size: 12px; background: var(--vk-sunken); }
|
|
2039
|
+
.vdg-prompt-body { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px; max-height: 320px; overflow: auto; }
|
|
2040
|
+
.vdg-prompt-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 14px 12px; }
|
|
2041
|
+
.vdg-code { display: block; padding: 6px 8px; border: 1px solid var(--vk-line-soft); background: var(--vk-sunken); font-family: var(--vk-mono); font-size: 12px; white-space: pre-wrap; overflow-wrap: anywhere; max-height: 160px; overflow: auto; }
|
|
2042
|
+
`;
|
|
2043
|
+
/** Add the stylesheet once per page; returns a function that removes it again. */
|
|
2044
|
+
function installStyle() {
|
|
2045
|
+
const existing = document.querySelector('style[data-plugin="vulcan-decision-guard"]');
|
|
2046
|
+
if (existing !== null)
|
|
2047
|
+
existing.remove();
|
|
2048
|
+
const tag = document.createElement('style');
|
|
2049
|
+
tag.dataset['plugin'] = 'vulcan-decision-guard';
|
|
2050
|
+
tag.textContent = CSS;
|
|
2051
|
+
document.head.appendChild(tag);
|
|
2052
|
+
return () => { tag.remove(); };
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
},
|
|
2056
|
+
"./ui": function (module, exports, require) {
|
|
2057
|
+
"use strict";
|
|
2058
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2059
|
+
exports.Section = Section;
|
|
2060
|
+
exports.Choices = Choices;
|
|
2061
|
+
exports.Segments = Segments;
|
|
2062
|
+
exports.Badge = Badge;
|
|
2063
|
+
exports.ErrorLine = ErrorLine;
|
|
2064
|
+
exports.useWatch = useWatch;
|
|
2065
|
+
exports.usePolling = usePolling;
|
|
2066
|
+
exports.useAction = useAction;
|
|
2067
|
+
exports.useSettings = useSettings;
|
|
2068
|
+
exports.Dialog = Dialog;
|
|
2069
|
+
exports.Popover = Popover;
|
|
2070
|
+
exports.ms = ms;
|
|
2071
|
+
exports.when = when;
|
|
2072
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
2073
|
+
/** Small Vulcan-style building blocks; the look lives in `style.ts`. */
|
|
2074
|
+
const react_1 = require("react");
|
|
2075
|
+
const react_dom_1 = require("react-dom");
|
|
2076
|
+
const i18n_1 = require("./i18n");
|
|
2077
|
+
const shared_1 = require("./shared");
|
|
2078
|
+
function Section({ title, lead, actions, flush, children }) {
|
|
2079
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: "vdg-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-section-head", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "vdg-section-title", children: title }), lead === undefined ? null : (0, jsx_runtime_1.jsx)("p", { className: "vdg-section-lead", children: lead })] }), actions === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "vdg-inline", children: actions })] }), (0, jsx_runtime_1.jsx)("div", { className: flush === true ? 'vdg-section-body flush' : 'vdg-section-body', children: children })] }));
|
|
2080
|
+
}
|
|
2081
|
+
/** Radio cards: one choice of a few, each with a short explanation. */
|
|
2082
|
+
function Choices({ label, value, options, disabled, onChoose }) {
|
|
2083
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "vdg-choices", role: "radiogroup", "aria-label": label, children: options.map(option => ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "radio", className: "vdg-choice", "aria-checked": value === option.value, "aria-disabled": disabled, onClick: () => { if (!disabled && value !== option.value)
|
|
2084
|
+
onChoose(option.value); }, children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-mark" }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-title", children: option.title }), option.hint === undefined ? null : (0, jsx_runtime_1.jsx)("span", { className: "vdg-choice-hint", children: option.hint })] })] }, option.value))) }));
|
|
2085
|
+
}
|
|
2086
|
+
/** A compact one-of-few switch. */
|
|
2087
|
+
function Segments({ label, value, options, disabled, onChoose }) {
|
|
2088
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "vdg-segments", role: "group", "aria-label": label, children: options.map(option => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-segment", "aria-pressed": value === option.value, disabled: disabled, onClick: () => { if (value !== option.value)
|
|
2089
|
+
onChoose(option.value); }, children: option.title }, option.value))) }));
|
|
2090
|
+
}
|
|
2091
|
+
function Badge({ tone, children }) {
|
|
2092
|
+
return (0, jsx_runtime_1.jsx)("span", { className: `vdg-badge ${tone}`, children: children });
|
|
2093
|
+
}
|
|
2094
|
+
function ErrorLine({ text }) {
|
|
2095
|
+
return text === undefined ? null : (0, jsx_runtime_1.jsx)("p", { className: "vdg-error", role: "alert", children: text });
|
|
2096
|
+
}
|
|
2097
|
+
function useWatch(watch) {
|
|
2098
|
+
return (0, react_1.useSyncExternalStore)(watch.subscribe, watch.getSnapshot);
|
|
2099
|
+
}
|
|
2100
|
+
/** Re-read `watch` now and every `ms` while mounted. */
|
|
2101
|
+
function usePolling(watch, ms) {
|
|
2102
|
+
(0, react_1.useEffect)(() => {
|
|
2103
|
+
watch.refresh();
|
|
2104
|
+
if (ms === undefined)
|
|
2105
|
+
return undefined;
|
|
2106
|
+
const timer = setInterval(() => { watch.refresh(); }, ms);
|
|
2107
|
+
return () => { clearInterval(timer); };
|
|
2108
|
+
}, [watch, ms]);
|
|
2109
|
+
}
|
|
2110
|
+
/** Run actions one at a time, keeping the last failure to show next to them. */
|
|
2111
|
+
function useAction(after) {
|
|
2112
|
+
const [busy, setBusy] = (0, react_1.useState)(undefined);
|
|
2113
|
+
const [error, setError] = (0, react_1.useState)(undefined);
|
|
2114
|
+
const act = (name, work) => {
|
|
2115
|
+
setBusy(name);
|
|
2116
|
+
setError(undefined);
|
|
2117
|
+
work().catch((failure) => {
|
|
2118
|
+
setError((0, shared_1.messageOf)(failure));
|
|
2119
|
+
}).finally(() => {
|
|
2120
|
+
setBusy(undefined);
|
|
2121
|
+
after?.();
|
|
2122
|
+
});
|
|
2123
|
+
};
|
|
2124
|
+
return { busy, error, act };
|
|
2125
|
+
}
|
|
2126
|
+
/** The plugin's accepted settings and an immediate save of single changes. */
|
|
2127
|
+
function useSettings(form, t) {
|
|
2128
|
+
const snapshot = (0, react_1.useSyncExternalStore)((listener) => form.subscribe(listener), () => form.getSnapshot());
|
|
2129
|
+
const [saving, setSaving] = (0, react_1.useState)(false);
|
|
2130
|
+
const [error, setError] = (0, react_1.useState)(undefined);
|
|
2131
|
+
const save = async (changes) => {
|
|
2132
|
+
setSaving(true);
|
|
2133
|
+
setError(undefined);
|
|
2134
|
+
try {
|
|
2135
|
+
const landed = await (0, shared_1.saveSettings)(form, changes);
|
|
2136
|
+
if (!landed)
|
|
2137
|
+
setError((0, i18n_1.fill)(t('saveFailed'), { error: t('readOnly') }));
|
|
2138
|
+
return landed;
|
|
2139
|
+
}
|
|
2140
|
+
catch (failure) {
|
|
2141
|
+
setError((0, i18n_1.fill)(t('saveFailed'), { error: (0, shared_1.messageOf)(failure) }));
|
|
2142
|
+
return false;
|
|
2143
|
+
}
|
|
2144
|
+
finally {
|
|
2145
|
+
setSaving(false);
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
return {
|
|
2149
|
+
status: snapshot.status,
|
|
2150
|
+
value: snapshot.value,
|
|
2151
|
+
writable: snapshot.writable,
|
|
2152
|
+
disabled: !snapshot.writable || saving,
|
|
2153
|
+
saving,
|
|
2154
|
+
error,
|
|
2155
|
+
save: (changes) => { void save(changes); },
|
|
2156
|
+
saveAsync: save,
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
/** A modal dialog over the page, closed by Escape, the scrim or its own controls. */
|
|
2160
|
+
function Dialog({ title, size, onClose, footer, children }) {
|
|
2161
|
+
(0, react_1.useEffect)(() => {
|
|
2162
|
+
const keydown = (event) => {
|
|
2163
|
+
if (event.key !== 'Escape')
|
|
2164
|
+
return;
|
|
2165
|
+
event.stopPropagation();
|
|
2166
|
+
event.preventDefault();
|
|
2167
|
+
onClose();
|
|
2168
|
+
};
|
|
2169
|
+
// Capture, so the Settings dialog underneath does not close as well.
|
|
2170
|
+
document.addEventListener('keydown', keydown, true);
|
|
2171
|
+
return () => { document.removeEventListener('keydown', keydown, true); };
|
|
2172
|
+
}, [onClose]);
|
|
2173
|
+
return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { className: "vdg vdg-overlay", onMouseDown: (event) => { if (event.target === event.currentTarget)
|
|
2174
|
+
onClose(); }, children: (0, jsx_runtime_1.jsxs)("div", { className: size === 'md' ? 'vdg-dialog md' : 'vdg-dialog', role: "dialog", "aria-modal": "true", "aria-label": title, children: [(0, jsx_runtime_1.jsxs)("div", { className: "vdg-dialog-head", children: [(0, jsx_runtime_1.jsx)("h3", { children: title }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "vdg-btn ghost sm", "aria-label": "\u00D7", onClick: onClose, children: "\u00D7" })] }), (0, jsx_runtime_1.jsx)("div", { className: "vdg-dialog-body", children: children }), (0, jsx_runtime_1.jsx)("div", { className: "vdg-dialog-foot", children: footer })] }) }), document.body);
|
|
2175
|
+
}
|
|
2176
|
+
/** A card anchored above `anchor`, closed by Escape or a press outside both. */
|
|
2177
|
+
function Popover({ anchor, label, onClose, children }) {
|
|
2178
|
+
const panel = (0, react_1.useRef)(null);
|
|
2179
|
+
const [position, setPosition] = (0, react_1.useState)(undefined);
|
|
2180
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
2181
|
+
const place = () => {
|
|
2182
|
+
const target = anchor.current?.getBoundingClientRect();
|
|
2183
|
+
const own = panel.current?.getBoundingClientRect();
|
|
2184
|
+
if (target === undefined || own === undefined)
|
|
2185
|
+
return;
|
|
2186
|
+
const margin = 12;
|
|
2187
|
+
const left = Math.min(Math.max(margin, target.right - own.width), window.innerWidth - own.width - margin);
|
|
2188
|
+
const above = target.top - own.height - 8;
|
|
2189
|
+
const top = above >= margin ? above : Math.min(target.bottom + 8, window.innerHeight - own.height - margin);
|
|
2190
|
+
setPosition({ left, top });
|
|
2191
|
+
};
|
|
2192
|
+
place();
|
|
2193
|
+
const observer = new ResizeObserver(place);
|
|
2194
|
+
if (panel.current !== null)
|
|
2195
|
+
observer.observe(panel.current);
|
|
2196
|
+
window.addEventListener('resize', place);
|
|
2197
|
+
window.addEventListener('scroll', place, true);
|
|
2198
|
+
return () => {
|
|
2199
|
+
observer.disconnect();
|
|
2200
|
+
window.removeEventListener('resize', place);
|
|
2201
|
+
window.removeEventListener('scroll', place, true);
|
|
2202
|
+
};
|
|
2203
|
+
}, [anchor]);
|
|
2204
|
+
(0, react_1.useEffect)(() => {
|
|
2205
|
+
const pointer = (event) => {
|
|
2206
|
+
const target = event.target;
|
|
2207
|
+
if (target === null || panel.current?.contains(target) === true || anchor.current?.contains(target) === true)
|
|
2208
|
+
return;
|
|
2209
|
+
// A press inside one of this plugin's dialogs opened from the card keeps it open.
|
|
2210
|
+
if (target instanceof Element && target.closest('.vdg-overlay') !== null)
|
|
2211
|
+
return;
|
|
2212
|
+
onClose();
|
|
2213
|
+
};
|
|
2214
|
+
const keydown = (event) => {
|
|
2215
|
+
if (event.key === 'Escape' && document.querySelector('.vdg-overlay') === null)
|
|
2216
|
+
onClose();
|
|
2217
|
+
};
|
|
2218
|
+
document.addEventListener('pointerdown', pointer, true);
|
|
2219
|
+
document.addEventListener('keydown', keydown);
|
|
2220
|
+
return () => {
|
|
2221
|
+
document.removeEventListener('pointerdown', pointer, true);
|
|
2222
|
+
document.removeEventListener('keydown', keydown);
|
|
2223
|
+
};
|
|
2224
|
+
}, [anchor, onClose]);
|
|
2225
|
+
return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { ref: panel, className: "vdg vdg-popover", role: "dialog", "aria-label": label, style: position === undefined ? { left: -9999, top: -9999 } : { left: position.left, top: position.top }, children: children }), document.body);
|
|
2226
|
+
}
|
|
2227
|
+
/** Milliseconds as the tables show them. */
|
|
2228
|
+
function ms(t, value) {
|
|
2229
|
+
return value === undefined ? '—' : (0, i18n_1.fill)(t('ms'), { value: Math.round(value) });
|
|
2230
|
+
}
|
|
2231
|
+
/** A short local date and time. */
|
|
2232
|
+
function when(at) {
|
|
2233
|
+
return new Intl.DateTimeFormat(undefined, { dateStyle: 'short', timeStyle: 'short' }).format(new Date(at));
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
},
|
|
2237
|
+
};
|
|
2238
|
+
var cache = {};
|
|
2239
|
+
function load(request) {
|
|
2240
|
+
if (!(request in definitions)) return platformRequire(request);
|
|
2241
|
+
if (!(request in cache)) {
|
|
2242
|
+
var module = { exports: {} };
|
|
2243
|
+
cache[request] = module;
|
|
2244
|
+
definitions[request](module, module.exports, load);
|
|
2245
|
+
}
|
|
2246
|
+
return cache[request].exports;
|
|
2247
|
+
}
|
|
2248
|
+
return load('./index');
|
|
2249
|
+
}
|
|
2250
|
+
});
|