@klarkxy/dsh-delegation 0.0.0-stage → 0.1.1
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 +42 -0
- package/README.md +32 -2
- package/cordis.patch.yml +5 -0
- package/docs/README.zh-CN.md +33 -0
- package/icon.svg +15 -0
- package/lib/client.inner.cjs +797 -0
- package/lib/client.inner.cjs.map +1 -0
- package/lib/client.js +806 -0
- package/lib/contracts.d.ts +34 -0
- package/lib/contracts.js +14 -0
- package/lib/contracts.js.map +1 -0
- package/lib/index.d.ts +136 -0
- package/lib/index.js +418 -0
- package/lib/index.js.map +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +151 -4
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
Object.values({
|
|
6
|
+
/** Surfaces, from the page up to the topmost nested card. */
|
|
7
|
+
surface: [
|
|
8
|
+
"--dsw-alias-bg-base",
|
|
9
|
+
"--dsw-alias-bg-layer-1",
|
|
10
|
+
"--dsw-alias-bg-layer-2",
|
|
11
|
+
"--dsw-alias-bg-layer-3",
|
|
12
|
+
"--dsw-alias-bg-layer-4",
|
|
13
|
+
"--dsw-alias-bg-module-platform",
|
|
14
|
+
"--dsw-alias-bg-overlay"
|
|
15
|
+
],
|
|
16
|
+
/** Hairlines. l1 is near-invisible on a menu surface, so menus use l2+. */
|
|
17
|
+
border: [
|
|
18
|
+
"--dsw-alias-border-l1",
|
|
19
|
+
"--dsw-alias-border-l2",
|
|
20
|
+
"--dsw-alias-border-l3",
|
|
21
|
+
"--dsw-alias-border-l4"
|
|
22
|
+
],
|
|
23
|
+
/** Text. `caption` is menu shortcut text, `dimmed` is placeholder text. */
|
|
24
|
+
label: [
|
|
25
|
+
"--dsw-alias-label-primary",
|
|
26
|
+
"--dsw-alias-label-secondary",
|
|
27
|
+
"--dsw-alias-label-tertiary",
|
|
28
|
+
"--dsw-alias-label-caption",
|
|
29
|
+
"--dsw-alias-label-dimmed",
|
|
30
|
+
"--dsw-alias-label-error",
|
|
31
|
+
"--dsw-alias-label-primary-foreground"
|
|
32
|
+
],
|
|
33
|
+
/** Interactive fills. The `-danger` hover belongs to destructive rows. */
|
|
34
|
+
interactive: [
|
|
35
|
+
"--dsw-alias-interactive-bg-hover",
|
|
36
|
+
"--dsw-alias-interactive-bg-active",
|
|
37
|
+
"--dsw-alias-interactive-bg-hover-danger"
|
|
38
|
+
],
|
|
39
|
+
/** Accent and status. `business` is the focus ring's default colour. */
|
|
40
|
+
accent: [
|
|
41
|
+
"--dsw-alias-brand-primary",
|
|
42
|
+
"--dsw-alias-button-primary-fill",
|
|
43
|
+
"--dsw-alias-button-primary-hover",
|
|
44
|
+
"--dsw-alias-button-tool-bar-fill",
|
|
45
|
+
"--dsw-alias-button-tool-bar-hover",
|
|
46
|
+
"--dsw-alias-button-ghost-active-fill",
|
|
47
|
+
"--dsw-alias-button-ghost-active-border"
|
|
48
|
+
],
|
|
49
|
+
state: [
|
|
50
|
+
"--dsw-alias-state-business-primary",
|
|
51
|
+
"--dsw-alias-state-success-primary",
|
|
52
|
+
"--dsw-alias-state-warn-primary",
|
|
53
|
+
"--dsw-alias-state-error-primary",
|
|
54
|
+
"--dsw-alias-state-idle-primary"
|
|
55
|
+
],
|
|
56
|
+
/** Corner scale: sm 8, md 12, lg the large card, panel the modal. */
|
|
57
|
+
radius: [
|
|
58
|
+
"--dsw-radius-sm",
|
|
59
|
+
"--dsw-radius-md",
|
|
60
|
+
"--dsw-radius-lg",
|
|
61
|
+
"--dsw-radius-panel"
|
|
62
|
+
],
|
|
63
|
+
/** An elevated surface draws its edge in the shadow, so it keeps border: 0. */
|
|
64
|
+
elevation: [
|
|
65
|
+
"--dsw-elevation-soft",
|
|
66
|
+
"--dsw-elevation-stroke",
|
|
67
|
+
"--dsw-elevation-stroke-color",
|
|
68
|
+
"--dsw-elevation-panel",
|
|
69
|
+
"--dsw-elevation-prominent"
|
|
70
|
+
],
|
|
71
|
+
/** Floats, masks and scrollbars. */
|
|
72
|
+
overlay: [
|
|
73
|
+
"--dsw-menu-surface-fill",
|
|
74
|
+
"--dsw-menu-backdrop-filter",
|
|
75
|
+
"--dsw-mask-blur",
|
|
76
|
+
"--dsw-alias-bg-mask-1",
|
|
77
|
+
"--dsw-alias-menu-icon",
|
|
78
|
+
"--dsw-alias-scrollbar-bg-l2",
|
|
79
|
+
"--dsw-alias-scrollbar-hover-l2",
|
|
80
|
+
"--dsw-shadow-lv3"
|
|
81
|
+
],
|
|
82
|
+
/** Motion, focus and type axis, published by the host rather than by a component. */
|
|
83
|
+
system: [
|
|
84
|
+
"--dsw-focus-ring-width",
|
|
85
|
+
"--dsw-focus-ring-color",
|
|
86
|
+
"--dsw-font-markdown-code-font-family",
|
|
87
|
+
"--ds-transition-duration",
|
|
88
|
+
"--ds-ease-in-out",
|
|
89
|
+
"--ds-font-family-code",
|
|
90
|
+
"--dsh-scrollbar-thumb",
|
|
91
|
+
"--dsh-scrollbar-thumb-hover",
|
|
92
|
+
"--dsh-content-font-size-secondary",
|
|
93
|
+
"--dsh-content-font-delta",
|
|
94
|
+
"--dsh-frame-top-clearance"
|
|
95
|
+
]
|
|
96
|
+
}).flat();
|
|
97
|
+
/**
|
|
98
|
+
* The host's focus ring shorthand, verbatim. Writing this per control is how
|
|
99
|
+
* rings drift apart; a stylesheet that draws its own focus style should use
|
|
100
|
+
* {@link officialFocus} instead. The colour fallback is the host's own default.
|
|
101
|
+
*/
|
|
102
|
+
const OFFICIAL_FOCUS_RING = "var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary))";
|
|
103
|
+
/**
|
|
104
|
+
* A complete `outline` declaration at the official geometry. `offset` defaults
|
|
105
|
+
* to the 2px the native controls use; the host's inline field rules sit at 1px
|
|
106
|
+
* and a segmented tab pulls the ring inside itself at -2px.
|
|
107
|
+
*/
|
|
108
|
+
function officialFocus(offset = "2px") {
|
|
109
|
+
return `outline: ${OFFICIAL_FOCUS_RING}; outline-offset: ${offset}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* An elevated surface. The host draws a raised surface's edge inside its
|
|
113
|
+
* shadow rather than as a border, so a card sets `border: 0` and takes the edge
|
|
114
|
+
* from the elevation — which is also why its border stays off the alias-border
|
|
115
|
+
* set that a flat, non-elevated rule would use.
|
|
116
|
+
*/
|
|
117
|
+
function officialElevation(level) {
|
|
118
|
+
return `border: 0; box-shadow: var(--dsw-elevation-${level})`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The shared stylesheet for a plugin UI, scoped under that plugin's root class.
|
|
122
|
+
*
|
|
123
|
+
* Scoping is what keeps this safe to always-bundle into every client: the rules
|
|
124
|
+
* can only match inside the plugin's own subtree, so two plugins can both mount
|
|
125
|
+
* `dsh-ui-card` without either styling the other's markup.
|
|
126
|
+
*
|
|
127
|
+
* Each recipe matches the root element *and* its descendants, because the
|
|
128
|
+
* commonest arrangement is one element that is both — a surface that carries
|
|
129
|
+
* the root class and `dsh-ui-card`, or a portaled dialog that is the root and
|
|
130
|
+
* the `dsh-ui-surface`. A descendant-only scope would silently leave those
|
|
131
|
+
* unstyled, with no error anywhere.
|
|
132
|
+
*
|
|
133
|
+
* A caveat worth knowing: `Modal` and `Menu` portal to `document.body`, so their
|
|
134
|
+
* content leaves the plugin's subtree entirely. A portaled surface therefore
|
|
135
|
+
* needs its own root class on an element inside the portal.
|
|
136
|
+
*
|
|
137
|
+
* Only patterns that primitives do not provide live here. If a control appears
|
|
138
|
+
* below that `Button`/`Input`/`Switch`/`Tag`/`Menu` already covers, that is a
|
|
139
|
+
* bug in this file: use the primitive and delete the recipe.
|
|
140
|
+
*/
|
|
141
|
+
function officialUiCss(roots) {
|
|
142
|
+
const scope = (typeof roots === "string" ? [roots] : roots).map((root) => `.${root}`).join(", ");
|
|
143
|
+
return `
|
|
144
|
+
/* Official DSH Web UI contract. Generated by @klarkxy/dsh-plugin-kit/official-ui.
|
|
145
|
+
* Do not hand-edit the copy inside a plugin: change this module so every plugin
|
|
146
|
+
* moves together. Scope: ${scope} */
|
|
147
|
+
/* The scope rule below stays open for the whole sheet: every rule after it
|
|
148
|
+
* nests with the parent selector, so one stylesheet can cover several unrelated
|
|
149
|
+
* roots without leaking into a neighbouring plugin. Its closing brace is last. */
|
|
150
|
+
${scope} {
|
|
151
|
+
color: var(--dsw-alias-label-primary);
|
|
152
|
+
font-family: inherit;
|
|
153
|
+
font-size: 14px;
|
|
154
|
+
line-height: 22px;
|
|
155
|
+
min-width: 0;
|
|
156
|
+
|
|
157
|
+
& *, & *::before, & *::after { box-sizing: border-box; }
|
|
158
|
+
& h1, & h2, & h3, & h4, & p, & figure { margin: 0; }
|
|
159
|
+
/* Keyboard focus only: pointer input keeps the native resting appearance. The
|
|
160
|
+
* ring needs no radius of its own — the outline already follows the control's
|
|
161
|
+
* own border-radius, and overriding it here would flatten every control. */
|
|
162
|
+
& :focus-visible { ${officialFocus()}; }
|
|
163
|
+
& [hidden] { display: none !important; }
|
|
164
|
+
|
|
165
|
+
/* --- Type tiers. The same 14/22 and 13/20 steps the primitives use. --- */
|
|
166
|
+
& .dsh-ui-title { font-size: 16px; line-height: 24px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
167
|
+
& .dsh-ui-heading { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
168
|
+
& .dsh-ui-compact { font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-primary); }
|
|
169
|
+
& .dsh-ui-meta { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
170
|
+
& .dsh-ui-hint { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
171
|
+
& .dsh-ui-error { font-size: 12px; line-height: 18px; color: var(--dsw-alias-state-error-primary); }
|
|
172
|
+
& .dsh-ui-warn { color: var(--dsw-alias-state-warn-primary); }
|
|
173
|
+
& .dsh-ui-success { color: var(--dsw-alias-state-success-primary); }
|
|
174
|
+
& .dsh-ui-muted { color: var(--dsw-alias-label-tertiary); }
|
|
175
|
+
& .dsh-ui-truncate { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
176
|
+
& .dsh-ui-wrap { min-width: 0; overflow-wrap: anywhere; }
|
|
177
|
+
|
|
178
|
+
/* --- Layout scaffolding. --- */
|
|
179
|
+
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
180
|
+
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
181
|
+
/* Plugin detail pages share one reading column. These are layout recipes;
|
|
182
|
+
* controls keep their own primitive styles. Form spacing follows the host's
|
|
183
|
+
* settings-form/fields.module.css and SettingsForm.module.css. */
|
|
184
|
+
& .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
|
|
185
|
+
& .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
|
|
186
|
+
&.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
|
|
187
|
+
& .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
|
|
188
|
+
& .dsh-ui-fields-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; min-width: 0; margin: 0; padding: 0; border: 0; }
|
|
189
|
+
@media (max-width: 560px) {
|
|
190
|
+
& .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
|
|
191
|
+
}
|
|
192
|
+
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
193
|
+
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
194
|
+
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
195
|
+
/* A 0.5px l2 rule is the host's section divider; reserve the same rhythm. */
|
|
196
|
+
& .dsh-ui-divider { height: 0.5px; background: var(--dsw-alias-border-l2); }
|
|
197
|
+
& .dsh-ui-section { display: flex; flex-direction: column; gap: 12px; padding-top: 16px; border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
198
|
+
|
|
199
|
+
/* --- Card: a raised surface whose edge lives in the shadow. --- */
|
|
200
|
+
& .dsh-ui-card {
|
|
201
|
+
${officialElevation("stroke")}
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: column;
|
|
204
|
+
gap: 12px;
|
|
205
|
+
padding: 16px;
|
|
206
|
+
border-radius: var(--dsw-radius-lg);
|
|
207
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
208
|
+
color: var(--dsw-alias-label-primary);
|
|
209
|
+
min-width: 0;
|
|
210
|
+
}
|
|
211
|
+
& .dsh-ui-card--flat { border: 0.5px solid var(--dsw-alias-border-l3); box-shadow: none; }
|
|
212
|
+
& .dsh-ui-card--nested { background: var(--dsw-alias-bg-layer-2); }
|
|
213
|
+
& .dsh-ui-card--plain { background: none; }
|
|
214
|
+
|
|
215
|
+
/* --- Field: label, control, help, error. The control itself is an
|
|
216
|
+
* official primitive, so this only owns the vertical rhythm. --- */
|
|
217
|
+
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
218
|
+
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
219
|
+
/* Grid fields share a row, so the stacked-field divider must not win later in
|
|
220
|
+
* the cascade. Reset it here, after the field recipe. */
|
|
221
|
+
& .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
|
|
222
|
+
& .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
223
|
+
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
224
|
+
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
225
|
+
& .dsh-ui-help p { margin: 0; }
|
|
226
|
+
& .dsh-ui-help p + p { margin-top: 8px; }
|
|
227
|
+
& .dsh-ui-required { color: var(--dsw-alias-state-error-primary); }
|
|
228
|
+
& .dsh-ui-control { width: 100%; min-width: 0; }
|
|
229
|
+
/* An invalid control is marked on the control, not only in the message. */
|
|
230
|
+
& .dsh-ui-control[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
231
|
+
& .dsh-ui-readonly { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
232
|
+
& .dsh-ui-readonly > dt { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
233
|
+
& .dsh-ui-readonly > dd { margin: 0; font-size: 13px; line-height: 20px; overflow-wrap: anywhere; }
|
|
234
|
+
|
|
235
|
+
/* --- Buttons row. A no-op container: it exists so actions line up on the
|
|
236
|
+
* trailing edge the way every native panel does. --- */
|
|
237
|
+
& .dsh-ui-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 4px; }
|
|
238
|
+
& .dsh-ui-actions-end { justify-content: flex-end; }
|
|
239
|
+
|
|
240
|
+
/* --- Banners. A banner is a left-rail notice, the shape used for warnings,
|
|
241
|
+
* deprecations and blocking instructions. --- */
|
|
242
|
+
& .dsh-ui-banner {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
gap: 8px;
|
|
246
|
+
padding: 10px 14px;
|
|
247
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
248
|
+
border-left: 3px solid var(--dsw-alias-state-warn-primary);
|
|
249
|
+
border-radius: var(--dsw-radius-md);
|
|
250
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
251
|
+
font-size: 13px;
|
|
252
|
+
line-height: 20px;
|
|
253
|
+
color: var(--dsw-alias-label-secondary);
|
|
254
|
+
}
|
|
255
|
+
& .dsh-ui-banner-title { font-weight: 500; color: var(--dsw-alias-state-warn-primary); }
|
|
256
|
+
& .dsh-ui-banner--info { border-left-color: var(--dsw-alias-state-business-primary); }
|
|
257
|
+
& .dsh-ui-banner--danger { border-left-color: var(--dsw-alias-state-error-primary); }
|
|
258
|
+
& .dsh-ui-banner--success { border-left-color: var(--dsw-alias-state-success-primary); }
|
|
259
|
+
& .dsh-ui-banner p { margin: 0; }
|
|
260
|
+
|
|
261
|
+
/* --- Inline notice: a transient confirmation with no chrome of its own. --- */
|
|
262
|
+
& .dsh-ui-notice { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-state-success-primary); }
|
|
263
|
+
& .dsh-ui-notice--error { color: var(--dsw-alias-state-error-primary); }
|
|
264
|
+
|
|
265
|
+
/* --- Empty and loading states. Both sit in a dashed well so an empty panel
|
|
266
|
+
* reads as "nothing here yet" rather than as a broken layout. --- */
|
|
267
|
+
& .dsh-ui-empty {
|
|
268
|
+
display: flex;
|
|
269
|
+
flex-direction: column;
|
|
270
|
+
align-items: flex-start;
|
|
271
|
+
gap: 6px;
|
|
272
|
+
padding: 20px 16px;
|
|
273
|
+
border: 0.5px dashed var(--dsw-alias-border-l3);
|
|
274
|
+
border-radius: var(--dsw-radius-md);
|
|
275
|
+
font-size: 13px;
|
|
276
|
+
line-height: 20px;
|
|
277
|
+
color: var(--dsw-alias-label-secondary);
|
|
278
|
+
}
|
|
279
|
+
& .dsh-ui-loading { display: flex; align-items: center; gap: 8px; padding: 20px 0; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-secondary); }
|
|
280
|
+
|
|
281
|
+
/* --- Selectable list. A row is a button-sized target with a hover fill and
|
|
282
|
+
* a selected fill; the name carries the weight, the description does not. --- */
|
|
283
|
+
& .dsh-ui-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
284
|
+
& .dsh-ui-list-scroll { max-height: min(66vh, 760px); overflow-y: auto; overflow-x: hidden; }
|
|
285
|
+
& .dsh-ui-list-row { border-radius: var(--dsw-radius-md); }
|
|
286
|
+
& .dsh-ui-list-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
287
|
+
& .dsh-ui-list-row[aria-selected='true'], & .dsh-ui-list-row[data-selected] { background: var(--dsw-alias-interactive-bg-active); }
|
|
288
|
+
& .dsh-ui-list-item {
|
|
289
|
+
display: flex;
|
|
290
|
+
flex-direction: column;
|
|
291
|
+
align-items: flex-start;
|
|
292
|
+
gap: 4px;
|
|
293
|
+
width: 100%;
|
|
294
|
+
min-width: 0;
|
|
295
|
+
padding: 10px 12px;
|
|
296
|
+
border: 0;
|
|
297
|
+
border-radius: var(--dsw-radius-md);
|
|
298
|
+
background: none;
|
|
299
|
+
font: inherit;
|
|
300
|
+
color: inherit;
|
|
301
|
+
text-align: start;
|
|
302
|
+
cursor: pointer;
|
|
303
|
+
}
|
|
304
|
+
& .dsh-ui-list-item:disabled { cursor: default; }
|
|
305
|
+
& .dsh-ui-list-item:disabled:hover { background: none; }
|
|
306
|
+
& .dsh-ui-list-name { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
307
|
+
& .dsh-ui-list-desc { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
308
|
+
|
|
309
|
+
/* --- Code and structured output. Wraps rather than scrolls, because a
|
|
310
|
+
* feature panel is read rather than scanned. --- */
|
|
311
|
+
& .dsh-ui-code {
|
|
312
|
+
padding: 10px 12px;
|
|
313
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
314
|
+
border-radius: var(--dsw-radius-md);
|
|
315
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
316
|
+
font-family: var(--ds-font-family-code);
|
|
317
|
+
font-size: 12px;
|
|
318
|
+
line-height: 20px;
|
|
319
|
+
color: var(--dsw-alias-label-secondary);
|
|
320
|
+
overflow-wrap: anywhere;
|
|
321
|
+
white-space: pre-wrap;
|
|
322
|
+
}
|
|
323
|
+
& .dsh-ui-scroll { max-height: 60vh; overflow: auto; overscroll-behavior: contain; }
|
|
324
|
+
|
|
325
|
+
/* --- A toggle row: label on the left, switch on the trailing edge. This is
|
|
326
|
+
* layout only; the switch itself is the official primitive. --- */
|
|
327
|
+
& .dsh-ui-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-width: 0; padding: 12px 0; }
|
|
328
|
+
& .dsh-ui-toggle-row + .dsh-ui-toggle-row { border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
329
|
+
& .dsh-ui-toggle-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
330
|
+
& .dsh-ui-toggle-label { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
331
|
+
|
|
332
|
+
/* --- Floating surface: a panel anchored to a trigger. This is the material
|
|
333
|
+
* a menu card is made of — the translucent fill plus its backdrop blur, with
|
|
334
|
+
* the edge riding inside the prominent elevation. A plugin that hand-rolls this
|
|
335
|
+
* is how tokens like --dsw-specific-menu get invented; use this instead. --- */
|
|
336
|
+
& .dsh-ui-surface {
|
|
337
|
+
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
338
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
339
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
340
|
+
position: relative;
|
|
341
|
+
display: flex;
|
|
342
|
+
flex-direction: column;
|
|
343
|
+
border: 0;
|
|
344
|
+
border-radius: var(--dsw-radius-lg);
|
|
345
|
+
background: var(--dsw-menu-surface-fill);
|
|
346
|
+
backdrop-filter: var(--dsw-menu-backdrop-filter);
|
|
347
|
+
box-shadow: var(--dsw-elevation-prominent);
|
|
348
|
+
color: var(--dsw-alias-label-primary);
|
|
349
|
+
font-size: 13px;
|
|
350
|
+
line-height: 20px;
|
|
351
|
+
min-width: 0;
|
|
352
|
+
}
|
|
353
|
+
& .dsh-ui-surface-body { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px 14px; overflow-y: auto; }
|
|
354
|
+
& .dsh-ui-surface-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
355
|
+
& .dsh-ui-surface-title { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
356
|
+
|
|
357
|
+
/* --- Native select. The primitives ship no select, so a plugin that offers a
|
|
358
|
+
* closed list of choices keeps the platform control and matches the official
|
|
359
|
+
* settings field geometry exactly (34px, 0.5px l4, radius-md, layer-3). A
|
|
360
|
+
* SegmentedControl is the primitive to reach for when the choices are few
|
|
361
|
+
* enough to show at once. --- */
|
|
362
|
+
& .dsh-ui-select {
|
|
363
|
+
box-sizing: border-box;
|
|
364
|
+
width: 100%;
|
|
365
|
+
min-width: 0;
|
|
366
|
+
height: 34px;
|
|
367
|
+
padding: 0 12px;
|
|
368
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
369
|
+
border-radius: var(--dsw-radius-md);
|
|
370
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
371
|
+
font: inherit;
|
|
372
|
+
font-size: 13px;
|
|
373
|
+
line-height: 1.5;
|
|
374
|
+
color: var(--dsw-alias-label-primary);
|
|
375
|
+
cursor: pointer;
|
|
376
|
+
}
|
|
377
|
+
& .dsh-ui-select:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
378
|
+
& .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
379
|
+
& .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
380
|
+
|
|
381
|
+
/* --- Native textarea. The primitives ship no textarea either, so a settings
|
|
382
|
+
* prompt keeps the platform element and the same field shell as the select:
|
|
383
|
+
* 0.5px l4, radius-md, layer-3, 13/1.5 type. It grows vertically instead of
|
|
384
|
+
* locking to the select's 34px row. --- */
|
|
385
|
+
& .dsh-ui-textarea {
|
|
386
|
+
box-sizing: border-box;
|
|
387
|
+
width: 100%;
|
|
388
|
+
min-width: 0;
|
|
389
|
+
resize: vertical;
|
|
390
|
+
padding: 8px 12px;
|
|
391
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
392
|
+
border-radius: var(--dsw-radius-md);
|
|
393
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
394
|
+
font: inherit;
|
|
395
|
+
font-size: 13px;
|
|
396
|
+
line-height: 1.5;
|
|
397
|
+
color: var(--dsw-alias-label-primary);
|
|
398
|
+
}
|
|
399
|
+
& .dsh-ui-textarea:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
400
|
+
& .dsh-ui-textarea:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
401
|
+
& .dsh-ui-textarea::placeholder { color: var(--dsw-alias-label-dimmed); }
|
|
402
|
+
& .dsh-ui-textarea[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
403
|
+
|
|
404
|
+
/* Long panels scroll inside themselves instead of growing the host page. */
|
|
405
|
+
& .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
406
|
+
& .dsh-ui-panel-wide { max-width: 100%; }
|
|
407
|
+
|
|
408
|
+
@media (prefers-reduced-motion: reduce) {
|
|
409
|
+
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
|
|
413
|
+
}
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/contracts.ts
|
|
416
|
+
const PLUGIN_NAME = "@klarkxy/dsh-delegation";
|
|
417
|
+
const RPC_CHANNEL = "/dsh-delegation";
|
|
418
|
+
const DELEGATION_CLIENT_SERVICE = "dshDelegationClient";
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/client.tsx
|
|
421
|
+
const name = "dsh-delegation-client";
|
|
422
|
+
const inject = [
|
|
423
|
+
"slots",
|
|
424
|
+
"connection",
|
|
425
|
+
"locale"
|
|
426
|
+
];
|
|
427
|
+
const NS = "dsh-delegation.action";
|
|
428
|
+
const zh = {
|
|
429
|
+
action: "委派",
|
|
430
|
+
mode: "委派模式",
|
|
431
|
+
titleOn: "本会话将使用委派模式:主代理负责拆解、委派和验收;会话开始后保持锁定",
|
|
432
|
+
titleOff: "开启委派模式:主代理把具体工作交给子代理;会话开始后保持锁定",
|
|
433
|
+
locked: "请在会话开始前选择委派模式",
|
|
434
|
+
incapable: "当前预设缺少子代理工具,需切换到带子代理工具的预设",
|
|
435
|
+
stale: "状态已在别处更改,已刷新",
|
|
436
|
+
failed: "切换失败"
|
|
437
|
+
};
|
|
438
|
+
const en = {
|
|
439
|
+
action: "Delegate",
|
|
440
|
+
mode: "Delegation Mode",
|
|
441
|
+
titleOn: "This session will use Delegation Mode: the main agent plans, delegates, and reviews. Locked once the session starts.",
|
|
442
|
+
titleOff: "Turn on Delegation Mode: the main agent hands the work to subagents. Locked once the session starts.",
|
|
443
|
+
locked: "Choose Delegation Mode before the session starts",
|
|
444
|
+
incapable: "Delegation Mode unavailable: this preset has no subagent tools",
|
|
445
|
+
stale: "State changed elsewhere; refreshed",
|
|
446
|
+
failed: "Could not toggle Delegation Mode"
|
|
447
|
+
};
|
|
448
|
+
/** A failed RPC keeps its code so the view can name a rejection it understands. */
|
|
449
|
+
var RpcError = class extends Error {
|
|
450
|
+
code;
|
|
451
|
+
constructor(code, message) {
|
|
452
|
+
super(message);
|
|
453
|
+
this.name = "RpcError";
|
|
454
|
+
this.code = code;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
async function rpc(call, endpoint, payload = {}) {
|
|
458
|
+
const result = await call(endpoint, payload);
|
|
459
|
+
if (!result || result.ok !== true) {
|
|
460
|
+
const failed = result && "error" in result ? result.error : void 0;
|
|
461
|
+
throw new RpcError(failed?.code ?? "unknown", failed?.message ?? "");
|
|
462
|
+
}
|
|
463
|
+
return result.value;
|
|
464
|
+
}
|
|
465
|
+
function DelegationAction({ sessionId, call, subscribeGeneration, useSession, useProjection, t }) {
|
|
466
|
+
const [status, setStatus] = (0, react.useState)();
|
|
467
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
468
|
+
const [notice, setNotice] = (0, react.useState)("");
|
|
469
|
+
const generationRef = (0, react.useRef)(0);
|
|
470
|
+
const readSeqRef = (0, react.useRef)(0);
|
|
471
|
+
const openState = useSession?.((snapshot) => snapshot.openState);
|
|
472
|
+
const sessionRunning = useSession?.((snapshot) => snapshot.running === true);
|
|
473
|
+
const sessionBlank = useSession?.((snapshot) => snapshot.blank);
|
|
474
|
+
const submitting = useSession?.((snapshot) => snapshot.awaitingFirstTurn === true || (snapshot.pendingSubmissions?.length ?? 0) > 0);
|
|
475
|
+
const preset = useProjection?.("agentPreset");
|
|
476
|
+
const load = (0, react.useCallback)(async () => {
|
|
477
|
+
const generation = generationRef.current;
|
|
478
|
+
const seq = ++readSeqRef.current;
|
|
479
|
+
try {
|
|
480
|
+
const next = await rpc(call, "session.status", { sessionId });
|
|
481
|
+
if (generationRef.current !== generation || seq !== readSeqRef.current) return;
|
|
482
|
+
setStatus((prev) => prev && prev.sessionId === next.sessionId && next.revision < prev.revision ? prev : next);
|
|
483
|
+
} catch {}
|
|
484
|
+
}, [call, sessionId]);
|
|
485
|
+
(0, react.useEffect)(() => {
|
|
486
|
+
generationRef.current += 1;
|
|
487
|
+
readSeqRef.current += 1;
|
|
488
|
+
setStatus(void 0);
|
|
489
|
+
setSaving(false);
|
|
490
|
+
setNotice("");
|
|
491
|
+
}, [sessionId]);
|
|
492
|
+
(0, react.useEffect)(() => {
|
|
493
|
+
load();
|
|
494
|
+
}, [
|
|
495
|
+
load,
|
|
496
|
+
openState,
|
|
497
|
+
sessionRunning,
|
|
498
|
+
sessionBlank,
|
|
499
|
+
preset
|
|
500
|
+
]);
|
|
501
|
+
(0, react.useEffect)(() => {
|
|
502
|
+
const off = subscribeGeneration?.(() => {
|
|
503
|
+
load();
|
|
504
|
+
});
|
|
505
|
+
const win = typeof window === "undefined" ? void 0 : window;
|
|
506
|
+
const onFocus = () => {
|
|
507
|
+
load();
|
|
508
|
+
};
|
|
509
|
+
win?.addEventListener("focus", onFocus);
|
|
510
|
+
return () => {
|
|
511
|
+
off?.();
|
|
512
|
+
win?.removeEventListener("focus", onFocus);
|
|
513
|
+
};
|
|
514
|
+
}, [load, subscribeGeneration]);
|
|
515
|
+
(0, react.useEffect)(() => {
|
|
516
|
+
if (!notice) return;
|
|
517
|
+
const timer = setTimeout(() => setNotice(""), 4e3);
|
|
518
|
+
return () => clearTimeout(timer);
|
|
519
|
+
}, [notice]);
|
|
520
|
+
const toggle = (0, react.useCallback)(async () => {
|
|
521
|
+
if (!status || saving || status.locked || sessionBlank === false || sessionRunning || submitting || status.capable !== true) return;
|
|
522
|
+
const generation = generationRef.current;
|
|
523
|
+
readSeqRef.current += 1;
|
|
524
|
+
setSaving(true);
|
|
525
|
+
setNotice("");
|
|
526
|
+
try {
|
|
527
|
+
const next = await rpc(call, "session.update", {
|
|
528
|
+
sessionId,
|
|
529
|
+
enabled: !status.enabled,
|
|
530
|
+
expectedRevision: status.revision
|
|
531
|
+
});
|
|
532
|
+
if (generationRef.current !== generation) return;
|
|
533
|
+
setStatus(next);
|
|
534
|
+
} catch (reason) {
|
|
535
|
+
if (generationRef.current !== generation) return;
|
|
536
|
+
if (reason instanceof RpcError && (reason.code === "stale" || reason.code === "session-locked")) {
|
|
537
|
+
setNotice(reason.code === "stale" ? "stale" : "locked");
|
|
538
|
+
load();
|
|
539
|
+
} else setNotice("failed");
|
|
540
|
+
} finally {
|
|
541
|
+
if (generationRef.current === generation) setSaving(false);
|
|
542
|
+
}
|
|
543
|
+
}, [
|
|
544
|
+
call,
|
|
545
|
+
load,
|
|
546
|
+
saving,
|
|
547
|
+
sessionId,
|
|
548
|
+
status,
|
|
549
|
+
sessionBlank,
|
|
550
|
+
sessionRunning,
|
|
551
|
+
submitting
|
|
552
|
+
]);
|
|
553
|
+
if (!status || status.role === "executor" || sessionBlank === false) return null;
|
|
554
|
+
const capable = status.capable === true;
|
|
555
|
+
const enabled = status.enabled;
|
|
556
|
+
const locked = status.locked || sessionRunning || submitting;
|
|
557
|
+
const tooltip = status.capable === false ? t("incapable") : locked ? t("locked") : notice ? t(notice) : enabled ? t("titleOn") : t("titleOff");
|
|
558
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
559
|
+
className: "dlg-root dlg-action",
|
|
560
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
561
|
+
label: tooltip,
|
|
562
|
+
side: "bottom",
|
|
563
|
+
gap: 4,
|
|
564
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
|
|
565
|
+
className: "dlg-chip",
|
|
566
|
+
active: enabled,
|
|
567
|
+
"aria-pressed": enabled,
|
|
568
|
+
"aria-label": t("mode"),
|
|
569
|
+
disabled: !capable || locked || saving,
|
|
570
|
+
onClick: () => void toggle(),
|
|
571
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUsersOutlineRegular, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
572
|
+
className: "dlg-chip-label",
|
|
573
|
+
children: t("action")
|
|
574
|
+
})]
|
|
575
|
+
})
|
|
576
|
+
}), notice ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
577
|
+
role: "status",
|
|
578
|
+
className: "dlg-note",
|
|
579
|
+
children: t(notice)
|
|
580
|
+
}) : null]
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
const clientCss = `${officialUiCss("dlg-root")}
|
|
584
|
+
.dlg-root.dlg-action { display: inline-flex; align-items: center; gap: 6px; }
|
|
585
|
+
.dlg-root .dlg-chip { display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 10px; border-radius: 999px; font-size: 13px; line-height: 20px; border: 1px solid var(--dsw-alias-border-l3); background: transparent; color: var(--dsw-alias-label-secondary); cursor: pointer; }
|
|
586
|
+
.dlg-root .dlg-chip svg { width: 16px; height: 16px; }
|
|
587
|
+
.dlg-root .dlg-chip:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
588
|
+
.dlg-root .dlg-chip[aria-pressed="true"] { background: var(--dsw-alias-button-primary-fill); border-color: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }
|
|
589
|
+
.dlg-root .dlg-chip[aria-pressed="true"]:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); border-color: var(--dsw-alias-button-primary-hover); }
|
|
590
|
+
.dlg-root .dlg-chip:disabled { opacity: 0.5; cursor: default; }
|
|
591
|
+
.dlg-root .dlg-note { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
592
|
+
`;
|
|
593
|
+
/** Host UI language, following live switches with the host's English fallback. */
|
|
594
|
+
function useHostLocale(client) {
|
|
595
|
+
const subscribe = (0, react.useCallback)((listener) => client.locale.subscribe?.(listener) ?? (() => {}), [client]);
|
|
596
|
+
const snapshot = (0, react.useCallback)(() => client.locale.getSnapshot?.().active ?? "en", [client]);
|
|
597
|
+
return (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot);
|
|
598
|
+
}
|
|
599
|
+
const settingsZh = {
|
|
600
|
+
default: "新会话默认启用委派",
|
|
601
|
+
defaultHint: "只影响之后新建的会话;发送第一条消息前可单独选择,会话开始后保持锁定。需要预设包含子代理工具。",
|
|
602
|
+
loading: "正在读取设置…",
|
|
603
|
+
saved: "已保存。",
|
|
604
|
+
stale: "设置已更新,请刷新后重试。",
|
|
605
|
+
failed: "无法读取委派设置。",
|
|
606
|
+
retry: "重试",
|
|
607
|
+
refresh: "刷新设置"
|
|
608
|
+
};
|
|
609
|
+
const settingsEn = {
|
|
610
|
+
default: "Enable Delegation Mode for new sessions",
|
|
611
|
+
defaultHint: "Only affects sessions created later. Choose before sending the first message; the choice stays locked once the session starts. Requires a preset with subagent tools.",
|
|
612
|
+
loading: "Loading settings…",
|
|
613
|
+
saved: "Saved.",
|
|
614
|
+
stale: "Settings changed; refresh and retry.",
|
|
615
|
+
failed: "Could not load delegation settings.",
|
|
616
|
+
retry: "Retry",
|
|
617
|
+
refresh: "Refresh settings"
|
|
618
|
+
};
|
|
619
|
+
function settingsCopy(locale) {
|
|
620
|
+
return String(locale).startsWith("zh") ? settingsZh : settingsEn;
|
|
621
|
+
}
|
|
622
|
+
/** Plugin-page card: the default flag applied to sessions created from now on. */
|
|
623
|
+
function DelegationSettingsPanel(props) {
|
|
624
|
+
const hostLocale = useHostLocale(props.client);
|
|
625
|
+
const text = settingsCopy(props.locale ?? hostLocale);
|
|
626
|
+
const [settings, setSettings] = (0, react.useState)();
|
|
627
|
+
const [error, setError] = (0, react.useState)("");
|
|
628
|
+
const [note, setNote] = (0, react.useState)(false);
|
|
629
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
630
|
+
const operation = (0, react.useRef)({
|
|
631
|
+
epoch: 0,
|
|
632
|
+
active: false,
|
|
633
|
+
writing: false
|
|
634
|
+
});
|
|
635
|
+
const call = (0, react.useCallback)((endpoint, payload = {}) => props.client.connection.rpc.call(RPC_CHANNEL, endpoint, payload), [props.client]);
|
|
636
|
+
const refresh = (0, react.useCallback)(() => {
|
|
637
|
+
const state = operation.current;
|
|
638
|
+
if (!state.active || state.writing) return;
|
|
639
|
+
const epoch = state.epoch;
|
|
640
|
+
setBusy(true);
|
|
641
|
+
rpc(call, "settings").then((next) => {
|
|
642
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
643
|
+
setSettings(next);
|
|
644
|
+
setError("");
|
|
645
|
+
}).catch(() => {
|
|
646
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
647
|
+
setError("failed");
|
|
648
|
+
}).finally(() => {
|
|
649
|
+
if (state.active && state.epoch === epoch) setBusy(false);
|
|
650
|
+
});
|
|
651
|
+
}, [call]);
|
|
652
|
+
const refreshRef = (0, react.useRef)(refresh);
|
|
653
|
+
refreshRef.current = refresh;
|
|
654
|
+
(0, react.useEffect)(() => {
|
|
655
|
+
const state = operation.current;
|
|
656
|
+
state.epoch++;
|
|
657
|
+
state.active = true;
|
|
658
|
+
state.writing = false;
|
|
659
|
+
setBusy(false);
|
|
660
|
+
setSettings(void 0);
|
|
661
|
+
setError("");
|
|
662
|
+
setNote(false);
|
|
663
|
+
refreshRef.current();
|
|
664
|
+
return () => {
|
|
665
|
+
state.active = false;
|
|
666
|
+
state.epoch++;
|
|
667
|
+
};
|
|
668
|
+
}, [call]);
|
|
669
|
+
const save = (0, react.useCallback)((defaultEnabled) => {
|
|
670
|
+
const state = operation.current;
|
|
671
|
+
if (!settings || !state.active || state.writing) return;
|
|
672
|
+
state.writing = true;
|
|
673
|
+
const epoch = state.epoch;
|
|
674
|
+
setBusy(true);
|
|
675
|
+
setNote(false);
|
|
676
|
+
setError("");
|
|
677
|
+
rpc(call, "settings.update", {
|
|
678
|
+
defaultEnabled,
|
|
679
|
+
expectedRevision: settings.revision
|
|
680
|
+
}).then((next) => {
|
|
681
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
682
|
+
setSettings(next);
|
|
683
|
+
setNote(true);
|
|
684
|
+
}).catch((reason) => {
|
|
685
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
686
|
+
setError(reason instanceof RpcError && reason.code === "stale" ? "stale" : "failed");
|
|
687
|
+
}).finally(() => {
|
|
688
|
+
if (state.active && state.epoch === epoch) {
|
|
689
|
+
state.writing = false;
|
|
690
|
+
setBusy(false);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
}, [call, settings]);
|
|
694
|
+
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
695
|
+
className: "dlg-root dlg-settings dsh-ui-page",
|
|
696
|
+
children: [error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
697
|
+
role: "alert",
|
|
698
|
+
className: "dsh-ui-error",
|
|
699
|
+
children: text[error]
|
|
700
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
701
|
+
role: "status",
|
|
702
|
+
className: "dsh-ui-hint",
|
|
703
|
+
children: text.loading
|
|
704
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
705
|
+
variant: "ghost",
|
|
706
|
+
size: "sm",
|
|
707
|
+
disabled: busy,
|
|
708
|
+
onClick: () => refreshRef.current(),
|
|
709
|
+
children: text.retry
|
|
710
|
+
})]
|
|
711
|
+
});
|
|
712
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
713
|
+
className: "dlg-root dlg-settings dsh-ui-page",
|
|
714
|
+
"data-testid": "delegation-settings",
|
|
715
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
716
|
+
className: "dlg-settings-status",
|
|
717
|
+
children: [
|
|
718
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
719
|
+
variant: "ghost",
|
|
720
|
+
size: "sm",
|
|
721
|
+
disabled: busy,
|
|
722
|
+
onClick: () => refreshRef.current(),
|
|
723
|
+
children: text.refresh
|
|
724
|
+
}),
|
|
725
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
726
|
+
role: "alert",
|
|
727
|
+
className: "dsh-ui-error",
|
|
728
|
+
children: text[error]
|
|
729
|
+
}),
|
|
730
|
+
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
731
|
+
role: "status",
|
|
732
|
+
className: "dsh-ui-notice",
|
|
733
|
+
children: text.saved
|
|
734
|
+
})
|
|
735
|
+
]
|
|
736
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {
|
|
737
|
+
className: "dlg-root dsh-ui-card",
|
|
738
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
739
|
+
className: "dsh-ui-toggle-row",
|
|
740
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
741
|
+
className: "dsh-ui-toggle-text",
|
|
742
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
743
|
+
className: "dsh-ui-toggle-label",
|
|
744
|
+
children: text.default
|
|
745
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
746
|
+
className: "dsh-ui-hint",
|
|
747
|
+
children: text.defaultHint
|
|
748
|
+
})]
|
|
749
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
750
|
+
checked: settings.defaultEnabled,
|
|
751
|
+
label: text.default,
|
|
752
|
+
disabled: busy,
|
|
753
|
+
onChange: (next) => save(next)
|
|
754
|
+
})]
|
|
755
|
+
})
|
|
756
|
+
})]
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function apply(ctx) {
|
|
760
|
+
const client = ctx;
|
|
761
|
+
ctx.provide(DELEGATION_CLIENT_SERVICE, { active: true });
|
|
762
|
+
ctx.effect(() => client.locale.register(NS, {
|
|
763
|
+
zh: { ...zh },
|
|
764
|
+
en: { ...en }
|
|
765
|
+
}), "delegation.locale");
|
|
766
|
+
ctx.effect(() => {
|
|
767
|
+
if (typeof document === "undefined") return () => {};
|
|
768
|
+
const style = document.createElement("style");
|
|
769
|
+
style.setAttribute("data-plugin", PLUGIN_NAME);
|
|
770
|
+
style.textContent = clientCss;
|
|
771
|
+
document.head.appendChild(style);
|
|
772
|
+
return () => style.remove();
|
|
773
|
+
}, "delegation.styles");
|
|
774
|
+
ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
775
|
+
name: "plugins.bundle.config",
|
|
776
|
+
key: PLUGIN_NAME
|
|
777
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DelegationSettingsPanel, { client }))), "delegation.settings");
|
|
778
|
+
ctx.effect(() => client.slots.inject("conversation.hero.options", () => client.slots.register({
|
|
779
|
+
name: "conversation.hero.options",
|
|
780
|
+
id: "delegation",
|
|
781
|
+
order: 20,
|
|
782
|
+
locale: NS,
|
|
783
|
+
inject: () => ({
|
|
784
|
+
call: (endpoint, payload) => client.connection.rpc.call(RPC_CHANNEL, endpoint, payload),
|
|
785
|
+
subscribeGeneration: (listener) => client.connection.generation?.subscribe(listener)
|
|
786
|
+
})
|
|
787
|
+
}, DelegationAction)), "delegation.slot");
|
|
788
|
+
}
|
|
789
|
+
//#endregion
|
|
790
|
+
exports.DelegationAction = DelegationAction;
|
|
791
|
+
exports.DelegationSettingsPanel = DelegationSettingsPanel;
|
|
792
|
+
exports.apply = apply;
|
|
793
|
+
exports.clientCss = clientCss;
|
|
794
|
+
exports.inject = inject;
|
|
795
|
+
exports.name = name;
|
|
796
|
+
|
|
797
|
+
//# sourceMappingURL=client.inner.cjs.map
|