@klarkxy/dsh-current-title 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -9
- package/docs/README.zh-CN.md +38 -6
- package/icon.svg +15 -0
- package/lib/client.inner.cjs +732 -76
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +732 -76
- package/lib/contracts.d.ts +21 -4
- package/lib/contracts.js +38 -5
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +21 -5
- package/lib/index.js +195 -113
- package/lib/index.js.map +1 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +40 -22
package/lib/client.js
CHANGED
|
@@ -167,24 +167,42 @@ window.__ModuleLoader__.load({
|
|
|
167
167
|
var exports = module.exports;
|
|
168
168
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
169
169
|
let react = require("react");
|
|
170
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
170
171
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
171
|
-
//#region ../dsh-
|
|
172
|
+
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
172
173
|
/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
|
|
173
174
|
function selectedSessionId(client) {
|
|
174
|
-
|
|
175
|
+
const workspace = client.uiWorkspace?.current;
|
|
176
|
+
return workspace ? workspace.getSnapshot()?.sessionId ?? "" : client.uiSession.adapter.current.getSnapshot().key ?? "";
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Owner props are data the host composed, never a Cordis context: reading an
|
|
180
|
+
* undeclared property off one throws instead of returning undefined, which
|
|
181
|
+
* would crash the slot entry and abdicate its cell for good. Tolerate a
|
|
182
|
+
* throwing read so a mistaken prop degrades to "not supplied".
|
|
183
|
+
*/
|
|
184
|
+
function read(source, key) {
|
|
185
|
+
if (source === null || typeof source !== "object") return void 0;
|
|
186
|
+
try {
|
|
187
|
+
return source[key];
|
|
188
|
+
} catch {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function readString(source, key) {
|
|
193
|
+
const value = read(source, key);
|
|
194
|
+
return typeof value === "string" ? value : void 0;
|
|
175
195
|
}
|
|
176
196
|
/** Owner props win; native settings only supplies close, so resolve its selected session. */
|
|
177
197
|
function useNativeSeat(client, props) {
|
|
178
|
-
const current = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => (client.uiWorkspace
|
|
198
|
+
const current = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => (client.uiWorkspace?.current ?? client.uiSession.adapter.current).subscribe(fn), [client]), (0, react.useCallback)(() => selectedSessionId(client), [client]), () => "");
|
|
179
199
|
const language = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => client.locale.subscribe(fn), [client]), (0, react.useCallback)(() => client.locale.getSnapshot().active, [client]), () => "en");
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const sessionId = typeof row.sessionId === "string" ? row.sessionId : typeof owner.sessionId === "string" ? owner.sessionId : current;
|
|
183
|
-
const requestedLocale = row.locale ?? owner.locale ?? language;
|
|
200
|
+
const sessionId = readString(props, "sessionId") ?? readString(read(props, "owner"), "sessionId") ?? current;
|
|
201
|
+
const requestedLocale = read(props, "locale") ?? read(read(props, "owner"), "locale") ?? language;
|
|
184
202
|
return {
|
|
185
203
|
sessionId,
|
|
186
204
|
locale: String(requestedLocale).startsWith("zh") ? "zh" : "en",
|
|
187
|
-
hidden:
|
|
205
|
+
hidden: read(props, "hidden") === true || read(read(props, "owner"), "hidden") === true
|
|
188
206
|
};
|
|
189
207
|
}
|
|
190
208
|
/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
|
|
@@ -240,6 +258,378 @@ function useFeatureRefresh(client, sessionId, refresh, working = false, enabled
|
|
|
240
258
|
enabled
|
|
241
259
|
]);
|
|
242
260
|
}
|
|
261
|
+
Object.values({
|
|
262
|
+
/** Surfaces, from the page up to the topmost nested card. */
|
|
263
|
+
surface: [
|
|
264
|
+
"--dsw-alias-bg-base",
|
|
265
|
+
"--dsw-alias-bg-layer-1",
|
|
266
|
+
"--dsw-alias-bg-layer-2",
|
|
267
|
+
"--dsw-alias-bg-layer-3",
|
|
268
|
+
"--dsw-alias-bg-layer-4",
|
|
269
|
+
"--dsw-alias-bg-module-platform",
|
|
270
|
+
"--dsw-alias-bg-overlay"
|
|
271
|
+
],
|
|
272
|
+
/** Hairlines. l1 is near-invisible on a menu surface, so menus use l2+. */
|
|
273
|
+
border: [
|
|
274
|
+
"--dsw-alias-border-l1",
|
|
275
|
+
"--dsw-alias-border-l2",
|
|
276
|
+
"--dsw-alias-border-l3",
|
|
277
|
+
"--dsw-alias-border-l4"
|
|
278
|
+
],
|
|
279
|
+
/** Text. `caption` is menu shortcut text, `dimmed` is placeholder text. */
|
|
280
|
+
label: [
|
|
281
|
+
"--dsw-alias-label-primary",
|
|
282
|
+
"--dsw-alias-label-secondary",
|
|
283
|
+
"--dsw-alias-label-tertiary",
|
|
284
|
+
"--dsw-alias-label-caption",
|
|
285
|
+
"--dsw-alias-label-dimmed",
|
|
286
|
+
"--dsw-alias-label-error",
|
|
287
|
+
"--dsw-alias-label-primary-foreground"
|
|
288
|
+
],
|
|
289
|
+
/** Interactive fills. The `-danger` hover belongs to destructive rows. */
|
|
290
|
+
interactive: [
|
|
291
|
+
"--dsw-alias-interactive-bg-hover",
|
|
292
|
+
"--dsw-alias-interactive-bg-active",
|
|
293
|
+
"--dsw-alias-interactive-bg-hover-danger"
|
|
294
|
+
],
|
|
295
|
+
/** Accent and status. `business` is the focus ring's default colour. */
|
|
296
|
+
accent: [
|
|
297
|
+
"--dsw-alias-brand-primary",
|
|
298
|
+
"--dsw-alias-button-primary-fill",
|
|
299
|
+
"--dsw-alias-button-primary-hover",
|
|
300
|
+
"--dsw-alias-button-tool-bar-fill",
|
|
301
|
+
"--dsw-alias-button-tool-bar-hover",
|
|
302
|
+
"--dsw-alias-button-ghost-active-fill",
|
|
303
|
+
"--dsw-alias-button-ghost-active-border"
|
|
304
|
+
],
|
|
305
|
+
state: [
|
|
306
|
+
"--dsw-alias-state-business-primary",
|
|
307
|
+
"--dsw-alias-state-success-primary",
|
|
308
|
+
"--dsw-alias-state-warn-primary",
|
|
309
|
+
"--dsw-alias-state-error-primary",
|
|
310
|
+
"--dsw-alias-state-idle-primary"
|
|
311
|
+
],
|
|
312
|
+
/** Corner scale: sm 8, md 12, lg the large card, panel the modal. */
|
|
313
|
+
radius: [
|
|
314
|
+
"--dsw-radius-sm",
|
|
315
|
+
"--dsw-radius-md",
|
|
316
|
+
"--dsw-radius-lg",
|
|
317
|
+
"--dsw-radius-panel"
|
|
318
|
+
],
|
|
319
|
+
/** An elevated surface draws its edge in the shadow, so it keeps border: 0. */
|
|
320
|
+
elevation: [
|
|
321
|
+
"--dsw-elevation-soft",
|
|
322
|
+
"--dsw-elevation-stroke",
|
|
323
|
+
"--dsw-elevation-stroke-color",
|
|
324
|
+
"--dsw-elevation-panel",
|
|
325
|
+
"--dsw-elevation-prominent"
|
|
326
|
+
],
|
|
327
|
+
/** Floats, masks and scrollbars. */
|
|
328
|
+
overlay: [
|
|
329
|
+
"--dsw-menu-surface-fill",
|
|
330
|
+
"--dsw-menu-backdrop-filter",
|
|
331
|
+
"--dsw-mask-blur",
|
|
332
|
+
"--dsw-alias-bg-mask-1",
|
|
333
|
+
"--dsw-alias-menu-icon",
|
|
334
|
+
"--dsw-alias-scrollbar-bg-l2",
|
|
335
|
+
"--dsw-alias-scrollbar-hover-l2",
|
|
336
|
+
"--dsw-shadow-lv3"
|
|
337
|
+
],
|
|
338
|
+
/** Motion, focus and type axis, published by the host rather than by a component. */
|
|
339
|
+
system: [
|
|
340
|
+
"--dsw-focus-ring-width",
|
|
341
|
+
"--dsw-focus-ring-color",
|
|
342
|
+
"--dsw-font-markdown-code-font-family",
|
|
343
|
+
"--ds-transition-duration",
|
|
344
|
+
"--ds-ease-in-out",
|
|
345
|
+
"--ds-font-family-code",
|
|
346
|
+
"--dsh-scrollbar-thumb",
|
|
347
|
+
"--dsh-scrollbar-thumb-hover",
|
|
348
|
+
"--dsh-content-font-size-secondary",
|
|
349
|
+
"--dsh-content-font-delta",
|
|
350
|
+
"--dsh-frame-top-clearance"
|
|
351
|
+
]
|
|
352
|
+
}).flat();
|
|
353
|
+
/**
|
|
354
|
+
* The host's focus ring shorthand, verbatim. Writing this per control is how
|
|
355
|
+
* rings drift apart; a stylesheet that draws its own focus style should use
|
|
356
|
+
* {@link officialFocus} instead. The colour fallback is the host's own default.
|
|
357
|
+
*/
|
|
358
|
+
const OFFICIAL_FOCUS_RING = "var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary))";
|
|
359
|
+
/**
|
|
360
|
+
* A complete `outline` declaration at the official geometry. `offset` defaults
|
|
361
|
+
* to the 2px the native controls use; the host's inline field rules sit at 1px
|
|
362
|
+
* and a segmented tab pulls the ring inside itself at -2px.
|
|
363
|
+
*/
|
|
364
|
+
function officialFocus(offset = "2px") {
|
|
365
|
+
return `outline: ${OFFICIAL_FOCUS_RING}; outline-offset: ${offset}`;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* An elevated surface. The host draws a raised surface's edge inside its
|
|
369
|
+
* shadow rather than as a border, so a card sets `border: 0` and takes the edge
|
|
370
|
+
* from the elevation — which is also why its border stays off the alias-border
|
|
371
|
+
* set that a flat, non-elevated rule would use.
|
|
372
|
+
*/
|
|
373
|
+
function officialElevation(level) {
|
|
374
|
+
return `border: 0; box-shadow: var(--dsw-elevation-${level})`;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* The shared stylesheet for a plugin UI, scoped under that plugin's root class.
|
|
378
|
+
*
|
|
379
|
+
* Scoping is what keeps this safe to always-bundle into every client: the rules
|
|
380
|
+
* can only match inside the plugin's own subtree, so two plugins can both mount
|
|
381
|
+
* `dsh-ui-card` without either styling the other's markup.
|
|
382
|
+
*
|
|
383
|
+
* Each recipe matches the root element *and* its descendants, because the
|
|
384
|
+
* commonest arrangement is one element that is both — a surface that carries
|
|
385
|
+
* the root class and `dsh-ui-card`, or a portaled dialog that is the root and
|
|
386
|
+
* the `dsh-ui-surface`. A descendant-only scope would silently leave those
|
|
387
|
+
* unstyled, with no error anywhere.
|
|
388
|
+
*
|
|
389
|
+
* A caveat worth knowing: `Modal` and `Menu` portal to `document.body`, so their
|
|
390
|
+
* content leaves the plugin's subtree entirely. A portaled surface therefore
|
|
391
|
+
* needs its own root class on an element inside the portal.
|
|
392
|
+
*
|
|
393
|
+
* Only patterns that primitives do not provide live here. If a control appears
|
|
394
|
+
* below that `Button`/`Input`/`Switch`/`Tag`/`Menu` already covers, that is a
|
|
395
|
+
* bug in this file: use the primitive and delete the recipe.
|
|
396
|
+
*/
|
|
397
|
+
function officialUiCss(roots) {
|
|
398
|
+
const scope = (typeof roots === "string" ? [roots] : roots).map((root) => `.${root}`).join(", ");
|
|
399
|
+
return `
|
|
400
|
+
/* Official DSH Web UI contract. Generated by @klarkxy/dsh-plugin-kit/official-ui.
|
|
401
|
+
* Do not hand-edit the copy inside a plugin: change this module so every plugin
|
|
402
|
+
* moves together. Scope: ${scope} */
|
|
403
|
+
/* The scope rule below stays open for the whole sheet: every rule after it
|
|
404
|
+
* nests with the parent selector, so one stylesheet can cover several unrelated
|
|
405
|
+
* roots without leaking into a neighbouring plugin. Its closing brace is last. */
|
|
406
|
+
${scope} {
|
|
407
|
+
color: var(--dsw-alias-label-primary);
|
|
408
|
+
font-family: inherit;
|
|
409
|
+
font-size: 14px;
|
|
410
|
+
line-height: 22px;
|
|
411
|
+
min-width: 0;
|
|
412
|
+
|
|
413
|
+
& *, & *::before, & *::after { box-sizing: border-box; }
|
|
414
|
+
& h1, & h2, & h3, & h4, & p, & figure { margin: 0; }
|
|
415
|
+
/* Keyboard focus only: pointer input keeps the native resting appearance. The
|
|
416
|
+
* ring needs no radius of its own — the outline already follows the control's
|
|
417
|
+
* own border-radius, and overriding it here would flatten every control. */
|
|
418
|
+
& :focus-visible { ${officialFocus()}; }
|
|
419
|
+
& [hidden] { display: none !important; }
|
|
420
|
+
|
|
421
|
+
/* --- Type tiers. The same 14/22 and 13/20 steps the primitives use. --- */
|
|
422
|
+
& .dsh-ui-title { font-size: 16px; line-height: 24px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
423
|
+
& .dsh-ui-heading { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
424
|
+
& .dsh-ui-compact { font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-primary); }
|
|
425
|
+
& .dsh-ui-meta { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
426
|
+
& .dsh-ui-hint { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
427
|
+
& .dsh-ui-error { font-size: 12px; line-height: 18px; color: var(--dsw-alias-state-error-primary); }
|
|
428
|
+
& .dsh-ui-warn { color: var(--dsw-alias-state-warn-primary); }
|
|
429
|
+
& .dsh-ui-success { color: var(--dsw-alias-state-success-primary); }
|
|
430
|
+
& .dsh-ui-muted { color: var(--dsw-alias-label-tertiary); }
|
|
431
|
+
& .dsh-ui-truncate { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
432
|
+
& .dsh-ui-wrap { min-width: 0; overflow-wrap: anywhere; }
|
|
433
|
+
|
|
434
|
+
/* --- Layout scaffolding. --- */
|
|
435
|
+
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
436
|
+
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
437
|
+
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
438
|
+
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
439
|
+
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
440
|
+
/* A 0.5px l2 rule is the host's section divider; reserve the same rhythm. */
|
|
441
|
+
& .dsh-ui-divider { height: 0.5px; background: var(--dsw-alias-border-l2); }
|
|
442
|
+
& .dsh-ui-section { display: flex; flex-direction: column; gap: 12px; padding-top: 16px; border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
443
|
+
|
|
444
|
+
/* --- Card: a raised surface whose edge lives in the shadow. --- */
|
|
445
|
+
& .dsh-ui-card {
|
|
446
|
+
${officialElevation("stroke")}
|
|
447
|
+
display: flex;
|
|
448
|
+
flex-direction: column;
|
|
449
|
+
gap: 12px;
|
|
450
|
+
padding: 16px;
|
|
451
|
+
border-radius: var(--dsw-radius-lg);
|
|
452
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
453
|
+
color: var(--dsw-alias-label-primary);
|
|
454
|
+
min-width: 0;
|
|
455
|
+
}
|
|
456
|
+
& .dsh-ui-card--flat { border: 0.5px solid var(--dsw-alias-border-l3); box-shadow: none; }
|
|
457
|
+
& .dsh-ui-card--nested { background: var(--dsw-alias-bg-layer-2); }
|
|
458
|
+
& .dsh-ui-card--plain { background: none; }
|
|
459
|
+
|
|
460
|
+
/* --- Field: label, control, help, error. The control itself is an
|
|
461
|
+
* official primitive, so this only owns the vertical rhythm. --- */
|
|
462
|
+
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
463
|
+
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
464
|
+
& .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); }
|
|
465
|
+
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
466
|
+
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
467
|
+
& .dsh-ui-help p { margin: 0; }
|
|
468
|
+
& .dsh-ui-help p + p { margin-top: 8px; }
|
|
469
|
+
& .dsh-ui-required { color: var(--dsw-alias-state-error-primary); }
|
|
470
|
+
& .dsh-ui-control { width: 100%; min-width: 0; }
|
|
471
|
+
/* An invalid control is marked on the control, not only in the message. */
|
|
472
|
+
& .dsh-ui-control[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
473
|
+
& .dsh-ui-readonly { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
474
|
+
& .dsh-ui-readonly > dt { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
475
|
+
& .dsh-ui-readonly > dd { margin: 0; font-size: 13px; line-height: 20px; overflow-wrap: anywhere; }
|
|
476
|
+
|
|
477
|
+
/* --- Buttons row. A no-op container: it exists so actions line up on the
|
|
478
|
+
* trailing edge the way every native panel does. --- */
|
|
479
|
+
& .dsh-ui-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 4px; }
|
|
480
|
+
& .dsh-ui-actions-end { justify-content: flex-end; }
|
|
481
|
+
|
|
482
|
+
/* --- Banners. A banner is a left-rail notice, the shape used for warnings,
|
|
483
|
+
* deprecations and blocking instructions. --- */
|
|
484
|
+
& .dsh-ui-banner {
|
|
485
|
+
display: flex;
|
|
486
|
+
flex-direction: column;
|
|
487
|
+
gap: 8px;
|
|
488
|
+
padding: 10px 14px;
|
|
489
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
490
|
+
border-left: 3px solid var(--dsw-alias-state-warn-primary);
|
|
491
|
+
border-radius: var(--dsw-radius-md);
|
|
492
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
493
|
+
font-size: 13px;
|
|
494
|
+
line-height: 20px;
|
|
495
|
+
color: var(--dsw-alias-label-secondary);
|
|
496
|
+
}
|
|
497
|
+
& .dsh-ui-banner-title { font-weight: 500; color: var(--dsw-alias-state-warn-primary); }
|
|
498
|
+
& .dsh-ui-banner--info { border-left-color: var(--dsw-alias-state-business-primary); }
|
|
499
|
+
& .dsh-ui-banner--danger { border-left-color: var(--dsw-alias-state-error-primary); }
|
|
500
|
+
& .dsh-ui-banner--success { border-left-color: var(--dsw-alias-state-success-primary); }
|
|
501
|
+
& .dsh-ui-banner p { margin: 0; }
|
|
502
|
+
|
|
503
|
+
/* --- Inline notice: a transient confirmation with no chrome of its own. --- */
|
|
504
|
+
& .dsh-ui-notice { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-state-success-primary); }
|
|
505
|
+
& .dsh-ui-notice--error { color: var(--dsw-alias-state-error-primary); }
|
|
506
|
+
|
|
507
|
+
/* --- Empty and loading states. Both sit in a dashed well so an empty panel
|
|
508
|
+
* reads as "nothing here yet" rather than as a broken layout. --- */
|
|
509
|
+
& .dsh-ui-empty {
|
|
510
|
+
display: flex;
|
|
511
|
+
flex-direction: column;
|
|
512
|
+
align-items: flex-start;
|
|
513
|
+
gap: 6px;
|
|
514
|
+
padding: 20px 16px;
|
|
515
|
+
border: 0.5px dashed var(--dsw-alias-border-l3);
|
|
516
|
+
border-radius: var(--dsw-radius-md);
|
|
517
|
+
font-size: 13px;
|
|
518
|
+
line-height: 20px;
|
|
519
|
+
color: var(--dsw-alias-label-secondary);
|
|
520
|
+
}
|
|
521
|
+
& .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); }
|
|
522
|
+
|
|
523
|
+
/* --- Selectable list. A row is a button-sized target with a hover fill and
|
|
524
|
+
* a selected fill; the name carries the weight, the description does not. --- */
|
|
525
|
+
& .dsh-ui-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
526
|
+
& .dsh-ui-list-scroll { max-height: min(66vh, 760px); overflow-y: auto; overflow-x: hidden; }
|
|
527
|
+
& .dsh-ui-list-row { border-radius: var(--dsw-radius-md); }
|
|
528
|
+
& .dsh-ui-list-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
529
|
+
& .dsh-ui-list-row[aria-selected='true'], & .dsh-ui-list-row[data-selected] { background: var(--dsw-alias-interactive-bg-active); }
|
|
530
|
+
& .dsh-ui-list-item {
|
|
531
|
+
display: flex;
|
|
532
|
+
flex-direction: column;
|
|
533
|
+
align-items: flex-start;
|
|
534
|
+
gap: 4px;
|
|
535
|
+
width: 100%;
|
|
536
|
+
min-width: 0;
|
|
537
|
+
padding: 10px 12px;
|
|
538
|
+
border: 0;
|
|
539
|
+
border-radius: var(--dsw-radius-md);
|
|
540
|
+
background: none;
|
|
541
|
+
font: inherit;
|
|
542
|
+
color: inherit;
|
|
543
|
+
text-align: start;
|
|
544
|
+
cursor: pointer;
|
|
545
|
+
}
|
|
546
|
+
& .dsh-ui-list-item:disabled { cursor: default; }
|
|
547
|
+
& .dsh-ui-list-item:disabled:hover { background: none; }
|
|
548
|
+
& .dsh-ui-list-name { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
549
|
+
& .dsh-ui-list-desc { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
550
|
+
|
|
551
|
+
/* --- Code and structured output. Wraps rather than scrolls, because a
|
|
552
|
+
* feature panel is read rather than scanned. --- */
|
|
553
|
+
& .dsh-ui-code {
|
|
554
|
+
padding: 10px 12px;
|
|
555
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
556
|
+
border-radius: var(--dsw-radius-md);
|
|
557
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
558
|
+
font-family: var(--ds-font-family-code, ui-monospace, 'Cascadia Mono', Consolas, monospace);
|
|
559
|
+
font-size: 12px;
|
|
560
|
+
line-height: 20px;
|
|
561
|
+
color: var(--dsw-alias-label-secondary);
|
|
562
|
+
overflow-wrap: anywhere;
|
|
563
|
+
white-space: pre-wrap;
|
|
564
|
+
}
|
|
565
|
+
& .dsh-ui-scroll { max-height: 60vh; overflow: auto; overscroll-behavior: contain; }
|
|
566
|
+
|
|
567
|
+
/* --- A toggle row: label on the left, switch on the trailing edge. This is
|
|
568
|
+
* layout only; the switch itself is the official primitive. --- */
|
|
569
|
+
& .dsh-ui-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-width: 0; padding: 12px 0; }
|
|
570
|
+
& .dsh-ui-toggle-row + .dsh-ui-toggle-row { border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
571
|
+
& .dsh-ui-toggle-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
572
|
+
& .dsh-ui-toggle-label { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
573
|
+
|
|
574
|
+
/* --- Floating surface: a panel anchored to a trigger. This is the material
|
|
575
|
+
* a menu card is made of — the translucent fill plus its backdrop blur, with
|
|
576
|
+
* the edge riding inside the prominent elevation. A plugin that hand-rolls this
|
|
577
|
+
* is how tokens like --dsw-specific-menu get invented; use this instead. --- */
|
|
578
|
+
& .dsh-ui-surface {
|
|
579
|
+
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
580
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
581
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
582
|
+
position: relative;
|
|
583
|
+
display: flex;
|
|
584
|
+
flex-direction: column;
|
|
585
|
+
border: 0;
|
|
586
|
+
border-radius: var(--dsw-radius-lg);
|
|
587
|
+
background: var(--dsw-menu-surface-fill);
|
|
588
|
+
backdrop-filter: var(--dsw-menu-backdrop-filter);
|
|
589
|
+
box-shadow: var(--dsw-elevation-prominent);
|
|
590
|
+
color: var(--dsw-alias-label-primary);
|
|
591
|
+
font-size: 13px;
|
|
592
|
+
line-height: 20px;
|
|
593
|
+
min-width: 0;
|
|
594
|
+
}
|
|
595
|
+
& .dsh-ui-surface-body { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px 14px; overflow-y: auto; }
|
|
596
|
+
& .dsh-ui-surface-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
597
|
+
& .dsh-ui-surface-title { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
598
|
+
|
|
599
|
+
/* --- Native select. The primitives ship no select, so a plugin that offers a
|
|
600
|
+
* closed list of choices keeps the platform control and matches the official
|
|
601
|
+
* settings field geometry exactly (34px, 0.5px l4, radius-md, layer-3). A
|
|
602
|
+
* SegmentedControl is the primitive to reach for when the choices are few
|
|
603
|
+
* enough to show at once. --- */
|
|
604
|
+
& .dsh-ui-select {
|
|
605
|
+
box-sizing: border-box;
|
|
606
|
+
width: 100%;
|
|
607
|
+
min-width: 0;
|
|
608
|
+
height: 34px;
|
|
609
|
+
padding: 0 12px;
|
|
610
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
611
|
+
border-radius: var(--dsw-radius-md);
|
|
612
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
613
|
+
font: inherit;
|
|
614
|
+
font-size: 13px;
|
|
615
|
+
line-height: 1.5;
|
|
616
|
+
color: var(--dsw-alias-label-primary);
|
|
617
|
+
cursor: pointer;
|
|
618
|
+
}
|
|
619
|
+
& .dsh-ui-select:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
620
|
+
& .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
621
|
+
& .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
622
|
+
|
|
623
|
+
/* Long panels scroll inside themselves instead of growing the host page. */
|
|
624
|
+
& .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
625
|
+
& .dsh-ui-panel-wide { max-width: 100%; }
|
|
626
|
+
|
|
627
|
+
@media (prefers-reduced-motion: reduce) {
|
|
628
|
+
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
|
|
632
|
+
}
|
|
243
633
|
//#endregion
|
|
244
634
|
//#region src/contracts.ts
|
|
245
635
|
const RPC_CHANNEL = "/dsh-current-title";
|
|
@@ -248,26 +638,58 @@ const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
|
248
638
|
//#region src/client-work.ts
|
|
249
639
|
const titleCopy = {
|
|
250
640
|
zh: {
|
|
251
|
-
label: "
|
|
641
|
+
label: "自动标题",
|
|
252
642
|
pinned: "已手动命名",
|
|
253
643
|
generating: "正在生成标题…",
|
|
254
644
|
regenerate: "重新生成",
|
|
255
645
|
locale: "类型标签语言",
|
|
256
646
|
auto: "自动",
|
|
647
|
+
prompt: "标题提示词",
|
|
648
|
+
promptHint: "留空用内置说明,仍返回类型和摘要;离开输入框即保存。",
|
|
649
|
+
loading: "正在读取标题设置…",
|
|
650
|
+
model: "标题模型",
|
|
651
|
+
modelDefault: "跟随会话模型",
|
|
652
|
+
effort: "思考强度",
|
|
653
|
+
effortDefault: "默认",
|
|
654
|
+
cadence: "更新节奏",
|
|
655
|
+
cadenceAll: "跟随当前任务(每条消息)",
|
|
656
|
+
cadenceFirst: "仅首条消息",
|
|
257
657
|
saved: "已保存。",
|
|
258
|
-
failed: "无法读取标题设置。"
|
|
658
|
+
failed: "无法读取标题设置。",
|
|
659
|
+
inactive: "标题服务未接管,新消息暂时不会自动更新。"
|
|
259
660
|
},
|
|
260
661
|
en: {
|
|
261
|
-
label: "
|
|
662
|
+
label: "Auto Title",
|
|
262
663
|
pinned: "Renamed manually",
|
|
263
664
|
generating: "Generating title…",
|
|
264
665
|
regenerate: "Regenerate",
|
|
265
666
|
locale: "Type-label language",
|
|
266
667
|
auto: "Auto",
|
|
668
|
+
prompt: "Title prompt",
|
|
669
|
+
promptHint: "Empty uses the built-in instruction; a type and summary are still returned. Saves on blur.",
|
|
670
|
+
loading: "Loading title settings…",
|
|
671
|
+
model: "Title model",
|
|
672
|
+
modelDefault: "Use the session model",
|
|
673
|
+
effort: "Reasoning effort",
|
|
674
|
+
effortDefault: "Default",
|
|
675
|
+
cadence: "Update cadence",
|
|
676
|
+
cadenceAll: "Track the current task (every message)",
|
|
677
|
+
cadenceFirst: "First message only",
|
|
267
678
|
saved: "Saved.",
|
|
268
|
-
failed: "Could not read title settings."
|
|
679
|
+
failed: "Could not read title settings.",
|
|
680
|
+
inactive: "Title generation is not active, so new messages will not rename this session."
|
|
269
681
|
}
|
|
270
682
|
};
|
|
683
|
+
/** `undefined` draft = not edited; an empty string is a real edit (use built-in instruction). */
|
|
684
|
+
function promptFieldValue(draft, saved) {
|
|
685
|
+
return draft ?? saved;
|
|
686
|
+
}
|
|
687
|
+
/** The prompt to persist on blur, or `undefined` when nothing changed. */
|
|
688
|
+
function promptToSave(draft, saved) {
|
|
689
|
+
if (draft === void 0) return void 0;
|
|
690
|
+
const next = draft.trim();
|
|
691
|
+
return next === saved ? void 0 : next;
|
|
692
|
+
}
|
|
271
693
|
function shouldSkipTitleRefresh(input) {
|
|
272
694
|
return input.busy;
|
|
273
695
|
}
|
|
@@ -348,7 +770,9 @@ async function runTitleSettingsSave(input) {
|
|
|
348
770
|
input.onBusy(true);
|
|
349
771
|
try {
|
|
350
772
|
const settings = await invoke(input.call, "settings", {
|
|
351
|
-
|
|
773
|
+
...input.prompt !== void 0 ? { prompt: input.prompt } : {},
|
|
774
|
+
...input.model !== void 0 ? { model: input.model } : {},
|
|
775
|
+
...input.cadence !== void 0 ? { cadence: input.cadence } : {},
|
|
352
776
|
expectedRevision: input.expectedRevision
|
|
353
777
|
});
|
|
354
778
|
if (!input.isCurrent()) return;
|
|
@@ -380,19 +804,65 @@ const name = "dsh-current-title-client";
|
|
|
380
804
|
const inject = [
|
|
381
805
|
"slots",
|
|
382
806
|
"connection",
|
|
807
|
+
"remote",
|
|
808
|
+
"remote.session",
|
|
383
809
|
"sessions",
|
|
384
810
|
"locale",
|
|
385
811
|
"uiWorkspace",
|
|
386
812
|
"uiSession"
|
|
387
813
|
];
|
|
814
|
+
function catalogChoices(value, selected) {
|
|
815
|
+
const envelope = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
816
|
+
const root = (envelope?.ok === true && envelope.value && typeof envelope.value === "object" && !Array.isArray(envelope.value) ? envelope.value : void 0) ?? envelope ?? {};
|
|
817
|
+
const groups = Array.isArray(root.groups) ? root.groups : [];
|
|
818
|
+
const choices = [];
|
|
819
|
+
for (const group of groups) {
|
|
820
|
+
if (!group || typeof group !== "object") continue;
|
|
821
|
+
const row = group;
|
|
822
|
+
const provider = typeof row.id === "string" ? row.id : "";
|
|
823
|
+
const providerName = typeof row.name === "string" && row.name ? row.name : provider;
|
|
824
|
+
if (!provider || !Array.isArray(row.models)) continue;
|
|
825
|
+
for (const model of row.models) {
|
|
826
|
+
if (!model || typeof model !== "object") continue;
|
|
827
|
+
const item = model;
|
|
828
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
829
|
+
if (!id) continue;
|
|
830
|
+
const name = typeof item.name === "string" && item.name ? item.name : id;
|
|
831
|
+
const reasoning = item.reasoning && typeof item.reasoning === "object" ? item.reasoning : void 0;
|
|
832
|
+
const efforts = Array.isArray(reasoning?.efforts) ? reasoning.efforts.flatMap((effort) => {
|
|
833
|
+
if (!effort || typeof effort !== "object") return [];
|
|
834
|
+
const entry = effort;
|
|
835
|
+
return typeof entry.id === "string" && entry.id ? [{
|
|
836
|
+
id: entry.id,
|
|
837
|
+
name: typeof entry.name === "string" && entry.name ? entry.name : entry.id
|
|
838
|
+
}] : [];
|
|
839
|
+
}) : [];
|
|
840
|
+
choices.push({
|
|
841
|
+
provider,
|
|
842
|
+
model: id,
|
|
843
|
+
label: `${providerName} / ${name}`,
|
|
844
|
+
efforts
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (selected.provider && selected.model && !choices.some((item) => item.provider === selected.provider && item.model === selected.model)) choices.push({
|
|
849
|
+
provider: selected.provider,
|
|
850
|
+
model: selected.model,
|
|
851
|
+
label: `${selected.provider} / ${selected.model}`,
|
|
852
|
+
efforts: []
|
|
853
|
+
});
|
|
854
|
+
return choices;
|
|
855
|
+
}
|
|
388
856
|
function TitleSettings(props) {
|
|
389
|
-
const seat = useNativeSeat(props.client
|
|
857
|
+
const seat = useNativeSeat(props.client);
|
|
390
858
|
const sessionId = seat.sessionId;
|
|
391
859
|
const text = titleCopy[seat.locale];
|
|
392
860
|
const [status, setStatus] = (0, react.useState)();
|
|
393
861
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
394
862
|
const [note, setNote] = (0, react.useState)("");
|
|
395
863
|
const [error, setError] = (0, react.useState)("");
|
|
864
|
+
const [prompt, setPrompt] = (0, react.useState)(void 0);
|
|
865
|
+
const [choices, setChoices] = (0, react.useState)([]);
|
|
396
866
|
const workRef = (0, react.useRef)(void 0);
|
|
397
867
|
if (!workRef.current) workRef.current = createTitleClientWork();
|
|
398
868
|
const work = workRef.current;
|
|
@@ -405,6 +875,26 @@ function TitleSettings(props) {
|
|
|
405
875
|
(0, react.useEffect)(() => () => {
|
|
406
876
|
work.dispose();
|
|
407
877
|
}, [work]);
|
|
878
|
+
(0, react.useEffect)(() => {
|
|
879
|
+
const catalog = props.client.remote?.session?.modelCatalog;
|
|
880
|
+
if (!catalog) return void 0;
|
|
881
|
+
let live = true;
|
|
882
|
+
catalog().then((value) => {
|
|
883
|
+
if (live) setChoices(catalogChoices(value, status?.settings.model ?? {
|
|
884
|
+
provider: "",
|
|
885
|
+
model: ""
|
|
886
|
+
}));
|
|
887
|
+
}).catch(() => {
|
|
888
|
+
if (live) setChoices([]);
|
|
889
|
+
});
|
|
890
|
+
return () => {
|
|
891
|
+
live = false;
|
|
892
|
+
};
|
|
893
|
+
}, [
|
|
894
|
+
props.client,
|
|
895
|
+
status?.settings.model.provider,
|
|
896
|
+
status?.settings.model.model
|
|
897
|
+
]);
|
|
408
898
|
(0, react.useEffect)(() => {
|
|
409
899
|
const generation = work.beginLoad();
|
|
410
900
|
const isCurrent = () => work.isLoad(generation);
|
|
@@ -456,113 +946,279 @@ function TitleSettings(props) {
|
|
|
456
946
|
work
|
|
457
947
|
]);
|
|
458
948
|
useFeatureRefresh(props.client, sessionId, refreshStatus, status?.session?.generating === true && !busy, Boolean(sessionId));
|
|
459
|
-
const
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
{
|
|
466
|
-
id: "zh",
|
|
467
|
-
label: "中文"
|
|
468
|
-
},
|
|
469
|
-
{
|
|
470
|
-
id: "en",
|
|
471
|
-
label: "English"
|
|
472
|
-
}
|
|
473
|
-
];
|
|
949
|
+
const savedPrompt = status?.settings.prompt ?? "";
|
|
950
|
+
const savedModel = status?.settings.model ?? {
|
|
951
|
+
provider: "",
|
|
952
|
+
model: ""
|
|
953
|
+
};
|
|
954
|
+
const modelKey = savedModel.provider && savedModel.model ? `${savedModel.provider}\u001f${savedModel.model}` : "";
|
|
474
955
|
const title = status?.session?.title;
|
|
475
956
|
const pinned = status?.session?.pinned === true;
|
|
476
957
|
const weOwn = status?.support.weOwn === true;
|
|
477
958
|
const canRegenerate = Boolean(sessionId) && weOwn && !busy;
|
|
959
|
+
const selectedChoice = choices.find((item) => item.provider === savedModel.provider && item.model === savedModel.model);
|
|
960
|
+
const efforts = selectedChoice ? [...selectedChoice.efforts] : [];
|
|
961
|
+
if (selectedChoice && savedModel.reasoningEffort && !efforts.some((item) => item.id === savedModel.reasoningEffort)) efforts.push({
|
|
962
|
+
id: savedModel.reasoningEffort,
|
|
963
|
+
name: savedModel.reasoningEffort
|
|
964
|
+
});
|
|
965
|
+
function saveSettings(patch) {
|
|
966
|
+
if (!status) return;
|
|
967
|
+
const token = work.beginRequest();
|
|
968
|
+
setError("");
|
|
969
|
+
setNote("");
|
|
970
|
+
runTitleSettingsSave({
|
|
971
|
+
call,
|
|
972
|
+
...patch,
|
|
973
|
+
expectedRevision: status.settings.revision,
|
|
974
|
+
isCurrent: () => work.isRequest(token),
|
|
975
|
+
savedMessage: text.saved,
|
|
976
|
+
failedMessage: text.failed,
|
|
977
|
+
onBusy: setBusy,
|
|
978
|
+
onSettings: (settings) => setStatus((current) => current ? {
|
|
979
|
+
...current,
|
|
980
|
+
settings
|
|
981
|
+
} : current),
|
|
982
|
+
onNote: setNote,
|
|
983
|
+
onError: setError
|
|
984
|
+
});
|
|
985
|
+
}
|
|
478
986
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
479
|
-
className: "current-title-settings",
|
|
987
|
+
className: "current-title-settings ct-root dsh-ui-panel",
|
|
480
988
|
"data-testid": "current-title-settings",
|
|
481
989
|
children: [
|
|
482
990
|
!status && !error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
483
991
|
role: "status",
|
|
484
|
-
|
|
992
|
+
className: "dsh-ui-hint",
|
|
993
|
+
children: text.loading
|
|
485
994
|
}) : null,
|
|
486
995
|
title ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
487
996
|
translate: "no",
|
|
997
|
+
className: "dsh-ui-title dsh-ui-wrap",
|
|
488
998
|
children: title
|
|
489
999
|
}) : null,
|
|
490
1000
|
pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
491
|
-
className: "
|
|
1001
|
+
className: "dsh-ui-meta",
|
|
492
1002
|
children: text.pinned
|
|
493
1003
|
}) : null,
|
|
494
1004
|
status?.session?.generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
495
1005
|
role: "status",
|
|
1006
|
+
className: "dsh-ui-hint",
|
|
496
1007
|
children: text.generating
|
|
497
1008
|
}) : null,
|
|
1009
|
+
status && !weOwn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1010
|
+
role: "status",
|
|
1011
|
+
className: "dsh-ui-hint",
|
|
1012
|
+
children: [text.inactive, status.support.limitation ? ` ${status.support.limitation}` : ""]
|
|
1013
|
+
}) : null,
|
|
498
1014
|
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
499
1015
|
role: "alert",
|
|
1016
|
+
className: "dsh-ui-error dsh-ui-wrap",
|
|
500
1017
|
children: error
|
|
501
1018
|
}) : null,
|
|
502
1019
|
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
503
1020
|
role: "status",
|
|
1021
|
+
className: "dsh-ui-notice",
|
|
504
1022
|
children: note
|
|
505
1023
|
}) : null,
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
setNote("");
|
|
515
|
-
runTitleRegenerateFlow({
|
|
516
|
-
call,
|
|
517
|
-
sessionId,
|
|
518
|
-
isCurrent: () => work.isRequest(token),
|
|
519
|
-
failedMessage: text.failed,
|
|
520
|
-
onBusy: setBusy,
|
|
521
|
-
onStatus: setStatus,
|
|
522
|
-
onError: setError,
|
|
523
|
-
onMarker: commitMarker
|
|
524
|
-
});
|
|
525
|
-
},
|
|
526
|
-
children: text.regenerate
|
|
527
|
-
}) : null,
|
|
528
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
529
|
-
disabled: busy || !status,
|
|
530
|
-
"aria-label": text.locale,
|
|
531
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", { children: text.locale }), modes.map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
532
|
-
type: "radio",
|
|
533
|
-
name: "current-title-locale",
|
|
534
|
-
value: mode.id,
|
|
535
|
-
checked: locale === mode.id,
|
|
536
|
-
onChange: () => {
|
|
537
|
-
if (!status) return;
|
|
1024
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1025
|
+
className: "dsh-ui-actions",
|
|
1026
|
+
children: sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1027
|
+
variant: "outline",
|
|
1028
|
+
size: "md",
|
|
1029
|
+
disabled: !canRegenerate,
|
|
1030
|
+
onClick: () => {
|
|
1031
|
+
if (!sessionId) return;
|
|
538
1032
|
const token = work.beginRequest();
|
|
539
1033
|
setError("");
|
|
540
1034
|
setNote("");
|
|
541
|
-
|
|
1035
|
+
runTitleRegenerateFlow({
|
|
542
1036
|
call,
|
|
543
|
-
|
|
544
|
-
expectedRevision: status.settings.revision,
|
|
1037
|
+
sessionId,
|
|
545
1038
|
isCurrent: () => work.isRequest(token),
|
|
546
|
-
savedMessage: text.saved,
|
|
547
1039
|
failedMessage: text.failed,
|
|
548
1040
|
onBusy: setBusy,
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}),
|
|
553
|
-
onNote: setNote,
|
|
554
|
-
onError: setError
|
|
1041
|
+
onStatus: setStatus,
|
|
1042
|
+
onError: setError,
|
|
1043
|
+
onMarker: commitMarker
|
|
555
1044
|
});
|
|
556
|
-
}
|
|
557
|
-
|
|
1045
|
+
},
|
|
1046
|
+
children: text.regenerate
|
|
1047
|
+
}) : null
|
|
1048
|
+
}),
|
|
1049
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1050
|
+
className: "dsh-ui-field",
|
|
1051
|
+
children: [
|
|
1052
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1053
|
+
className: "dsh-ui-label",
|
|
1054
|
+
children: text.prompt
|
|
1055
|
+
}),
|
|
1056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1057
|
+
className: "ct-prompt",
|
|
1058
|
+
value: status ? promptFieldValue(prompt, savedPrompt) : "",
|
|
1059
|
+
maxLength: 4e3,
|
|
1060
|
+
rows: 5,
|
|
1061
|
+
disabled: busy || !status,
|
|
1062
|
+
onChange: (event) => {
|
|
1063
|
+
setPrompt(event.target.value);
|
|
1064
|
+
setNote("");
|
|
1065
|
+
},
|
|
1066
|
+
onBlur: () => {
|
|
1067
|
+
if (!status) return;
|
|
1068
|
+
const next = promptToSave(prompt, savedPrompt);
|
|
1069
|
+
if (next === void 0) {
|
|
1070
|
+
setPrompt(void 0);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const token = work.beginRequest();
|
|
1074
|
+
setError("");
|
|
1075
|
+
runTitleSettingsSave({
|
|
1076
|
+
call,
|
|
1077
|
+
prompt: next,
|
|
1078
|
+
expectedRevision: status.settings.revision,
|
|
1079
|
+
isCurrent: () => work.isRequest(token),
|
|
1080
|
+
savedMessage: text.saved,
|
|
1081
|
+
failedMessage: text.failed,
|
|
1082
|
+
onBusy: setBusy,
|
|
1083
|
+
onSettings: (settings) => {
|
|
1084
|
+
setStatus((current) => current ? {
|
|
1085
|
+
...current,
|
|
1086
|
+
settings
|
|
1087
|
+
} : current);
|
|
1088
|
+
setPrompt(void 0);
|
|
1089
|
+
},
|
|
1090
|
+
onNote: setNote,
|
|
1091
|
+
onError: setError
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
}),
|
|
1095
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1096
|
+
className: "dsh-ui-help",
|
|
1097
|
+
children: text.promptHint
|
|
1098
|
+
})
|
|
1099
|
+
]
|
|
1100
|
+
}),
|
|
1101
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1102
|
+
className: "dsh-ui-field",
|
|
1103
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1104
|
+
className: "dsh-ui-label",
|
|
1105
|
+
children: text.model
|
|
1106
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1107
|
+
className: "dsh-ui-select",
|
|
1108
|
+
value: modelKey,
|
|
1109
|
+
disabled: busy || !status,
|
|
1110
|
+
onChange: (event) => {
|
|
1111
|
+
const value = event.target.value;
|
|
1112
|
+
const choice = choices.find((item) => `${item.provider}\u001f${item.model}` === value);
|
|
1113
|
+
saveSettings({ model: choice ? {
|
|
1114
|
+
provider: choice.provider,
|
|
1115
|
+
model: choice.model
|
|
1116
|
+
} : {
|
|
1117
|
+
provider: "",
|
|
1118
|
+
model: ""
|
|
1119
|
+
} });
|
|
1120
|
+
},
|
|
1121
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1122
|
+
value: "",
|
|
1123
|
+
children: text.modelDefault
|
|
1124
|
+
}), choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1125
|
+
value: `${choice.provider}\u001f${choice.model}`,
|
|
1126
|
+
children: choice.label
|
|
1127
|
+
}, `${choice.provider}\u001f${choice.model}`))]
|
|
1128
|
+
})]
|
|
1129
|
+
}),
|
|
1130
|
+
selectedChoice && efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1131
|
+
className: "dsh-ui-field",
|
|
1132
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1133
|
+
className: "dsh-ui-label",
|
|
1134
|
+
children: text.effort
|
|
1135
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1136
|
+
className: "dsh-ui-select",
|
|
1137
|
+
value: savedModel.reasoningEffort ?? "",
|
|
1138
|
+
disabled: busy || !status,
|
|
1139
|
+
onChange: (event) => {
|
|
1140
|
+
const effort = event.target.value;
|
|
1141
|
+
const model = {
|
|
1142
|
+
provider: savedModel.provider,
|
|
1143
|
+
model: savedModel.model
|
|
1144
|
+
};
|
|
1145
|
+
saveSettings({ model: effort ? {
|
|
1146
|
+
...model,
|
|
1147
|
+
reasoningEffort: effort
|
|
1148
|
+
} : model });
|
|
1149
|
+
},
|
|
1150
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1151
|
+
value: "",
|
|
1152
|
+
children: text.effortDefault
|
|
1153
|
+
}), efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1154
|
+
value: effort.id,
|
|
1155
|
+
children: effort.name
|
|
1156
|
+
}, effort.id))]
|
|
1157
|
+
})]
|
|
1158
|
+
}) : null,
|
|
1159
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1160
|
+
className: "dsh-ui-field",
|
|
1161
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1162
|
+
className: "dsh-ui-label",
|
|
1163
|
+
children: text.cadence
|
|
1164
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1165
|
+
className: "dsh-ui-select",
|
|
1166
|
+
value: status?.settings.cadence ?? "all-prompts",
|
|
1167
|
+
disabled: busy || !status,
|
|
1168
|
+
onChange: (event) => {
|
|
1169
|
+
saveSettings({ cadence: event.target.value === "first-prompt" ? "first-prompt" : "all-prompts" });
|
|
1170
|
+
},
|
|
1171
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1172
|
+
value: "all-prompts",
|
|
1173
|
+
children: text.cadenceAll
|
|
1174
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1175
|
+
value: "first-prompt",
|
|
1176
|
+
children: text.cadenceFirst
|
|
1177
|
+
})]
|
|
1178
|
+
})]
|
|
558
1179
|
})
|
|
559
1180
|
]
|
|
560
1181
|
});
|
|
561
1182
|
}
|
|
1183
|
+
const styles = `${officialUiCss("ct-root")}
|
|
1184
|
+
.ct-root { max-width: 760px; }
|
|
1185
|
+
.ct-prompt {
|
|
1186
|
+
box-sizing: border-box;
|
|
1187
|
+
width: 100%;
|
|
1188
|
+
min-width: 0;
|
|
1189
|
+
min-height: 96px;
|
|
1190
|
+
resize: vertical;
|
|
1191
|
+
padding: 8px 12px;
|
|
1192
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
1193
|
+
border-radius: var(--dsw-radius-md);
|
|
1194
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
1195
|
+
font: inherit;
|
|
1196
|
+
font-size: 13px;
|
|
1197
|
+
line-height: 1.5;
|
|
1198
|
+
color: var(--dsw-alias-label-primary);
|
|
1199
|
+
}
|
|
1200
|
+
.ct-prompt:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
1201
|
+
.ct-prompt::placeholder { color: var(--dsw-alias-label-dimmed); }
|
|
1202
|
+
`;
|
|
562
1203
|
function apply(ctx) {
|
|
563
1204
|
const client = ctx;
|
|
564
1205
|
const marker = createTitleClientMarker();
|
|
565
1206
|
ctx.provide(TITLE_CLIENT_SERVICE, marker);
|
|
1207
|
+
ctx.effect(() => {
|
|
1208
|
+
if (typeof document === "undefined") return () => {};
|
|
1209
|
+
const style = document.createElement("style");
|
|
1210
|
+
style.setAttribute("data-plugin", name);
|
|
1211
|
+
style.textContent = styles;
|
|
1212
|
+
document.head.appendChild(style);
|
|
1213
|
+
return () => style.remove();
|
|
1214
|
+
}, "current-title.styles");
|
|
1215
|
+
ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
1216
|
+
name: "plugins.bundle.config",
|
|
1217
|
+
key: "@klarkxy/dsh-current-title"
|
|
1218
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettings, {
|
|
1219
|
+
client,
|
|
1220
|
+
marker
|
|
1221
|
+
}))), "current-title.settings");
|
|
566
1222
|
ctx.effect(() => {
|
|
567
1223
|
let live = true;
|
|
568
1224
|
client.connection.rpc.call(RPC_CHANNEL, "status", {}).then((result) => {
|