dsh-input-assistant 0.0.0-stage → 0.1.4

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +100 -0
  4. package/README.md +215 -3
  5. package/cordis.patch.yml +3 -0
  6. package/docs/CHANGELOG-0.1.2.md +102 -0
  7. package/docs/evaluation.md +111 -0
  8. package/docs/images/dark.png +0 -0
  9. package/docs/images/light.png +0 -0
  10. package/docs/images/optimize.png +0 -0
  11. package/docs/references.md +39 -0
  12. package/docs/smoke-comparison.md +37 -0
  13. package/docs/validation.md +180 -0
  14. package/lib/browser/components.d.ts +11 -0
  15. package/lib/browser/controller.d.ts +187 -0
  16. package/lib/browser/dom.d.ts +29 -0
  17. package/lib/browser/floating.d.ts +21 -0
  18. package/lib/browser/index.d.ts +6 -0
  19. package/lib/browser/public.d.ts +2 -0
  20. package/lib/browser/runtime.d.ts +55 -0
  21. package/lib/browser/settings.d.ts +8 -0
  22. package/lib/browser/styles.d.ts +1 -0
  23. package/lib/client.js +2646 -0
  24. package/lib/client.meta.json +274 -0
  25. package/lib/core/config.d.ts +17 -0
  26. package/lib/core/context.d.ts +4 -0
  27. package/lib/core/diff.d.ts +6 -0
  28. package/lib/core/generation.d.ts +15 -0
  29. package/lib/core/index.d.ts +9 -0
  30. package/lib/core/models.d.ts +15 -0
  31. package/lib/core/protection.d.ts +18 -0
  32. package/lib/core/scheduler.d.ts +10 -0
  33. package/lib/core/snapshot.d.ts +2 -0
  34. package/lib/core/types.d.ts +92 -0
  35. package/lib/core.js +750 -0
  36. package/lib/core.js.map +7 -0
  37. package/lib/host/rpc.d.ts +31 -0
  38. package/lib/host/runtime.d.ts +28 -0
  39. package/lib/host/storage.d.ts +62 -0
  40. package/lib/host/transcript.d.ts +19 -0
  41. package/lib/index.d.ts +66 -0
  42. package/lib/index.js +1164 -0
  43. package/lib/index.js.map +7 -0
  44. package/package.json +128 -3
