@klarkxy/dsh-magpie 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,967 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
3
+ let react = require("react");
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-magpie";
417
+ const RPC_CHANNEL = "/dsh-magpie";
418
+ /** Wire endpoint names; the host RPC transport only admits lowercase/dotted names. */
419
+ const RPC_ENDPOINTS = {
420
+ status: "status",
421
+ saveConnection: "connection.save",
422
+ test: "test",
423
+ sync: "sync",
424
+ setOptions: "options.set",
425
+ disconnect: "disconnect"
426
+ };
427
+ /** The three llm-pi-ai provider routes, named exactly as `magpie dsh` writes them. */
428
+ const ROUTES = {
429
+ completions: "magpie",
430
+ responses: "magpie-responses",
431
+ anthropic: "magpie-anthropic"
432
+ };
433
+ ROUTES.completions, ROUTES.responses, ROUTES.anthropic;
434
+ //#endregion
435
+ //#region src/client.tsx
436
+ const name = "dsh-magpie-client";
437
+ const inject = [
438
+ "slots",
439
+ "connection",
440
+ "locale"
441
+ ];
442
+ const styles = `${officialUiCss(".mgp-root")}
443
+ .mgp-root .mgp-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
444
+ .mgp-root .mgp-row > .mgp-grow { flex: 1 1 220px; }
445
+ .mgp-root .mgp-danger { color: var(--dsw-alias-state-error-primary); }
446
+ `;
447
+ const copy = {
448
+ zh: {
449
+ loading: "正在读取状态…",
450
+ failed: "操作失败:",
451
+ retry: "重试",
452
+ connection: "连接",
453
+ address: "网关地址",
454
+ addressHelp: "本机、局域网或远程的 magpie 网关,例如 http://127.0.0.1:3425。",
455
+ key: "网关 Key",
456
+ keyHelp: "非本机网关需要 Key;本机网关留空会自动使用占位 Key。",
457
+ keyPlaceholder: "输入网关 Key",
458
+ keyConfigured: "已保存",
459
+ keyClear: "清除",
460
+ test: "测试",
461
+ save: "保存",
462
+ saved: "已保存。",
463
+ sync: "同步",
464
+ syncNow: "立即同步",
465
+ syncing: "正在同步…",
466
+ syncOnStart: "启动时同步",
467
+ syncOnStartHelp: "每次启动本 profile 时同步一次。",
468
+ lastSync: "上次同步",
469
+ never: "从未同步",
470
+ models: (n) => `${n} 个模型`,
471
+ warnings: "警告",
472
+ readonlyHint: "当前 profile 的设置只读,无法写入。",
473
+ defaultModel: "默认模型",
474
+ defaultModelHelp: "把某个 magpie 模型设为新会话的默认模型。",
475
+ keepCurrent: "不更改",
476
+ webSearch: "网络搜索",
477
+ webSearchHelp: "通过网关用所选模型执行网络搜索。",
478
+ sameAsDefault: "与默认模型相同",
479
+ disconnect: "断开连接",
480
+ confirmDisconnect: "确认断开?",
481
+ disconnectHelp: "移除三条 magpie 供应商路由并还原默认模型与网络搜索;已保存的 Key 保留。",
482
+ testOk: (n) => `连接正常,网关返回 ${n} 个模型。`,
483
+ notConnected: "尚未连接。"
484
+ },
485
+ en: {
486
+ loading: "Loading status…",
487
+ failed: "Action failed: ",
488
+ retry: "Retry",
489
+ connection: "Connection",
490
+ address: "Gateway address",
491
+ addressHelp: "A magpie gateway on this computer, your LAN, or elsewhere, e.g. http://127.0.0.1:3425.",
492
+ key: "Gateway key",
493
+ keyHelp: "Non-local gateways need a key; a local gateway uses a placeholder automatically.",
494
+ keyPlaceholder: "Enter gateway key",
495
+ keyConfigured: "Saved",
496
+ keyClear: "Clear",
497
+ test: "Test",
498
+ save: "Save",
499
+ saved: "Saved.",
500
+ sync: "Sync",
501
+ syncNow: "Sync now",
502
+ syncing: "Syncing…",
503
+ syncOnStart: "Sync at startup",
504
+ syncOnStartHelp: "Run one sync every time this profile starts.",
505
+ lastSync: "Last sync",
506
+ never: "Never synced",
507
+ models: (n) => `${n} models`,
508
+ warnings: "Warnings",
509
+ readonlyHint: "Settings are read-only in this profile; nothing can be written.",
510
+ defaultModel: "Default model",
511
+ defaultModelHelp: "Promote a magpie model to the default for new conversations.",
512
+ keepCurrent: "Don't change",
513
+ webSearch: "Web search",
514
+ webSearchHelp: "Run web search through the gateway with the selected model.",
515
+ sameAsDefault: "Same as default model",
516
+ disconnect: "Disconnect",
517
+ confirmDisconnect: "Confirm disconnect?",
518
+ disconnectHelp: "Removes the three magpie provider routes and reverts the default model and web search; the saved key is kept.",
519
+ testOk: (n) => `Connection OK: the gateway returned ${n} models.`,
520
+ notConnected: "Not connected yet."
521
+ }
522
+ };
523
+ var RpcError = class extends Error {
524
+ code;
525
+ constructor(code, message) {
526
+ super(message);
527
+ this.code = code;
528
+ this.name = "RpcError";
529
+ }
530
+ };
531
+ const SYNC_POLL_MS = 1500;
532
+ function useHostLocale(client) {
533
+ const subscribe = (0, react.useCallback)((listener) => client.locale?.subscribe(listener) ?? (() => {}), [client]);
534
+ const snapshot = (0, react.useCallback)(() => client.locale?.getSnapshot().active ?? "zh", [client]);
535
+ return String((0, react.useSyncExternalStore)(subscribe, snapshot, snapshot)).startsWith("zh") ? "zh" : "en";
536
+ }
537
+ function ConfirmButton(props) {
538
+ const [armed, setArmed] = (0, react.useState)(false);
539
+ const timer = (0, react.useRef)(void 0);
540
+ (0, react.useEffect)(() => () => clearTimeout(timer.current), []);
541
+ const disarm = () => {
542
+ clearTimeout(timer.current);
543
+ setArmed(false);
544
+ };
545
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
546
+ variant: "outline",
547
+ size: "sm",
548
+ className: "mgp-danger",
549
+ disabled: props.disabled,
550
+ onClick: () => {
551
+ if (!armed) {
552
+ setArmed(true);
553
+ timer.current = setTimeout(() => setArmed(false), 3e3);
554
+ return;
555
+ }
556
+ disarm();
557
+ props.onConfirm();
558
+ },
559
+ onBlur: disarm,
560
+ children: armed ? props.confirmLabel : props.label
561
+ });
562
+ }
563
+ function MagpieSettingsPage({ client }) {
564
+ const locale = useHostLocale(client);
565
+ const text = copy[locale];
566
+ const [status, setStatus] = (0, react.useState)();
567
+ const [address, setAddress] = (0, react.useState)("");
568
+ const [addressDirty, setAddressDirty] = (0, react.useState)(false);
569
+ const [keyDraft, setKeyDraft] = (0, react.useState)("");
570
+ const [busy, setBusy] = (0, react.useState)("");
571
+ const [note, setNote] = (0, react.useState)("");
572
+ const [error, setError] = (0, react.useState)("");
573
+ const mounted = (0, react.useRef)(true);
574
+ const call = (0, react.useCallback)(async (endpoint, payload = {}) => {
575
+ const result = await client.connection.rpc.call(RPC_CHANNEL, endpoint, payload);
576
+ if (!result.ok) throw new RpcError(result.error.code, result.error.message);
577
+ return result.value;
578
+ }, [client]);
579
+ const load = (0, react.useCallback)(async () => {
580
+ try {
581
+ const next = await call(RPC_ENDPOINTS.status);
582
+ if (!mounted.current) return;
583
+ setStatus(next);
584
+ setAddress((current) => addressDirty ? current : next.address);
585
+ setError("");
586
+ } catch (reason) {
587
+ if (mounted.current) setError(reason instanceof Error ? reason.message : String(reason));
588
+ }
589
+ }, [call, addressDirty]);
590
+ (0, react.useEffect)(() => {
591
+ mounted.current = true;
592
+ load();
593
+ return () => {
594
+ mounted.current = false;
595
+ };
596
+ }, [load]);
597
+ (0, react.useEffect)(() => {
598
+ if (!status?.syncing) return;
599
+ const timer = setInterval(() => {
600
+ load();
601
+ }, SYNC_POLL_MS);
602
+ return () => clearInterval(timer);
603
+ }, [status?.syncing, load]);
604
+ const action = (0, react.useCallback)(async (name, run) => {
605
+ if (busy) return;
606
+ setBusy(name);
607
+ setNote("");
608
+ setError("");
609
+ try {
610
+ await run();
611
+ } catch (reason) {
612
+ if (mounted.current) setError(reason instanceof Error ? reason.message : String(reason));
613
+ } finally {
614
+ if (mounted.current) setBusy("");
615
+ }
616
+ }, [busy]);
617
+ const refresh = (next) => {
618
+ setStatus(next);
619
+ setAddress((current) => addressDirty ? current : next.address);
620
+ };
621
+ if (!status) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
622
+ className: "mgp-root dsh-ui-page dsh-ui-form",
623
+ "data-testid": "magpie-settings",
624
+ children: [error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
625
+ role: "alert",
626
+ className: "dsh-ui-error",
627
+ children: [text.failed, error]
628
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
629
+ role: "status",
630
+ className: "dsh-ui-hint",
631
+ children: text.loading
632
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
633
+ variant: "ghost",
634
+ size: "sm",
635
+ disabled: Boolean(busy),
636
+ onClick: () => void load(),
637
+ children: text.retry
638
+ })]
639
+ });
640
+ const models = status.models;
641
+ const defaultChoices = models.map((model) => model.id);
642
+ if (status.options.defaultModel && !defaultChoices.includes(status.options.defaultModel)) defaultChoices.unshift(status.options.defaultModel);
643
+ const modelLabel = (id) => {
644
+ const found = models.find((model) => model.id === id);
645
+ return found ? `${found.name ?? id} · ${found.route}` : id;
646
+ };
647
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
648
+ className: "mgp-root dsh-ui-page dsh-ui-form",
649
+ "data-testid": "magpie-settings",
650
+ children: [
651
+ status.writable === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
652
+ role: "status",
653
+ className: "dsh-ui-hint",
654
+ children: text.readonlyHint
655
+ }) : null,
656
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
657
+ role: "alert",
658
+ className: "dsh-ui-error",
659
+ children: [text.failed, error]
660
+ }) : null,
661
+ note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
662
+ role: "status",
663
+ className: "dsh-ui-notice",
664
+ children: note
665
+ }) : null,
666
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
667
+ className: "dsh-ui-card",
668
+ children: [
669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
670
+ className: "dsh-ui-label",
671
+ children: text.connection
672
+ }),
673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
674
+ className: "dsh-ui-field",
675
+ children: [
676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
677
+ className: "dsh-ui-label",
678
+ children: text.address
679
+ }),
680
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
681
+ value: addressDirty ? address : status.address,
682
+ placeholder: "http://127.0.0.1:3425",
683
+ onChange: (event) => {
684
+ setAddress(event.target.value);
685
+ setAddressDirty(true);
686
+ }
687
+ }),
688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
689
+ className: "dsh-ui-help",
690
+ children: text.addressHelp
691
+ })
692
+ ]
693
+ }),
694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
695
+ className: "dsh-ui-field",
696
+ children: [
697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
698
+ className: "dsh-ui-label",
699
+ children: [
700
+ text.key,
701
+ " ",
702
+ status.keyConfigured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
703
+ tone: "success",
704
+ children: text.keyConfigured
705
+ }) : null
706
+ ]
707
+ }),
708
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
709
+ className: "mgp-row",
710
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
711
+ className: "mgp-grow",
712
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
713
+ type: "password",
714
+ value: keyDraft,
715
+ placeholder: text.keyPlaceholder,
716
+ onChange: (event) => setKeyDraft(event.target.value)
717
+ })
718
+ }), status.keyConfigured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
719
+ variant: "ghost",
720
+ size: "sm",
721
+ disabled: Boolean(busy),
722
+ onClick: () => void action("clear-key", async () => {
723
+ refresh(await call(RPC_ENDPOINTS.saveConnection, { clearKey: true }));
724
+ setNote(text.saved);
725
+ }),
726
+ children: text.keyClear
727
+ }) : null]
728
+ }),
729
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
730
+ className: "dsh-ui-help",
731
+ children: text.keyHelp
732
+ })
733
+ ]
734
+ }),
735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
736
+ className: "dsh-ui-form-footer mgp-row",
737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
738
+ variant: "outline",
739
+ size: "md",
740
+ disabled: Boolean(busy),
741
+ onClick: () => void action("test", async () => {
742
+ if (addressDirty || keyDraft) {
743
+ refresh(await call(RPC_ENDPOINTS.saveConnection, {
744
+ address,
745
+ apiKey: keyDraft || void 0
746
+ }));
747
+ setKeyDraft("");
748
+ setAddressDirty(false);
749
+ }
750
+ const result = await call(RPC_ENDPOINTS.test);
751
+ setNote(text.testOk(result.models));
752
+ await load();
753
+ }),
754
+ children: text.test
755
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
756
+ variant: "primary",
757
+ size: "md",
758
+ disabled: Boolean(busy) || !addressDirty && !keyDraft,
759
+ onClick: () => void action("save", async () => {
760
+ refresh(await call(RPC_ENDPOINTS.saveConnection, {
761
+ address,
762
+ apiKey: keyDraft || void 0
763
+ }));
764
+ setKeyDraft("");
765
+ setAddressDirty(false);
766
+ setNote(text.saved);
767
+ }),
768
+ children: text.save
769
+ })]
770
+ })
771
+ ]
772
+ }),
773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
774
+ className: "dsh-ui-card",
775
+ children: [
776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
777
+ className: "mgp-row",
778
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
779
+ className: "dsh-ui-label mgp-grow",
780
+ children: text.sync
781
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
782
+ variant: "outline",
783
+ size: "md",
784
+ disabled: Boolean(busy) || status.syncing,
785
+ onClick: () => void action("sync", async () => {
786
+ refresh(await call(RPC_ENDPOINTS.sync));
787
+ setNote(text.saved);
788
+ }),
789
+ children: status.syncing ? text.syncing : text.syncNow
790
+ })]
791
+ }),
792
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
793
+ className: "dsh-ui-field",
794
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
795
+ className: "mgp-row",
796
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
797
+ checked: status.options.syncOnStart,
798
+ disabled: Boolean(busy),
799
+ label: text.syncOnStart,
800
+ onChange: (next) => void action("options", async () => {
801
+ refresh(await call(RPC_ENDPOINTS.setOptions, { syncOnStart: next }));
802
+ })
803
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
804
+ className: "dsh-ui-label",
805
+ children: text.syncOnStart
806
+ })]
807
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
808
+ className: "dsh-ui-help",
809
+ children: text.syncOnStartHelp
810
+ })]
811
+ }),
812
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
813
+ className: "dsh-ui-meta",
814
+ children: [
815
+ text.lastSync,
816
+ ": ",
817
+ status.lastSync ? `${new Date(status.lastSync.at).toLocaleString()} · ${Object.entries(status.lastSync.routes).map(([route, count]) => `${route}: ${count}`).join(" · ")}` : text.never
818
+ ]
819
+ }),
820
+ status.lastSync?.warnings.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
821
+ className: "dsh-ui-label",
822
+ children: text.warnings
823
+ }), status.lastSync.warnings.map((warning, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
824
+ className: "dsh-ui-hint",
825
+ children: warning
826
+ }, index))] }) : null,
827
+ status.lastError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
828
+ role: "alert",
829
+ className: "dsh-ui-error",
830
+ children: [
831
+ new Date(status.lastError.at).toLocaleString(),
832
+ " · ",
833
+ status.lastError.message
834
+ ]
835
+ }) : null,
836
+ status.gateway ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
837
+ className: "dsh-ui-hint",
838
+ children: [
839
+ status.gateway.name,
840
+ status.gateway.version ? ` ${status.gateway.version}` : "",
841
+ " · ",
842
+ text.models(models.length)
843
+ ]
844
+ }) : null
845
+ ]
846
+ }),
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {
848
+ className: "dsh-ui-card",
849
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
850
+ className: "dsh-ui-field",
851
+ children: [
852
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
853
+ className: "dsh-ui-label",
854
+ children: text.defaultModel
855
+ }),
856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
857
+ className: "dsh-ui-select",
858
+ value: status.options.defaultModel ?? "",
859
+ disabled: Boolean(busy),
860
+ onChange: (event) => void action("options", async () => {
861
+ refresh(await call(RPC_ENDPOINTS.setOptions, { defaultModel: event.target.value || null }));
862
+ }),
863
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
864
+ value: "",
865
+ children: text.keepCurrent
866
+ }), defaultChoices.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
867
+ value: id,
868
+ children: modelLabel(id)
869
+ }, id))]
870
+ }),
871
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
872
+ className: "dsh-ui-help",
873
+ children: text.defaultModelHelp
874
+ })
875
+ ]
876
+ })
877
+ }),
878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
879
+ className: "dsh-ui-card",
880
+ children: [
881
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
882
+ className: "mgp-row",
883
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
884
+ className: "dsh-ui-label mgp-grow",
885
+ children: text.webSearch
886
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
887
+ checked: status.options.webSearch,
888
+ disabled: Boolean(busy),
889
+ label: text.webSearch,
890
+ onChange: (next) => void action("options", async () => {
891
+ refresh(await call(RPC_ENDPOINTS.setOptions, { webSearch: next }));
892
+ })
893
+ })]
894
+ }),
895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
896
+ className: "dsh-ui-help",
897
+ children: text.webSearchHelp
898
+ }),
899
+ status.options.webSearch ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
900
+ className: "dsh-ui-field",
901
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
902
+ className: "dsh-ui-select",
903
+ value: status.options.webSearchModel ?? "",
904
+ disabled: Boolean(busy),
905
+ onChange: (event) => void action("options", async () => {
906
+ refresh(await call(RPC_ENDPOINTS.setOptions, { webSearchModel: event.target.value || null }));
907
+ }),
908
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
909
+ value: "",
910
+ children: text.sameAsDefault
911
+ }), models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
912
+ value: model.id,
913
+ children: model.name ?? model.id
914
+ }, model.id))]
915
+ })
916
+ }) : null
917
+ ]
918
+ }),
919
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {
920
+ className: "dsh-ui-card",
921
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
922
+ className: "mgp-row",
923
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
924
+ className: "mgp-grow",
925
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
926
+ className: "dsh-ui-label",
927
+ children: text.disconnect
928
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
929
+ className: "dsh-ui-help",
930
+ children: text.disconnectHelp
931
+ })]
932
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
933
+ label: text.disconnect,
934
+ confirmLabel: text.confirmDisconnect,
935
+ disabled: Boolean(busy),
936
+ onConfirm: () => void action("disconnect", async () => {
937
+ refresh(await call(RPC_ENDPOINTS.disconnect));
938
+ setNote(text.saved);
939
+ })
940
+ })]
941
+ })
942
+ })
943
+ ]
944
+ });
945
+ }
946
+ function apply(ctx) {
947
+ const client = ctx;
948
+ ctx.effect(() => {
949
+ if (typeof document === "undefined") return () => {};
950
+ const style = document.createElement("style");
951
+ style.setAttribute("data-plugin", PLUGIN_NAME);
952
+ style.textContent = styles;
953
+ document.head.appendChild(style);
954
+ return () => style.remove();
955
+ }, "magpie.styles");
956
+ ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
957
+ name: "plugins.bundle.config",
958
+ key: PLUGIN_NAME
959
+ }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MagpieSettingsPage, { client }))), "magpie.settings");
960
+ }
961
+ //#endregion
962
+ exports.MagpieSettingsPage = MagpieSettingsPage;
963
+ exports.apply = apply;
964
+ exports.inject = inject;
965
+ exports.name = name;
966
+
967
+ //# sourceMappingURL=client.inner.cjs.map