@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.inner.cjs
CHANGED
|
@@ -1,23 +1,41 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let react = require("react");
|
|
3
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
3
4
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
-
//#region ../dsh-
|
|
5
|
+
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
5
6
|
/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
|
|
6
7
|
function selectedSessionId(client) {
|
|
7
|
-
|
|
8
|
+
const workspace = client.uiWorkspace?.current;
|
|
9
|
+
return workspace ? workspace.getSnapshot()?.sessionId ?? "" : client.uiSession.adapter.current.getSnapshot().key ?? "";
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Owner props are data the host composed, never a Cordis context: reading an
|
|
13
|
+
* undeclared property off one throws instead of returning undefined, which
|
|
14
|
+
* would crash the slot entry and abdicate its cell for good. Tolerate a
|
|
15
|
+
* throwing read so a mistaken prop degrades to "not supplied".
|
|
16
|
+
*/
|
|
17
|
+
function read(source, key) {
|
|
18
|
+
if (source === null || typeof source !== "object") return void 0;
|
|
19
|
+
try {
|
|
20
|
+
return source[key];
|
|
21
|
+
} catch {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function readString(source, key) {
|
|
26
|
+
const value = read(source, key);
|
|
27
|
+
return typeof value === "string" ? value : void 0;
|
|
8
28
|
}
|
|
9
29
|
/** Owner props win; native settings only supplies close, so resolve its selected session. */
|
|
10
30
|
function useNativeSeat(client, props) {
|
|
11
|
-
const current = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => (client.uiWorkspace
|
|
31
|
+
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]), () => "");
|
|
12
32
|
const language = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => client.locale.subscribe(fn), [client]), (0, react.useCallback)(() => client.locale.getSnapshot().active, [client]), () => "en");
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const sessionId = typeof row.sessionId === "string" ? row.sessionId : typeof owner.sessionId === "string" ? owner.sessionId : current;
|
|
16
|
-
const requestedLocale = row.locale ?? owner.locale ?? language;
|
|
33
|
+
const sessionId = readString(props, "sessionId") ?? readString(read(props, "owner"), "sessionId") ?? current;
|
|
34
|
+
const requestedLocale = read(props, "locale") ?? read(read(props, "owner"), "locale") ?? language;
|
|
17
35
|
return {
|
|
18
36
|
sessionId,
|
|
19
37
|
locale: String(requestedLocale).startsWith("zh") ? "zh" : "en",
|
|
20
|
-
hidden:
|
|
38
|
+
hidden: read(props, "hidden") === true || read(read(props, "owner"), "hidden") === true
|
|
21
39
|
};
|
|
22
40
|
}
|
|
23
41
|
/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
|
|
@@ -73,6 +91,378 @@ function useFeatureRefresh(client, sessionId, refresh, working = false, enabled
|
|
|
73
91
|
enabled
|
|
74
92
|
]);
|
|
75
93
|
}
|
|
94
|
+
Object.values({
|
|
95
|
+
/** Surfaces, from the page up to the topmost nested card. */
|
|
96
|
+
surface: [
|
|
97
|
+
"--dsw-alias-bg-base",
|
|
98
|
+
"--dsw-alias-bg-layer-1",
|
|
99
|
+
"--dsw-alias-bg-layer-2",
|
|
100
|
+
"--dsw-alias-bg-layer-3",
|
|
101
|
+
"--dsw-alias-bg-layer-4",
|
|
102
|
+
"--dsw-alias-bg-module-platform",
|
|
103
|
+
"--dsw-alias-bg-overlay"
|
|
104
|
+
],
|
|
105
|
+
/** Hairlines. l1 is near-invisible on a menu surface, so menus use l2+. */
|
|
106
|
+
border: [
|
|
107
|
+
"--dsw-alias-border-l1",
|
|
108
|
+
"--dsw-alias-border-l2",
|
|
109
|
+
"--dsw-alias-border-l3",
|
|
110
|
+
"--dsw-alias-border-l4"
|
|
111
|
+
],
|
|
112
|
+
/** Text. `caption` is menu shortcut text, `dimmed` is placeholder text. */
|
|
113
|
+
label: [
|
|
114
|
+
"--dsw-alias-label-primary",
|
|
115
|
+
"--dsw-alias-label-secondary",
|
|
116
|
+
"--dsw-alias-label-tertiary",
|
|
117
|
+
"--dsw-alias-label-caption",
|
|
118
|
+
"--dsw-alias-label-dimmed",
|
|
119
|
+
"--dsw-alias-label-error",
|
|
120
|
+
"--dsw-alias-label-primary-foreground"
|
|
121
|
+
],
|
|
122
|
+
/** Interactive fills. The `-danger` hover belongs to destructive rows. */
|
|
123
|
+
interactive: [
|
|
124
|
+
"--dsw-alias-interactive-bg-hover",
|
|
125
|
+
"--dsw-alias-interactive-bg-active",
|
|
126
|
+
"--dsw-alias-interactive-bg-hover-danger"
|
|
127
|
+
],
|
|
128
|
+
/** Accent and status. `business` is the focus ring's default colour. */
|
|
129
|
+
accent: [
|
|
130
|
+
"--dsw-alias-brand-primary",
|
|
131
|
+
"--dsw-alias-button-primary-fill",
|
|
132
|
+
"--dsw-alias-button-primary-hover",
|
|
133
|
+
"--dsw-alias-button-tool-bar-fill",
|
|
134
|
+
"--dsw-alias-button-tool-bar-hover",
|
|
135
|
+
"--dsw-alias-button-ghost-active-fill",
|
|
136
|
+
"--dsw-alias-button-ghost-active-border"
|
|
137
|
+
],
|
|
138
|
+
state: [
|
|
139
|
+
"--dsw-alias-state-business-primary",
|
|
140
|
+
"--dsw-alias-state-success-primary",
|
|
141
|
+
"--dsw-alias-state-warn-primary",
|
|
142
|
+
"--dsw-alias-state-error-primary",
|
|
143
|
+
"--dsw-alias-state-idle-primary"
|
|
144
|
+
],
|
|
145
|
+
/** Corner scale: sm 8, md 12, lg the large card, panel the modal. */
|
|
146
|
+
radius: [
|
|
147
|
+
"--dsw-radius-sm",
|
|
148
|
+
"--dsw-radius-md",
|
|
149
|
+
"--dsw-radius-lg",
|
|
150
|
+
"--dsw-radius-panel"
|
|
151
|
+
],
|
|
152
|
+
/** An elevated surface draws its edge in the shadow, so it keeps border: 0. */
|
|
153
|
+
elevation: [
|
|
154
|
+
"--dsw-elevation-soft",
|
|
155
|
+
"--dsw-elevation-stroke",
|
|
156
|
+
"--dsw-elevation-stroke-color",
|
|
157
|
+
"--dsw-elevation-panel",
|
|
158
|
+
"--dsw-elevation-prominent"
|
|
159
|
+
],
|
|
160
|
+
/** Floats, masks and scrollbars. */
|
|
161
|
+
overlay: [
|
|
162
|
+
"--dsw-menu-surface-fill",
|
|
163
|
+
"--dsw-menu-backdrop-filter",
|
|
164
|
+
"--dsw-mask-blur",
|
|
165
|
+
"--dsw-alias-bg-mask-1",
|
|
166
|
+
"--dsw-alias-menu-icon",
|
|
167
|
+
"--dsw-alias-scrollbar-bg-l2",
|
|
168
|
+
"--dsw-alias-scrollbar-hover-l2",
|
|
169
|
+
"--dsw-shadow-lv3"
|
|
170
|
+
],
|
|
171
|
+
/** Motion, focus and type axis, published by the host rather than by a component. */
|
|
172
|
+
system: [
|
|
173
|
+
"--dsw-focus-ring-width",
|
|
174
|
+
"--dsw-focus-ring-color",
|
|
175
|
+
"--dsw-font-markdown-code-font-family",
|
|
176
|
+
"--ds-transition-duration",
|
|
177
|
+
"--ds-ease-in-out",
|
|
178
|
+
"--ds-font-family-code",
|
|
179
|
+
"--dsh-scrollbar-thumb",
|
|
180
|
+
"--dsh-scrollbar-thumb-hover",
|
|
181
|
+
"--dsh-content-font-size-secondary",
|
|
182
|
+
"--dsh-content-font-delta",
|
|
183
|
+
"--dsh-frame-top-clearance"
|
|
184
|
+
]
|
|
185
|
+
}).flat();
|
|
186
|
+
/**
|
|
187
|
+
* The host's focus ring shorthand, verbatim. Writing this per control is how
|
|
188
|
+
* rings drift apart; a stylesheet that draws its own focus style should use
|
|
189
|
+
* {@link officialFocus} instead. The colour fallback is the host's own default.
|
|
190
|
+
*/
|
|
191
|
+
const OFFICIAL_FOCUS_RING = "var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary))";
|
|
192
|
+
/**
|
|
193
|
+
* A complete `outline` declaration at the official geometry. `offset` defaults
|
|
194
|
+
* to the 2px the native controls use; the host's inline field rules sit at 1px
|
|
195
|
+
* and a segmented tab pulls the ring inside itself at -2px.
|
|
196
|
+
*/
|
|
197
|
+
function officialFocus(offset = "2px") {
|
|
198
|
+
return `outline: ${OFFICIAL_FOCUS_RING}; outline-offset: ${offset}`;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* An elevated surface. The host draws a raised surface's edge inside its
|
|
202
|
+
* shadow rather than as a border, so a card sets `border: 0` and takes the edge
|
|
203
|
+
* from the elevation — which is also why its border stays off the alias-border
|
|
204
|
+
* set that a flat, non-elevated rule would use.
|
|
205
|
+
*/
|
|
206
|
+
function officialElevation(level) {
|
|
207
|
+
return `border: 0; box-shadow: var(--dsw-elevation-${level})`;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The shared stylesheet for a plugin UI, scoped under that plugin's root class.
|
|
211
|
+
*
|
|
212
|
+
* Scoping is what keeps this safe to always-bundle into every client: the rules
|
|
213
|
+
* can only match inside the plugin's own subtree, so two plugins can both mount
|
|
214
|
+
* `dsh-ui-card` without either styling the other's markup.
|
|
215
|
+
*
|
|
216
|
+
* Each recipe matches the root element *and* its descendants, because the
|
|
217
|
+
* commonest arrangement is one element that is both — a surface that carries
|
|
218
|
+
* the root class and `dsh-ui-card`, or a portaled dialog that is the root and
|
|
219
|
+
* the `dsh-ui-surface`. A descendant-only scope would silently leave those
|
|
220
|
+
* unstyled, with no error anywhere.
|
|
221
|
+
*
|
|
222
|
+
* A caveat worth knowing: `Modal` and `Menu` portal to `document.body`, so their
|
|
223
|
+
* content leaves the plugin's subtree entirely. A portaled surface therefore
|
|
224
|
+
* needs its own root class on an element inside the portal.
|
|
225
|
+
*
|
|
226
|
+
* Only patterns that primitives do not provide live here. If a control appears
|
|
227
|
+
* below that `Button`/`Input`/`Switch`/`Tag`/`Menu` already covers, that is a
|
|
228
|
+
* bug in this file: use the primitive and delete the recipe.
|
|
229
|
+
*/
|
|
230
|
+
function officialUiCss(roots) {
|
|
231
|
+
const scope = (typeof roots === "string" ? [roots] : roots).map((root) => `.${root}`).join(", ");
|
|
232
|
+
return `
|
|
233
|
+
/* Official DSH Web UI contract. Generated by @klarkxy/dsh-plugin-kit/official-ui.
|
|
234
|
+
* Do not hand-edit the copy inside a plugin: change this module so every plugin
|
|
235
|
+
* moves together. Scope: ${scope} */
|
|
236
|
+
/* The scope rule below stays open for the whole sheet: every rule after it
|
|
237
|
+
* nests with the parent selector, so one stylesheet can cover several unrelated
|
|
238
|
+
* roots without leaking into a neighbouring plugin. Its closing brace is last. */
|
|
239
|
+
${scope} {
|
|
240
|
+
color: var(--dsw-alias-label-primary);
|
|
241
|
+
font-family: inherit;
|
|
242
|
+
font-size: 14px;
|
|
243
|
+
line-height: 22px;
|
|
244
|
+
min-width: 0;
|
|
245
|
+
|
|
246
|
+
& *, & *::before, & *::after { box-sizing: border-box; }
|
|
247
|
+
& h1, & h2, & h3, & h4, & p, & figure { margin: 0; }
|
|
248
|
+
/* Keyboard focus only: pointer input keeps the native resting appearance. The
|
|
249
|
+
* ring needs no radius of its own — the outline already follows the control's
|
|
250
|
+
* own border-radius, and overriding it here would flatten every control. */
|
|
251
|
+
& :focus-visible { ${officialFocus()}; }
|
|
252
|
+
& [hidden] { display: none !important; }
|
|
253
|
+
|
|
254
|
+
/* --- Type tiers. The same 14/22 and 13/20 steps the primitives use. --- */
|
|
255
|
+
& .dsh-ui-title { font-size: 16px; line-height: 24px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
256
|
+
& .dsh-ui-heading { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
257
|
+
& .dsh-ui-compact { font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-primary); }
|
|
258
|
+
& .dsh-ui-meta { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
259
|
+
& .dsh-ui-hint { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
260
|
+
& .dsh-ui-error { font-size: 12px; line-height: 18px; color: var(--dsw-alias-state-error-primary); }
|
|
261
|
+
& .dsh-ui-warn { color: var(--dsw-alias-state-warn-primary); }
|
|
262
|
+
& .dsh-ui-success { color: var(--dsw-alias-state-success-primary); }
|
|
263
|
+
& .dsh-ui-muted { color: var(--dsw-alias-label-tertiary); }
|
|
264
|
+
& .dsh-ui-truncate { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
265
|
+
& .dsh-ui-wrap { min-width: 0; overflow-wrap: anywhere; }
|
|
266
|
+
|
|
267
|
+
/* --- Layout scaffolding. --- */
|
|
268
|
+
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
269
|
+
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
270
|
+
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
271
|
+
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
272
|
+
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
273
|
+
/* A 0.5px l2 rule is the host's section divider; reserve the same rhythm. */
|
|
274
|
+
& .dsh-ui-divider { height: 0.5px; background: var(--dsw-alias-border-l2); }
|
|
275
|
+
& .dsh-ui-section { display: flex; flex-direction: column; gap: 12px; padding-top: 16px; border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
276
|
+
|
|
277
|
+
/* --- Card: a raised surface whose edge lives in the shadow. --- */
|
|
278
|
+
& .dsh-ui-card {
|
|
279
|
+
${officialElevation("stroke")}
|
|
280
|
+
display: flex;
|
|
281
|
+
flex-direction: column;
|
|
282
|
+
gap: 12px;
|
|
283
|
+
padding: 16px;
|
|
284
|
+
border-radius: var(--dsw-radius-lg);
|
|
285
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
286
|
+
color: var(--dsw-alias-label-primary);
|
|
287
|
+
min-width: 0;
|
|
288
|
+
}
|
|
289
|
+
& .dsh-ui-card--flat { border: 0.5px solid var(--dsw-alias-border-l3); box-shadow: none; }
|
|
290
|
+
& .dsh-ui-card--nested { background: var(--dsw-alias-bg-layer-2); }
|
|
291
|
+
& .dsh-ui-card--plain { background: none; }
|
|
292
|
+
|
|
293
|
+
/* --- Field: label, control, help, error. The control itself is an
|
|
294
|
+
* official primitive, so this only owns the vertical rhythm. --- */
|
|
295
|
+
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
296
|
+
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
297
|
+
& .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); }
|
|
298
|
+
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
299
|
+
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
300
|
+
& .dsh-ui-help p { margin: 0; }
|
|
301
|
+
& .dsh-ui-help p + p { margin-top: 8px; }
|
|
302
|
+
& .dsh-ui-required { color: var(--dsw-alias-state-error-primary); }
|
|
303
|
+
& .dsh-ui-control { width: 100%; min-width: 0; }
|
|
304
|
+
/* An invalid control is marked on the control, not only in the message. */
|
|
305
|
+
& .dsh-ui-control[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
306
|
+
& .dsh-ui-readonly { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
307
|
+
& .dsh-ui-readonly > dt { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
308
|
+
& .dsh-ui-readonly > dd { margin: 0; font-size: 13px; line-height: 20px; overflow-wrap: anywhere; }
|
|
309
|
+
|
|
310
|
+
/* --- Buttons row. A no-op container: it exists so actions line up on the
|
|
311
|
+
* trailing edge the way every native panel does. --- */
|
|
312
|
+
& .dsh-ui-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 4px; }
|
|
313
|
+
& .dsh-ui-actions-end { justify-content: flex-end; }
|
|
314
|
+
|
|
315
|
+
/* --- Banners. A banner is a left-rail notice, the shape used for warnings,
|
|
316
|
+
* deprecations and blocking instructions. --- */
|
|
317
|
+
& .dsh-ui-banner {
|
|
318
|
+
display: flex;
|
|
319
|
+
flex-direction: column;
|
|
320
|
+
gap: 8px;
|
|
321
|
+
padding: 10px 14px;
|
|
322
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
323
|
+
border-left: 3px solid var(--dsw-alias-state-warn-primary);
|
|
324
|
+
border-radius: var(--dsw-radius-md);
|
|
325
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
326
|
+
font-size: 13px;
|
|
327
|
+
line-height: 20px;
|
|
328
|
+
color: var(--dsw-alias-label-secondary);
|
|
329
|
+
}
|
|
330
|
+
& .dsh-ui-banner-title { font-weight: 500; color: var(--dsw-alias-state-warn-primary); }
|
|
331
|
+
& .dsh-ui-banner--info { border-left-color: var(--dsw-alias-state-business-primary); }
|
|
332
|
+
& .dsh-ui-banner--danger { border-left-color: var(--dsw-alias-state-error-primary); }
|
|
333
|
+
& .dsh-ui-banner--success { border-left-color: var(--dsw-alias-state-success-primary); }
|
|
334
|
+
& .dsh-ui-banner p { margin: 0; }
|
|
335
|
+
|
|
336
|
+
/* --- Inline notice: a transient confirmation with no chrome of its own. --- */
|
|
337
|
+
& .dsh-ui-notice { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-state-success-primary); }
|
|
338
|
+
& .dsh-ui-notice--error { color: var(--dsw-alias-state-error-primary); }
|
|
339
|
+
|
|
340
|
+
/* --- Empty and loading states. Both sit in a dashed well so an empty panel
|
|
341
|
+
* reads as "nothing here yet" rather than as a broken layout. --- */
|
|
342
|
+
& .dsh-ui-empty {
|
|
343
|
+
display: flex;
|
|
344
|
+
flex-direction: column;
|
|
345
|
+
align-items: flex-start;
|
|
346
|
+
gap: 6px;
|
|
347
|
+
padding: 20px 16px;
|
|
348
|
+
border: 0.5px dashed var(--dsw-alias-border-l3);
|
|
349
|
+
border-radius: var(--dsw-radius-md);
|
|
350
|
+
font-size: 13px;
|
|
351
|
+
line-height: 20px;
|
|
352
|
+
color: var(--dsw-alias-label-secondary);
|
|
353
|
+
}
|
|
354
|
+
& .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); }
|
|
355
|
+
|
|
356
|
+
/* --- Selectable list. A row is a button-sized target with a hover fill and
|
|
357
|
+
* a selected fill; the name carries the weight, the description does not. --- */
|
|
358
|
+
& .dsh-ui-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
359
|
+
& .dsh-ui-list-scroll { max-height: min(66vh, 760px); overflow-y: auto; overflow-x: hidden; }
|
|
360
|
+
& .dsh-ui-list-row { border-radius: var(--dsw-radius-md); }
|
|
361
|
+
& .dsh-ui-list-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
362
|
+
& .dsh-ui-list-row[aria-selected='true'], & .dsh-ui-list-row[data-selected] { background: var(--dsw-alias-interactive-bg-active); }
|
|
363
|
+
& .dsh-ui-list-item {
|
|
364
|
+
display: flex;
|
|
365
|
+
flex-direction: column;
|
|
366
|
+
align-items: flex-start;
|
|
367
|
+
gap: 4px;
|
|
368
|
+
width: 100%;
|
|
369
|
+
min-width: 0;
|
|
370
|
+
padding: 10px 12px;
|
|
371
|
+
border: 0;
|
|
372
|
+
border-radius: var(--dsw-radius-md);
|
|
373
|
+
background: none;
|
|
374
|
+
font: inherit;
|
|
375
|
+
color: inherit;
|
|
376
|
+
text-align: start;
|
|
377
|
+
cursor: pointer;
|
|
378
|
+
}
|
|
379
|
+
& .dsh-ui-list-item:disabled { cursor: default; }
|
|
380
|
+
& .dsh-ui-list-item:disabled:hover { background: none; }
|
|
381
|
+
& .dsh-ui-list-name { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
382
|
+
& .dsh-ui-list-desc { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
383
|
+
|
|
384
|
+
/* --- Code and structured output. Wraps rather than scrolls, because a
|
|
385
|
+
* feature panel is read rather than scanned. --- */
|
|
386
|
+
& .dsh-ui-code {
|
|
387
|
+
padding: 10px 12px;
|
|
388
|
+
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
389
|
+
border-radius: var(--dsw-radius-md);
|
|
390
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
391
|
+
font-family: var(--ds-font-family-code, ui-monospace, 'Cascadia Mono', Consolas, monospace);
|
|
392
|
+
font-size: 12px;
|
|
393
|
+
line-height: 20px;
|
|
394
|
+
color: var(--dsw-alias-label-secondary);
|
|
395
|
+
overflow-wrap: anywhere;
|
|
396
|
+
white-space: pre-wrap;
|
|
397
|
+
}
|
|
398
|
+
& .dsh-ui-scroll { max-height: 60vh; overflow: auto; overscroll-behavior: contain; }
|
|
399
|
+
|
|
400
|
+
/* --- A toggle row: label on the left, switch on the trailing edge. This is
|
|
401
|
+
* layout only; the switch itself is the official primitive. --- */
|
|
402
|
+
& .dsh-ui-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-width: 0; padding: 12px 0; }
|
|
403
|
+
& .dsh-ui-toggle-row + .dsh-ui-toggle-row { border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
404
|
+
& .dsh-ui-toggle-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
405
|
+
& .dsh-ui-toggle-label { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
406
|
+
|
|
407
|
+
/* --- Floating surface: a panel anchored to a trigger. This is the material
|
|
408
|
+
* a menu card is made of — the translucent fill plus its backdrop blur, with
|
|
409
|
+
* the edge riding inside the prominent elevation. A plugin that hand-rolls this
|
|
410
|
+
* is how tokens like --dsw-specific-menu get invented; use this instead. --- */
|
|
411
|
+
& .dsh-ui-surface {
|
|
412
|
+
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
413
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
414
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
415
|
+
position: relative;
|
|
416
|
+
display: flex;
|
|
417
|
+
flex-direction: column;
|
|
418
|
+
border: 0;
|
|
419
|
+
border-radius: var(--dsw-radius-lg);
|
|
420
|
+
background: var(--dsw-menu-surface-fill);
|
|
421
|
+
backdrop-filter: var(--dsw-menu-backdrop-filter);
|
|
422
|
+
box-shadow: var(--dsw-elevation-prominent);
|
|
423
|
+
color: var(--dsw-alias-label-primary);
|
|
424
|
+
font-size: 13px;
|
|
425
|
+
line-height: 20px;
|
|
426
|
+
min-width: 0;
|
|
427
|
+
}
|
|
428
|
+
& .dsh-ui-surface-body { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px 14px; overflow-y: auto; }
|
|
429
|
+
& .dsh-ui-surface-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
430
|
+
& .dsh-ui-surface-title { font-size: 14px; line-height: 22px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
431
|
+
|
|
432
|
+
/* --- Native select. The primitives ship no select, so a plugin that offers a
|
|
433
|
+
* closed list of choices keeps the platform control and matches the official
|
|
434
|
+
* settings field geometry exactly (34px, 0.5px l4, radius-md, layer-3). A
|
|
435
|
+
* SegmentedControl is the primitive to reach for when the choices are few
|
|
436
|
+
* enough to show at once. --- */
|
|
437
|
+
& .dsh-ui-select {
|
|
438
|
+
box-sizing: border-box;
|
|
439
|
+
width: 100%;
|
|
440
|
+
min-width: 0;
|
|
441
|
+
height: 34px;
|
|
442
|
+
padding: 0 12px;
|
|
443
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
444
|
+
border-radius: var(--dsw-radius-md);
|
|
445
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
446
|
+
font: inherit;
|
|
447
|
+
font-size: 13px;
|
|
448
|
+
line-height: 1.5;
|
|
449
|
+
color: var(--dsw-alias-label-primary);
|
|
450
|
+
cursor: pointer;
|
|
451
|
+
}
|
|
452
|
+
& .dsh-ui-select:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
453
|
+
& .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
454
|
+
& .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
455
|
+
|
|
456
|
+
/* Long panels scroll inside themselves instead of growing the host page. */
|
|
457
|
+
& .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
458
|
+
& .dsh-ui-panel-wide { max-width: 100%; }
|
|
459
|
+
|
|
460
|
+
@media (prefers-reduced-motion: reduce) {
|
|
461
|
+
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
|
|
465
|
+
}
|
|
76
466
|
//#endregion
|
|
77
467
|
//#region src/contracts.ts
|
|
78
468
|
const RPC_CHANNEL = "/dsh-current-title";
|
|
@@ -81,26 +471,58 @@ const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
|
81
471
|
//#region src/client-work.ts
|
|
82
472
|
const titleCopy = {
|
|
83
473
|
zh: {
|
|
84
|
-
label: "
|
|
474
|
+
label: "自动标题",
|
|
85
475
|
pinned: "已手动命名",
|
|
86
476
|
generating: "正在生成标题…",
|
|
87
477
|
regenerate: "重新生成",
|
|
88
478
|
locale: "类型标签语言",
|
|
89
479
|
auto: "自动",
|
|
480
|
+
prompt: "标题提示词",
|
|
481
|
+
promptHint: "留空用内置说明,仍返回类型和摘要;离开输入框即保存。",
|
|
482
|
+
loading: "正在读取标题设置…",
|
|
483
|
+
model: "标题模型",
|
|
484
|
+
modelDefault: "跟随会话模型",
|
|
485
|
+
effort: "思考强度",
|
|
486
|
+
effortDefault: "默认",
|
|
487
|
+
cadence: "更新节奏",
|
|
488
|
+
cadenceAll: "跟随当前任务(每条消息)",
|
|
489
|
+
cadenceFirst: "仅首条消息",
|
|
90
490
|
saved: "已保存。",
|
|
91
|
-
failed: "无法读取标题设置。"
|
|
491
|
+
failed: "无法读取标题设置。",
|
|
492
|
+
inactive: "标题服务未接管,新消息暂时不会自动更新。"
|
|
92
493
|
},
|
|
93
494
|
en: {
|
|
94
|
-
label: "
|
|
495
|
+
label: "Auto Title",
|
|
95
496
|
pinned: "Renamed manually",
|
|
96
497
|
generating: "Generating title…",
|
|
97
498
|
regenerate: "Regenerate",
|
|
98
499
|
locale: "Type-label language",
|
|
99
500
|
auto: "Auto",
|
|
501
|
+
prompt: "Title prompt",
|
|
502
|
+
promptHint: "Empty uses the built-in instruction; a type and summary are still returned. Saves on blur.",
|
|
503
|
+
loading: "Loading title settings…",
|
|
504
|
+
model: "Title model",
|
|
505
|
+
modelDefault: "Use the session model",
|
|
506
|
+
effort: "Reasoning effort",
|
|
507
|
+
effortDefault: "Default",
|
|
508
|
+
cadence: "Update cadence",
|
|
509
|
+
cadenceAll: "Track the current task (every message)",
|
|
510
|
+
cadenceFirst: "First message only",
|
|
100
511
|
saved: "Saved.",
|
|
101
|
-
failed: "Could not read title settings."
|
|
512
|
+
failed: "Could not read title settings.",
|
|
513
|
+
inactive: "Title generation is not active, so new messages will not rename this session."
|
|
102
514
|
}
|
|
103
515
|
};
|
|
516
|
+
/** `undefined` draft = not edited; an empty string is a real edit (use built-in instruction). */
|
|
517
|
+
function promptFieldValue(draft, saved) {
|
|
518
|
+
return draft ?? saved;
|
|
519
|
+
}
|
|
520
|
+
/** The prompt to persist on blur, or `undefined` when nothing changed. */
|
|
521
|
+
function promptToSave(draft, saved) {
|
|
522
|
+
if (draft === void 0) return void 0;
|
|
523
|
+
const next = draft.trim();
|
|
524
|
+
return next === saved ? void 0 : next;
|
|
525
|
+
}
|
|
104
526
|
function shouldSkipTitleRefresh(input) {
|
|
105
527
|
return input.busy;
|
|
106
528
|
}
|
|
@@ -181,7 +603,9 @@ async function runTitleSettingsSave(input) {
|
|
|
181
603
|
input.onBusy(true);
|
|
182
604
|
try {
|
|
183
605
|
const settings = await invoke(input.call, "settings", {
|
|
184
|
-
|
|
606
|
+
...input.prompt !== void 0 ? { prompt: input.prompt } : {},
|
|
607
|
+
...input.model !== void 0 ? { model: input.model } : {},
|
|
608
|
+
...input.cadence !== void 0 ? { cadence: input.cadence } : {},
|
|
185
609
|
expectedRevision: input.expectedRevision
|
|
186
610
|
});
|
|
187
611
|
if (!input.isCurrent()) return;
|
|
@@ -213,19 +637,65 @@ const name = "dsh-current-title-client";
|
|
|
213
637
|
const inject = [
|
|
214
638
|
"slots",
|
|
215
639
|
"connection",
|
|
640
|
+
"remote",
|
|
641
|
+
"remote.session",
|
|
216
642
|
"sessions",
|
|
217
643
|
"locale",
|
|
218
644
|
"uiWorkspace",
|
|
219
645
|
"uiSession"
|
|
220
646
|
];
|
|
647
|
+
function catalogChoices(value, selected) {
|
|
648
|
+
const envelope = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
649
|
+
const root = (envelope?.ok === true && envelope.value && typeof envelope.value === "object" && !Array.isArray(envelope.value) ? envelope.value : void 0) ?? envelope ?? {};
|
|
650
|
+
const groups = Array.isArray(root.groups) ? root.groups : [];
|
|
651
|
+
const choices = [];
|
|
652
|
+
for (const group of groups) {
|
|
653
|
+
if (!group || typeof group !== "object") continue;
|
|
654
|
+
const row = group;
|
|
655
|
+
const provider = typeof row.id === "string" ? row.id : "";
|
|
656
|
+
const providerName = typeof row.name === "string" && row.name ? row.name : provider;
|
|
657
|
+
if (!provider || !Array.isArray(row.models)) continue;
|
|
658
|
+
for (const model of row.models) {
|
|
659
|
+
if (!model || typeof model !== "object") continue;
|
|
660
|
+
const item = model;
|
|
661
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
662
|
+
if (!id) continue;
|
|
663
|
+
const name = typeof item.name === "string" && item.name ? item.name : id;
|
|
664
|
+
const reasoning = item.reasoning && typeof item.reasoning === "object" ? item.reasoning : void 0;
|
|
665
|
+
const efforts = Array.isArray(reasoning?.efforts) ? reasoning.efforts.flatMap((effort) => {
|
|
666
|
+
if (!effort || typeof effort !== "object") return [];
|
|
667
|
+
const entry = effort;
|
|
668
|
+
return typeof entry.id === "string" && entry.id ? [{
|
|
669
|
+
id: entry.id,
|
|
670
|
+
name: typeof entry.name === "string" && entry.name ? entry.name : entry.id
|
|
671
|
+
}] : [];
|
|
672
|
+
}) : [];
|
|
673
|
+
choices.push({
|
|
674
|
+
provider,
|
|
675
|
+
model: id,
|
|
676
|
+
label: `${providerName} / ${name}`,
|
|
677
|
+
efforts
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (selected.provider && selected.model && !choices.some((item) => item.provider === selected.provider && item.model === selected.model)) choices.push({
|
|
682
|
+
provider: selected.provider,
|
|
683
|
+
model: selected.model,
|
|
684
|
+
label: `${selected.provider} / ${selected.model}`,
|
|
685
|
+
efforts: []
|
|
686
|
+
});
|
|
687
|
+
return choices;
|
|
688
|
+
}
|
|
221
689
|
function TitleSettings(props) {
|
|
222
|
-
const seat = useNativeSeat(props.client
|
|
690
|
+
const seat = useNativeSeat(props.client);
|
|
223
691
|
const sessionId = seat.sessionId;
|
|
224
692
|
const text = titleCopy[seat.locale];
|
|
225
693
|
const [status, setStatus] = (0, react.useState)();
|
|
226
694
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
227
695
|
const [note, setNote] = (0, react.useState)("");
|
|
228
696
|
const [error, setError] = (0, react.useState)("");
|
|
697
|
+
const [prompt, setPrompt] = (0, react.useState)(void 0);
|
|
698
|
+
const [choices, setChoices] = (0, react.useState)([]);
|
|
229
699
|
const workRef = (0, react.useRef)(void 0);
|
|
230
700
|
if (!workRef.current) workRef.current = createTitleClientWork();
|
|
231
701
|
const work = workRef.current;
|
|
@@ -238,6 +708,26 @@ function TitleSettings(props) {
|
|
|
238
708
|
(0, react.useEffect)(() => () => {
|
|
239
709
|
work.dispose();
|
|
240
710
|
}, [work]);
|
|
711
|
+
(0, react.useEffect)(() => {
|
|
712
|
+
const catalog = props.client.remote?.session?.modelCatalog;
|
|
713
|
+
if (!catalog) return void 0;
|
|
714
|
+
let live = true;
|
|
715
|
+
catalog().then((value) => {
|
|
716
|
+
if (live) setChoices(catalogChoices(value, status?.settings.model ?? {
|
|
717
|
+
provider: "",
|
|
718
|
+
model: ""
|
|
719
|
+
}));
|
|
720
|
+
}).catch(() => {
|
|
721
|
+
if (live) setChoices([]);
|
|
722
|
+
});
|
|
723
|
+
return () => {
|
|
724
|
+
live = false;
|
|
725
|
+
};
|
|
726
|
+
}, [
|
|
727
|
+
props.client,
|
|
728
|
+
status?.settings.model.provider,
|
|
729
|
+
status?.settings.model.model
|
|
730
|
+
]);
|
|
241
731
|
(0, react.useEffect)(() => {
|
|
242
732
|
const generation = work.beginLoad();
|
|
243
733
|
const isCurrent = () => work.isLoad(generation);
|
|
@@ -289,113 +779,279 @@ function TitleSettings(props) {
|
|
|
289
779
|
work
|
|
290
780
|
]);
|
|
291
781
|
useFeatureRefresh(props.client, sessionId, refreshStatus, status?.session?.generating === true && !busy, Boolean(sessionId));
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
{
|
|
299
|
-
id: "zh",
|
|
300
|
-
label: "中文"
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
id: "en",
|
|
304
|
-
label: "English"
|
|
305
|
-
}
|
|
306
|
-
];
|
|
782
|
+
const savedPrompt = status?.settings.prompt ?? "";
|
|
783
|
+
const savedModel = status?.settings.model ?? {
|
|
784
|
+
provider: "",
|
|
785
|
+
model: ""
|
|
786
|
+
};
|
|
787
|
+
const modelKey = savedModel.provider && savedModel.model ? `${savedModel.provider}\u001f${savedModel.model}` : "";
|
|
307
788
|
const title = status?.session?.title;
|
|
308
789
|
const pinned = status?.session?.pinned === true;
|
|
309
790
|
const weOwn = status?.support.weOwn === true;
|
|
310
791
|
const canRegenerate = Boolean(sessionId) && weOwn && !busy;
|
|
792
|
+
const selectedChoice = choices.find((item) => item.provider === savedModel.provider && item.model === savedModel.model);
|
|
793
|
+
const efforts = selectedChoice ? [...selectedChoice.efforts] : [];
|
|
794
|
+
if (selectedChoice && savedModel.reasoningEffort && !efforts.some((item) => item.id === savedModel.reasoningEffort)) efforts.push({
|
|
795
|
+
id: savedModel.reasoningEffort,
|
|
796
|
+
name: savedModel.reasoningEffort
|
|
797
|
+
});
|
|
798
|
+
function saveSettings(patch) {
|
|
799
|
+
if (!status) return;
|
|
800
|
+
const token = work.beginRequest();
|
|
801
|
+
setError("");
|
|
802
|
+
setNote("");
|
|
803
|
+
runTitleSettingsSave({
|
|
804
|
+
call,
|
|
805
|
+
...patch,
|
|
806
|
+
expectedRevision: status.settings.revision,
|
|
807
|
+
isCurrent: () => work.isRequest(token),
|
|
808
|
+
savedMessage: text.saved,
|
|
809
|
+
failedMessage: text.failed,
|
|
810
|
+
onBusy: setBusy,
|
|
811
|
+
onSettings: (settings) => setStatus((current) => current ? {
|
|
812
|
+
...current,
|
|
813
|
+
settings
|
|
814
|
+
} : current),
|
|
815
|
+
onNote: setNote,
|
|
816
|
+
onError: setError
|
|
817
|
+
});
|
|
818
|
+
}
|
|
311
819
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
312
|
-
className: "current-title-settings",
|
|
820
|
+
className: "current-title-settings ct-root dsh-ui-panel",
|
|
313
821
|
"data-testid": "current-title-settings",
|
|
314
822
|
children: [
|
|
315
823
|
!status && !error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
316
824
|
role: "status",
|
|
317
|
-
|
|
825
|
+
className: "dsh-ui-hint",
|
|
826
|
+
children: text.loading
|
|
318
827
|
}) : null,
|
|
319
828
|
title ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
320
829
|
translate: "no",
|
|
830
|
+
className: "dsh-ui-title dsh-ui-wrap",
|
|
321
831
|
children: title
|
|
322
832
|
}) : null,
|
|
323
833
|
pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
324
|
-
className: "
|
|
834
|
+
className: "dsh-ui-meta",
|
|
325
835
|
children: text.pinned
|
|
326
836
|
}) : null,
|
|
327
837
|
status?.session?.generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
328
838
|
role: "status",
|
|
839
|
+
className: "dsh-ui-hint",
|
|
329
840
|
children: text.generating
|
|
330
841
|
}) : null,
|
|
842
|
+
status && !weOwn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
843
|
+
role: "status",
|
|
844
|
+
className: "dsh-ui-hint",
|
|
845
|
+
children: [text.inactive, status.support.limitation ? ` ${status.support.limitation}` : ""]
|
|
846
|
+
}) : null,
|
|
331
847
|
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
332
848
|
role: "alert",
|
|
849
|
+
className: "dsh-ui-error dsh-ui-wrap",
|
|
333
850
|
children: error
|
|
334
851
|
}) : null,
|
|
335
852
|
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
336
853
|
role: "status",
|
|
854
|
+
className: "dsh-ui-notice",
|
|
337
855
|
children: note
|
|
338
856
|
}) : null,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
setNote("");
|
|
348
|
-
runTitleRegenerateFlow({
|
|
349
|
-
call,
|
|
350
|
-
sessionId,
|
|
351
|
-
isCurrent: () => work.isRequest(token),
|
|
352
|
-
failedMessage: text.failed,
|
|
353
|
-
onBusy: setBusy,
|
|
354
|
-
onStatus: setStatus,
|
|
355
|
-
onError: setError,
|
|
356
|
-
onMarker: commitMarker
|
|
357
|
-
});
|
|
358
|
-
},
|
|
359
|
-
children: text.regenerate
|
|
360
|
-
}) : null,
|
|
361
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
362
|
-
disabled: busy || !status,
|
|
363
|
-
"aria-label": text.locale,
|
|
364
|
-
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", {
|
|
365
|
-
type: "radio",
|
|
366
|
-
name: "current-title-locale",
|
|
367
|
-
value: mode.id,
|
|
368
|
-
checked: locale === mode.id,
|
|
369
|
-
onChange: () => {
|
|
370
|
-
if (!status) return;
|
|
857
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
858
|
+
className: "dsh-ui-actions",
|
|
859
|
+
children: sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
860
|
+
variant: "outline",
|
|
861
|
+
size: "md",
|
|
862
|
+
disabled: !canRegenerate,
|
|
863
|
+
onClick: () => {
|
|
864
|
+
if (!sessionId) return;
|
|
371
865
|
const token = work.beginRequest();
|
|
372
866
|
setError("");
|
|
373
867
|
setNote("");
|
|
374
|
-
|
|
868
|
+
runTitleRegenerateFlow({
|
|
375
869
|
call,
|
|
376
|
-
|
|
377
|
-
expectedRevision: status.settings.revision,
|
|
870
|
+
sessionId,
|
|
378
871
|
isCurrent: () => work.isRequest(token),
|
|
379
|
-
savedMessage: text.saved,
|
|
380
872
|
failedMessage: text.failed,
|
|
381
873
|
onBusy: setBusy,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}),
|
|
386
|
-
onNote: setNote,
|
|
387
|
-
onError: setError
|
|
874
|
+
onStatus: setStatus,
|
|
875
|
+
onError: setError,
|
|
876
|
+
onMarker: commitMarker
|
|
388
877
|
});
|
|
389
|
-
}
|
|
390
|
-
|
|
878
|
+
},
|
|
879
|
+
children: text.regenerate
|
|
880
|
+
}) : null
|
|
881
|
+
}),
|
|
882
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
883
|
+
className: "dsh-ui-field",
|
|
884
|
+
children: [
|
|
885
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
886
|
+
className: "dsh-ui-label",
|
|
887
|
+
children: text.prompt
|
|
888
|
+
}),
|
|
889
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
890
|
+
className: "ct-prompt",
|
|
891
|
+
value: status ? promptFieldValue(prompt, savedPrompt) : "",
|
|
892
|
+
maxLength: 4e3,
|
|
893
|
+
rows: 5,
|
|
894
|
+
disabled: busy || !status,
|
|
895
|
+
onChange: (event) => {
|
|
896
|
+
setPrompt(event.target.value);
|
|
897
|
+
setNote("");
|
|
898
|
+
},
|
|
899
|
+
onBlur: () => {
|
|
900
|
+
if (!status) return;
|
|
901
|
+
const next = promptToSave(prompt, savedPrompt);
|
|
902
|
+
if (next === void 0) {
|
|
903
|
+
setPrompt(void 0);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const token = work.beginRequest();
|
|
907
|
+
setError("");
|
|
908
|
+
runTitleSettingsSave({
|
|
909
|
+
call,
|
|
910
|
+
prompt: next,
|
|
911
|
+
expectedRevision: status.settings.revision,
|
|
912
|
+
isCurrent: () => work.isRequest(token),
|
|
913
|
+
savedMessage: text.saved,
|
|
914
|
+
failedMessage: text.failed,
|
|
915
|
+
onBusy: setBusy,
|
|
916
|
+
onSettings: (settings) => {
|
|
917
|
+
setStatus((current) => current ? {
|
|
918
|
+
...current,
|
|
919
|
+
settings
|
|
920
|
+
} : current);
|
|
921
|
+
setPrompt(void 0);
|
|
922
|
+
},
|
|
923
|
+
onNote: setNote,
|
|
924
|
+
onError: setError
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
}),
|
|
928
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
929
|
+
className: "dsh-ui-help",
|
|
930
|
+
children: text.promptHint
|
|
931
|
+
})
|
|
932
|
+
]
|
|
933
|
+
}),
|
|
934
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
935
|
+
className: "dsh-ui-field",
|
|
936
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
937
|
+
className: "dsh-ui-label",
|
|
938
|
+
children: text.model
|
|
939
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
940
|
+
className: "dsh-ui-select",
|
|
941
|
+
value: modelKey,
|
|
942
|
+
disabled: busy || !status,
|
|
943
|
+
onChange: (event) => {
|
|
944
|
+
const value = event.target.value;
|
|
945
|
+
const choice = choices.find((item) => `${item.provider}\u001f${item.model}` === value);
|
|
946
|
+
saveSettings({ model: choice ? {
|
|
947
|
+
provider: choice.provider,
|
|
948
|
+
model: choice.model
|
|
949
|
+
} : {
|
|
950
|
+
provider: "",
|
|
951
|
+
model: ""
|
|
952
|
+
} });
|
|
953
|
+
},
|
|
954
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
955
|
+
value: "",
|
|
956
|
+
children: text.modelDefault
|
|
957
|
+
}), choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
958
|
+
value: `${choice.provider}\u001f${choice.model}`,
|
|
959
|
+
children: choice.label
|
|
960
|
+
}, `${choice.provider}\u001f${choice.model}`))]
|
|
961
|
+
})]
|
|
962
|
+
}),
|
|
963
|
+
selectedChoice && efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
964
|
+
className: "dsh-ui-field",
|
|
965
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
966
|
+
className: "dsh-ui-label",
|
|
967
|
+
children: text.effort
|
|
968
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
969
|
+
className: "dsh-ui-select",
|
|
970
|
+
value: savedModel.reasoningEffort ?? "",
|
|
971
|
+
disabled: busy || !status,
|
|
972
|
+
onChange: (event) => {
|
|
973
|
+
const effort = event.target.value;
|
|
974
|
+
const model = {
|
|
975
|
+
provider: savedModel.provider,
|
|
976
|
+
model: savedModel.model
|
|
977
|
+
};
|
|
978
|
+
saveSettings({ model: effort ? {
|
|
979
|
+
...model,
|
|
980
|
+
reasoningEffort: effort
|
|
981
|
+
} : model });
|
|
982
|
+
},
|
|
983
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
984
|
+
value: "",
|
|
985
|
+
children: text.effortDefault
|
|
986
|
+
}), efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
987
|
+
value: effort.id,
|
|
988
|
+
children: effort.name
|
|
989
|
+
}, effort.id))]
|
|
990
|
+
})]
|
|
991
|
+
}) : null,
|
|
992
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
993
|
+
className: "dsh-ui-field",
|
|
994
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
995
|
+
className: "dsh-ui-label",
|
|
996
|
+
children: text.cadence
|
|
997
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
998
|
+
className: "dsh-ui-select",
|
|
999
|
+
value: status?.settings.cadence ?? "all-prompts",
|
|
1000
|
+
disabled: busy || !status,
|
|
1001
|
+
onChange: (event) => {
|
|
1002
|
+
saveSettings({ cadence: event.target.value === "first-prompt" ? "first-prompt" : "all-prompts" });
|
|
1003
|
+
},
|
|
1004
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1005
|
+
value: "all-prompts",
|
|
1006
|
+
children: text.cadenceAll
|
|
1007
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1008
|
+
value: "first-prompt",
|
|
1009
|
+
children: text.cadenceFirst
|
|
1010
|
+
})]
|
|
1011
|
+
})]
|
|
391
1012
|
})
|
|
392
1013
|
]
|
|
393
1014
|
});
|
|
394
1015
|
}
|
|
1016
|
+
const styles = `${officialUiCss("ct-root")}
|
|
1017
|
+
.ct-root { max-width: 760px; }
|
|
1018
|
+
.ct-prompt {
|
|
1019
|
+
box-sizing: border-box;
|
|
1020
|
+
width: 100%;
|
|
1021
|
+
min-width: 0;
|
|
1022
|
+
min-height: 96px;
|
|
1023
|
+
resize: vertical;
|
|
1024
|
+
padding: 8px 12px;
|
|
1025
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
1026
|
+
border-radius: var(--dsw-radius-md);
|
|
1027
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
1028
|
+
font: inherit;
|
|
1029
|
+
font-size: 13px;
|
|
1030
|
+
line-height: 1.5;
|
|
1031
|
+
color: var(--dsw-alias-label-primary);
|
|
1032
|
+
}
|
|
1033
|
+
.ct-prompt:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
1034
|
+
.ct-prompt::placeholder { color: var(--dsw-alias-label-dimmed); }
|
|
1035
|
+
`;
|
|
395
1036
|
function apply(ctx) {
|
|
396
1037
|
const client = ctx;
|
|
397
1038
|
const marker = createTitleClientMarker();
|
|
398
1039
|
ctx.provide(TITLE_CLIENT_SERVICE, marker);
|
|
1040
|
+
ctx.effect(() => {
|
|
1041
|
+
if (typeof document === "undefined") return () => {};
|
|
1042
|
+
const style = document.createElement("style");
|
|
1043
|
+
style.setAttribute("data-plugin", name);
|
|
1044
|
+
style.textContent = styles;
|
|
1045
|
+
document.head.appendChild(style);
|
|
1046
|
+
return () => style.remove();
|
|
1047
|
+
}, "current-title.styles");
|
|
1048
|
+
ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
1049
|
+
name: "plugins.bundle.config",
|
|
1050
|
+
key: "@klarkxy/dsh-current-title"
|
|
1051
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettings, {
|
|
1052
|
+
client,
|
|
1053
|
+
marker
|
|
1054
|
+
}))), "current-title.settings");
|
|
399
1055
|
ctx.effect(() => {
|
|
400
1056
|
let live = true;
|
|
401
1057
|
client.connection.rpc.call(RPC_CHANNEL, "status", {}).then((result) => {
|