package/lib/client.js ADDED
@@ -0,0 +1,2646 @@
1
+ window.__ModuleLoader__.load({id:"dsh-input-assistant",factory:(require)=>{
2
+ var module={exports:{}};var exports=module.exports;
3
+ "use strict";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/browser/index.ts
23
+ var index_exports = {};
24
+ __export(index_exports, {
25
+ BrowserRuntime: () => BrowserRuntime,
26
+ InputAssistantController: () => InputAssistantController,
27
+ apply: () => apply,
28
+ inject: () => inject,
29
+ name: () => name
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/browser/components.tsx
34
+ var import_react = require("react");
35
+ var import_react_dom = require("react-dom");
36
+
37
+ // src/core/diff.ts
38
+ var tokenPattern = /(\s+|[^\p{L}\p{N}_]|[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}])/u;
39
+ var split = (text) => text.split(tokenPattern).filter(Boolean);
40
+ var coarsePattern = /(\n+|[。!?;,、.!?;]+\s*)/u;
41
+ var splitCoarse = (text) => text.split(coarsePattern).filter(Boolean);
42
+ var MAX_CELLS = 1e6;
43
+ function diffTokens(a, b) {
44
+ const result = [];
45
+ const push = (type, value) => {
46
+ if (!value) return;
47
+ const last = result[result.length - 1];
48
+ if (last?.type === type) last.value += value;
49
+ else result.push({ type, value });
50
+ };
51
+ const rows = Array.from({ length: a.length + 1 }, () => new Uint32Array(b.length + 1));
52
+ for (let i2 = a.length - 1; i2 >= 0; i2--) {
53
+ for (let j2 = b.length - 1; j2 >= 0; j2--) {
54
+ rows[i2][j2] = a[i2] === b[j2] ? rows[i2 + 1][j2 + 1] + 1 : Math.max(rows[i2 + 1][j2], rows[i2][j2 + 1]);
55
+ }
56
+ }
57
+ let i = 0, j = 0;
58
+ while (i < a.length || j < b.length) {
59
+ if (i < a.length && j < b.length && a[i] === b[j]) {
60
+ push("equal", a[i++]);
61
+ j++;
62
+ } else if (i < a.length && (j === b.length || rows[i + 1][j] >= rows[i][j + 1])) push("delete", a[i++]);
63
+ else push("insert", b[j++]);
64
+ }
65
+ return result;
66
+ }
67
+ function diffText(before, after) {
68
+ if (before === after) return [{ type: "equal", value: before }];
69
+ const a = split(before), b = split(after);
70
+ let head = 0, tail = 0;
71
+ while (head < a.length && head < b.length && a[head] === b[head]) head++;
72
+ while (tail < a.length - head && tail < b.length - head && a[a.length - tail - 1] === b[b.length - tail - 1]) tail++;
73
+ const middleA = a.slice(head, a.length - tail), middleB = b.slice(head, b.length - tail);
74
+ const result = [];
75
+ const push = (type, value) => {
76
+ if (!value) return;
77
+ const last = result[result.length - 1];
78
+ if (last?.type === type) last.value += value;
79
+ else result.push({ type, value });
80
+ };
81
+ push("equal", a.slice(0, head).join(""));
82
+ if (middleA.length === 0) push("insert", middleB.join(""));
83
+ else if (middleB.length === 0) push("delete", middleA.join(""));
84
+ else if (middleA.length * middleB.length <= MAX_CELLS) for (const part of diffTokens(middleA, middleB)) push(part.type, part.value);
85
+ else {
86
+ const coarseA = splitCoarse(middleA.join("")), coarseB = splitCoarse(middleB.join(""));
87
+ if (coarseA.length * coarseB.length <= MAX_CELLS) for (const part of diffTokens(coarseA, coarseB)) push(part.type, part.value);
88
+ else {
89
+ push("delete", middleA.join(""));
90
+ push("insert", middleB.join(""));
91
+ }
92
+ }
93
+ push("equal", tail ? a.slice(a.length - tail).join("") : "");
94
+ return result;
95
+ }
96
+
97
+ // src/browser/dom.ts
98
+ var CHIP_SELECTOR = "[data-composer-chip],[data-composer-text-ref],[data-composer-command]";
99
+ function findEditor(anchor) {
100
+ return anchor?.closest("[data-composer-card]")?.querySelector("[data-composer-input]") ?? null;
101
+ }
102
+ function focusedIn(editor) {
103
+ return editor !== null && (document.activeElement === editor || editor.contains(document.activeElement));
104
+ }
105
+ function referenceOverlaps(span, references) {
106
+ let shrink = 0;
107
+ for (const reference of references) {
108
+ const start = reference.offset - shrink;
109
+ if (span.start < start + 1 && span.end > start) return true;
110
+ shrink += reference.length - 1;
111
+ }
112
+ return false;
113
+ }
114
+ function clipboardOffset(detectOffset, references) {
115
+ let shrink = 0;
116
+ for (const reference of references) {
117
+ const start = reference.offset - shrink;
118
+ if (detectOffset <= start) break;
119
+ shrink += reference.length - 1;
120
+ }
121
+ return detectOffset + shrink;
122
+ }
123
+ function selectedPlainText(editor, draft, span, references) {
124
+ if (span.start === span.end) return null;
125
+ if (referenceOverlaps(span, references)) return null;
126
+ const selection = editor?.ownerDocument.getSelection();
127
+ if (!editor || !selection || selection.rangeCount !== 1) return null;
128
+ const range = selection.getRangeAt(0);
129
+ if (!editor.contains(range.startContainer) || !editor.contains(range.endContainer)) return null;
130
+ for (const chip of editor.querySelectorAll(CHIP_SELECTOR)) {
131
+ if (range.intersectsNode(chip)) return null;
132
+ }
133
+ const clipboardStart = clipboardOffset(span.start, references);
134
+ const clipboardEnd = clipboardOffset(span.end, references);
135
+ const text = draft.slice(clipboardStart, clipboardEnd);
136
+ if (!text) return null;
137
+ if (references.length && range.toString() !== text) return null;
138
+ return { text, clipboardStart, clipboardEnd };
139
+ }
140
+ function measureGhost(editor, empty) {
141
+ if (!editor || !focusedIn(editor)) return null;
142
+ const editorBox = editor.getBoundingClientRect();
143
+ if (editorBox.width <= 0 || editorBox.height <= 0) return null;
144
+ const style = getComputedStyle(editor);
145
+ const selection = editor.ownerDocument.getSelection();
146
+ let rect = null;
147
+ if (selection?.rangeCount === 1) {
148
+ const range = selection.getRangeAt(0);
149
+ if (!range.collapsed || !editor.contains(range.startContainer)) return null;
150
+ if (typeof range.getBoundingClientRect === "function") {
151
+ const measured = range.getBoundingClientRect();
152
+ if (measured.height > 0 && measured.left >= editorBox.left - 1 && measured.left <= editorBox.right + 1) rect = measured;
153
+ }
154
+ if (!rect && !empty && range.startContainer.nodeType === Node.TEXT_NODE && range.startOffset > 0) {
155
+ const preceding = range.cloneRange();
156
+ preceding.setStart(range.startContainer, range.startOffset - 1);
157
+ const last = preceding.getBoundingClientRect();
158
+ if (last.height > 0) rect = new DOMRect(last.right, last.top, 0, last.height);
159
+ }
160
+ }
161
+ if (!rect && empty) {
162
+ rect = new DOMRect(editorBox.left + parseFloat(style.paddingLeft || "0"), editorBox.top + parseFloat(style.paddingTop || "0"), 0, parseFloat(style.lineHeight) || parseFloat(style.fontSize));
163
+ }
164
+ if (!rect) return null;
165
+ const viewport = window.visualViewport;
166
+ const visible = {
167
+ left: Math.max(editorBox.left, viewport?.offsetLeft ?? 0),
168
+ top: Math.max(editorBox.top, viewport?.offsetTop ?? 0),
169
+ right: Math.min(editorBox.right, (viewport?.offsetLeft ?? 0) + (viewport?.width ?? window.innerWidth)),
170
+ bottom: Math.min(editorBox.bottom, (viewport?.offsetTop ?? 0) + (viewport?.height ?? window.innerHeight))
171
+ };
172
+ for (let ancestor = editor.parentElement; ancestor && ancestor !== document.body; ancestor = ancestor.parentElement) {
173
+ const clipping = getComputedStyle(ancestor);
174
+ const paint = /paint|strict|content/.test(clipping.contain);
175
+ const clipX = paint || /hidden|clip|auto|scroll/.test(clipping.overflowX || clipping.overflow);
176
+ const clipY = paint || /hidden|clip|auto|scroll/.test(clipping.overflowY || clipping.overflow);
177
+ if (clipX || clipY) {
178
+ const box = ancestor.getBoundingClientRect();
179
+ if (clipX) {
180
+ visible.left = Math.max(visible.left, box.left);
181
+ visible.right = Math.min(visible.right, box.right);
182
+ }
183
+ if (clipY) {
184
+ visible.top = Math.max(visible.top, box.top);
185
+ visible.bottom = Math.min(visible.bottom, box.bottom);
186
+ }
187
+ }
188
+ }
189
+ if (rect.left < visible.left - 1 || rect.top < visible.top - 1 || rect.bottom > visible.bottom + 1) return null;
190
+ const width = Math.min(editorBox.right - parseFloat(style.paddingRight || "0"), visible.right) - rect.left;
191
+ if (width < 24) return null;
192
+ return {
193
+ left: rect.left,
194
+ top: rect.top,
195
+ width,
196
+ maxHeight: visible.bottom - rect.top,
197
+ font: style.font,
198
+ lineHeight: style.lineHeight,
199
+ letterSpacing: style.letterSpacing
200
+ };
201
+ }
202
+ function nativeMenuOpen(editor) {
203
+ const scope = editor?.closest("[data-composer-seat]") ?? editor?.closest("[data-composer-card]");
204
+ return Boolean(scope?.querySelector('[data-trigger-menu],[role="listbox"]'));
205
+ }
206
+ function canUseTab(event, editor, composing) {
207
+ return event.key === "Tab" && !event.defaultPrevented && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey && !event.isComposing && event.keyCode !== 229 && !composing && focusedIn(editor) && !editor?.hasAttribute("data-composer-composing") && !nativeMenuOpen(editor);
208
+ }
209
+
210
+ // src/browser/floating.ts
211
+ function placeFloating(anchor, contentHeight, viewport, preferredWidth = 440) {
212
+ const margin = 12, gap = 8;
213
+ const width = Math.max(1, Math.min(preferredWidth, viewport.width - margin * 2));
214
+ const left = Math.max(viewport.left + margin, Math.min(anchor.right - width, viewport.left + viewport.width - margin - width));
215
+ const above = Math.max(0, anchor.top - viewport.top - margin - gap);
216
+ const below = Math.max(0, viewport.top + viewport.height - margin - anchor.bottom - gap);
217
+ const maximum = Math.max(1, Math.min(560, viewport.height - margin * 2));
218
+ const useAbove = above >= Math.min(contentHeight, maximum) || above >= below;
219
+ const room = useAbove ? above : below;
220
+ if (room < Math.min(120, maximum)) return { left, top: viewport.top + margin, width, maxHeight: maximum };
221
+ const maxHeight = Math.min(maximum, room);
222
+ const height = Math.min(Math.max(1, contentHeight), maxHeight);
223
+ const top = useAbove ? anchor.top - gap - height : anchor.bottom + gap;
224
+ return { left, top: Math.max(viewport.top + margin, top), width, maxHeight };
225
+ }
226
+
227
+ // src/browser/components.tsx
228
+ var import_jsx_runtime = require("react/jsx-runtime");
229
+ var EMPTY_MODEL = { current: null };
230
+ var noopSubscribe = () => () => {
231
+ };
232
+ function useFloatingPlacement(anchor, surface, visible, card = false) {
233
+ const [placement, setPlacement] = (0, import_react.useState)(null);
234
+ (0, import_react.useLayoutEffect)(() => {
235
+ if (!visible) {
236
+ setPlacement(null);
237
+ return;
238
+ }
239
+ const update = () => {
240
+ const trigger = card ? findEditor(anchor.current)?.closest("[data-composer-card]") : anchor.current;
241
+ const element = surface.current;
242
+ if (!trigger || !element) return;
243
+ const viewport = window.visualViewport;
244
+ const bounds = {
245
+ left: viewport?.offsetLeft ?? 0,
246
+ top: viewport?.offsetTop ?? 0,
247
+ width: viewport?.width ?? window.innerWidth,
248
+ height: viewport?.height ?? window.innerHeight
249
+ };
250
+ const rect = trigger.getBoundingClientRect();
251
+ const next = placeFloating(
252
+ rect,
253
+ element.scrollHeight || element.getBoundingClientRect().height || 160,
254
+ bounds,
255
+ card ? Math.min(640, rect.width || 480) : 480
256
+ );
257
+ setPlacement((previous) => previous && Object.keys(next).every((key) => previous[key] === next[key]) ? previous : next);
258
+ };
259
+ update();
260
+ window.addEventListener("resize", update);
261
+ window.addEventListener("scroll", update, true);
262
+ window.visualViewport?.addEventListener("resize", update);
263
+ window.visualViewport?.addEventListener("scroll", update);
264
+ const observer = typeof ResizeObserver === "function" ? new ResizeObserver(update) : null;
265
+ if (surface.current) observer?.observe(surface.current);
266
+ if (anchor.current) observer?.observe(anchor.current);
267
+ return () => {
268
+ window.removeEventListener("resize", update);
269
+ window.removeEventListener("scroll", update, true);
270
+ window.visualViewport?.removeEventListener("resize", update);
271
+ window.visualViewport?.removeEventListener("scroll", update);
272
+ observer?.disconnect();
273
+ };
274
+ }, [anchor, surface, visible, card]);
275
+ return placement;
276
+ }
277
+ function useController(runtime, sessionId) {
278
+ const controller = (0, import_react.useMemo)(() => runtime.controllerFor(sessionId), [runtime, sessionId]);
279
+ const state = (0, import_react.useSyncExternalStore)(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
280
+ (0, import_react.useEffect)(() => runtime.retain(sessionId), [runtime, sessionId]);
281
+ return { controller, state };
282
+ }
283
+ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions }) {
284
+ const { controller, state } = useController(runtime, sessionId);
285
+ const input = useInput((value) => value);
286
+ const running = useSession((value) => value.running);
287
+ const removed = useSession((value) => value.removed);
288
+ const generation = (0, import_react.useSyncExternalStore)(
289
+ runtime.connection.generation.subscribe.bind(runtime.connection.generation),
290
+ () => runtime.transport.generation(),
291
+ () => null
292
+ );
293
+ const directory = (0, import_react.useMemo)(() => runtime.models?.directoryFor(sessionId), [runtime, sessionId]);
294
+ const model = (0, import_react.useSyncExternalStore)(
295
+ directory?.store.subscribe.bind(directory.store) ?? noopSubscribe,
296
+ directory?.store.getSnapshot.bind(directory.store) ?? (() => EMPTY_MODEL),
297
+ () => EMPTY_MODEL
298
+ );
299
+ const anchor = (0, import_react.useRef)(null);
300
+ const menuRef = (0, import_react.useRef)(null);
301
+ const noticeRef = (0, import_react.useRef)(null);
302
+ const previewRef = (0, import_react.useRef)(null);
303
+ const previewFocused = (0, import_react.useRef)(false);
304
+ const setPreviewRef = (0, import_react.useCallback)((node) => {
305
+ const previous = previewRef.current;
306
+ if (!node && previous && (previewFocused.current || previous.contains(document.activeElement))) {
307
+ queueMicrotask(() => {
308
+ if (!previous.isConnected && document.activeElement === document.body && anchor.current?.isConnected) findEditor(anchor.current)?.focus();
309
+ });
310
+ }
311
+ previewRef.current = node;
312
+ previewFocused.current = Boolean(node?.contains(document.activeElement));
313
+ }, []);
314
+ const menuTrigger = (0, import_react.useRef)(null);
315
+ const keyboardOpen = (0, import_react.useRef)(false);
316
+ const keyboardOptimize = (0, import_react.useRef)(false);
317
+ const composing = (0, import_react.useRef)(false);
318
+ const [isComposing, setIsComposing] = (0, import_react.useState)(false);
319
+ const menuId = (0, import_react.useId)();
320
+ const [open, setOpen] = (0, import_react.useState)(false);
321
+ const menuPlacement = useFloatingPlacement(anchor, menuRef, open);
322
+ const previewPlacement = useFloatingPlacement(anchor, previewRef, Boolean(state.preview), true);
323
+ const noticePlacement = useFloatingPlacement(anchor, noticeRef, !open && !state.preview && Boolean(state.notice || state.deletedDrafts.length || state.pendingUndoDrafts.length));
324
+ (0, import_react.useEffect)(() => {
325
+ const focusChanged = (event) => {
326
+ previewFocused.current = event.target instanceof Node && Boolean(previewRef.current?.contains(event.target));
327
+ };
328
+ const pointerChanged = (event) => {
329
+ if (event.target instanceof Node && !previewRef.current?.contains(event.target)) previewFocused.current = false;
330
+ };
331
+ document.addEventListener("focusin", focusChanged);
332
+ document.addEventListener("pointerdown", pointerChanged);
333
+ return () => {
334
+ document.removeEventListener("focusin", focusChanged);
335
+ document.removeEventListener("pointerdown", pointerChanged);
336
+ };
337
+ }, []);
338
+ (0, import_react.useEffect)(() => {
339
+ void directory?.load().catch(() => {
340
+ });
341
+ }, [directory]);
342
+ (0, import_react.useLayoutEffect)(() => {
343
+ if (removed) {
344
+ controller.dispose();
345
+ return;
346
+ }
347
+ controller.bind({ sessionId, input, actions: inputActions, running, editor: findEditor(anchor.current), route: model.current ?? void 0 });
348
+ }, [controller, sessionId, input, inputActions, running, model.current, removed, generation]);
349
+ (0, import_react.useEffect)(() => {
350
+ const editor = findEditor(anchor.current);
351
+ if (!editor) return;
352
+ const selectionChanged = () => controller.notifySelectionChange();
353
+ const compositionStart = () => {
354
+ composing.current = true;
355
+ setIsComposing(true);
356
+ controller.setComposing(true);
357
+ };
358
+ const compositionEnd = () => {
359
+ composing.current = false;
360
+ setIsComposing(false);
361
+ controller.setComposing(false);
362
+ };
363
+ const keyDown = (event) => {
364
+ if (event.defaultPrevented || composing.current || event.isComposing || event.keyCode === 229 || editor.hasAttribute("data-composer-composing") || nativeMenuOpen(editor)) return;
365
+ const current = controller.getSnapshot();
366
+ if (event.key === "Escape") {
367
+ const target = document.activeElement;
368
+ const inside = focusedIn(editor) || anchor.current?.contains(target) || menuRef.current?.contains(target) || previewRef.current?.contains(target);
369
+ if (!inside) return;
370
+ if (current.preview) {
371
+ controller.dismissOptimization();
372
+ editor.focus();
373
+ } else if (open) {
374
+ setOpen(false);
375
+ menuTrigger.current?.focus();
376
+ } else if (current.suggestion) controller.dismissSuggestion();
377
+ else return;
378
+ event.preventDefault();
379
+ return;
380
+ }
381
+ if (!current.suggestion || !canUseTab(event, editor, false)) return;
382
+ if (![...document.querySelectorAll("[data-dsh-ia-visible-suggestion]")].some((node) => node.getAttribute("data-dsh-ia-session") === sessionId)) return;
383
+ if (controller.acceptSuggestion()) event.preventDefault();
384
+ };
385
+ document.addEventListener("selectionchange", selectionChanged);
386
+ editor.addEventListener("focus", selectionChanged);
387
+ editor.addEventListener("blur", selectionChanged);
388
+ editor.addEventListener("compositionstart", compositionStart);
389
+ editor.addEventListener("compositionend", compositionEnd);
390
+ window.addEventListener("keydown", keyDown);
391
+ return () => {
392
+ document.removeEventListener("selectionchange", selectionChanged);
393
+ editor.removeEventListener("focus", selectionChanged);
394
+ editor.removeEventListener("blur", selectionChanged);
395
+ editor.removeEventListener("compositionstart", compositionStart);
396
+ editor.removeEventListener("compositionend", compositionEnd);
397
+ window.removeEventListener("keydown", keyDown);
398
+ };
399
+ }, [controller, sessionId, open]);
400
+ (0, import_react.useLayoutEffect)(() => {
401
+ if (open && menuPlacement && keyboardOpen.current) {
402
+ (menuRef.current?.querySelector("button:not(:disabled),select:not(:disabled),input:not(:disabled)") ?? menuRef.current)?.focus();
403
+ keyboardOpen.current = false;
404
+ }
405
+ }, [open, menuPlacement]);
406
+ (0, import_react.useLayoutEffect)(() => {
407
+ if (state.preview && previewPlacement && (keyboardOptimize.current || anchor.current?.contains(document.activeElement))) {
408
+ previewRef.current?.querySelector("button:not(:disabled)")?.focus();
409
+ keyboardOptimize.current = false;
410
+ }
411
+ }, [state.preview, previewPlacement]);
412
+ (0, import_react.useEffect)(() => {
413
+ if (state.preview) setOpen(false);
414
+ }, [state.preview]);
415
+ (0, import_react.useEffect)(() => {
416
+ if (!open) return;
417
+ const close = (event) => {
418
+ if (event.target instanceof Node && !anchor.current?.contains(event.target) && !menuRef.current?.contains(event.target)) setOpen(false);
419
+ };
420
+ document.addEventListener("pointerdown", close);
421
+ return () => {
422
+ document.removeEventListener("pointerdown", close);
423
+ };
424
+ }, [open]);
425
+ const preserveSelection = (event) => {
426
+ event.preventDefault();
427
+ };
428
+ const canEdit = input.phase === "plain" && !state.busy && !state.settingsSaving;
429
+ const canOptimize = canEdit && !state.preview && state.settings.optimizeEnabled;
430
+ const pure = !input.occurrences.length && !input.attachmentIds.length;
431
+ const auxiliaryRoute = state.settings.provider && state.settings.model ? { provider: state.settings.provider, model: state.settings.model } : model.current ?? state.status?.route;
432
+ const requestingSuggestion = state.suggestionStatus === "generating";
433
+ const canRequestSuggestion = canEdit && pure && !isComposing && !state.preview && state.connected && !running && !state.status?.running && !requestingSuggestion;
434
+ const retryLabel = state.suggestionStatus === "failed" || state.suggestionStatus === "abstained" ? "\u91CD\u8BD5\u672C\u6B21\u5EFA\u8BAE" : "\u751F\u6210\u5EFA\u8BAE";
435
+ const predict = () => {
436
+ findEditor(anchor.current)?.focus();
437
+ controller.retrySuggestion();
438
+ };
439
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia dsh-ia-root", ref: anchor, "data-dsh-input-assistant": "", children: [
440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
441
+ "button",
442
+ {
443
+ className: "dsh-ia-trigger",
444
+ type: "button",
445
+ disabled: !canOptimize || !input.draft.trim() || !state.connected,
446
+ onPointerDown: preserveSelection,
447
+ onClick: (event) => {
448
+ keyboardOptimize.current = event.detail === 0;
449
+ void controller.optimize();
450
+ },
451
+ title: "\u6709\u9009\u533A\u65F6\u4F18\u5316\u9009\u533A\uFF0C\u5426\u5219\u4F18\u5316\u6574\u7A3F",
452
+ children: state.busy === "optimize" ? "\u4F18\u5316\u4E2D\u2026" : "\u4F18\u5316"
453
+ }
454
+ ),
455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
456
+ "button",
457
+ {
458
+ className: "dsh-ia-trigger dsh-ia-predict-btn",
459
+ type: "button",
460
+ "aria-label": "\u9884\u6D4B",
461
+ disabled: !canRequestSuggestion,
462
+ onPointerDown: preserveSelection,
463
+ onClick: predict,
464
+ title: "\u751F\u6210\u4E00\u6B21\u9884\u6D4B\uFF0C\u67E5\u770B\u5019\u9009\u540E\u7531\u4F60\u91C7\u7EB3\uFF1B\u4E0D\u4F1A\u76F4\u63A5\u6539\u5199\u6216\u53D1\u9001",
465
+ children: requestingSuggestion ? "\u9884\u6D4B\u4E2D\u2026" : "\u9884\u6D4B"
466
+ }
467
+ ),
468
+ state.busy === "optimize" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ia-cancel-btn", type: "button", onClick: () => {
469
+ controller.cancelOperation();
470
+ findEditor(anchor.current)?.focus();
471
+ }, children: "\u53D6\u6D88\u4F18\u5316" }),
472
+ !state.busy && requestingSuggestion && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ia-cancel-btn", type: "button", onClick: () => {
473
+ controller.cancelOperation();
474
+ findEditor(anchor.current)?.focus();
475
+ }, children: "\u53D6\u6D88\u9884\u6D4B" }),
476
+ state.busy === "stash" && state.stashClearPending && !state.stashClearCancelled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ia-cancel-btn", type: "button", onClick: () => controller.cancelOperation(), children: "\u53D6\u6D88\u6E05\u7A7A" }),
477
+ !open && !state.preview && state.suggestionStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-ia-inline-status", children: [
478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { role: "status", children: "\u5EFA\u8BAE\u672A\u5B8C\u6210" }),
479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
480
+ "button",
481
+ {
482
+ type: "button",
483
+ "aria-label": "\u91CD\u8BD5\u5EFA\u8BAE",
484
+ title: state.suggestionMessage,
485
+ disabled: !canRequestSuggestion,
486
+ onPointerDown: preserveSelection,
487
+ onClick: predict,
488
+ children: "\u91CD\u8BD5"
489
+ }
490
+ )
491
+ ] }),
492
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
493
+ "button",
494
+ {
495
+ ref: menuTrigger,
496
+ className: "dsh-ia-menu-btn",
497
+ type: "button",
498
+ "aria-label": "\u8F93\u5165\u52A9\u624B\u83DC\u5355",
499
+ "aria-expanded": open,
500
+ "aria-haspopup": "dialog",
501
+ "aria-controls": menuId,
502
+ title: auxiliaryRoute ? `\u8F85\u52A9\u6A21\u578B\uFF1A${auxiliaryRoute.provider} / ${auxiliaryRoute.model}` : "\u8F93\u5165\u52A9\u624B\u83DC\u5355",
503
+ onPointerDown: preserveSelection,
504
+ onClick: (event) => {
505
+ keyboardOpen.current = event.detail === 0;
506
+ setOpen((value) => !value);
507
+ },
508
+ children: "\u25BE"
509
+ }
510
+ ),
511
+ !open && !state.preview && (state.notice || state.deletedDrafts.length > 0 || state.pendingUndoDrafts.length > 0) && (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
512
+ "div",
513
+ {
514
+ ref: noticeRef,
515
+ role: "status",
516
+ className: "dsh-ia dsh-ia-notice",
517
+ style: noticePlacement ?? { visibility: "hidden" },
518
+ children: [
519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: state.notice }),
520
+ state.deletedDrafts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(state.busy), onClick: () => {
521
+ void controller.undoDeleteDrafts();
522
+ }, children: "\u64A4\u9500\u5220\u9664" }),
523
+ state.deletedDrafts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
524
+ void copyText(state.deletedDrafts.map((draft) => draft.text).join("\n\n"), controller, "\u5907\u4EFD\u5DF2\u590D\u5236");
525
+ }, children: "\u590D\u5236\u5220\u9664\u5907\u4EFD" }),
526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PendingUndo, { drafts: state.pendingUndoDrafts, controller, busy: Boolean(state.busy) }),
527
+ state.notice && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": "\u5173\u95ED\u63D0\u793A", onClick: () => controller.clearNotice(), children: "\xD7" })
528
+ ]
529
+ }
530
+ ), document.body),
531
+ state.preview && (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
532
+ "div",
533
+ {
534
+ ref: setPreviewRef,
535
+ className: "dsh-ia dsh-ia-menu dsh-ia-preview",
536
+ role: "dialog",
537
+ "aria-label": "\u4F18\u5316\u9884\u89C8",
538
+ style: previewPlacement ?? { visibility: "hidden" },
539
+ children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "\u4F18\u5316\u9884\u89C8" }),
541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ia-muted", children: "\u539F\u7A3F\u5C1A\u672A\u4FEE\u6539\u3002\u67E5\u770B\u589E\u5220\u5185\u5BB9\u540E\u51B3\u5B9A\u662F\u5426\u91C7\u7EB3\uFF1B\u91C7\u7EB3\u540E\u4ECD\u7531\u4F60\u53D1\u9001\u3002" }),
542
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiffView, { before: state.preview.beforeTarget, after: state.preview.text }),
543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
545
+ "button",
546
+ {
547
+ type: "button",
548
+ disabled: !state.connected || state.settingsSaving || Boolean(state.busy),
549
+ onPointerDown: preserveSelection,
550
+ onClick: () => {
551
+ if (controller.applyOptimization()) findEditor(anchor.current)?.focus();
552
+ },
553
+ children: "\u91C7\u7EB3\u4F18\u5316"
554
+ }
555
+ ),
556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
557
+ controller.dismissOptimization();
558
+ findEditor(anchor.current)?.focus();
559
+ }, children: "\u4FDD\u7559\u539F\u6587" })
560
+ ] })
561
+ ]
562
+ }
563
+ ), document.body),
564
+ open && (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { id: menuId, ref: menuRef, role: "dialog", tabIndex: -1, className: "dsh-ia dsh-ia-menu", "aria-label": "\u8F93\u5165\u52A9\u624B", style: menuPlacement ?? { visibility: "hidden" }, children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "\u8F93\u5165\u52A9\u624B" }),
566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u5F53\u524D\u4F1A\u8BDD\u8349\u7A3F\u4E0E\u5FEB\u6377\u64CD\u4F5C \xB7 \u91C7\u7EB3\u540E\u7531\u4F60\u53D1\u9001" }),
567
+ state.settingsSaving && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "status", children: "\u8BBE\u7F6E\u4FDD\u5B58\u4E2D\u2026" }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
569
+ "\u4F18\u5316\u5F3A\u5EA6 ",
570
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
571
+ "select",
572
+ {
573
+ "aria-label": "\u4F18\u5316\u5F3A\u5EA6",
574
+ value: state.settings.optimizationStrength,
575
+ disabled: state.settingsSaving || !state.settings.optimizeEnabled,
576
+ onChange: (event) => {
577
+ void controller.updateSettings({ optimizationStrength: event.target.value });
578
+ },
579
+ children: [
580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "light", children: "\u8F7B\u5EA6\u6DA6\u8272" }),
581
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "standard", children: "\u6807\u51C6\u4F18\u5316" }),
582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "deep", children: "\u6DF1\u5EA6\u68B3\u7406" })
583
+ ]
584
+ }
585
+ )
586
+ ] }),
587
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u4F18\u5316\u5F3A\u5EA6\u4E3A\u5168\u5C40\u8BBE\u7F6E\uFF0C\u5BF9\u6240\u6709\u4F1A\u8BDD\u751F\u6548\u3002" }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
589
+ "\u9884\u6D4B\u8BE6\u7565\u5EA6 ",
590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
591
+ "select",
592
+ {
593
+ "aria-label": "\u9884\u6D4B\u8BE6\u7565\u5EA6",
594
+ value: state.settings.predictionDetail,
595
+ disabled: state.settingsSaving,
596
+ onChange: (event) => {
597
+ void controller.updateSettings({ predictionDetail: event.target.value });
598
+ },
599
+ children: [
600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "concise", children: "\u7B80\u660E" }),
601
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "standard", children: "\u6807\u51C6" }),
602
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "detailed", children: "\u8BE6\u5C3D" })
603
+ ]
604
+ }
605
+ )
606
+ ] }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u9884\u6D4B\u8BE6\u7565\u5EA6\u4E3A\u5168\u5C40\u8BBE\u7F6E\uFF1B\u8BE6\u5C3D\u6A21\u5F0F\u53EF\u63D0\u4F9B\u591A\u884C\u7ED3\u6784\uFF0C\u4ECD\u9075\u5B88\u957F\u5EA6\u4E0A\u9650\u3002" }),
608
+ (state.notice || state.deletedDrafts.length > 0 || state.pendingUndoDrafts.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-notice", role: "status", children: [
609
+ state.notice,
610
+ state.deletedDrafts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(state.busy), onClick: () => {
611
+ void controller.undoDeleteDrafts();
612
+ }, children: "\u64A4\u9500\u5220\u9664" }),
613
+ state.deletedDrafts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
614
+ void copyText(state.deletedDrafts.map((draft) => draft.text).join("\n\n"), controller, "\u5907\u4EFD\u5DF2\u590D\u5236");
615
+ }, children: "\u590D\u5236\u5220\u9664\u5907\u4EFD" }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PendingUndo, { drafts: state.pendingUndoDrafts, controller, busy: Boolean(state.busy) })
617
+ ] }),
618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", disabled: !canOptimize, onPointerDown: preserveSelection, onClick: () => controller.lockSelection(), children: [
620
+ "\u4FDD\u62A4\u9009\u4E2D\u539F\u6587",
621
+ state.locked.length ? ` (${state.locked.length})` : ""
622
+ ] }),
623
+ state.locked.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => controller.clearLocks(), children: "\u6E05\u9664\u4FDD\u62A4" })
624
+ ] }),
625
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u4EE3\u7801\u3001\u8DEF\u5F84\u548C\u7F51\u5740\u81EA\u52A8\u4FDD\u62A4\uFF1B\u624B\u52A8\u4FDD\u62A4\u5728\u7F16\u8F91\u8349\u7A3F\u540E\u6E05\u9664\u3002" }),
626
+ state.change && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ia-section", children: [
627
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u67E5\u770B\u6539\u52A8" }),
628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiffView, { before: state.change.beforeTarget, after: state.change.afterTarget }),
629
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
630
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !controller.canRestore(), onPointerDown: preserveSelection, onClick: () => controller.restoreOriginal(), children: "\u8FD8\u539F" }),
631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
632
+ void copyText(state.change.beforeTarget, controller);
633
+ }, children: "\u590D\u5236\u539F\u6587" })
634
+ ] }),
635
+ !controller.canRestore() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u8349\u7A3F\u5DF2\u6709\u65B0\u7F16\u8F91\uFF0C\u539F\u6587\u4ECD\u53EF\u590D\u5236\u3002" })
636
+ ] }),
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dsh-ia-section", children: [
638
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h4", { children: [
639
+ "\u8349\u7A3F\u6682\u5B58 (",
640
+ state.drafts.length,
641
+ "/3)"
642
+ ] }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !state.settings.stashEnabled || !canEdit || !pure || !input.draft.trim() || state.drafts.length >= 3, onPointerDown: preserveSelection, onClick: () => {
644
+ void controller.stash();
645
+ }, children: "\u6682\u5B58\u5E76\u6E05\u7A7A" }),
646
+ !state.settings.stashEnabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ia-muted", children: "\u6682\u5B58\u5DF2\u5173\u95ED\uFF1B\u5386\u53F2\u8349\u7A3F\u4ECD\u53EF\u67E5\u770B\u548C\u5220\u9664\uFF0C\u91CD\u65B0\u542F\u7528\u540E\u53EF\u6062\u590D\u3002" }),
647
+ !pure && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ia-muted", children: "\u6682\u5B58\u548C\u6062\u590D\u4EC5\u652F\u6301\u65E0\u5F15\u7528\u3001\u65E0\u9644\u4EF6\u7684\u7EAF\u6587\u672C\u8349\u7A3F\u3002" }),
648
+ state.drafts.map((draft) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
649
+ DraftCard,
650
+ {
651
+ draft,
652
+ controller,
653
+ canEdit: state.settings.stashEnabled && canEdit && pure,
654
+ canReplace: state.settings.stashEnabled && canEdit && pure && Boolean(input.draft.trim()) && state.drafts.length >= 3
655
+ },
656
+ draft.id
657
+ )),
658
+ state.drafts.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ia-muted", children: "\u6682\u5B58\u540E\u53EF\u4EE5\u5148\u8FFD\u95EE\u522B\u7684\uFF0C\u518D\u627E\u56DE\u539F\u6765\u7684\u8349\u7A3F\u3002" }),
659
+ state.drafts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(state.busy), onClick: () => {
660
+ void controller.clearDrafts();
661
+ }, children: "\u6E05\u7A7A\u6682\u5B58" })
662
+ ] }),
663
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dsh-ia-section", children: [
664
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { children: "\u8F93\u5165\u5EFA\u8BAE" }),
665
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-muted dsh-ia-route", children: [
666
+ "\u8F85\u52A9\u6A21\u578B\uFF1A",
667
+ auxiliaryRoute ? `${auxiliaryRoute.provider} / ${auxiliaryRoute.model}` : "\u5C1A\u672A\u9009\u62E9",
668
+ state.settings.provider ? "\uFF08\u56FA\u5B9A\uFF09" : "\uFF08\u8DDF\u968F\u8F93\u5165\u6846\uFF09"
669
+ ] }),
670
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-suggestion-state", role: "status", "aria-label": "\u8F93\u5165\u5EFA\u8BAE\u72B6\u6001", "aria-live": "polite", children: state.suggestionMessage || "\u53EF\u624B\u52A8\u751F\u6210\u4E00\u6B21\u5EFA\u8BAE\uFF1B\u5F00\u542F\u81EA\u52A8\u5EFA\u8BAE\u540E\u4E5F\u4F1A\u6309\u8BBE\u7F6E\u89E6\u53D1\u3002" }),
671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "Tab \u91C7\u7EB3\uFF0CEsc \u5FFD\u7565\uFF1B\u5EFA\u8BAE\u4E0D\u4F1A\u81EA\u52A8\u53D1\u9001\u3002" }),
672
+ !pure && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u624B\u52A8\u5EFA\u8BAE\u4EC5\u652F\u6301\u65E0\u9644\u4EF6\u3001\u65E0\u5F15\u7528\u7684\u7EAF\u6587\u672C\u8349\u7A3F\u3002" }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
674
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !canRequestSuggestion, onPointerDown: preserveSelection, onClick: () => {
675
+ predict();
676
+ setOpen(false);
677
+ }, children: retryLabel }),
678
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => controller.dismissSuggestion(), children: "\u6682\u505C\u81F3\u4E0B\u6B21\u7F16\u8F91" })
679
+ ] })
680
+ ] }),
681
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ia-section", children: [
682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u672C\u6B21\u4F7F\u7528\u7EDF\u8BA1" }),
683
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "dsh-ia-muted", children: [
684
+ "\u5F53\u524D\u4F1A\u8BDD\uFF1A\u5C55\u793A ",
685
+ state.metrics.exposures,
686
+ " \u6B21\uFF0C\u91C7\u7EB3 ",
687
+ state.metrics.accepted,
688
+ " \u6B21\uFF0C \u91C7\u7EB3\u540E\u7EE7\u7EED\u7F16\u8F91 ",
689
+ state.metrics.editedAcceptances,
690
+ " \u6B21\uFF0C\u7F16\u8F91\u5B57\u6570\u7EA6 ",
691
+ state.metrics.editedCharacters,
692
+ "\uFF0C\u8F85\u52A9\u8C03\u7528 ",
693
+ state.metrics.calls,
694
+ " \u6B21\u3002"
695
+ ] }),
696
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u4EC5\u4FDD\u7559\u5728\u672C\u6B21\u63D2\u4EF6\u8FD0\u884C\u5185\uFF0C\u4E0D\u4E0A\u4F20\uFF1B\u5FFD\u7565\u5EFA\u8BAE\u4E0D\u4F1A\u88AB\u89C6\u4E3A\u8D1F\u53CD\u9988\u3002" })
697
+ ] }),
698
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ia-muted", children: "\u529F\u80FD\u5F00\u5173\u3001\u663E\u793A\u65B9\u5F0F\u4E0E\u8F85\u52A9\u6A21\u578B\uFF1ADSH \u8BBE\u7F6E \u2192 \u8F93\u5165\u52A9\u624B\uFF08\u5168\u5C40\u8BBE\u7F6E\uFF09" })
699
+ ] }), document.body)
700
+ ] });
701
+ }
702
+ function DiffView({ before, after }) {
703
+ const parts = (0, import_react.useMemo)(() => diffText(before, after), [before, after]);
704
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-pre dsh-ia-diff", children: parts.map((part, index) => part.type === "insert" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ins", { children: part.value }, index) : part.type === "delete" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("del", { children: part.value }, index) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: part.value }, index)) });
705
+ }
706
+ function PendingUndo({ drafts, controller, busy }) {
707
+ if (!drafts.length) return null;
708
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-pending-undo", children: [
709
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [
710
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { children: [
711
+ "\u6682\u672A\u6062\u590D\u7684\u8349\u7A3F (",
712
+ drafts.length,
713
+ ")"
714
+ ] }),
715
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-pre", children: drafts.map((draft) => draft.text).join("\n\n") })
716
+ ] }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
718
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => {
719
+ void controller.undoDeleteDrafts("pending");
720
+ }, children: "\u91CD\u8BD5\u6062\u590D\u8349\u7A3F" }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
722
+ void copyText(drafts.map((draft) => draft.text).join("\n\n"), controller, "\u5F85\u6062\u590D\u8349\u7A3F\u5DF2\u590D\u5236");
723
+ }, children: "\u590D\u5236\u5F85\u6062\u590D\u8349\u7A3F" }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => controller.discardPendingUndo(), children: "\u653E\u5F03\u6B64\u6062\u590D\u5907\u4EFD" })
725
+ ] })
726
+ ] });
727
+ }
728
+ function DraftCard({ draft, controller, canEdit, canReplace }) {
729
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ia-draft", children: [
730
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: draft.text.split("\n")[0].slice(0, 50) || "\u672A\u547D\u540D\u8349\u7A3F" }),
731
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", { children: new Date(draft.createdAt).toLocaleString() }),
732
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-pre", children: draft.text }),
733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
734
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !canEdit, onPointerDown: (event) => event.preventDefault(), onClick: () => controller.restoreDraft(draft, "append"), children: "\u8FFD\u52A0" }),
735
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !canEdit, onPointerDown: (event) => event.preventDefault(), onClick: () => controller.restoreDraft(draft, "replace"), children: "\u66FF\u6362" }),
736
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
737
+ void copyText(draft.text, controller, "\u8349\u7A3F\u5DF2\u590D\u5236");
738
+ }, children: "\u590D\u5236\u8349\u7A3F" }),
739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(controller.getSnapshot().busy), onClick: () => {
740
+ void controller.deleteDraft(draft.id);
741
+ }, children: "\u5220\u9664" }),
742
+ canReplace && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onPointerDown: (event) => event.preventDefault(), onClick: () => {
743
+ void controller.stash(draft.id);
744
+ }, children: "\u7528\u5F53\u524D\u8349\u7A3F\u66FF\u6362\u6B64\u6761" })
745
+ ] })
746
+ ] });
747
+ }
748
+ async function copyText(text, controller, success = "\u539F\u6587\u5DF2\u590D\u5236") {
749
+ try {
750
+ await navigator.clipboard.writeText(text);
751
+ controller.notice(success);
752
+ } catch {
753
+ controller.notice("\u590D\u5236\u4E0D\u53EF\u7528\uFF0C\u8BF7\u5C55\u5F00\u5BF9\u5E94\u6587\u672C\u540E\u624B\u52A8\u9009\u62E9\u590D\u5236");
754
+ }
755
+ }
756
+ function AssistantOverlay({ runtime, sessionId }) {
757
+ const { controller, state } = useController(runtime, sessionId);
758
+ const anchor = (0, import_react.useRef)(null);
759
+ const panelRef = (0, import_react.useRef)(null);
760
+ const [geometry, setGeometry] = (0, import_react.useState)(null);
761
+ const suggestion = state.suggestion;
762
+ const displayMode = state.settings.predictionDetail === "detailed" ? "panel" : state.settings.displayMode;
763
+ const panelPlacement = useFloatingPlacement(anchor, panelRef, Boolean(suggestion) && displayMode === "panel", true);
764
+ (0, import_react.useEffect)(() => {
765
+ if (suggestion && (displayMode === "panel" && panelPlacement || geometry)) controller.markExposed(suggestion.snapshot.requestId);
766
+ }, [controller, suggestion, geometry, panelPlacement, displayMode]);
767
+ (0, import_react.useLayoutEffect)(() => {
768
+ if (!suggestion || displayMode !== "ghost") {
769
+ setGeometry(null);
770
+ return;
771
+ }
772
+ const update = () => setGeometry(measureGhost(findEditor(anchor.current), suggestion.snapshot.draft === ""));
773
+ update();
774
+ window.addEventListener("resize", update);
775
+ window.addEventListener("scroll", update, true);
776
+ document.addEventListener("selectionchange", update);
777
+ const editor = findEditor(anchor.current);
778
+ editor?.addEventListener("focus", update);
779
+ editor?.addEventListener("blur", update);
780
+ const resize = typeof ResizeObserver === "function" ? new ResizeObserver(update) : null;
781
+ if (editor) resize?.observe(editor);
782
+ return () => {
783
+ window.removeEventListener("resize", update);
784
+ window.removeEventListener("scroll", update, true);
785
+ document.removeEventListener("selectionchange", update);
786
+ editor?.removeEventListener("focus", update);
787
+ editor?.removeEventListener("blur", update);
788
+ resize?.disconnect();
789
+ };
790
+ }, [suggestion, displayMode]);
791
+ const ghostStyle = geometry ? {
792
+ left: geometry.left,
793
+ top: geometry.top,
794
+ width: geometry.width,
795
+ maxHeight: `min(3lh, ${geometry.maxHeight}px)`,
796
+ font: geometry.font,
797
+ lineHeight: geometry.lineHeight,
798
+ letterSpacing: geometry.letterSpacing
799
+ } : void 0;
800
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { ref: anchor, className: "dsh-ia", style: { display: "contents" }, children: [
801
+ suggestion && displayMode === "panel" && (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
802
+ "div",
803
+ {
804
+ ref: panelRef,
805
+ className: "dsh-ia dsh-ia-candidate",
806
+ role: "status",
807
+ style: panelPlacement ?? { visibility: "hidden" },
808
+ "data-dsh-ia-visible-suggestion": panelPlacement ? "" : void 0,
809
+ "data-dsh-ia-session": sessionId,
810
+ children: [
811
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-ia-candidate-text", children: suggestion.text }),
812
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onPointerDown: (event) => event.preventDefault(), onClick: () => controller.acceptSuggestion(), children: "\u91C7\u7EB3" }),
813
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": "\u5FFD\u7565\u5EFA\u8BAE", onClick: () => controller.dismissSuggestion(), children: "\xD7" })
814
+ ]
815
+ }
816
+ ), document.body),
817
+ suggestion && geometry && displayMode === "ghost" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
818
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-ia-sr-only", role: "status", "aria-atomic": "true", children: [
819
+ "\u6709\u8F93\u5165\u5EFA\u8BAE\uFF1A",
820
+ suggestion.text,
821
+ "\u3002\u6309 Tab \u91C7\u7EB3\uFF0C\u6309 Esc \u5FFD\u7565\u3002"
822
+ ] }),
823
+ suggestion.snapshot.draft === "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { hidden: true, "data-dsh-ia-empty-ghost": "" }),
824
+ (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
825
+ "span",
826
+ {
827
+ className: "dsh-ia dsh-ia-ghost",
828
+ style: ghostStyle,
829
+ "aria-hidden": "true",
830
+ "data-dsh-ia-visible-suggestion": "",
831
+ "data-dsh-ia-session": sessionId,
832
+ children: suggestion.text
833
+ }
834
+ ), document.body)
835
+ ] })
836
+ ] });
837
+ }
838
+
839
+ // src/core/types.ts
840
+ var DEFAULT_CONFIG = Object.freeze({
841
+ optimizeEnabled: true,
842
+ stashEnabled: true,
843
+ optimizationStrength: "standard",
844
+ nextEnabled: true,
845
+ completionEnabled: true,
846
+ predictionDetail: "standard",
847
+ displayMode: "ghost",
848
+ debounceMs: 600,
849
+ minIntervalMs: 2e3,
850
+ maxHistoryChars: 16e3,
851
+ maxDraftChars: 8e3,
852
+ maxNextChars: 120,
853
+ maxCompletionChars: 80,
854
+ optimizeTimeoutMs: 6e4,
855
+ suggestionTimeoutMs: 15e3,
856
+ provider: "",
857
+ model: ""
858
+ });
859
+ var AssistantError = class extends Error {
860
+ constructor(code, message) {
861
+ super(message);
862
+ this.code = code;
863
+ this.name = "AssistantError";
864
+ }
865
+ };
866
+
867
+ // src/core/snapshot.ts
868
+ function snapshotMatches(expected, current, checkCaret = true) {
869
+ return expected.sessionId === current.sessionId && expected.sourceTurnId === current.sourceTurnId && expected.connectionGeneration === current.connectionGeneration && expected.routeKey === current.routeKey && expected.draftRev === current.draftRev && expected.draft === current.draft && (!checkCaret || expected.span.start === current.span.start && expected.span.end === current.span.end);
870
+ }
871
+
872
+ // src/core/context.ts
873
+ var charCount = (text) => Array.from(text).length;
874
+
875
+ // src/browser/controller.ts
876
+ var emptyMetrics = () => ({ exposures: 0, accepted: 0, editedAcceptances: 0, editedCharacters: 0, calls: 0, totalLatencyMs: 0 });
877
+ var initial = () => ({
878
+ status: null,
879
+ settings: { ...DEFAULT_CONFIG },
880
+ busy: null,
881
+ stashClearCancelled: false,
882
+ stashClearPending: false,
883
+ notice: "",
884
+ suggestion: null,
885
+ suggestionStatus: "idle",
886
+ suggestionMessage: "",
887
+ preview: null,
888
+ change: null,
889
+ drafts: [],
890
+ deletedDrafts: [],
891
+ pendingUndoDrafts: [],
892
+ locked: [],
893
+ connected: false,
894
+ settingsSaving: false,
895
+ metrics: emptyMetrics()
896
+ });
897
+ var InputAssistantController = class {
898
+ constructor(sessionId, transport, metrics, saveSettings, undo) {
899
+ this.sessionId = sessionId;
900
+ this.transport = transport;
901
+ this.saveSettings = saveSettings;
902
+ if (metrics) this.state.metrics = { ...metrics };
903
+ if (undo && undo.generation === transport.generation()) {
904
+ this.clientGeneration = undo.generation;
905
+ this.state.pendingUndoDrafts = undo.pending.map((draft) => ({ ...draft }));
906
+ if (undo.expiresAt > Date.now()) {
907
+ this.state.deletedDrafts = undo.recent.map((draft) => ({ ...draft }));
908
+ this.undoExpiresAt = undo.expiresAt;
909
+ this.armUndoExpiry();
910
+ }
911
+ }
912
+ }
913
+ state = initial();
914
+ listeners = /* @__PURE__ */ new Set();
915
+ binding = null;
916
+ active = null;
917
+ timer;
918
+ /** Cache key the armed completion timer was scheduled for; null when no timer is armed. */
919
+ timerKey = null;
920
+ manualTimerKey = null;
921
+ poll;
922
+ undoTimer;
923
+ undoExpiresAt = 0;
924
+ requestPrefix = Array.from(crypto.getRandomValues(new Uint32Array(4)), (word) => word.toString(36)).join("-");
925
+ completionRetryAt = 0;
926
+ polling = false;
927
+ lifecycle = new AbortController();
928
+ disposed = false;
929
+ composing = false;
930
+ settingsPending = false;
931
+ settingsEpoch = 0;
932
+ draftsEpoch = 0;
933
+ stashEpoch = 0;
934
+ nextTurns = /* @__PURE__ */ new Set();
935
+ failedNextKey = null;
936
+ nextCache = null;
937
+ completionCache = /* @__PURE__ */ new Map();
938
+ failedCompletionKey = null;
939
+ lastCompletionStart = -Infinity;
940
+ suppressUntilEdit = null;
941
+ sequence = 0;
942
+ clientGeneration = null;
943
+ lockedRanges = [];
944
+ exposed = /* @__PURE__ */ new Set();
945
+ acceptance = null;
946
+ getUndoSnapshot() {
947
+ return {
948
+ recent: this.undoExpiresAt > Date.now() ? this.state.deletedDrafts.map((draft) => ({ ...draft })) : [],
949
+ expiresAt: this.undoExpiresAt,
950
+ pending: this.state.pendingUndoDrafts.map((draft) => ({ ...draft })),
951
+ generation: this.clientGeneration
952
+ };
953
+ }
954
+ armUndoExpiry() {
955
+ clearTimeout(this.undoTimer);
956
+ if (!Number.isFinite(this.undoExpiresAt)) return;
957
+ this.undoTimer = setTimeout(() => {
958
+ this.undoExpiresAt = 0;
959
+ this.patch({ deletedDrafts: [], notice: this.state.notice === "\u5DF2\u5220\u9664\u6682\u5B58\uFF0C10 \u79D2\u5185\u53EF\u64A4\u9500" ? "" : this.state.notice });
960
+ }, Math.max(0, this.undoExpiresAt - Date.now()));
961
+ }
962
+ getSnapshot = () => this.state;
963
+ subscribe = (listener) => {
964
+ this.listeners.add(listener);
965
+ return () => {
966
+ this.listeners.delete(listener);
967
+ };
968
+ };
969
+ patch(update) {
970
+ if (this.disposed) return;
971
+ this.state = { ...this.state, ...update };
972
+ for (const listener of this.listeners) listener();
973
+ }
974
+ start() {
975
+ if (this.poll || this.disposed) return;
976
+ void this.refresh();
977
+ void this.refreshDrafts();
978
+ this.schedulePoll();
979
+ document.addEventListener("visibilitychange", this.visibilityChanged);
980
+ }
981
+ visibilityChanged = () => {
982
+ clearTimeout(this.poll);
983
+ if (document.visibilityState === "hidden") {
984
+ if (this.active?.snapshot.operation !== "optimize" && !this.active?.snapshot.manual) this.cancelActive();
985
+ this.clearTimer();
986
+ this.patch({ suggestion: null, suggestionStatus: "paused", suggestionMessage: "\u9875\u9762\u5DF2\u9690\u85CF\uFF0C\u81EA\u52A8\u5EFA\u8BAE\u5DF2\u6682\u505C" });
987
+ } else {
988
+ void this.refresh();
989
+ this.driveSuggestions();
990
+ }
991
+ this.schedulePoll();
992
+ };
993
+ schedulePoll() {
994
+ const delay = document.visibilityState === "hidden" ? 1e4 : this.binding?.running || this.state.busy ? 900 : 3e3;
995
+ this.poll = setTimeout(() => {
996
+ void this.refresh();
997
+ this.schedulePoll();
998
+ }, delay);
999
+ }
1000
+ dispose() {
1001
+ if (this.disposed) return;
1002
+ this.cancelActive();
1003
+ this.clearTimer();
1004
+ clearTimeout(this.poll);
1005
+ clearTimeout(this.undoTimer);
1006
+ document.removeEventListener("visibilitychange", this.visibilityChanged);
1007
+ this.lifecycle.abort();
1008
+ this.disposed = true;
1009
+ this.listeners.clear();
1010
+ this.binding = null;
1011
+ }
1012
+ bind(binding) {
1013
+ if (this.disposed || binding.sessionId !== this.sessionId) return;
1014
+ const previous = this.binding;
1015
+ this.binding = binding;
1016
+ const generation = this.transport.generation();
1017
+ if (generation !== this.clientGeneration) {
1018
+ this.clientGeneration = generation;
1019
+ this.invalidate(true);
1020
+ clearTimeout(this.undoTimer);
1021
+ this.undoExpiresAt = 0;
1022
+ this.patch({ connected: false, status: null, drafts: [], deletedDrafts: [], pendingUndoDrafts: [] });
1023
+ if (generation !== null && this.poll) {
1024
+ void this.refresh();
1025
+ void this.refreshDrafts();
1026
+ }
1027
+ }
1028
+ const changed = previous && (previous.input.draftRev !== binding.input.draftRev || previous.input.draft !== binding.input.draft || this.routeKey(previous.route) !== this.routeKey(binding.route) || previous.running !== binding.running || previous.input.phase !== binding.input.phase || previous.input.attachmentIds.length !== binding.input.attachmentIds.length || previous.input.occurrences.length !== binding.input.occurrences.length);
1029
+ if (changed) {
1030
+ this.cancelActive();
1031
+ if (previous.input.draft !== binding.input.draft) this.observeAcceptedEdit(binding.input.draft);
1032
+ if (previous.input.draft !== binding.input.draft) this.lockedRanges = [];
1033
+ if (previous.input.draft !== binding.input.draft) this.failedCompletionKey = null;
1034
+ this.patch({ suggestion: null, suggestionStatus: "idle", suggestionMessage: "", preview: null, ...previous.input.draft !== binding.input.draft ? { locked: [] } : {} });
1035
+ if (this.suppressUntilEdit !== null && binding.input.draft !== this.suppressUntilEdit) this.suppressUntilEdit = null;
1036
+ }
1037
+ this.driveSuggestions();
1038
+ }
1039
+ notifySelectionChange() {
1040
+ if (nativeMenuOpen(this.binding?.editor ?? null)) {
1041
+ if (this.active?.snapshot.operation !== "optimize") this.cancelActive();
1042
+ this.clearTimer();
1043
+ this.patch({ suggestion: null });
1044
+ return;
1045
+ }
1046
+ if (this.active && this.active.snapshot.operation !== "optimize" && !this.sameCaret(this.active.caret)) this.cancelActive();
1047
+ if (this.state.suggestion && !this.isCurrent(this.state.suggestion.snapshot, true)) this.patch({ suggestion: null });
1048
+ this.driveSuggestions();
1049
+ }
1050
+ setComposing(value) {
1051
+ this.composing = value;
1052
+ if (value) {
1053
+ this.cancelActive();
1054
+ this.clearTimer();
1055
+ this.patch({ suggestion: null });
1056
+ } else this.driveSuggestions();
1057
+ }
1058
+ dismissSuggestion() {
1059
+ this.clearTimer();
1060
+ this.cancelActive();
1061
+ this.patch({ suggestion: null, suggestionStatus: "paused", suggestionMessage: "\u5DF2\u6682\u505C\u81F3\u4E0B\u6B21\u7F16\u8F91" });
1062
+ this.suppressUntilEdit = this.binding?.input.draft ?? "";
1063
+ }
1064
+ dismissOptimization() {
1065
+ this.suppressUntilEdit = this.binding?.input.draft ?? "";
1066
+ this.patch({ preview: null, notice: "\u5DF2\u4FDD\u7559\u539F\u6587" });
1067
+ }
1068
+ cancelOperation() {
1069
+ if (this.state.busy === "stash" && this.state.stashClearPending) {
1070
+ this.stashEpoch++;
1071
+ this.patch({ stashClearCancelled: true, notice: "\u5DF2\u53D6\u6D88\u6E05\u7A7A\uFF1B\u4FDD\u5B58\u5B8C\u6210\u540E\u4ECD\u4FDD\u7559\u5F53\u524D\u8F93\u5165" });
1072
+ return;
1073
+ }
1074
+ if (this.state.busy === "stash") {
1075
+ this.patch({ notice: "\u5B58\u50A8\u64CD\u4F5C\u6B63\u5728\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u518D\u64CD\u4F5C" });
1076
+ return;
1077
+ }
1078
+ this.cancelActive();
1079
+ this.clearTimer();
1080
+ this.suppressUntilEdit = this.binding?.input.draft ?? "";
1081
+ this.patch({ preview: null, suggestion: null, notice: "\u5DF2\u53D6\u6D88\uFF0C\u539F\u7A3F\u5DF2\u4FDD\u7559" });
1082
+ }
1083
+ clearNotice() {
1084
+ this.patch({ notice: "" });
1085
+ }
1086
+ notice(text) {
1087
+ this.patch({ notice: text });
1088
+ }
1089
+ async refresh() {
1090
+ if (this.disposed || this.polling || this.transport.generation() === null) return;
1091
+ const generation = this.transport.generation();
1092
+ const settingsEpoch = this.settingsEpoch;
1093
+ this.polling = true;
1094
+ try {
1095
+ const status = await this.transport.call("status", { sessionId: this.sessionId }, this.lifecycle.signal);
1096
+ if (this.disposed || this.transport.generation() !== generation || settingsEpoch !== this.settingsEpoch) return;
1097
+ const previous = this.state.status;
1098
+ const changed = previous && (status.sourceTurnId !== previous.sourceTurnId || status.generation !== previous.generation || this.routeKey(status.route) !== this.routeKey(previous.route) || status.running !== previous.running || JSON.stringify(status.settings) !== JSON.stringify(previous.settings));
1099
+ if (changed) this.invalidate(true);
1100
+ if (!previous || changed || !this.state.connected) this.patch({ status, settings: status.settings, connected: true });
1101
+ this.driveSuggestions();
1102
+ } catch {
1103
+ if (!this.disposed && this.transport.generation() === generation && settingsEpoch === this.settingsEpoch) {
1104
+ this.invalidate(false, false);
1105
+ this.patch({ connected: false });
1106
+ }
1107
+ } finally {
1108
+ this.polling = false;
1109
+ }
1110
+ }
1111
+ async updateSettings(update) {
1112
+ if (this.settingsPending) return;
1113
+ if (this.saveSettings) {
1114
+ try {
1115
+ await this.saveSettings(update);
1116
+ this.patch({ notice: "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58" });
1117
+ } catch (error) {
1118
+ this.patch({ notice: this.errorMessage(error) });
1119
+ }
1120
+ return;
1121
+ }
1122
+ this.beginSettingsUpdate();
1123
+ try {
1124
+ const settings = await this.transport.call("settings/set", update, this.lifecycle.signal);
1125
+ this.applySettings(settings);
1126
+ this.patch({ notice: "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58" });
1127
+ } catch (error) {
1128
+ this.patch({ notice: this.errorMessage(error) });
1129
+ } finally {
1130
+ this.endSettingsUpdate();
1131
+ }
1132
+ }
1133
+ beginSettingsUpdate() {
1134
+ this.settingsEpoch++;
1135
+ this.settingsPending = true;
1136
+ this.invalidate(false);
1137
+ this.patch({ settingsSaving: true });
1138
+ }
1139
+ endSettingsUpdate() {
1140
+ this.settingsPending = false;
1141
+ this.patch({ settingsSaving: false });
1142
+ this.driveSuggestions();
1143
+ }
1144
+ /** A settings page can update every mounted composer without waiting for its status poll. */
1145
+ applySettings(settings) {
1146
+ this.settingsEpoch++;
1147
+ this.invalidate(true);
1148
+ this.patch({ settings, ...this.state.status ? { status: { ...this.state.status, settings } } : {} });
1149
+ this.driveSuggestions();
1150
+ }
1151
+ lockSelection() {
1152
+ const captured = this.captureTarget(true);
1153
+ if (!captured) return;
1154
+ if (this.state.locked.includes(captured.text)) return;
1155
+ this.lockedRanges.push({ text: captured.text, span: captured.span });
1156
+ this.patch({ locked: [...this.state.locked, captured.text], notice: "\u5DF2\u4FDD\u62A4\u9009\u4E2D\u539F\u6587\uFF1B\u7F16\u8F91\u8349\u7A3F\u540E\u9700\u91CD\u65B0\u9501\u5B9A" });
1157
+ }
1158
+ clearLocks() {
1159
+ this.lockedRanges = [];
1160
+ this.patch({ locked: [], notice: "\u5DF2\u6E05\u9664\u624B\u52A8\u4FDD\u62A4\u7247\u6BB5" });
1161
+ }
1162
+ async optimize() {
1163
+ if (this.settingsPending) return;
1164
+ if (!this.state.settings.optimizeEnabled) {
1165
+ this.patch({ notice: "\u63D0\u793A\u8BCD\u4F18\u5316\u5DF2\u5173\u95ED\uFF0C\u53EF\u5728\u8F93\u5165\u52A9\u624B\u8BBE\u7F6E\u4E2D\u542F\u7528" });
1166
+ return;
1167
+ }
1168
+ const binding = this.binding;
1169
+ if (!binding || this.state.busy) return;
1170
+ const target = this.captureTarget(false);
1171
+ if (!target || !target.text.trim()) return;
1172
+ if (this.lockedRanges.some((lock) => target.span.start < lock.span.end && target.span.end > lock.span.start && !(target.span.start <= lock.span.start && target.span.end >= lock.span.end))) {
1173
+ this.patch({ notice: "\u9009\u533A\u8DE8\u8D8A\u4E86\u53D7\u4FDD\u62A4\u7247\u6BB5\u7684\u8FB9\u754C\uFF0C\u8BF7\u5B8C\u6574\u5305\u542B\u6216\u907F\u5F00\u8BE5\u7247\u6BB5" });
1174
+ return;
1175
+ }
1176
+ if (charCount(target.text) > this.state.settings.maxDraftChars) {
1177
+ this.patch({ notice: `\u5F85\u4F18\u5316\u5185\u5BB9\u8D85\u8FC7 ${this.state.settings.maxDraftChars} \u5B57\u7B26\uFF0C\u8BF7\u9009\u62E9\u8F83\u77ED\u7247\u6BB5` });
1178
+ return;
1179
+ }
1180
+ this.clearTimer();
1181
+ this.cancelActive();
1182
+ this.patch({ suggestion: null, preview: null, busy: "optimize", notice: "" });
1183
+ const snapshot = this.snapshot("optimize", target.span, target.text);
1184
+ if (!snapshot) {
1185
+ this.patch({ busy: null, notice: "\u8FDE\u63A5\u6216\u6A21\u578B\u5C1A\u672A\u5C31\u7EEA\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" });
1186
+ return;
1187
+ }
1188
+ const active = this.begin(snapshot);
1189
+ try {
1190
+ const result = await this.transport.call("generate", snapshot, active.controller.signal);
1191
+ if (this.active !== active || !this.isCurrent(snapshot, false) || !this.validResult(result, snapshot)) return;
1192
+ if (result.text === null || !result.text.trim()) {
1193
+ this.patch({ notice: "\u6CA1\u6709\u53EF\u5E94\u7528\u7684\u4F18\u5316\u7ED3\u679C\uFF0C\u539F\u6587\u5DF2\u4FDD\u7559" });
1194
+ return;
1195
+ }
1196
+ this.patch({ preview: {
1197
+ request: snapshot,
1198
+ text: result.text,
1199
+ beforeTarget: target.text,
1200
+ clipboardStart: target.clipboardStart,
1201
+ clipboardEnd: target.clipboardEnd,
1202
+ clientGeneration: active.clientGeneration
1203
+ }, notice: "" });
1204
+ } catch (error) {
1205
+ if (!active.controller.signal.aborted) this.patch({ notice: this.errorMessage(error) });
1206
+ } finally {
1207
+ this.recordLatency(active);
1208
+ if (this.active === active) {
1209
+ this.active = null;
1210
+ this.patch({ busy: null });
1211
+ }
1212
+ }
1213
+ }
1214
+ applyOptimization() {
1215
+ const preview = this.state.preview, binding = this.binding;
1216
+ if (!preview || !binding || !this.state.connected || this.state.busy || this.settingsPending || this.composing || !this.state.settings.optimizeEnabled || !this.isCurrent(preview.request, false) || preview.clientGeneration !== this.transport.generation()) {
1217
+ this.patch({ preview: null, notice: "\u8349\u7A3F\u6216\u4F1A\u8BDD\u5DF2\u53D8\u5316\uFF0C\u539F\u6587\u5DF2\u4FDD\u7559" });
1218
+ return false;
1219
+ }
1220
+ const { request, text, beforeTarget, clipboardStart, clipboardEnd, clientGeneration } = preview;
1221
+ const before = binding.input.draft;
1222
+ const after = before.slice(0, clipboardStart) + text + before.slice(clipboardEnd);
1223
+ if (!binding.actions.insertText(text, request.span)) {
1224
+ this.patch({ preview: null, notice: "\u8349\u7A3F\u5DF2\u53D8\u5316\uFF0C\u5DF2\u653E\u5F03\u672C\u6B21\u7ED3\u679C" });
1225
+ return false;
1226
+ }
1227
+ binding.actions.persistDraft?.();
1228
+ this.suppressUntilEdit = after;
1229
+ this.patch({
1230
+ preview: null,
1231
+ change: {
1232
+ before,
1233
+ after,
1234
+ beforeTarget,
1235
+ afterTarget: text,
1236
+ span: { ...request.span, end: request.span.start + text.length },
1237
+ sourceTurnId: request.sourceTurnId,
1238
+ connectionGeneration: request.connectionGeneration,
1239
+ routeKey: request.routeKey,
1240
+ clientGeneration
1241
+ },
1242
+ notice: "\u5DF2\u91C7\u7EB3\u4F18\u5316\uFF0C\u53EF\u67E5\u770B\u6539\u52A8\u6216\u8FD8\u539F"
1243
+ });
1244
+ return true;
1245
+ }
1246
+ canRestore() {
1247
+ const change = this.state.change, binding = this.binding, status = this.state.status;
1248
+ return Boolean(change && binding && status && this.state.connected && binding.input.draft === change.after && binding.input.phase === "plain" && status.sourceTurnId === change.sourceTurnId && status.generation === change.connectionGeneration && this.routeKey(binding.route ?? status.route) === change.routeKey && this.transport.generation() === change.clientGeneration);
1249
+ }
1250
+ restoreOriginal() {
1251
+ const binding = this.binding, change = this.state.change;
1252
+ if (!binding || !change || !this.canRestore()) {
1253
+ this.patch({ notice: "\u8349\u7A3F\u5DF2\u6709\u65B0\u7F16\u8F91\uFF0C\u53EF\u5728\u6539\u52A8\u9762\u677F\u590D\u5236\u539F\u6587" });
1254
+ return false;
1255
+ }
1256
+ this.cancelActive();
1257
+ const applied = binding.actions.insertText(change.beforeTarget, { ...change.span, draftRev: binding.input.draftRev });
1258
+ if (applied) {
1259
+ binding.actions.persistDraft?.();
1260
+ this.suppressUntilEdit = change.before;
1261
+ this.patch({ change: null, suggestion: null, notice: "\u5DF2\u8FD8\u539F\u4F18\u5316\u524D\u7684\u5185\u5BB9" });
1262
+ }
1263
+ return applied;
1264
+ }
1265
+ acceptSuggestion() {
1266
+ const suggestion = this.state.suggestion, binding = this.binding;
1267
+ if (!suggestion || !binding || !this.isCurrent(suggestion.snapshot, true) || !this.eligible() || !suggestion.snapshot.manual && (suggestion.kind === "next" ? !this.state.settings.nextEnabled : !this.state.settings.completionEnabled)) {
1268
+ this.patch({ suggestion: null });
1269
+ return false;
1270
+ }
1271
+ const applied = binding.actions.insertText(suggestion.text, suggestion.snapshot.span);
1272
+ this.patch({ suggestion: null, suggestionStatus: applied ? "paused" : "idle", suggestionMessage: applied ? "\u5DF2\u91C7\u7EB3\uFF0C\u7EE7\u7EED\u7F16\u8F91\u540E\u6062\u590D\u81EA\u52A8\u5EFA\u8BAE" : "" });
1273
+ if (applied) {
1274
+ binding.actions.persistDraft?.();
1275
+ this.suppressUntilEdit = binding.input.draft + suggestion.text;
1276
+ this.acceptance = { expected: this.suppressUntilEdit, edits: 0, counted: false };
1277
+ this.patch({ metrics: { ...this.state.metrics, accepted: this.state.metrics.accepted + 1 } });
1278
+ }
1279
+ return applied;
1280
+ }
1281
+ /** Called only after the overlay has actually rendered a candidate. */
1282
+ markExposed(requestId) {
1283
+ const suggestion = this.state.suggestion;
1284
+ if (!suggestion || suggestion.snapshot.requestId !== requestId) return;
1285
+ const key = JSON.stringify([suggestion.snapshot.sourceTurnId, suggestion.snapshot.routeKey, suggestion.snapshot.draft, suggestion.text, suggestion.kind]);
1286
+ if (this.exposed.has(key)) return;
1287
+ if (this.exposed.size >= 512) this.exposed.clear();
1288
+ this.exposed.add(key);
1289
+ this.patch({ metrics: { ...this.state.metrics, exposures: this.state.metrics.exposures + 1 } });
1290
+ }
1291
+ async refreshDrafts() {
1292
+ const generation = this.transport.generation(), epoch = this.draftsEpoch;
1293
+ if (generation === null) return;
1294
+ try {
1295
+ const drafts = await this.transport.call("drafts/list", { sessionId: this.sessionId }, this.lifecycle.signal);
1296
+ if (generation === this.transport.generation() && epoch === this.draftsEpoch) this.patch({ drafts });
1297
+ } catch (error) {
1298
+ if (!this.lifecycle.signal.aborted && generation === this.transport.generation() && epoch === this.draftsEpoch) this.patch({ notice: this.errorMessage(error) });
1299
+ }
1300
+ }
1301
+ async stash(replaceId) {
1302
+ if (this.settingsPending) return;
1303
+ if (!this.state.settings.stashEnabled) {
1304
+ this.patch({ notice: "\u8349\u7A3F\u6682\u5B58\u5DF2\u5173\u95ED\uFF0C\u5DF2\u4FDD\u5B58\u7684\u8349\u7A3F\u4ECD\u53EF\u67E5\u770B\u548C\u5220\u9664" });
1305
+ return;
1306
+ }
1307
+ const binding = this.binding;
1308
+ if (!binding || this.state.busy) return;
1309
+ if (!this.pureText() || !binding.input.draft.trim()) {
1310
+ this.patch({ notice: "\u6682\u5B58\u4EC5\u652F\u6301\u65E0\u5F15\u7528\u3001\u65E0\u9644\u4EF6\u7684\u975E\u7A7A\u7EAF\u6587\u672C\u8349\u7A3F" });
1311
+ return;
1312
+ }
1313
+ if (this.state.drafts.length >= 3 && !replaceId) {
1314
+ this.patch({ notice: "\u5DF2\u6682\u5B58\u4E09\u6761\uFF0C\u8BF7\u5220\u9664\u4E00\u6761\u6216\u9009\u62E9\u66FF\u6362" });
1315
+ return;
1316
+ }
1317
+ const original = binding.input.draft, rev = binding.input.draftRev, clientGeneration = this.transport.generation();
1318
+ const stashEpoch = ++this.stashEpoch;
1319
+ const settingsEpoch = this.settingsEpoch, routeKey = this.routeKey(binding.route ?? this.state.status?.route);
1320
+ this.draftsEpoch++;
1321
+ this.cancelActive();
1322
+ this.clearTimer();
1323
+ this.patch({ busy: "stash", stashClearPending: true, stashClearCancelled: false, suggestion: null, notice: "" });
1324
+ try {
1325
+ await this.transport.call("drafts/save", { sessionId: this.sessionId, text: original, ...replaceId ? { replaceId } : {} }, this.lifecycle.signal);
1326
+ await this.refreshDrafts();
1327
+ const live = this.binding;
1328
+ if (stashEpoch !== this.stashEpoch) {
1329
+ this.patch({ notice: "\u8349\u7A3F\u5DF2\u4FDD\u5B58\uFF1B\u5DF2\u6309\u53D6\u6D88\u8981\u6C42\u4FDD\u7559\u5F53\u524D\u8F93\u5165" });
1330
+ return;
1331
+ }
1332
+ if (!this.settingsPending && settingsEpoch === this.settingsEpoch && this.state.settings.stashEnabled && this.routeKey(live?.route ?? this.state.status?.route) === routeKey && live?.input.draftRev === rev && live.input.draft === original && live.input.phase === "plain" && clientGeneration === this.transport.generation() && this.pureText()) {
1333
+ if (live.actions.insertText("", { start: 0, end: original.length, draftRev: rev })) {
1334
+ live.actions.persistDraft?.();
1335
+ this.suppressUntilEdit = "";
1336
+ this.patch({ notice: "\u8349\u7A3F\u5DF2\u6682\u5B58\u5E76\u6E05\u7A7A\uFF1B\u53EF\u64A4\u9500\u6E05\u7A7A" });
1337
+ return;
1338
+ }
1339
+ }
1340
+ this.patch({ notice: "\u5DF2\u4FDD\u5B58\u5FEB\u7167\uFF1B\u5F53\u524D\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u4FDD\u7559\u5F53\u524D\u8F93\u5165" });
1341
+ } catch (error) {
1342
+ this.patch({ notice: this.errorMessage(error) });
1343
+ } finally {
1344
+ this.patch({ busy: null, stashClearCancelled: false, stashClearPending: false });
1345
+ }
1346
+ }
1347
+ restoreDraft(draft, mode) {
1348
+ if (this.settingsPending) return false;
1349
+ const binding = this.binding;
1350
+ if (!this.state.settings.stashEnabled) {
1351
+ this.patch({ notice: "\u8349\u7A3F\u6682\u5B58\u5DF2\u5173\u95ED\uFF0C\u8BF7\u91CD\u65B0\u542F\u7528\u540E\u6062\u590D" });
1352
+ return false;
1353
+ }
1354
+ if (!this.state.connected || !this.state.status) {
1355
+ this.patch({ notice: "\u8FDE\u63A5\u5C1A\u672A\u5C31\u7EEA\uFF0C\u5DF2\u4FDD\u7559\u5F53\u524D\u8F93\u5165" });
1356
+ return false;
1357
+ }
1358
+ if (!binding || !this.pureText()) {
1359
+ this.patch({ notice: "\u8BF7\u5148\u79FB\u9664\u5F15\u7528\u548C\u9644\u4EF6\uFF0C\u518D\u6062\u590D\u7EAF\u6587\u672C\u8349\u7A3F" });
1360
+ return false;
1361
+ }
1362
+ this.cancelActive();
1363
+ this.clearTimer();
1364
+ const span = { start: mode === "replace" ? 0 : binding.input.draft.length, end: binding.input.draft.length, draftRev: binding.input.draftRev };
1365
+ const text = mode === "append" && binding.input.draft ? `
1366
+ ${draft.text}` : draft.text;
1367
+ const applied = binding.actions.insertText(text, span);
1368
+ if (applied) {
1369
+ binding.actions.persistDraft?.();
1370
+ this.suppressUntilEdit = mode === "replace" ? text : binding.input.draft + text;
1371
+ this.patch({ suggestion: null, notice: "\u8349\u7A3F\u5DF2\u6062\u590D\uFF0C\u5C1A\u672A\u53D1\u9001" });
1372
+ }
1373
+ return applied;
1374
+ }
1375
+ async deleteDraft(id) {
1376
+ await this.mutateDraft("drafts/delete", { sessionId: this.sessionId, id });
1377
+ }
1378
+ async clearDrafts() {
1379
+ await this.mutateDraft("drafts/clear", { sessionId: this.sessionId });
1380
+ }
1381
+ async mutateDraft(method, data) {
1382
+ if (this.state.busy || this.transport.generation() === null) return;
1383
+ if (this.undoExpiresAt === Infinity && this.state.deletedDrafts.length && this.state.pendingUndoDrafts.length) {
1384
+ this.patch({ notice: "\u4E24\u6279\u5F85\u6062\u590D\u5907\u4EFD\u5C1A\u672A\u5904\u7406\uFF0C\u8BF7\u5148\u590D\u5236\u6216\u653E\u5F03\u4E00\u6279\uFF0C\u518D\u5220\u9664\u65B0\u7684\u6682\u5B58" });
1385
+ return;
1386
+ }
1387
+ const generation = this.transport.generation();
1388
+ this.draftsEpoch++;
1389
+ this.patch({ busy: "stash" });
1390
+ try {
1391
+ const result = await this.transport.call(method, data, this.lifecycle.signal);
1392
+ if (generation !== this.transport.generation()) return;
1393
+ if (result.removed.length) {
1394
+ clearTimeout(this.undoTimer);
1395
+ this.patch({ drafts: result.drafts, deletedDrafts: result.removed, notice: "\u5DF2\u5220\u9664\u6682\u5B58\uFF0C10 \u79D2\u5185\u53EF\u64A4\u9500" });
1396
+ this.undoExpiresAt = Date.now() + 1e4;
1397
+ this.armUndoExpiry();
1398
+ } else this.patch({ drafts: result.drafts });
1399
+ } catch (error) {
1400
+ this.patch({ notice: this.errorMessage(error) });
1401
+ } finally {
1402
+ this.patch({ busy: null });
1403
+ }
1404
+ }
1405
+ async undoDeleteDrafts(target = "recent") {
1406
+ const deleted = target === "pending" ? this.state.pendingUndoDrafts : this.state.deletedDrafts;
1407
+ if (!deleted.length || this.state.busy || this.transport.generation() === null) return;
1408
+ const generation = this.transport.generation();
1409
+ this.draftsEpoch++;
1410
+ if (target === "recent") clearTimeout(this.undoTimer);
1411
+ this.patch({ busy: "stash" });
1412
+ try {
1413
+ const drafts = await this.transport.call("drafts/restore", { sessionId: this.sessionId, drafts: deleted }, this.lifecycle.signal);
1414
+ if (generation === this.transport.generation()) {
1415
+ if (target === "recent") this.undoExpiresAt = 0;
1416
+ this.patch({ drafts, ...target === "recent" ? { deletedDrafts: [] } : { pendingUndoDrafts: [] }, notice: "\u5DF2\u64A4\u9500\u5220\u9664" });
1417
+ }
1418
+ } catch (error) {
1419
+ if (generation === this.transport.generation()) {
1420
+ if (target === "recent" && !this.state.pendingUndoDrafts.length) {
1421
+ this.undoExpiresAt = 0;
1422
+ this.patch({ pendingUndoDrafts: deleted, deletedDrafts: [] });
1423
+ } else if (target === "recent") this.undoExpiresAt = Infinity;
1424
+ this.patch({ notice: `${this.errorMessage(error)}\uFF1B\u5F85\u6062\u590D\u6587\u5B57\u5DF2\u4FDD\u7559\uFF0C\u53EF\u91CD\u8BD5\u6216\u590D\u5236` });
1425
+ }
1426
+ } finally {
1427
+ this.patch({ busy: null });
1428
+ }
1429
+ }
1430
+ discardPendingUndo() {
1431
+ if (this.undoExpiresAt === Infinity) {
1432
+ this.undoExpiresAt = 0;
1433
+ this.patch({ pendingUndoDrafts: this.state.deletedDrafts, deletedDrafts: [], notice: "\u5DF2\u653E\u5F03\u9009\u5B9A\u6062\u590D\u5907\u4EFD\uFF0C\u5176\u4F59\u5907\u4EFD\u4ECD\u4FDD\u7559" });
1434
+ } else this.patch({ pendingUndoDrafts: [], notice: "\u5DF2\u653E\u5F03\u9009\u5B9A\u6062\u590D\u5907\u4EFD" });
1435
+ }
1436
+ retrySuggestion() {
1437
+ if (!this.eligibleForManual()) {
1438
+ this.patch({ notice: "\u8BF7\u5728\u53EF\u89C1\u3001\u5DF2\u8FDE\u63A5\u4E14\u7A7A\u95F2\u7684\u7EAF\u6587\u672C\u8F93\u5165\u6846\u4E2D\uFF0C\u5C06\u5149\u6807\u79FB\u5230\u672B\u5C3E\u540E\u751F\u6210\u5EFA\u8BAE" });
1439
+ return;
1440
+ }
1441
+ const binding = this.binding, span = binding.actions.captureInsertion();
1442
+ if (span.start !== span.end || span.end !== binding.input.draft.length || !focusedIn(binding.editor)) {
1443
+ this.patch({ notice: "\u8BF7\u5C06\u5149\u6807\u79FB\u81F3\u8349\u7A3F\u672B\u5C3E\u540E\u751F\u6210\u5EFA\u8BAE" });
1444
+ return;
1445
+ }
1446
+ const operation = binding.input.draft.length ? "complete" : "next";
1447
+ if (operation === "next" && !this.state.status?.sourceTurnId || operation === "complete" && !binding.input.draft.trim()) {
1448
+ this.patch({ notice: "\u8BF7\u5148\u8F93\u5165\u8349\u7A3F\uFF0C\u6216\u7B49\u5F85\u4E00\u8F6E\u56DE\u590D\u5B8C\u6210\u540E\u518D\u9884\u6D4B\u4E0B\u4E00\u53E5" });
1449
+ return;
1450
+ }
1451
+ this.clearTimer();
1452
+ this.cancelActive();
1453
+ this.suppressUntilEdit = null;
1454
+ this.failedCompletionKey = null;
1455
+ this.failedNextKey = null;
1456
+ this.completionCache.delete(this.completionKey());
1457
+ this.nextTurns.delete(this.nextKey());
1458
+ this.patch({ suggestion: null, notice: "" });
1459
+ void this.requestSuggestion(operation, true);
1460
+ }
1461
+ eligibleForManual() {
1462
+ return this.pureText() && document.visibilityState !== "hidden" && !this.composing && !this.settingsPending && !this.binding?.running && !this.state.status?.running && !this.state.busy && !this.state.preview && this.state.connected && charCount(this.binding?.input.draft ?? "") <= this.state.settings.maxDraftChars && !nativeMenuOpen(this.binding?.editor ?? null);
1463
+ }
1464
+ suggestionStatus(status, message) {
1465
+ if (this.state.suggestionStatus !== status || this.state.suggestionMessage !== message) this.patch({ suggestionStatus: status, suggestionMessage: message });
1466
+ }
1467
+ captureTarget(requireSelection) {
1468
+ const binding = this.binding;
1469
+ if (!binding || binding.input.phase !== "plain") {
1470
+ this.patch({ notice: "\u5F53\u524D\u8F93\u5165\u6846\u6682\u65F6\u4E0D\u53EF\u7F16\u8F91" });
1471
+ return null;
1472
+ }
1473
+ const span = binding.actions.captureInsertion();
1474
+ if (span.start !== span.end) {
1475
+ const selected = selectedPlainText(binding.editor, binding.input.draft, span, binding.input.occurrences);
1476
+ if (!selected) {
1477
+ this.patch({ notice: "\u8BF7\u9009\u62E9\u5B8C\u5168\u4F4D\u4E8E\u666E\u901A\u6587\u5B57\u4E2D\u7684\u7247\u6BB5\uFF0C\u907F\u514D\u8DE8\u8D8A\u5F15\u7528\u6807\u7B7E" });
1478
+ return null;
1479
+ }
1480
+ return { ...selected, span };
1481
+ }
1482
+ if (requireSelection) {
1483
+ this.patch({ notice: "\u8BF7\u5148\u5728\u8F93\u5165\u6846\u9009\u4E2D\u8981\u4FDD\u62A4\u7684\u6587\u5B57" });
1484
+ return null;
1485
+ }
1486
+ if (binding.input.occurrences.length) {
1487
+ this.patch({ notice: "\u542B\u5F15\u7528\u6807\u7B7E\u65F6\uFF0C\u8BF7\u9009\u62E9\u4E0D\u8DE8\u6807\u7B7E\u7684\u7EAF\u6587\u5B57\u7247\u6BB5\u4F18\u5316" });
1488
+ return null;
1489
+ }
1490
+ return { text: binding.input.draft, span: { start: 0, end: binding.input.draft.length, draftRev: binding.input.draftRev }, clipboardStart: 0, clipboardEnd: binding.input.draft.length };
1491
+ }
1492
+ pureText() {
1493
+ const input = this.binding?.input;
1494
+ return Boolean(input && input.phase === "plain" && !input.occurrences.length && !input.attachmentIds.length);
1495
+ }
1496
+ eligible() {
1497
+ return this.pureText() && document.visibilityState !== "hidden" && !this.composing && !this.settingsPending && !this.binding?.running && !this.state.status?.running && !this.state.busy && !this.state.preview && this.state.connected && this.suppressUntilEdit === null;
1498
+ }
1499
+ driveSuggestions() {
1500
+ const binding = this.binding, status = this.state.status;
1501
+ if (!binding || !status || !this.eligible() || this.active) {
1502
+ this.clearTimer();
1503
+ return;
1504
+ }
1505
+ if (nativeMenuOpen(binding.editor)) {
1506
+ this.clearTimer();
1507
+ return;
1508
+ }
1509
+ const span = binding.actions.captureInsertion();
1510
+ if (span.start !== span.end || span.end !== binding.input.draft.length || !focusedIn(binding.editor)) {
1511
+ this.clearTimer();
1512
+ return;
1513
+ }
1514
+ if (this.manualTimerKey === this.completionKey() && this.timer !== void 0) return;
1515
+ if (this.state.suggestion?.snapshot.manual && this.isCurrent(this.state.suggestion.snapshot, true)) return;
1516
+ const turn = status.sourceTurnId;
1517
+ const route = this.routeKey(binding.route ?? status.route);
1518
+ if (binding.input.draft === "") {
1519
+ this.clearTimer();
1520
+ if (!turn) return;
1521
+ const key = this.nextKey();
1522
+ if (!this.state.settings.nextEnabled) {
1523
+ if (["idle", "waiting", "paused"].includes(this.state.suggestionStatus)) this.suggestionStatus("paused", "\u81EA\u52A8\u9884\u6D4B\u5DF2\u5173\u95ED\uFF0C\u53EF\u624B\u52A8\u751F\u6210");
1524
+ return;
1525
+ }
1526
+ if (this.nextTurns.has(key) || key === this.failedNextKey) return;
1527
+ void this.requestSuggestion("next");
1528
+ return;
1529
+ }
1530
+ if (!this.state.settings.completionEnabled || charCount(binding.input.draft) > this.state.settings.maxDraftChars) {
1531
+ this.clearTimer();
1532
+ if (!this.state.settings.completionEnabled && !this.state.suggestion && ["idle", "waiting", "paused"].includes(this.state.suggestionStatus)) this.suggestionStatus("paused", "\u81EA\u52A8\u7EED\u5199\u5DF2\u5173\u95ED\uFF0C\u53EF\u624B\u52A8\u751F\u6210");
1533
+ return;
1534
+ }
1535
+ if (this.nextCache && this.nextCache.turn === turn && this.nextCache.route === route && this.nextCache.generation === status.generation && this.nextCache.text.startsWith(binding.input.draft)) {
1536
+ this.clearTimer();
1537
+ const suffix = this.nextCache.text.slice(binding.input.draft.length);
1538
+ const existing = this.state.suggestion;
1539
+ if (existing && existing.text === suffix && existing.kind === "complete" && this.isCurrent(existing.snapshot, true)) return;
1540
+ const snapshot = this.snapshot("complete", span);
1541
+ if (suffix && charCount(suffix) <= this.state.settings.maxCompletionChars && snapshot) this.patch({ suggestion: { text: suffix, snapshot, kind: "complete" } });
1542
+ return;
1543
+ }
1544
+ const cacheKey = this.completionKey();
1545
+ if (cacheKey === this.failedCompletionKey) {
1546
+ this.clearTimer();
1547
+ return;
1548
+ }
1549
+ if (this.completionCache.has(cacheKey)) {
1550
+ this.clearTimer();
1551
+ const text = this.completionCache.get(cacheKey);
1552
+ if (!text) {
1553
+ this.suggestionStatus("abstained", "\u6A21\u578B\u672C\u6B21\u672A\u7ED9\u51FA\u5EFA\u8BAE\uFF0C\u53EF\u624B\u52A8\u91CD\u8BD5");
1554
+ return;
1555
+ }
1556
+ const existing = this.state.suggestion;
1557
+ if (existing && existing.text === text && existing.kind === "complete" && this.isCurrent(existing.snapshot, true)) return;
1558
+ const snapshot = this.snapshot("complete", span);
1559
+ if (text && snapshot) this.patch({ suggestion: { text, snapshot, kind: "complete" } });
1560
+ return;
1561
+ }
1562
+ if (this.timer !== void 0 && this.timerKey === cacheKey) return;
1563
+ this.clearTimer();
1564
+ const delay = Math.max(this.state.settings.debounceMs, this.lastCompletionStart + this.state.settings.minIntervalMs - Date.now(), this.completionRetryAt - Date.now());
1565
+ this.timerKey = cacheKey;
1566
+ this.suggestionStatus(this.completionRetryAt > Date.now() ? "throttled" : "waiting", this.completionRetryAt > Date.now() ? "\u7B49\u5F85\u6700\u5C0F\u8BF7\u6C42\u95F4\u9694\u7ED3\u675F" : "\u7B49\u5F85\u8F93\u5165\u505C\u987F");
1567
+ this.timer = setTimeout(() => {
1568
+ this.timer = void 0;
1569
+ this.timerKey = null;
1570
+ void this.requestSuggestion("complete");
1571
+ }, delay);
1572
+ }
1573
+ async requestSuggestion(operation, manual = false, deferOnce = true) {
1574
+ const binding = this.binding;
1575
+ if (!binding || !this.eligible()) return;
1576
+ if (!manual && !(operation === "next" ? this.state.settings.nextEnabled : this.state.settings.completionEnabled)) return;
1577
+ const span = binding.actions.captureInsertion(), snapshot = this.snapshot(operation, span);
1578
+ if (!snapshot || span.start !== span.end || span.end !== binding.input.draft.length) return;
1579
+ if (manual) snapshot.manual = true;
1580
+ if (operation === "complete") this.lastCompletionStart = Date.now();
1581
+ const active = this.begin(snapshot), cacheKey = this.completionKey(), nextKey = this.nextKey();
1582
+ this.suggestionStatus("generating", manual ? "\u6B63\u5728\u751F\u6210\u672C\u6B21\u5EFA\u8BAE" : "\u6B63\u5728\u751F\u6210\u5EFA\u8BAE");
1583
+ let retry = false;
1584
+ try {
1585
+ const result = await this.transport.call("generate", snapshot, active.controller.signal);
1586
+ if (this.active !== active || !this.isCurrent(snapshot, true)) return;
1587
+ if (!this.validResult(result, snapshot)) {
1588
+ if (operation === "complete") this.failedCompletionKey = cacheKey;
1589
+ else this.failedNextKey = nextKey;
1590
+ this.suggestionStatus("failed", "\u5EFA\u8BAE\u8FD4\u56DE\u683C\u5F0F\u65E0\u6548\uFF0C\u53EF\u624B\u52A8\u91CD\u8BD5");
1591
+ return;
1592
+ }
1593
+ if (result.outcome === "throttled") {
1594
+ if (operation === "complete") {
1595
+ this.completionRetryAt = Date.now() + result.retryAfterMs;
1596
+ retry = !manual || deferOnce;
1597
+ this.suggestionStatus("throttled", "\u7B49\u5F85\u6700\u5C0F\u8BF7\u6C42\u95F4\u9694\u7ED3\u675F\uFF0C\u53EF\u7A0D\u540E\u91CD\u8BD5");
1598
+ }
1599
+ return;
1600
+ }
1601
+ if (operation === "complete") {
1602
+ this.completionCache.set(cacheKey, result.text);
1603
+ if (this.completionCache.size > 64) this.completionCache.delete(this.completionCache.keys().next().value);
1604
+ } else {
1605
+ if (this.nextTurns.size >= 512) this.nextTurns.clear();
1606
+ this.nextTurns.add(nextKey);
1607
+ }
1608
+ if (operation === "next" && result.text) this.nextCache = { text: result.text, turn: snapshot.sourceTurnId, route: snapshot.routeKey, generation: snapshot.connectionGeneration };
1609
+ if (result.text) this.patch({ suggestion: { text: result.text, snapshot, kind: operation }, suggestionStatus: "ready", suggestionMessage: "Tab \u91C7\u7EB3\uFF0CEsc \u5FFD\u7565" });
1610
+ else this.suggestionStatus("abstained", "\u6A21\u578B\u672C\u6B21\u672A\u7ED9\u51FA\u5EFA\u8BAE\uFF0C\u53EF\u624B\u52A8\u91CD\u8BD5");
1611
+ } catch {
1612
+ if (operation === "complete" && !active.controller.signal.aborted && this.active === active && this.isCurrent(snapshot, false)) {
1613
+ this.failedCompletionKey = cacheKey;
1614
+ }
1615
+ if (operation === "next" && !active.controller.signal.aborted && this.active === active && this.isCurrent(snapshot, false)) this.failedNextKey = nextKey;
1616
+ if (!active.controller.signal.aborted && this.active === active && this.isCurrent(snapshot, false)) this.suggestionStatus("failed", "\u5EFA\u8BAE\u8BF7\u6C42\u672A\u5B8C\u6210\uFF0C\u53EF\u624B\u52A8\u91CD\u8BD5");
1617
+ } finally {
1618
+ this.recordLatency(active);
1619
+ if (this.active === active) {
1620
+ this.active = null;
1621
+ if (retry && manual) {
1622
+ this.timerKey = cacheKey;
1623
+ this.manualTimerKey = cacheKey;
1624
+ this.timer = setTimeout(() => {
1625
+ this.timer = void 0;
1626
+ this.timerKey = null;
1627
+ this.manualTimerKey = null;
1628
+ void this.requestSuggestion(operation, true, false);
1629
+ }, Math.max(1, this.completionRetryAt - Date.now()));
1630
+ } else if (retry) this.driveSuggestions();
1631
+ }
1632
+ }
1633
+ }
1634
+ snapshot(operation, span, targetText) {
1635
+ const binding = this.binding, status = this.state.status;
1636
+ if (!binding || !status || !this.state.connected) return null;
1637
+ const route = binding.route ?? status.route;
1638
+ return {
1639
+ operation,
1640
+ sessionId: this.sessionId,
1641
+ requestId: `${this.requestPrefix}-${++this.sequence}`,
1642
+ sourceTurnId: status.sourceTurnId,
1643
+ draft: binding.input.draft,
1644
+ draftRev: binding.input.draftRev,
1645
+ span: { ...span },
1646
+ connectionGeneration: status.generation,
1647
+ routeKey: this.routeKey(route),
1648
+ ...targetText === void 0 ? {} : { targetText },
1649
+ ...this.state.locked.length ? { lockedFragments: this.state.locked.filter((text) => (targetText ?? binding.input.draft).includes(text)) } : {},
1650
+ ...route ? { provider: route.provider, model: route.model } : {}
1651
+ };
1652
+ }
1653
+ begin(snapshot) {
1654
+ this.cancelActive();
1655
+ const active = {
1656
+ snapshot,
1657
+ controller: new AbortController(),
1658
+ clientGeneration: this.transport.generation(),
1659
+ caret: this.binding.actions.captureInsertion(),
1660
+ startedAt: Date.now(),
1661
+ latencyRecorded: false
1662
+ };
1663
+ this.active = active;
1664
+ this.patch({ metrics: { ...this.state.metrics, calls: this.state.metrics.calls + 1 } });
1665
+ return active;
1666
+ }
1667
+ observeAcceptedEdit(draft) {
1668
+ const acceptance = this.acceptance;
1669
+ if (!acceptance) return;
1670
+ if (!draft) {
1671
+ this.acceptance = null;
1672
+ return;
1673
+ }
1674
+ if (draft === acceptance.expected && !acceptance.counted) return;
1675
+ let start = 0, end = 0;
1676
+ while (start < draft.length && start < acceptance.expected.length && draft[start] === acceptance.expected[start]) start++;
1677
+ while (end < draft.length - start && end < acceptance.expected.length - start && draft[draft.length - end - 1] === acceptance.expected[acceptance.expected.length - end - 1]) end++;
1678
+ const edits = draft.length + acceptance.expected.length - 2 * start - 2 * end;
1679
+ this.patch({ metrics: {
1680
+ ...this.state.metrics,
1681
+ editedAcceptances: this.state.metrics.editedAcceptances + (acceptance.counted ? 0 : 1),
1682
+ editedCharacters: this.state.metrics.editedCharacters + edits - acceptance.edits
1683
+ } });
1684
+ acceptance.counted = true;
1685
+ acceptance.edits = edits;
1686
+ }
1687
+ isCurrent(snapshot, checkCaret) {
1688
+ const binding = this.binding, status = this.state.status;
1689
+ return Boolean(binding && status && !this.disposed && this.transport.generation() === this.clientGeneration && this.transport.generation() !== null && binding.sessionId === snapshot.sessionId && binding.input.phase === "plain" && snapshotMatches(snapshot, {
1690
+ sessionId: binding.sessionId,
1691
+ sourceTurnId: status.sourceTurnId,
1692
+ draft: binding.input.draft,
1693
+ draftRev: binding.input.draftRev,
1694
+ span: binding.actions.captureInsertion(),
1695
+ connectionGeneration: status.generation,
1696
+ routeKey: this.routeKey(binding.route ?? status.route)
1697
+ }, checkCaret));
1698
+ }
1699
+ validResult(result, request) {
1700
+ return result.requestId === request.requestId && result.sessionId === request.sessionId && result.sourceTurnId === request.sourceTurnId && result.draftRev === request.draftRev && result.connectionGeneration === request.connectionGeneration && result.routeKey === request.routeKey && result.span.start === request.span.start && result.span.end === request.span.end && (result.text === null || typeof result.text === "string") && (result.outcome === void 0 || result.outcome === "suggestion" && typeof result.text === "string" || result.outcome === "abstained" && result.text === null || result.outcome === "throttled" && result.text === null && Number.isFinite(result.retryAfterMs) && result.retryAfterMs > 0 && result.retryAfterMs <= 3e4);
1701
+ }
1702
+ sameCaret(span) {
1703
+ const current = this.binding?.actions.captureInsertion();
1704
+ return Boolean(current && current.start === span.start && current.end === span.end && current.draftRev === span.draftRev);
1705
+ }
1706
+ routeKey(route) {
1707
+ return route ? JSON.stringify([route.provider, route.model]) : "";
1708
+ }
1709
+ nextKey() {
1710
+ const settings = this.state.settings;
1711
+ const route = settings.provider && settings.model ? { provider: settings.provider, model: settings.model } : this.binding?.route ?? this.state.status?.route;
1712
+ return JSON.stringify([this.state.status?.generation, this.state.status?.sourceTurnId, this.routeKey(route)]);
1713
+ }
1714
+ completionKey() {
1715
+ return JSON.stringify([this.state.status?.generation, this.state.status?.sourceTurnId, this.routeKey(this.binding?.route ?? this.state.status?.route), this.binding?.input.draft]);
1716
+ }
1717
+ invalidate(dropCache, cancelInFlight = true) {
1718
+ if (cancelInFlight) this.cancelActive();
1719
+ else if (this.active?.snapshot.operation !== "optimize") this.cancelActive();
1720
+ this.clearTimer();
1721
+ if (dropCache) {
1722
+ this.nextCache = null;
1723
+ this.completionCache.clear();
1724
+ this.failedCompletionKey = null;
1725
+ this.failedNextKey = null;
1726
+ this.completionRetryAt = 0;
1727
+ }
1728
+ this.patch({ suggestion: null, ...cancelInFlight ? { preview: null } : {} });
1729
+ }
1730
+ clearTimer() {
1731
+ clearTimeout(this.timer);
1732
+ this.timer = void 0;
1733
+ this.timerKey = null;
1734
+ this.manualTimerKey = null;
1735
+ }
1736
+ cancelActive() {
1737
+ const active = this.active;
1738
+ if (!active) return;
1739
+ this.active = null;
1740
+ this.recordLatency(active);
1741
+ active.controller.abort();
1742
+ void this.transport.call("cancel", { sessionId: this.sessionId, requestId: active.snapshot.requestId }).catch(() => {
1743
+ });
1744
+ if (active.snapshot.operation === "optimize") this.patch({ busy: null });
1745
+ else this.suggestionStatus("idle", "");
1746
+ }
1747
+ recordLatency(active) {
1748
+ if (active.latencyRecorded) return;
1749
+ active.latencyRecorded = true;
1750
+ this.patch({ metrics: { ...this.state.metrics, totalLatencyMs: this.state.metrics.totalLatencyMs + Math.max(0, Date.now() - active.startedAt) } });
1751
+ }
1752
+ errorMessage(error) {
1753
+ return error instanceof Error ? error.message : "\u64CD\u4F5C\u5931\u8D25\uFF0C\u539F\u7A3F\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
1754
+ }
1755
+ };
1756
+
1757
+ // src/browser/runtime.ts
1758
+ var BrowserRuntime = class {
1759
+ constructor(connection, models) {
1760
+ this.connection = connection;
1761
+ this.models = models;
1762
+ this.transport = {
1763
+ generation: () => {
1764
+ const generation = connection.generation.getSnapshot();
1765
+ return generation ? String(generation.id) : null;
1766
+ },
1767
+ call: async (method, data, signal) => {
1768
+ const result = await connection.rpc.call("/api", "input-assistant/call", { args: { payload: { method, data } } }, signal);
1769
+ if (!result.ok) throw new Error(result.error.message || `\u8F93\u5165\u52A9\u624B\u8BF7\u6C42\u5931\u8D25 (${result.error.code || "unknown"})`);
1770
+ return result.value;
1771
+ }
1772
+ };
1773
+ }
1774
+ entries = /* @__PURE__ */ new Map();
1775
+ disposed = false;
1776
+ lifetime = new AbortController();
1777
+ pendingSettings = 0;
1778
+ settingsQueue = Promise.resolve();
1779
+ metrics = /* @__PURE__ */ new Map();
1780
+ undo = /* @__PURE__ */ new Map();
1781
+ transport;
1782
+ controllerFor(sessionId) {
1783
+ let entry = this.entries.get(sessionId);
1784
+ if (!entry) {
1785
+ const saved = this.undo.get(sessionId);
1786
+ clearTimeout(saved?.timer);
1787
+ this.undo.delete(sessionId);
1788
+ entry = { controller: new InputAssistantController(sessionId, this.transport, this.metrics.get(sessionId), (update) => this.saveSettings(update), saved?.snapshot), refs: 0 };
1789
+ this.entries.set(sessionId, entry);
1790
+ if (this.pendingSettings) entry.controller.beginSettingsUpdate();
1791
+ }
1792
+ return entry.controller;
1793
+ }
1794
+ retain(sessionId) {
1795
+ const controller = this.controllerFor(sessionId), entry = this.entries.get(sessionId);
1796
+ entry.waitForMutation?.();
1797
+ entry.waitForMutation = void 0;
1798
+ entry.refs++;
1799
+ controller.start();
1800
+ return () => {
1801
+ entry.refs--;
1802
+ queueMicrotask(() => {
1803
+ if (entry.refs === 0 && this.entries.get(sessionId) === entry) {
1804
+ const release = () => {
1805
+ if (entry.refs !== 0 || this.entries.get(sessionId) !== entry) return;
1806
+ if (controller.getSnapshot().busy === "stash") {
1807
+ entry.waitForMutation ??= controller.subscribe(() => {
1808
+ if (controller.getSnapshot().busy !== "stash") {
1809
+ entry.waitForMutation?.();
1810
+ entry.waitForMutation = void 0;
1811
+ release();
1812
+ }
1813
+ });
1814
+ return;
1815
+ }
1816
+ this.metrics.set(sessionId, { ...controller.getSnapshot().metrics });
1817
+ this.saveUndo(sessionId, controller.getUndoSnapshot());
1818
+ controller.dispose();
1819
+ this.entries.delete(sessionId);
1820
+ };
1821
+ release();
1822
+ }
1823
+ });
1824
+ };
1825
+ }
1826
+ saveUndo(sessionId, snapshot) {
1827
+ clearTimeout(this.undo.get(sessionId)?.timer);
1828
+ this.undo.delete(sessionId);
1829
+ if (!snapshot.recent.length && !snapshot.pending.length) return;
1830
+ const record = { snapshot };
1831
+ if (snapshot.recent.length && Number.isFinite(snapshot.expiresAt)) {
1832
+ record.timer = setTimeout(() => {
1833
+ if (this.undo.get(sessionId) !== record) return;
1834
+ record.snapshot = { ...record.snapshot, recent: [], expiresAt: 0 };
1835
+ if (!record.snapshot.pending.length) this.undo.delete(sessionId);
1836
+ }, Math.max(0, snapshot.expiresAt - Date.now()));
1837
+ }
1838
+ this.undo.set(sessionId, record);
1839
+ }
1840
+ applySettings(settings) {
1841
+ for (const entry of this.entries.values()) entry.controller.applySettings(settings);
1842
+ }
1843
+ async saveSettings(update) {
1844
+ if (this.disposed) throw new Error("\u8F93\u5165\u52A9\u624B\u5DF2\u5378\u8F7D");
1845
+ const generation = this.transport.generation();
1846
+ if (generation === null) throw new Error("\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u91CD\u8FDE\u540E\u4FDD\u5B58");
1847
+ if (this.pendingSettings++ === 0) for (const entry of this.entries.values()) entry.controller.beginSettingsUpdate();
1848
+ const task = this.settingsQueue.then(async () => {
1849
+ if (this.disposed) throw new Error("\u8F93\u5165\u52A9\u624B\u5DF2\u5378\u8F7D");
1850
+ if (generation !== this.transport.generation()) throw new Error("\u8FDE\u63A5\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u52A0\u8F7D\u8BBE\u7F6E");
1851
+ const settings = await this.transport.call("settings/set", update, this.lifetime.signal);
1852
+ if (this.disposed || generation !== this.transport.generation()) throw new Error("\u8FDE\u63A5\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u52A0\u8F7D\u8BBE\u7F6E");
1853
+ this.applySettings(settings);
1854
+ return settings;
1855
+ });
1856
+ this.settingsQueue = task.then(() => {
1857
+ }, () => {
1858
+ });
1859
+ try {
1860
+ return await task;
1861
+ } finally {
1862
+ if (--this.pendingSettings === 0) for (const entry of this.entries.values()) entry.controller.endSettingsUpdate();
1863
+ }
1864
+ }
1865
+ dispose() {
1866
+ if (this.disposed) return;
1867
+ this.disposed = true;
1868
+ this.lifetime.abort();
1869
+ for (const entry of this.entries.values()) {
1870
+ entry.waitForMutation?.();
1871
+ entry.controller.dispose();
1872
+ }
1873
+ this.entries.clear();
1874
+ this.metrics.clear();
1875
+ for (const record of this.undo.values()) clearTimeout(record.timer);
1876
+ this.undo.clear();
1877
+ }
1878
+ };
1879
+
1880
+ // src/browser/styles.ts
1881
+ var STYLE_TEXT = `
1882
+ .dsh-ia {
1883
+ --dsh-ia-fallback-bg: #ffffff;
1884
+ --dsh-ia-fallback-fg: #111827;
1885
+ --dsh-ia-fallback-muted: #4b5563;
1886
+ --dsh-ia-fallback-border: #d1d5db;
1887
+ --dsh-ia-accent: var(--dsw-alias-state-business-primary, #3b82f6);
1888
+ --dsh-ia-accent-subtle: rgba(59, 130, 246, 0.1);
1889
+ --dsh-ia-pill-bg: var(--dsw-alias-interactive-bg-subtle, rgba(0, 0, 0, 0.04));
1890
+ --dsh-ia-pill-hover: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.08));
1891
+ font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
1892
+ color: var(--dsw-alias-label-primary, var(--dsh-ia-fallback-fg));
1893
+ font-size: 13px;
1894
+ line-height: 1.5;
1895
+ box-sizing: border-box;
1896
+ color-scheme: light;
1897
+ }
1898
+
1899
+ body[data-ds-dark-theme] .dsh-ia {
1900
+ --dsh-ia-fallback-bg: #1f2329;
1901
+ --dsh-ia-fallback-fg: #f3f4f6;
1902
+ --dsh-ia-fallback-muted: #9ca3af;
1903
+ --dsh-ia-fallback-border: #374151;
1904
+ --dsh-ia-pill-bg: var(--dsw-alias-interactive-bg-subtle, rgba(255, 255, 255, 0.06));
1905
+ --dsh-ia-pill-hover: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.12));
1906
+ color-scheme: dark;
1907
+ }
1908
+
1909
+ .dsh-ia * { box-sizing: border-box; }
1910
+ .dsh-ia button, .dsh-ia input, .dsh-ia select { font: inherit; color: inherit; }
1911
+
1912
+ /* Global buttons within dsh-ia */
1913
+ .dsh-ia button {
1914
+ border: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
1915
+ border-radius: 7px;
1916
+ padding: 5px 11px;
1917
+ background: var(--dsw-alias-bg-layer-1, transparent);
1918
+ color: var(--dsw-alias-label-primary, inherit);
1919
+ cursor: pointer;
1920
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, opacity 0.15s ease, transform 0.15s ease;
1921
+ display: inline-flex;
1922
+ align-items: center;
1923
+ justify-content: center;
1924
+ gap: 5px;
1925
+ }
1926
+ .dsh-ia button:hover:not(:disabled) {
1927
+ background: var(--dsw-alias-interactive-bg-hover, #8882);
1928
+ border-color: var(--dsw-alias-state-business-primary, var(--dsh-ia-accent));
1929
+ }
1930
+ .dsh-ia button:active:not(:disabled) {
1931
+ transform: translateY(1px);
1932
+ }
1933
+ .dsh-ia button:disabled {
1934
+ opacity: 0.45;
1935
+ cursor: default;
1936
+ transform: none;
1937
+ }
1938
+ .dsh-ia button:focus-visible, .dsh-ia input:focus-visible, .dsh-ia select:focus-visible {
1939
+ outline: 2px solid var(--dsw-alias-state-business-primary, #3b82f6);
1940
+ outline-offset: 2px;
1941
+ }
1942
+
1943
+ /* Smart Pill Trigger Group in Input Card */
1944
+ .dsh-ia-root {
1945
+ display: inline-flex;
1946
+ align-items: center;
1947
+ background: var(--dsh-ia-pill-bg);
1948
+ border: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
1949
+ border-radius: 8px;
1950
+ padding: 2px;
1951
+ gap: 2px;
1952
+ position: relative;
1953
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
1954
+ }
1955
+ .dsh-ia-root .dsh-ia-trigger {
1956
+ border: none;
1957
+ background: transparent;
1958
+ padding: 4px 10px;
1959
+ font-weight: 500;
1960
+ border-radius: 6px;
1961
+ white-space: nowrap;
1962
+ }
1963
+ .dsh-ia-root .dsh-ia-trigger:hover:not(:disabled) {
1964
+ background: var(--dsh-ia-pill-hover);
1965
+ }
1966
+ .dsh-ia-root .dsh-ia-menu-btn {
1967
+ border: none;
1968
+ background: transparent;
1969
+ padding: 4px 6px;
1970
+ border-radius: 6px;
1971
+ min-width: 24px;
1972
+ }
1973
+ .dsh-ia-root .dsh-ia-menu-btn:hover:not(:disabled) {
1974
+ background: var(--dsh-ia-pill-hover);
1975
+ }
1976
+ .dsh-ia-root .dsh-ia-cancel-btn {
1977
+ border: none;
1978
+ background: rgba(239, 68, 68, 0.12);
1979
+ color: #ef4444;
1980
+ font-size: 11px;
1981
+ padding: 4px 8px;
1982
+ border-radius: 6px;
1983
+ }
1984
+ .dsh-ia-root .dsh-ia-cancel-btn:hover {
1985
+ background: rgba(239, 68, 68, 0.2);
1986
+ }
1987
+
1988
+ /* Floating Menu Panel */
1989
+ .dsh-ia-menu {
1990
+ position: fixed;
1991
+ width: min(480px, calc(100vw - 24px));
1992
+ max-height: calc(100dvh - 24px);
1993
+ overflow-y: auto;
1994
+ overscroll-behavior: contain;
1995
+ padding: 16px;
1996
+ border: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
1997
+ border-radius: 14px;
1998
+ background: var(--dsw-alias-bg-layer-2, var(--dsh-ia-fallback-bg));
1999
+ box-shadow: 0 12px 36px rgba(0, 0, 0, 0.22);
2000
+ z-index: 1000;
2001
+ display: flex;
2002
+ flex-direction: column;
2003
+ gap: 12px;
2004
+ }
2005
+ .dsh-ia-preview {
2006
+ z-index: 1001;
2007
+ width: min(580px, calc(100vw - 24px));
2008
+ }
2009
+ .dsh-ia-preview .dsh-ia-pre {
2010
+ margin-top: 10px;
2011
+ max-height: 360px;
2012
+ }
2013
+
2014
+ /* Form Controls & Fields */
2015
+ .dsh-ia-row {
2016
+ display: flex;
2017
+ align-items: center;
2018
+ gap: 10px;
2019
+ flex-wrap: wrap;
2020
+ margin: 6px 0;
2021
+ }
2022
+ .dsh-ia-row label {
2023
+ display: inline-flex;
2024
+ align-items: center;
2025
+ gap: 6px;
2026
+ cursor: pointer;
2027
+ user-select: none;
2028
+ }
2029
+ .dsh-ia-field {
2030
+ display: flex;
2031
+ flex-direction: column;
2032
+ gap: 5px;
2033
+ margin: 6px 0;
2034
+ }
2035
+ .dsh-ia-field input, .dsh-ia select {
2036
+ border: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
2037
+ border-radius: 7px;
2038
+ background: var(--dsw-alias-bg-layer-1, var(--dsh-ia-fallback-bg));
2039
+ padding: 7px 10px;
2040
+ color: var(--dsw-alias-label-primary, inherit);
2041
+ transition: border-color 0.15s ease;
2042
+ }
2043
+ .dsh-ia-field input:focus, .dsh-ia select:focus {
2044
+ border-color: var(--dsw-alias-state-business-primary, var(--dsh-ia-accent));
2045
+ }
2046
+ .dsh-ia-field input { width: 100%; }
2047
+ .dsh-ia-muted {
2048
+ color: var(--dsw-alias-label-secondary, var(--dsh-ia-fallback-muted));
2049
+ font-size: 12px;
2050
+ line-height: 1.45;
2051
+ }
2052
+
2053
+ /* Sections */
2054
+ .dsh-ia-section {
2055
+ border-top: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
2056
+ margin-top: 8px;
2057
+ padding-top: 10px;
2058
+ }
2059
+ .dsh-ia-section h4 {
2060
+ font-size: 13px;
2061
+ margin: 0 0 8px;
2062
+ font-weight: 600;
2063
+ }
2064
+
2065
+ /* Notices & Alerts */
2066
+ .dsh-ia-notice {
2067
+ position: fixed;
2068
+ max-width: min(360px, calc(100vw - 32px));
2069
+ overflow: auto;
2070
+ padding: 6px 12px;
2071
+ background: var(--dsw-alias-bg-layer-2, var(--dsh-ia-fallback-bg));
2072
+ border: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
2073
+ border-radius: 20px;
2074
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14);
2075
+ z-index: 1002;
2076
+ display: flex;
2077
+ align-items: center;
2078
+ gap: 8px;
2079
+ font-size: 12px;
2080
+ animation: dshIaFadeIn 0.2s ease-out;
2081
+ }
2082
+ @keyframes dshIaFadeIn {
2083
+ from { opacity: 0; transform: translateY(4px); }
2084
+ to { opacity: 1; transform: translateY(0); }
2085
+ }
2086
+ .dsh-ia-menu .dsh-ia-notice {
2087
+ position: static;
2088
+ max-width: 100%;
2089
+ margin: 6px 0;
2090
+ box-shadow: none;
2091
+ }
2092
+ .dsh-ia-notice button { margin-left: 6px; padding: 3px 8px; font-size: 11px; }
2093
+
2094
+ /* Diff / Pre formatting */
2095
+ .dsh-ia-pre {
2096
+ white-space: pre-wrap;
2097
+ overflow-wrap: anywhere;
2098
+ font: inherit;
2099
+ font-size: 12px;
2100
+ max-height: 200px;
2101
+ overflow-y: auto;
2102
+ padding: 10px;
2103
+ border: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
2104
+ border-radius: 8px;
2105
+ background: var(--dsw-alias-bg-module-platform, rgba(128, 128, 128, 0.08));
2106
+ }
2107
+ .dsh-ia-diff ins {
2108
+ background: rgba(39, 163, 91, 0.22);
2109
+ color: #107c41;
2110
+ text-decoration: none;
2111
+ border-radius: 2px;
2112
+ padding: 0 2px;
2113
+ }
2114
+ body[data-ds-dark-theme] .dsh-ia-diff ins {
2115
+ color: #4ade80;
2116
+ background: rgba(34, 197, 94, 0.22);
2117
+ }
2118
+ .dsh-ia-diff del {
2119
+ background: rgba(217, 69, 69, 0.22);
2120
+ color: #c42b1c;
2121
+ text-decoration: line-through;
2122
+ border-radius: 2px;
2123
+ padding: 0 2px;
2124
+ }
2125
+ body[data-ds-dark-theme] .dsh-ia-diff del {
2126
+ color: #f87171;
2127
+ background: rgba(239, 68, 68, 0.22);
2128
+ }
2129
+
2130
+ /* Draft Card */
2131
+ .dsh-ia-draft {
2132
+ border: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
2133
+ border-radius: 9px;
2134
+ padding: 9px 12px;
2135
+ margin: 8px 0;
2136
+ background: var(--dsw-alias-bg-layer-1, transparent);
2137
+ transition: border-color 0.15s ease;
2138
+ }
2139
+ .dsh-ia-draft summary {
2140
+ cursor: pointer;
2141
+ overflow-wrap: anywhere;
2142
+ font-weight: 500;
2143
+ }
2144
+ .dsh-ia-draft time {
2145
+ display: block;
2146
+ font-size: 11px;
2147
+ color: var(--dsw-alias-label-tertiary, var(--dsh-ia-fallback-muted));
2148
+ margin: 3px 0 6px;
2149
+ }
2150
+
2151
+ /* Candidate Panel & Ghost Text */
2152
+ .dsh-ia-candidate {
2153
+ position: fixed;
2154
+ display: flex;
2155
+ align-items: center;
2156
+ gap: 9px;
2157
+ padding: 10px 14px;
2158
+ max-width: min(640px, calc(100vw - 32px));
2159
+ max-height: 240px;
2160
+ overflow-y: auto;
2161
+ border: 1px solid var(--dsw-alias-border-l2, var(--dsh-ia-fallback-border));
2162
+ border-radius: 11px;
2163
+ background: var(--dsw-alias-bg-layer-2, var(--dsh-ia-fallback-bg));
2164
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.2);
2165
+ pointer-events: auto;
2166
+ z-index: 998;
2167
+ }
2168
+ .dsh-ia-candidate-text {
2169
+ flex: 1;
2170
+ white-space: pre-wrap;
2171
+ overflow-wrap: anywhere;
2172
+ font-size: 13px;
2173
+ line-height: 1.5;
2174
+ }
2175
+ .dsh-ia-ghost {
2176
+ position: fixed;
2177
+ white-space: pre-wrap;
2178
+ word-break: break-word;
2179
+ overflow-wrap: break-word;
2180
+ overflow: hidden;
2181
+ max-height: 100%;
2182
+ pointer-events: none;
2183
+ user-select: none;
2184
+ color: var(--dsw-alias-label-tertiary, #76808d);
2185
+ opacity: 0.88;
2186
+ z-index: 34;
2187
+ }
2188
+ [data-composer-card]:has([data-dsh-ia-empty-ghost]) [data-composer-placeholder] {
2189
+ visibility: hidden;
2190
+ }
2191
+
2192
+ /* Settings Form page */
2193
+ .dsh-ia-settings { width: 100%; max-width: 760px; }
2194
+ .dsh-ia-settings h2 { font-size: 18px; margin: 0 0 8px; }
2195
+ .dsh-ia-settings h3 { font-size: 14px; margin: 0 0 10px; font-weight: 600; }
2196
+ .dsh-ia-settings fieldset { padding: 0; margin: 0; border: 0; min-width: 0; }
2197
+ .dsh-ia-settings-toggles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }
2198
+ .dsh-ia-settings-toggles label { display: flex; align-items: center; gap: 7px; cursor: pointer; }
2199
+ .dsh-ia-sr-only {
2200
+ position: absolute;
2201
+ width: 1px;
2202
+ height: 1px;
2203
+ padding: 0;
2204
+ margin: -1px;
2205
+ overflow: hidden;
2206
+ clip-path: inset(50%);
2207
+ white-space: nowrap;
2208
+ border: 0;
2209
+ }
2210
+
2211
+ .dsh-ia-preview .dsh-ia-pre { max-height: 320px; }
2212
+ .dsh-ia-notice { flex-wrap: wrap; border-radius: 12px; }
2213
+ .dsh-ia-route, .dsh-ia-suggestion-state { overflow-wrap: anywhere; }
2214
+ .dsh-ia-suggestion-state { margin: 6px 0; }
2215
+ .dsh-ia-inline-status { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--dsw-alias-label-secondary,var(--dsh-ia-fallback-muted)); }
2216
+ .dsh-ia summary:focus-visible { outline: 2px solid var(--dsh-ia-accent); outline-offset: 2px; }
2217
+ .dsh-ia-settings-presets { display: grid; grid-template-columns: repeat(auto-fit,minmax(180px,1fr)); gap: 10px; }
2218
+ .dsh-ia .dsh-ia-preset { display: flex; flex-direction: column; align-items: flex-start; text-align: left; padding: 12px; gap: 5px; }
2219
+ .dsh-ia .dsh-ia-preset[aria-pressed=true] { border-color: var(--dsh-ia-accent); background: var(--dsh-ia-accent-subtle); }
2220
+ .dsh-ia-setting-grid { display: grid; grid-template-columns: repeat(auto-fit,minmax(min(280px,100%),1fr)); gap: 14px; margin-top: 12px; }
2221
+ .dsh-ia-number-control { display: flex; align-items: center; gap: 10px; }
2222
+ .dsh-ia-number-control input[type=number] { width: 100px; flex: 0 0 auto; }
2223
+ .dsh-ia-number-control input[type=range] { min-width: 0; flex: 1; accent-color: var(--dsh-ia-accent); }
2224
+ .dsh-ia-setting-heading { display: flex; justify-content: space-between; gap: 8px; align-items: center; }
2225
+ .dsh-ia-setting-heading label { font-weight: 500; }
2226
+ @media (prefers-reduced-motion: reduce) { .dsh-ia *, .dsh-ia-notice { transition: none !important; animation: none !important; } }
2227
+ `;
2228
+
2229
+ // src/browser/settings.tsx
2230
+ var import_react2 = require("react");
2231
+
2232
+ // src/core/config.ts
2233
+ var numeric = {
2234
+ debounceMs: [200, 5e3],
2235
+ minIntervalMs: [500, 3e4],
2236
+ maxHistoryChars: [1e3, 64e3],
2237
+ maxDraftChars: [100, 32e3],
2238
+ maxNextChars: [20, 1e3],
2239
+ maxCompletionChars: [10, 500],
2240
+ optimizeTimeoutMs: [1e3, 12e4],
2241
+ suggestionTimeoutMs: [1e3, 6e4]
2242
+ };
2243
+ var booleans = ["optimizeEnabled", "stashEnabled", "nextEnabled", "completionEnabled"];
2244
+ var strings = ["provider", "model"];
2245
+ function validate(config) {
2246
+ for (const key of booleans) {
2247
+ if (typeof config[key] !== "boolean") throw new AssistantError("CONFIG", `${key} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
2248
+ }
2249
+ if (!["ghost", "panel"].includes(config.displayMode)) throw new AssistantError("CONFIG", "\u663E\u793A\u65B9\u5F0F\u5FC5\u987B\u662F ghost \u6216 panel");
2250
+ if (!["light", "standard", "deep"].includes(config.optimizationStrength)) throw new AssistantError("CONFIG", "\u4F18\u5316\u5F3A\u5EA6\u5FC5\u987B\u662F light\u3001standard \u6216 deep");
2251
+ if (!["concise", "standard", "detailed"].includes(config.predictionDetail)) throw new AssistantError("CONFIG", "\u9884\u6D4B\u8BE6\u7565\u5EA6\u5FC5\u987B\u662F concise\u3001standard \u6216 detailed");
2252
+ for (const [key, [min, max]] of Object.entries(numeric)) {
2253
+ const value = config[key];
2254
+ if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
2255
+ throw new AssistantError("CONFIG", `${key} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
2256
+ }
2257
+ }
2258
+ for (const key of strings) {
2259
+ if (typeof config[key] !== "string" || config[key].length > 200) throw new AssistantError("CONFIG", `${key} \u65E0\u6548`);
2260
+ config[key] = config[key].trim();
2261
+ }
2262
+ if (Boolean(config.provider) !== Boolean(config.model)) throw new AssistantError("CONFIG", "\u56FA\u5B9A\u8F85\u52A9\u6A21\u578B\u9700\u540C\u65F6\u586B\u5199\u63D0\u4F9B\u5546\u548C\u6A21\u578B\uFF1B\u7559\u7A7A\u5219\u8DDF\u968F\u8F93\u5165\u6846");
2263
+ return config;
2264
+ }
2265
+ function resolveConfig(input = {}) {
2266
+ for (const key of Object.keys(input)) if (!(key in DEFAULT_CONFIG)) throw new AssistantError("CONFIG", `\u672A\u77E5\u914D\u7F6E\uFF1A${key}`);
2267
+ return validate({ ...DEFAULT_CONFIG, ...input });
2268
+ }
2269
+
2270
+ // src/browser/settings.tsx
2271
+ var import_jsx_runtime2 = require("react/jsx-runtime");
2272
+ var NUMBERS = [
2273
+ ["debounceMs", "\u7EED\u5199\u505C\u987F", "\u6BEB\u79D2", 200, 5e3, 50],
2274
+ ["minIntervalMs", "\u7EED\u5199\u8BF7\u6C42\u6700\u5C0F\u95F4\u9694", "\u6BEB\u79D2", 500, 3e4, 100],
2275
+ ["maxHistoryChars", "\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u4E0A\u9650", "\u5B57\u7B26", 1e3, 64e3, 500],
2276
+ ["maxDraftChars", "\u5F85\u5904\u7406\u8349\u7A3F\u4E0A\u9650", "\u5B57\u7B26", 100, 32e3, 100],
2277
+ ["maxNextChars", "\u4E0B\u4E00\u53E5\u957F\u5EA6\u4E0A\u9650", "\u5B57\u7B26", 20, 1e3, 10],
2278
+ ["maxCompletionChars", "\u7EED\u5199\u957F\u5EA6\u4E0A\u9650", "\u5B57\u7B26", 10, 500, 5],
2279
+ ["optimizeTimeoutMs", "\u4F18\u5316\u8D85\u65F6", "\u6BEB\u79D2", 1e3, 12e4, 1e3],
2280
+ ["suggestionTimeoutMs", "\u81EA\u52A8\u5EFA\u8BAE\u8D85\u65F6", "\u6BEB\u79D2", 1e3, 6e4, 500]
2281
+ ];
2282
+ var PRESETS = [
2283
+ { name: "\u654F\u6377\u6A21\u5F0F", description: "\u66F4\u77ED\u505C\u987F\u4E0E\u7CBE\u7B80\u9884\u6D4B\uFF0C\u9002\u5408\u5FEB\u901F\u8F93\u5165\u3002", values: {
2284
+ debounceMs: 300,
2285
+ minIntervalMs: 1e3,
2286
+ optimizationStrength: "light",
2287
+ predictionDetail: "concise",
2288
+ maxNextChars: 60,
2289
+ maxCompletionChars: 40
2290
+ } },
2291
+ { name: "\u6807\u51C6\u5E73\u8861", description: "\u5E73\u8861\u54CD\u5E94\u65F6\u673A\u4E0E\u6A21\u578B\u7528\u91CF\uFF0C\u9002\u5408\u65E5\u5E38\u5BF9\u8BDD\u3002", values: {
2292
+ debounceMs: 600,
2293
+ minIntervalMs: 2e3,
2294
+ optimizationStrength: "standard",
2295
+ predictionDetail: "standard",
2296
+ maxNextChars: 120,
2297
+ maxCompletionChars: 80
2298
+ } },
2299
+ { name: "\u8282\u6D41\u7701\u6D41", description: "\u5EF6\u957F\u505C\u987F\u4E0E\u8BF7\u6C42\u95F4\u9694\uFF0C\u51CF\u5C11\u81EA\u52A8\u8BF7\u6C42\u3002", values: {
2300
+ debounceMs: 1500,
2301
+ minIntervalMs: 5e3,
2302
+ optimizationStrength: "standard",
2303
+ predictionDetail: "concise",
2304
+ maxNextChars: 80,
2305
+ maxCompletionChars: 40
2306
+ } }
2307
+ ];
2308
+ var formatValue = (value, unit) => Number.isNaN(value) ? "\u672A\u586B\u5199" : unit === "\u6BEB\u79D2" && value >= 1e3 ? `\u7EA6 ${Number((value / 1e3).toFixed(1))} \u79D2` : `${value} ${unit}`;
2309
+ function AssistantSettings({ runtime }) {
2310
+ const fieldsId = (0, import_react2.useId)();
2311
+ const generation = (0, import_react2.useSyncExternalStore)(
2312
+ runtime.connection.generation.subscribe.bind(runtime.connection.generation),
2313
+ () => runtime.transport.generation(),
2314
+ () => null
2315
+ );
2316
+ const [accepted, setAccepted] = (0, import_react2.useState)(null);
2317
+ const [form, setForm] = (0, import_react2.useState)({ ...DEFAULT_CONFIG });
2318
+ const [loading, setLoading] = (0, import_react2.useState)(true);
2319
+ const [saving, setSaving] = (0, import_react2.useState)(false);
2320
+ const [message, setMessage] = (0, import_react2.useState)("");
2321
+ const [error, setError] = (0, import_react2.useState)("");
2322
+ const [loadFailed, setLoadFailed] = (0, import_react2.useState)(false);
2323
+ const [reload, setReload] = (0, import_react2.useState)(0);
2324
+ const [catalog, setCatalog] = (0, import_react2.useState)(null);
2325
+ const [catalogLoading, setCatalogLoading] = (0, import_react2.useState)(false);
2326
+ const [catalogError, setCatalogError] = (0, import_react2.useState)("");
2327
+ const [catalogReload, setCatalogReload] = (0, import_react2.useState)(0);
2328
+ (0, import_react2.useEffect)(() => {
2329
+ const abort = new AbortController();
2330
+ setLoading(true);
2331
+ setLoadFailed(false);
2332
+ setAccepted(null);
2333
+ setError("");
2334
+ setMessage("");
2335
+ if (generation === null) {
2336
+ setLoading(false);
2337
+ setLoadFailed(true);
2338
+ setError("\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u91CD\u8FDE\u540E\u52A0\u8F7D\u8BBE\u7F6E");
2339
+ return () => abort.abort();
2340
+ }
2341
+ void runtime.transport.call("settings/get", {}, abort.signal).then((settings) => {
2342
+ if (abort.signal.aborted) return;
2343
+ const normalized = resolveConfig(settings);
2344
+ setAccepted(normalized);
2345
+ setForm(normalized);
2346
+ setLoading(false);
2347
+ }).catch((reason) => {
2348
+ if (!abort.signal.aborted) {
2349
+ setLoading(false);
2350
+ setLoadFailed(true);
2351
+ setError(reason instanceof Error ? reason.message : "\u8BBE\u7F6E\u52A0\u8F7D\u5931\u8D25");
2352
+ }
2353
+ });
2354
+ return () => abort.abort();
2355
+ }, [runtime, generation, reload]);
2356
+ (0, import_react2.useEffect)(() => {
2357
+ const abort = new AbortController();
2358
+ setCatalog(null);
2359
+ setCatalogError("");
2360
+ if (generation === null) {
2361
+ setCatalogLoading(false);
2362
+ return () => abort.abort();
2363
+ }
2364
+ setCatalogLoading(true);
2365
+ void runtime.transport.call("models/list", {}, abort.signal).then((value) => {
2366
+ if (!abort.signal.aborted) {
2367
+ setCatalog(value);
2368
+ setCatalogLoading(false);
2369
+ }
2370
+ }).catch((reason) => {
2371
+ if (!abort.signal.aborted) {
2372
+ setCatalogLoading(false);
2373
+ setCatalogError(reason instanceof Error ? reason.message : "\u6A21\u578B\u76EE\u5F55\u52A0\u8F7D\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u6216\u624B\u52A8\u586B\u5199");
2374
+ }
2375
+ });
2376
+ return () => abort.abort();
2377
+ }, [runtime, generation, catalogReload]);
2378
+ const change = (key, value) => {
2379
+ setForm((previous) => ({ ...previous, [key]: value }));
2380
+ setMessage("");
2381
+ setError("");
2382
+ };
2383
+ const save = async (event) => {
2384
+ event.preventDefault();
2385
+ if (!accepted || saving || generation === null) return;
2386
+ setError("");
2387
+ setMessage("");
2388
+ let draft;
2389
+ try {
2390
+ draft = resolveConfig(form);
2391
+ } catch (reason) {
2392
+ setError(reason instanceof Error ? reason.message : "\u8BBE\u7F6E\u65E0\u6548");
2393
+ return;
2394
+ }
2395
+ setSaving(true);
2396
+ try {
2397
+ const patch = Object.fromEntries(Object.entries(draft).filter(([key, value]) => value !== accepted[key]));
2398
+ const settings = await runtime.saveSettings(patch);
2399
+ if (runtime.transport.generation() !== generation) return;
2400
+ setAccepted(settings);
2401
+ setForm(settings);
2402
+ setMessage("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548");
2403
+ } catch (reason) {
2404
+ setError(`\u4FDD\u5B58\u672A\u786E\u8BA4\uFF1A${reason instanceof Error ? reason.message : "\u8BF7\u91CD\u8BD5"}\uFF1B\u8BF7\u91CD\u65B0\u6253\u5F00\u8BBE\u7F6E\u6838\u5BF9`);
2405
+ } finally {
2406
+ setSaving(false);
2407
+ }
2408
+ };
2409
+ const changed = accepted !== null && JSON.stringify(form) !== JSON.stringify(accepted);
2410
+ const selection = form.provider || form.model ? JSON.stringify([form.provider, form.model]) : "";
2411
+ const available = catalog?.groups.some((group) => group.provider === form.provider && group.models.some((model) => model.id === form.model)) ?? false;
2412
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { className: "dsh-ia dsh-ia-settings", "aria-label": "\u8F93\u5165\u52A9\u624B\u8BBE\u7F6E", onSubmit: (event) => {
2413
+ void save(event);
2414
+ }, children: [
2415
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { children: "\u8F93\u5165\u52A9\u624B" }),
2416
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u4EE5\u4E0B\u4E3A\u5168\u5C40\u8BBE\u7F6E\uFF0C\u5BF9\u6240\u6709\u4F1A\u8BDD\u751F\u6548\u3002\u5404\u529F\u80FD\u72EC\u7ACB\u5F00\u5173\uFF0C\u91C7\u7EB3\u5185\u5BB9\u540E\u4ECD\u7531\u4F60\u53D1\u9001\u3002" }),
2417
+ loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" }),
2418
+ message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: message }),
2419
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", children: error }),
2420
+ loadFailed && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", disabled: loading || generation === null, onClick: () => setReload((value) => value + 1), children: "\u91CD\u65B0\u52A0\u8F7D" }),
2421
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("fieldset", { disabled: loading || saving || generation === null || !accepted, children: [
2422
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "dsh-ia-section", children: [
2423
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u9884\u8BBE\u6863\u4F4D" }),
2424
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u9884\u8BBE\u53EA\u4FEE\u6539\u4E0B\u65B9\u8868\u5355\uFF0C\u70B9\u51FB\u4FDD\u5B58\u540E\u624D\u5BF9\u6240\u6709\u4F1A\u8BDD\u751F\u6548\uFF1B\u4E0D\u4F1A\u7ACB\u5373\u8C03\u7528\u6A21\u578B\u3002" }),
2425
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-ia-settings-presets", children: PRESETS.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2426
+ "button",
2427
+ {
2428
+ type: "button",
2429
+ className: "dsh-ia-preset",
2430
+ "aria-label": preset.name,
2431
+ "aria-pressed": Object.entries(preset.values).every(([key, value]) => form[key] === value),
2432
+ onClick: () => {
2433
+ setForm((previous) => ({ ...previous, ...preset.values }));
2434
+ setMessage(`\u5DF2\u5957\u7528\u300C${preset.name}\u300D\uFF0C\u4FDD\u5B58\u540E\u751F\u6548`);
2435
+ setError("");
2436
+ },
2437
+ children: [
2438
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: preset.name }),
2439
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-ia-muted", children: preset.description })
2440
+ ]
2441
+ },
2442
+ preset.name
2443
+ )) })
2444
+ ] }),
2445
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "dsh-ia-section", children: [
2446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u529F\u80FD\u5F00\u5173" }),
2447
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-ia-settings-toggles", children: [
2448
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
2449
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: form.optimizeEnabled, onChange: (event) => change("optimizeEnabled", event.target.checked) }),
2450
+ "\u63D0\u793A\u8BCD\u4F18\u5316"
2451
+ ] }),
2452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
2453
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: form.nextEnabled, onChange: (event) => change("nextEnabled", event.target.checked) }),
2454
+ "\u56DE\u590D\u540E\u9884\u6D4B"
2455
+ ] }),
2456
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
2457
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: form.completionEnabled, onChange: (event) => change("completionEnabled", event.target.checked) }),
2458
+ "\u8F93\u5165\u7EED\u5199"
2459
+ ] }),
2460
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
2461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: form.stashEnabled, onChange: (event) => change("stashEnabled", event.target.checked) }),
2462
+ "\u8349\u7A3F\u6682\u5B58"
2463
+ ] })
2464
+ ] }),
2465
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u5173\u95ED\u6682\u5B58\u540E\u4ECD\u53EF\u67E5\u770B\u548C\u5220\u9664\u5386\u53F2\u8349\u7A3F\uFF1B\u5DF2\u6709\u4F18\u5316\u6539\u52A8\u4ECD\u53EF\u67E5\u770B\u548C\u8FD8\u539F\u3002" }),
2466
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u56DE\u590D\u540E\u9884\u6D4B\u4E0E\u8F93\u5165\u7EED\u5199\u5F00\u542F\u540E\u4F1A\u81EA\u52A8\u8C03\u7528\u6A21\u578B\uFF0C\u6D88\u8017\u6A21\u578B\u7528\u91CF\u3002" })
2467
+ ] }),
2468
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "dsh-ia-section", children: [
2469
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u4F18\u5316\u4E0E\u5EFA\u8BAE" }),
2470
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
2471
+ "\u4F18\u5316\u5F3A\u5EA6",
2472
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: form.optimizationStrength, onChange: (event) => change("optimizationStrength", event.target.value), children: [
2473
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "light", children: "\u8F7B\u5EA6\u6DA6\u8272" }),
2474
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "standard", children: "\u6807\u51C6\u4F18\u5316" }),
2475
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "deep", children: "\u6DF1\u5EA6\u68B3\u7406" })
2476
+ ] })
2477
+ ] }),
2478
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u8F7B\u5EA6\u6DA6\u8272\u53EA\u4FEE\u6B63\u8868\u8FBE\uFF1B\u6807\u51C6\u4F18\u5316\u6574\u7406\u76EE\u6807\u4E0E\u7EA6\u675F\uFF1B\u6DF1\u5EA6\u68B3\u7406\u53EF\u91CD\u7EC4\u7ED3\u6784\u3002\u5404\u6863\u4F4D\u5747\u8981\u6C42\u4FDD\u7559\u539F\u610F\uFF0C\u4E0D\u65B0\u589E\u4EFB\u52A1\u6216\u6388\u6743\uFF1B\u5148\u9884\u89C8\u5DEE\u5F02\uFF0C\u91C7\u7EB3\u540E\u624D\u5199\u5165\u8349\u7A3F\u3002" }),
2479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
2480
+ "\u9884\u6D4B\u8BE6\u7565\u5EA6",
2481
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { "aria-label": "\u9884\u6D4B\u8BE6\u7565\u5EA6", value: form.predictionDetail, onChange: (event) => change("predictionDetail", event.target.value), children: [
2482
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "concise", children: "\u7B80\u660E" }),
2483
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "standard", children: "\u6807\u51C6" }),
2484
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "detailed", children: "\u8BE6\u5C3D" })
2485
+ ] })
2486
+ ] }),
2487
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u7B80\u660E\u4FA7\u91CD\u77ED\u53E5\uFF0C\u6807\u51C6\u63D0\u4F9B\u81EA\u7136\u8854\u63A5\uFF0C\u8BE6\u5C3D\u5141\u8BB8\u591A\u884C\u7ED3\u6784\u5E76\u4EE5\u5019\u9009\u9762\u677F\u5B8C\u6574\u5C55\u793A\u3002\u5404\u6863\u5747\u9075\u5B88\u4F60\u8BBE\u7F6E\u7684\u957F\u5EA6\u4E0A\u9650\u3002" }),
2488
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
2489
+ "\u5EFA\u8BAE\u663E\u793A\u65B9\u5F0F",
2490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: form.displayMode, onChange: (event) => change("displayMode", event.target.value), children: [
2491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "ghost", children: "\u7070\u5B57" }),
2492
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "panel", children: "\u5019\u9009\u9762\u677F" })
2493
+ ] })
2494
+ ] })
2495
+ ] }),
2496
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "dsh-ia-section", children: [
2497
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u8F85\u52A9\u6A21\u578B" }),
2498
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u4EC5\u8BBE\u7F6E\u8F93\u5165\u52A9\u624B\u4F7F\u7528\u7684\u6A21\u578B\uFF0C\u4E0D\u6539\u53D8\u4E3B\u4F1A\u8BDD\u6A21\u578B\u3002\u8F85\u52A9\u8C03\u7528\u5355\u72EC\u6D88\u8017\u6A21\u578B\u7528\u91CF\u3002" }),
2499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
2500
+ "\u8F85\u52A9\u6A21\u578B\u9009\u62E9",
2501
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { "aria-label": "\u8F85\u52A9\u6A21\u578B\u9009\u62E9", value: selection, onChange: (event) => {
2502
+ const [provider, model] = event.target.value ? JSON.parse(event.target.value) : ["", ""];
2503
+ setForm((previous) => ({ ...previous, provider, model }));
2504
+ setMessage("");
2505
+ setError("");
2506
+ }, children: [
2507
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: "\u8DDF\u968F\u8F93\u5165\u6846\u6A21\u578B" }),
2508
+ selection && !available && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("option", { value: selection, children: [
2509
+ catalog ? "\u5F53\u524D\u624B\u52A8\u9009\u62E9\uFF0F\u76EE\u5F55\u672A\u5217\u51FA" : "\u5F53\u524D\u9009\u62E9\uFF0F\u76EE\u5F55\u5C1A\u672A\u786E\u8BA4",
2510
+ "\uFF1A",
2511
+ form.provider || "\u672A\u586B\u63D0\u4F9B\u5546",
2512
+ " / ",
2513
+ form.model || "\u672A\u586B\u6A21\u578B"
2514
+ ] }),
2515
+ catalog?.groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("optgroup", { label: group.name, children: group.models.map((model) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: JSON.stringify([group.provider, model.id]), children: model.name }, model.id)) }, group.provider))
2516
+ ] })
2517
+ ] }),
2518
+ catalogLoading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: "\u6B63\u5728\u52A0\u8F7D\u6A21\u578B\u76EE\u5F55\u2026" }),
2519
+ catalogError && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { role: "status", children: [
2520
+ "\u6A21\u578B\u76EE\u5F55\uFF1A",
2521
+ catalogError
2522
+ ] }),
2523
+ catalog && catalog.groups.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u5BBF\u4E3B\u6682\u65E0\u53EF\u7528\u6A21\u578B\uFF1B\u53EF\u5237\u65B0\u76EE\u5F55\u6216\u624B\u52A8\u586B\u5199\u3002" }),
2524
+ catalog && catalog.failures.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { role: "status", children: [
2525
+ "\u90E8\u5206\u63D0\u4F9B\u5546\u76EE\u5F55\u52A0\u8F7D\u5931\u8D25\uFF1A",
2526
+ catalog.failures.map((item) => item.name).join("\u3001"),
2527
+ "\u3002\u5DF2\u52A0\u8F7D\u6A21\u578B\u4ECD\u53EF\u9009\u62E9\u3002"
2528
+ ] }),
2529
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", disabled: catalogLoading || generation === null, onClick: () => setCatalogReload((value) => value + 1), children: "\u5237\u65B0\u6A21\u578B\u5217\u8868" }),
2530
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { children: [
2531
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("summary", { children: "\u624B\u52A8\u586B\u5199\u6A21\u578B ID" }),
2532
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u76EE\u5F55\u6682\u65F6\u4E0D\u53EF\u7528\u6216\u4F7F\u7528\u81EA\u5B9A\u4E49\u6A21\u578B\u65F6\uFF0C\u53EF\u586B\u5199\u63D0\u4F9B\u5546\u4E0E\u6A21\u578B ID\uFF1B\u4E24\u9879\u540C\u65F6\u7559\u7A7A\u5219\u8DDF\u968F\u8F93\u5165\u6846\u3002" }),
2533
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
2534
+ "\u63D0\u4F9B\u5546",
2535
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { value: form.provider, onChange: (event) => change("provider", event.target.value), placeholder: "\u8DDF\u968F\u8F93\u5165\u6846", maxLength: 200, autoComplete: "off" })
2536
+ ] }),
2537
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
2538
+ "\u6A21\u578B",
2539
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { value: form.model, onChange: (event) => change("model", event.target.value), placeholder: "\u8DDF\u968F\u8F93\u5165\u6846", maxLength: 200, autoComplete: "off" })
2540
+ ] })
2541
+ ] })
2542
+ ] }),
2543
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { className: "dsh-ia-section", children: [
2544
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("summary", { children: "\u9AD8\u7EA7\u8BBE\u7F6E" }),
2545
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u6ED1\u5757\u7528\u4E8E\u5FEB\u901F\u8C03\u6574\uFF0C\u6570\u503C\u6846\u652F\u6301\u7CBE\u786E\u6574\u6570\u8F93\u5165\u3002" }),
2546
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-ia-setting-grid", children: NUMBERS.map(([key, label, unit, min, max, step]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-ia-field", children: [
2547
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-ia-setting-heading", children: [
2548
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { htmlFor: `${fieldsId}-${key}`, children: [
2549
+ label,
2550
+ "\uFF08",
2551
+ unit,
2552
+ "\uFF09"
2553
+ ] }),
2554
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-ia-muted", children: formatValue(form[key], unit) })
2555
+ ] }),
2556
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-ia-number-control", children: [
2557
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2558
+ "input",
2559
+ {
2560
+ id: `${fieldsId}-${key}`,
2561
+ type: "number",
2562
+ "aria-label": `${label}\uFF08${unit}\uFF09`,
2563
+ value: Number.isNaN(form[key]) ? "" : form[key],
2564
+ min,
2565
+ max,
2566
+ step: 1,
2567
+ required: true,
2568
+ onChange: (event) => change(key, event.target.value === "" ? NaN : Number(event.target.value))
2569
+ }
2570
+ ),
2571
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2572
+ "input",
2573
+ {
2574
+ type: "range",
2575
+ tabIndex: -1,
2576
+ "aria-hidden": "true",
2577
+ min,
2578
+ max,
2579
+ step,
2580
+ value: Number.isNaN(form[key]) ? min : Math.min(max, min + Math.round((form[key] - min) / step) * step),
2581
+ onChange: (event) => change(key, Number(event.target.value))
2582
+ }
2583
+ )
2584
+ ] }),
2585
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-ia-muted", children: [
2586
+ min.toLocaleString(),
2587
+ "\u2013",
2588
+ max.toLocaleString(),
2589
+ " ",
2590
+ unit
2591
+ ] })
2592
+ ] }, key)) })
2593
+ ] }),
2594
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-ia-row", children: [
2595
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", disabled: !changed, children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u8BBE\u7F6E" }),
2596
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", disabled: !changed, onClick: () => {
2597
+ if (accepted) setForm(accepted);
2598
+ setError("");
2599
+ setMessage("\u5DF2\u653E\u5F03\u672A\u4FDD\u5B58\u7684\u4FEE\u6539");
2600
+ }, children: "\u653E\u5F03\u4FEE\u6539" }),
2601
+ changed && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-ia-muted", children: "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539" })
2602
+ ] })
2603
+ ] })
2604
+ ] });
2605
+ }
2606
+
2607
+ // src/browser/index.ts
2608
+ var name = "input-assistant.client";
2609
+ var inject = ["slots", "connection", "modelDirectories"];
2610
+ function apply(ctx) {
2611
+ const connection = ctx.get("connection");
2612
+ const models = ctx.get("modelDirectories");
2613
+ const runtime = new BrowserRuntime(connection, models);
2614
+ ctx.effect(() => {
2615
+ const style = document.createElement("style");
2616
+ style.dataset.dshInputAssistant = "";
2617
+ style.textContent = STYLE_TEXT;
2618
+ document.head.append(style);
2619
+ return () => {
2620
+ runtime.dispose();
2621
+ style.remove();
2622
+ };
2623
+ }, "input-assistant: browser lifetime");
2624
+ ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
2625
+ name: "conversation.input.right",
2626
+ id: "input-assistant",
2627
+ order: 40,
2628
+ inject: () => ({ runtime })
2629
+ }, AssistantRight));
2630
+ ctx.slots.inject("conversation.input.overlay", () => ctx.slots.register({
2631
+ name: "conversation.input.overlay",
2632
+ id: "input-assistant",
2633
+ order: 40,
2634
+ inject: () => ({ runtime })
2635
+ }, AssistantOverlay));
2636
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
2637
+ name: "settings.section",
2638
+ id: "input-assistant",
2639
+ order: 65,
2640
+ label: "\u8F93\u5165\u52A9\u624B",
2641
+ inject: () => ({ runtime })
2642
+ }, AssistantSettings));
2643
+ }
2644
+
2645
+ return module.exports;
2646
+ }});