dsh-input-assistant 0.1.3 → 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 (42) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/LICENSE +21 -21
  3. package/README.en.md +100 -0
  4. package/README.md +215 -184
  5. package/cordis.patch.yml +3 -3
  6. package/docs/CHANGELOG-0.1.2.md +102 -102
  7. package/docs/evaluation.md +111 -111
  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 -39
  12. package/docs/smoke-comparison.md +37 -37
  13. package/docs/validation.md +180 -100
  14. package/lib/browser/components.d.ts +1 -1
  15. package/lib/browser/controller.d.ts +47 -4
  16. package/lib/browser/dom.d.ts +1 -1
  17. package/lib/browser/index.d.ts +1 -1
  18. package/lib/browser/public.d.ts +2 -2
  19. package/lib/browser/runtime.d.ts +4 -2
  20. package/lib/browser/settings.d.ts +1 -1
  21. package/lib/browser/styles.d.ts +1 -1
  22. package/lib/client.js +1164 -222
  23. package/lib/client.meta.json +40 -40
  24. package/lib/core/config.d.ts +1 -1
  25. package/lib/core/context.d.ts +1 -1
  26. package/lib/core/generation.d.ts +2 -2
  27. package/lib/core/index.d.ts +9 -8
  28. package/lib/core/models.d.ts +15 -0
  29. package/lib/core/protection.d.ts +3 -4
  30. package/lib/core/scheduler.d.ts +1 -1
  31. package/lib/core/snapshot.d.ts +1 -1
  32. package/lib/core/types.d.ts +11 -0
  33. package/lib/core.js +280 -53
  34. package/lib/core.js.map +2 -2
  35. package/lib/host/rpc.d.ts +2 -0
  36. package/lib/host/runtime.d.ts +5 -3
  37. package/lib/host/storage.d.ts +11 -3
  38. package/lib/host/transcript.d.ts +1 -1
  39. package/lib/index.d.ts +5 -3
  40. package/lib/index.js +451 -156
  41. package/lib/index.js.map +4 -4
  42. package/package.json +10 -3
package/lib/client.js CHANGED
@@ -252,7 +252,7 @@ function useFloatingPlacement(anchor, surface, visible, card = false) {
252
252
  rect,
253
253
  element.scrollHeight || element.getBoundingClientRect().height || 160,
254
254
  bounds,
255
- card ? Math.min(640, rect.width || 440) : 440
255
+ card ? Math.min(640, rect.width || 480) : 480
256
256
  );
257
257
  setPlacement((previous) => previous && Object.keys(next).every((key) => previous[key] === next[key]) ? previous : next);
258
258
  };
@@ -299,19 +299,46 @@ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions
299
299
  const anchor = (0, import_react.useRef)(null);
300
300
  const menuRef = (0, import_react.useRef)(null);
301
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)();
302
320
  const [open, setOpen] = (0, import_react.useState)(false);
303
321
  const menuPlacement = useFloatingPlacement(anchor, menuRef, open);
304
- const noticePlacement = useFloatingPlacement(anchor, noticeRef, !open && Boolean(state.notice));
305
- const [provider, setProvider] = (0, import_react.useState)(state.settings.provider);
306
- const [modelId, setModelId] = (0, import_react.useState)(state.settings.model);
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
+ }, []);
307
338
  (0, import_react.useEffect)(() => {
308
339
  void directory?.load().catch(() => {
309
340
  });
310
341
  }, [directory]);
311
- (0, import_react.useEffect)(() => {
312
- setProvider(state.settings.provider);
313
- setModelId(state.settings.model);
314
- }, [state.settings.provider, state.settings.model]);
315
342
  (0, import_react.useLayoutEffect)(() => {
316
343
  if (removed) {
317
344
  controller.dispose();
@@ -323,10 +350,35 @@ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions
323
350
  const editor = findEditor(anchor.current);
324
351
  if (!editor) return;
325
352
  const selectionChanged = () => controller.notifySelectionChange();
326
- const compositionStart = () => controller.setComposing(true);
327
- const compositionEnd = () => controller.setComposing(false);
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
+ };
328
363
  const keyDown = (event) => {
329
- if (!controller.getSnapshot().suggestion || !canUseTab(event, editor, false)) return;
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;
330
382
  if (![...document.querySelectorAll("[data-dsh-ia-visible-suggestion]")].some((node) => node.getAttribute("data-dsh-ia-session") === sessionId)) return;
331
383
  if (controller.acceptSuggestion()) event.preventDefault();
332
384
  };
@@ -344,31 +396,46 @@ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions
344
396
  editor.removeEventListener("compositionend", compositionEnd);
345
397
  window.removeEventListener("keydown", keyDown);
346
398
  };
347
- }, [controller, sessionId]);
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]);
348
415
  (0, import_react.useEffect)(() => {
349
416
  if (!open) return;
350
417
  const close = (event) => {
351
418
  if (event.target instanceof Node && !anchor.current?.contains(event.target) && !menuRef.current?.contains(event.target)) setOpen(false);
352
419
  };
353
- const escape = (event) => {
354
- if (event.key === "Escape" && !event.defaultPrevented) {
355
- setOpen(false);
356
- event.preventDefault();
357
- }
358
- };
359
420
  document.addEventListener("pointerdown", close);
360
- window.addEventListener("keydown", escape);
361
421
  return () => {
362
422
  document.removeEventListener("pointerdown", close);
363
- window.removeEventListener("keydown", escape);
364
423
  };
365
424
  }, [open]);
366
425
  const preserveSelection = (event) => {
367
426
  event.preventDefault();
368
427
  };
369
428
  const canEdit = input.phase === "plain" && !state.busy && !state.settingsSaving;
370
- const canOptimize = canEdit && state.settings.optimizeEnabled;
429
+ const canOptimize = canEdit && !state.preview && state.settings.optimizeEnabled;
371
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
+ };
372
439
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia dsh-ia-root", ref: anchor, "data-dsh-input-assistant": "", children: [
373
440
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
374
441
  "button",
@@ -377,43 +444,127 @@ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions
377
444
  type: "button",
378
445
  disabled: !canOptimize || !input.draft.trim() || !state.connected,
379
446
  onPointerDown: preserveSelection,
380
- onClick: () => {
447
+ onClick: (event) => {
448
+ keyboardOptimize.current = event.detail === 0;
381
449
  void controller.optimize();
382
450
  },
383
451
  title: "\u6709\u9009\u533A\u65F6\u4F18\u5316\u9009\u533A\uFF0C\u5426\u5219\u4F18\u5316\u6574\u7A3F",
384
452
  children: state.busy === "optimize" ? "\u4F18\u5316\u4E2D\u2026" : "\u4F18\u5316"
385
453
  }
386
454
  ),
387
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": "\u8F93\u5165\u52A9\u624B\u83DC\u5355", "aria-expanded": open, onPointerDown: preserveSelection, onClick: () => setOpen((value) => !value), children: "\u25BE" }),
388
- !open && state.notice && (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
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)(
389
512
  "div",
390
513
  {
391
514
  ref: noticeRef,
392
515
  role: "status",
393
516
  className: "dsh-ia dsh-ia-notice",
394
517
  style: noticePlacement ?? { visibility: "hidden" },
395
- onClick: () => controller.clearNotice(),
396
- children: state.notice
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
+ ]
397
562
  }
398
563
  ), document.body),
399
- open && (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: menuRef, className: "dsh-ia dsh-ia-menu", "aria-label": "\u8F93\u5165\u52A9\u624B", style: menuPlacement ?? { visibility: "hidden" }, children: [
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: [
400
565
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "\u8F93\u5165\u52A9\u624B" }),
401
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u5F53\u524D\u4F1A\u8BDD \xB7 \u91C7\u7EB3\u540E\u7531\u4F60\u53D1\u9001" }),
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" }),
402
567
  state.settingsSaving && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "status", children: "\u8BBE\u7F6E\u4FDD\u5B58\u4E2D\u2026" }),
403
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
404
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
405
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", disabled: state.settingsSaving, checked: state.settings.optimizeEnabled, onChange: (event) => {
406
- void controller.updateSettings({ optimizeEnabled: event.target.checked });
407
- } }),
408
- "\u63D0\u793A\u8BCD\u4F18\u5316"
409
- ] }),
410
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
411
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", disabled: state.settingsSaving, checked: state.settings.stashEnabled, onChange: (event) => {
412
- void controller.updateSettings({ stashEnabled: event.target.checked });
413
- } }),
414
- "\u8349\u7A3F\u6682\u5B58"
415
- ] })
416
- ] }),
417
568
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
418
569
  "\u4F18\u5316\u5F3A\u5EA6 ",
419
570
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -433,7 +584,37 @@ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions
433
584
  }
434
585
  )
435
586
  ] }),
436
- state.notice && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-notice", role: "status", children: state.notice }),
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
+ ] }),
437
618
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
438
619
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", disabled: !canOptimize, onPointerDown: preserveSelection, onClick: () => controller.lockSelection(), children: [
439
620
  "\u4FDD\u62A4\u9009\u4E2D\u539F\u6587",
@@ -444,7 +625,7 @@ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions
444
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" }),
445
626
  state.change && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ia-section", children: [
446
627
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u67E5\u770B\u6539\u52A8" }),
447
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-pre dsh-ia-diff", children: diffText(state.change.beforeTarget, state.change.afterTarget).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)) }),
628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiffView, { before: state.change.beforeTarget, after: state.change.afterTarget }),
448
629
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
449
630
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !controller.canRestore(), onPointerDown: preserveSelection, onClick: () => controller.restoreOriginal(), children: "\u8FD8\u539F" }),
450
631
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
@@ -475,51 +656,27 @@ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions
475
656
  draft.id
476
657
  )),
477
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" }),
478
- state.drafts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
659
+ state.drafts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(state.busy), onClick: () => {
479
660
  void controller.clearDrafts();
480
661
  }, children: "\u6E05\u7A7A\u6682\u5B58" })
481
662
  ] }),
482
663
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dsh-ia-section", children: [
483
664
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { children: "\u8F93\u5165\u5EFA\u8BAE" }),
484
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ia-row", children: [
485
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
486
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", disabled: state.settingsSaving, checked: state.settings.nextEnabled, onChange: (event) => {
487
- void controller.updateSettings({ nextEnabled: event.target.checked });
488
- } }),
489
- "\u56DE\u590D\u540E\u9884\u6D4B"
490
- ] }),
491
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
492
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", disabled: state.settingsSaving, checked: state.settings.completionEnabled, onChange: (event) => {
493
- void controller.updateSettings({ completionEnabled: event.target.checked });
494
- } }),
495
- "\u8F93\u5165\u7EED\u5199"
496
- ] })
497
- ] }),
498
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
499
- "\u663E\u793A\u65B9\u5F0F ",
500
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { "aria-label": "\u663E\u793A\u65B9\u5F0F", disabled: state.settingsSaving, value: state.settings.displayMode, onChange: (event) => {
501
- void controller.updateSettings({ displayMode: event.target.value });
502
- }, children: [
503
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "ghost", children: "\u7070\u5B57" }),
504
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "panel", children: "\u5019\u9009\u9762\u677F" })
505
- ] })
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"
506
669
  ] }),
507
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => controller.dismissSuggestion(), children: "\u6682\u505C\u81F3\u4E0B\u6B21\u7F16\u8F91" }) })
508
- ] }),
509
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ia-section", children: [
510
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u8F85\u52A9\u6A21\u578B\u8BBE\u7F6E" }),
511
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ia-muted", children: "\u4E24\u9879\u7559\u7A7A\u65F6\u8DDF\u968F\u8F93\u5165\u6846\u6A21\u578B\uFF1B\u8F85\u52A9\u8C03\u7528\u5355\u72EC\u6D88\u8017\u6A21\u578B\u7528\u91CF\u3002" }),
512
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dsh-ia-field", children: [
513
- "\u63D0\u4F9B\u5546",
514
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { disabled: state.settingsSaving, value: provider, onChange: (event) => setProvider(event.target.value), placeholder: "\u8DDF\u968F\u8F93\u5165\u6846", autoComplete: "off" })
515
- ] }),
516
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dsh-ia-field", children: [
517
- "\u6A21\u578B",
518
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { disabled: state.settingsSaving, value: modelId, onChange: (event) => setModelId(event.target.value), placeholder: "\u8DDF\u968F\u8F93\u5165\u6846", autoComplete: "off" })
519
- ] }),
520
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: state.settingsSaving, onClick: () => {
521
- void controller.updateSettings({ provider, model: modelId });
522
- }, children: "\u4FDD\u5B58\u6A21\u578B\u8BBE\u7F6E" })
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
+ ] })
523
680
  ] }),
524
681
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ia-section", children: [
525
682
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u672C\u6B21\u4F7F\u7528\u7EDF\u8BA1" }),
@@ -538,10 +695,36 @@ function AssistantRight({ runtime, sessionId, useInput, useSession, inputActions
538
695
  ] }),
539
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" })
540
697
  ] }),
541
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ia-muted", children: "\u66F4\u591A\u8BBE\u7F6E\uFF1ADSH \u8BBE\u7F6E \u2192 \u8F93\u5165\u52A9\u624B" })
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" })
542
699
  ] }), document.body)
543
700
  ] });
544
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
+ }
545
728
  function DraftCard({ draft, controller, canEdit, canReplace }) {
546
729
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ia-draft", children: [
547
730
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: draft.text.split("\n")[0].slice(0, 50) || "\u672A\u547D\u540D\u8349\u7A3F" }),
@@ -551,6 +734,9 @@ function DraftCard({ draft, controller, canEdit, canReplace }) {
551
734
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !canEdit, onPointerDown: (event) => event.preventDefault(), onClick: () => controller.restoreDraft(draft, "append"), children: "\u8FFD\u52A0" }),
552
735
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: !canEdit, onPointerDown: (event) => event.preventDefault(), onClick: () => controller.restoreDraft(draft, "replace"), children: "\u66FF\u6362" }),
553
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: () => {
554
740
  void controller.deleteDraft(draft.id);
555
741
  }, children: "\u5220\u9664" }),
556
742
  canReplace && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onPointerDown: (event) => event.preventDefault(), onClick: () => {
@@ -559,12 +745,12 @@ function DraftCard({ draft, controller, canEdit, canReplace }) {
559
745
  ] })
560
746
  ] });
561
747
  }
562
- async function copyText(text, controller) {
748
+ async function copyText(text, controller, success = "\u539F\u6587\u5DF2\u590D\u5236") {
563
749
  try {
564
750
  await navigator.clipboard.writeText(text);
565
- controller.notice("\u539F\u6587\u5DF2\u590D\u5236");
751
+ controller.notice(success);
566
752
  } catch {
567
- controller.notice("\u590D\u5236\u4E0D\u53EF\u7528\uFF0C\u8BF7\u5728\u6539\u52A8\u9762\u677F\u624B\u52A8\u9009\u62E9\u539F\u6587");
753
+ controller.notice("\u590D\u5236\u4E0D\u53EF\u7528\uFF0C\u8BF7\u5C55\u5F00\u5BF9\u5E94\u6587\u672C\u540E\u624B\u52A8\u9009\u62E9\u590D\u5236");
568
754
  }
569
755
  }
570
756
  function AssistantOverlay({ runtime, sessionId }) {
@@ -573,12 +759,13 @@ function AssistantOverlay({ runtime, sessionId }) {
573
759
  const panelRef = (0, import_react.useRef)(null);
574
760
  const [geometry, setGeometry] = (0, import_react.useState)(null);
575
761
  const suggestion = state.suggestion;
576
- const panelPlacement = useFloatingPlacement(anchor, panelRef, Boolean(suggestion) && state.settings.displayMode === "panel", true);
762
+ const displayMode = state.settings.predictionDetail === "detailed" ? "panel" : state.settings.displayMode;
763
+ const panelPlacement = useFloatingPlacement(anchor, panelRef, Boolean(suggestion) && displayMode === "panel", true);
577
764
  (0, import_react.useEffect)(() => {
578
- if (suggestion && (state.settings.displayMode === "panel" && panelPlacement || geometry)) controller.markExposed(suggestion.snapshot.requestId);
579
- }, [controller, suggestion, geometry, panelPlacement, state.settings.displayMode]);
765
+ if (suggestion && (displayMode === "panel" && panelPlacement || geometry)) controller.markExposed(suggestion.snapshot.requestId);
766
+ }, [controller, suggestion, geometry, panelPlacement, displayMode]);
580
767
  (0, import_react.useLayoutEffect)(() => {
581
- if (!suggestion || state.settings.displayMode !== "ghost") {
768
+ if (!suggestion || displayMode !== "ghost") {
582
769
  setGeometry(null);
583
770
  return;
584
771
  }
@@ -600,7 +787,7 @@ function AssistantOverlay({ runtime, sessionId }) {
600
787
  editor?.removeEventListener("blur", update);
601
788
  resize?.disconnect();
602
789
  };
603
- }, [suggestion, state.settings.displayMode]);
790
+ }, [suggestion, displayMode]);
604
791
  const ghostStyle = geometry ? {
605
792
  left: geometry.left,
606
793
  top: geometry.top,
@@ -611,7 +798,7 @@ function AssistantOverlay({ runtime, sessionId }) {
611
798
  letterSpacing: geometry.letterSpacing
612
799
  } : void 0;
613
800
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { ref: anchor, className: "dsh-ia", style: { display: "contents" }, children: [
614
- suggestion && state.settings.displayMode === "panel" && (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
801
+ suggestion && displayMode === "panel" && (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
615
802
  "div",
616
803
  {
617
804
  ref: panelRef,
@@ -627,7 +814,12 @@ function AssistantOverlay({ runtime, sessionId }) {
627
814
  ]
628
815
  }
629
816
  ), document.body),
630
- suggestion && geometry && state.settings.displayMode === "ghost" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
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
+ ] }),
631
823
  suggestion.snapshot.draft === "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { hidden: true, "data-dsh-ia-empty-ghost": "" }),
632
824
  (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
633
825
  "span",
@@ -651,6 +843,7 @@ var DEFAULT_CONFIG = Object.freeze({
651
843
  optimizationStrength: "standard",
652
844
  nextEnabled: true,
653
845
  completionEnabled: true,
846
+ predictionDetail: "standard",
654
847
  displayMode: "ghost",
655
848
  debounceMs: 600,
656
849
  minIntervalMs: 2e3,
@@ -685,21 +878,37 @@ var initial = () => ({
685
878
  status: null,
686
879
  settings: { ...DEFAULT_CONFIG },
687
880
  busy: null,
881
+ stashClearCancelled: false,
882
+ stashClearPending: false,
688
883
  notice: "",
689
884
  suggestion: null,
885
+ suggestionStatus: "idle",
886
+ suggestionMessage: "",
887
+ preview: null,
690
888
  change: null,
691
889
  drafts: [],
890
+ deletedDrafts: [],
891
+ pendingUndoDrafts: [],
692
892
  locked: [],
693
893
  connected: false,
694
894
  settingsSaving: false,
695
895
  metrics: emptyMetrics()
696
896
  });
697
897
  var InputAssistantController = class {
698
- constructor(sessionId, transport, metrics, saveSettings) {
898
+ constructor(sessionId, transport, metrics, saveSettings, undo) {
699
899
  this.sessionId = sessionId;
700
900
  this.transport = transport;
701
901
  this.saveSettings = saveSettings;
702
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
+ }
703
912
  }
704
913
  state = initial();
705
914
  listeners = /* @__PURE__ */ new Set();
@@ -708,14 +917,22 @@ var InputAssistantController = class {
708
917
  timer;
709
918
  /** Cache key the armed completion timer was scheduled for; null when no timer is armed. */
710
919
  timerKey = null;
920
+ manualTimerKey = null;
711
921
  poll;
922
+ undoTimer;
923
+ undoExpiresAt = 0;
924
+ requestPrefix = Array.from(crypto.getRandomValues(new Uint32Array(4)), (word) => word.toString(36)).join("-");
925
+ completionRetryAt = 0;
712
926
  polling = false;
713
927
  lifecycle = new AbortController();
714
928
  disposed = false;
715
929
  composing = false;
716
930
  settingsPending = false;
717
931
  settingsEpoch = 0;
932
+ draftsEpoch = 0;
933
+ stashEpoch = 0;
718
934
  nextTurns = /* @__PURE__ */ new Set();
935
+ failedNextKey = null;
719
936
  nextCache = null;
720
937
  completionCache = /* @__PURE__ */ new Map();
721
938
  failedCompletionKey = null;
@@ -726,6 +943,22 @@ var InputAssistantController = class {
726
943
  lockedRanges = [];
727
944
  exposed = /* @__PURE__ */ new Set();
728
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
+ }
729
962
  getSnapshot = () => this.state;
730
963
  subscribe = (listener) => {
731
964
  this.listeners.add(listener);
@@ -742,15 +975,35 @@ var InputAssistantController = class {
742
975
  if (this.poll || this.disposed) return;
743
976
  void this.refresh();
744
977
  void this.refreshDrafts();
745
- this.poll = setInterval(() => {
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(() => {
746
996
  void this.refresh();
747
- }, 900);
997
+ this.schedulePoll();
998
+ }, delay);
748
999
  }
749
1000
  dispose() {
750
1001
  if (this.disposed) return;
751
1002
  this.cancelActive();
752
1003
  this.clearTimer();
753
- clearInterval(this.poll);
1004
+ clearTimeout(this.poll);
1005
+ clearTimeout(this.undoTimer);
1006
+ document.removeEventListener("visibilitychange", this.visibilityChanged);
754
1007
  this.lifecycle.abort();
755
1008
  this.disposed = true;
756
1009
  this.listeners.clear();
@@ -764,7 +1017,9 @@ var InputAssistantController = class {
764
1017
  if (generation !== this.clientGeneration) {
765
1018
  this.clientGeneration = generation;
766
1019
  this.invalidate(true);
767
- this.patch({ connected: false, status: null, drafts: [] });
1020
+ clearTimeout(this.undoTimer);
1021
+ this.undoExpiresAt = 0;
1022
+ this.patch({ connected: false, status: null, drafts: [], deletedDrafts: [], pendingUndoDrafts: [] });
768
1023
  if (generation !== null && this.poll) {
769
1024
  void this.refresh();
770
1025
  void this.refreshDrafts();
@@ -776,7 +1031,7 @@ var InputAssistantController = class {
776
1031
  if (previous.input.draft !== binding.input.draft) this.observeAcceptedEdit(binding.input.draft);
777
1032
  if (previous.input.draft !== binding.input.draft) this.lockedRanges = [];
778
1033
  if (previous.input.draft !== binding.input.draft) this.failedCompletionKey = null;
779
- this.patch({ suggestion: null, ...previous.input.draft !== binding.input.draft ? { locked: [] } : {} });
1034
+ this.patch({ suggestion: null, suggestionStatus: "idle", suggestionMessage: "", preview: null, ...previous.input.draft !== binding.input.draft ? { locked: [] } : {} });
780
1035
  if (this.suppressUntilEdit !== null && binding.input.draft !== this.suppressUntilEdit) this.suppressUntilEdit = null;
781
1036
  }
782
1037
  this.driveSuggestions();
@@ -803,8 +1058,27 @@ var InputAssistantController = class {
803
1058
  dismissSuggestion() {
804
1059
  this.clearTimer();
805
1060
  this.cancelActive();
806
- this.patch({ suggestion: null });
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();
807
1080
  this.suppressUntilEdit = this.binding?.input.draft ?? "";
1081
+ this.patch({ preview: null, suggestion: null, notice: "\u5DF2\u53D6\u6D88\uFF0C\u539F\u7A3F\u5DF2\u4FDD\u7559" });
808
1082
  }
809
1083
  clearNotice() {
810
1084
  this.patch({ notice: "" });
@@ -823,10 +1097,10 @@ var InputAssistantController = class {
823
1097
  const previous = this.state.status;
824
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));
825
1099
  if (changed) this.invalidate(true);
826
- this.patch({ status, settings: status.settings, connected: this.transport.generation() !== null });
1100
+ if (!previous || changed || !this.state.connected) this.patch({ status, settings: status.settings, connected: true });
827
1101
  this.driveSuggestions();
828
1102
  } catch {
829
- if (!this.disposed) {
1103
+ if (!this.disposed && this.transport.generation() === generation && settingsEpoch === this.settingsEpoch) {
830
1104
  this.invalidate(false, false);
831
1105
  this.patch({ connected: false });
832
1106
  }
@@ -905,7 +1179,7 @@ var InputAssistantController = class {
905
1179
  }
906
1180
  this.clearTimer();
907
1181
  this.cancelActive();
908
- this.patch({ suggestion: null, busy: "optimize", notice: "" });
1182
+ this.patch({ suggestion: null, preview: null, busy: "optimize", notice: "" });
909
1183
  const snapshot = this.snapshot("optimize", target.span, target.text);
910
1184
  if (!snapshot) {
911
1185
  this.patch({ busy: null, notice: "\u8FDE\u63A5\u6216\u6A21\u578B\u5C1A\u672A\u5C31\u7EEA\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" });
@@ -919,24 +1193,14 @@ var InputAssistantController = class {
919
1193
  this.patch({ notice: "\u6CA1\u6709\u53EF\u5E94\u7528\u7684\u4F18\u5316\u7ED3\u679C\uFF0C\u539F\u6587\u5DF2\u4FDD\u7559" });
920
1194
  return;
921
1195
  }
922
- const after = binding.input.draft.slice(0, target.clipboardStart) + result.text + binding.input.draft.slice(target.clipboardEnd);
923
- if (!binding.actions.insertText(result.text, target.span)) {
924
- this.patch({ notice: "\u8349\u7A3F\u5DF2\u53D8\u5316\uFF0C\u5DF2\u653E\u5F03\u672C\u6B21\u7ED3\u679C" });
925
- return;
926
- }
927
- binding.actions.persistDraft?.();
928
- this.suppressUntilEdit = after;
929
- this.patch({ change: {
930
- before: binding.input.draft,
931
- after,
1196
+ this.patch({ preview: {
1197
+ request: snapshot,
1198
+ text: result.text,
932
1199
  beforeTarget: target.text,
933
- afterTarget: result.text,
934
- span: { ...target.span, end: target.span.start + result.text.length },
935
- sourceTurnId: snapshot.sourceTurnId,
936
- connectionGeneration: snapshot.connectionGeneration,
937
- routeKey: snapshot.routeKey,
1200
+ clipboardStart: target.clipboardStart,
1201
+ clipboardEnd: target.clipboardEnd,
938
1202
  clientGeneration: active.clientGeneration
939
- }, notice: "\u5DF2\u4F18\u5316\uFF0C\u53EF\u67E5\u770B\u6539\u52A8\u6216\u64A4\u9500" });
1203
+ }, notice: "" });
940
1204
  } catch (error) {
941
1205
  if (!active.controller.signal.aborted) this.patch({ notice: this.errorMessage(error) });
942
1206
  } finally {
@@ -947,6 +1211,38 @@ var InputAssistantController = class {
947
1211
  }
948
1212
  }
949
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
+ }
950
1246
  canRestore() {
951
1247
  const change = this.state.change, binding = this.binding, status = this.state.status;
952
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);
@@ -968,12 +1264,12 @@ var InputAssistantController = class {
968
1264
  }
969
1265
  acceptSuggestion() {
970
1266
  const suggestion = this.state.suggestion, binding = this.binding;
971
- if (!suggestion || !binding || !this.isCurrent(suggestion.snapshot, true) || !this.eligible() || (suggestion.kind === "next" ? !this.state.settings.nextEnabled : !this.state.settings.completionEnabled)) {
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)) {
972
1268
  this.patch({ suggestion: null });
973
1269
  return false;
974
1270
  }
975
1271
  const applied = binding.actions.insertText(suggestion.text, suggestion.snapshot.span);
976
- this.patch({ suggestion: null });
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" : "" });
977
1273
  if (applied) {
978
1274
  binding.actions.persistDraft?.();
979
1275
  this.suppressUntilEdit = binding.input.draft + suggestion.text;
@@ -993,13 +1289,13 @@ var InputAssistantController = class {
993
1289
  this.patch({ metrics: { ...this.state.metrics, exposures: this.state.metrics.exposures + 1 } });
994
1290
  }
995
1291
  async refreshDrafts() {
996
- const generation = this.transport.generation();
1292
+ const generation = this.transport.generation(), epoch = this.draftsEpoch;
997
1293
  if (generation === null) return;
998
1294
  try {
999
1295
  const drafts = await this.transport.call("drafts/list", { sessionId: this.sessionId }, this.lifecycle.signal);
1000
- if (generation === this.transport.generation()) this.patch({ drafts });
1296
+ if (generation === this.transport.generation() && epoch === this.draftsEpoch) this.patch({ drafts });
1001
1297
  } catch (error) {
1002
- if (!this.lifecycle.signal.aborted) this.patch({ notice: this.errorMessage(error) });
1298
+ if (!this.lifecycle.signal.aborted && generation === this.transport.generation() && epoch === this.draftsEpoch) this.patch({ notice: this.errorMessage(error) });
1003
1299
  }
1004
1300
  }
1005
1301
  async stash(replaceId) {
@@ -1019,14 +1315,21 @@ var InputAssistantController = class {
1019
1315
  return;
1020
1316
  }
1021
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++;
1022
1321
  this.cancelActive();
1023
1322
  this.clearTimer();
1024
- this.patch({ busy: "stash", suggestion: null, notice: "" });
1323
+ this.patch({ busy: "stash", stashClearPending: true, stashClearCancelled: false, suggestion: null, notice: "" });
1025
1324
  try {
1026
1325
  await this.transport.call("drafts/save", { sessionId: this.sessionId, text: original, ...replaceId ? { replaceId } : {} }, this.lifecycle.signal);
1027
1326
  await this.refreshDrafts();
1028
1327
  const live = this.binding;
1029
- if (!this.settingsPending && this.state.settings.stashEnabled && live?.input.draftRev === rev && live.input.draft === original && live.input.phase === "plain" && clientGeneration === this.transport.generation() && this.pureText()) {
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()) {
1030
1333
  if (live.actions.insertText("", { start: 0, end: original.length, draftRev: rev })) {
1031
1334
  live.actions.persistDraft?.();
1032
1335
  this.suppressUntilEdit = "";
@@ -1038,7 +1341,7 @@ var InputAssistantController = class {
1038
1341
  } catch (error) {
1039
1342
  this.patch({ notice: this.errorMessage(error) });
1040
1343
  } finally {
1041
- this.patch({ busy: null });
1344
+ this.patch({ busy: null, stashClearCancelled: false, stashClearPending: false });
1042
1345
  }
1043
1346
  }
1044
1347
  restoreDraft(draft, mode) {
@@ -1076,12 +1379,90 @@ ${draft.text}` : draft.text;
1076
1379
  await this.mutateDraft("drafts/clear", { sessionId: this.sessionId });
1077
1380
  }
1078
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" });
1079
1390
  try {
1080
- await this.transport.call(method, data, this.lifecycle.signal);
1081
- await this.refreshDrafts();
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 });
1082
1399
  } catch (error) {
1083
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;
1084
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 });
1085
1466
  }
1086
1467
  captureTarget(requireSelection) {
1087
1468
  const binding = this.binding;
@@ -1113,7 +1494,7 @@ ${draft.text}` : draft.text;
1113
1494
  return Boolean(input && input.phase === "plain" && !input.occurrences.length && !input.attachmentIds.length);
1114
1495
  }
1115
1496
  eligible() {
1116
- return this.pureText() && !this.composing && !this.settingsPending && !this.binding?.running && !this.state.status?.running && !this.state.busy && this.state.connected && this.suppressUntilEdit === null;
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;
1117
1498
  }
1118
1499
  driveSuggestions() {
1119
1500
  const binding = this.binding, status = this.state.status;
@@ -1130,24 +1511,32 @@ ${draft.text}` : draft.text;
1130
1511
  this.clearTimer();
1131
1512
  return;
1132
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;
1133
1516
  const turn = status.sourceTurnId;
1134
1517
  const route = this.routeKey(binding.route ?? status.route);
1135
1518
  if (binding.input.draft === "") {
1136
1519
  this.clearTimer();
1137
1520
  if (!turn) return;
1138
- if (!this.state.settings.nextEnabled || this.nextTurns.has(`${status.generation}:${turn}:${route}`)) return;
1139
- if (this.nextTurns.size >= 512) this.nextTurns.clear();
1140
- this.nextTurns.add(`${status.generation}:${turn}:${route}`);
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;
1141
1527
  void this.requestSuggestion("next");
1142
1528
  return;
1143
1529
  }
1144
1530
  if (!this.state.settings.completionEnabled || charCount(binding.input.draft) > this.state.settings.maxDraftChars) {
1145
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");
1146
1533
  return;
1147
1534
  }
1148
1535
  if (this.nextCache && this.nextCache.turn === turn && this.nextCache.route === route && this.nextCache.generation === status.generation && this.nextCache.text.startsWith(binding.input.draft)) {
1149
1536
  this.clearTimer();
1150
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;
1151
1540
  const snapshot = this.snapshot("complete", span);
1152
1541
  if (suffix && charCount(suffix) <= this.state.settings.maxCompletionChars && snapshot) this.patch({ suggestion: { text: suffix, snapshot, kind: "complete" } });
1153
1542
  return;
@@ -1159,47 +1548,87 @@ ${draft.text}` : draft.text;
1159
1548
  }
1160
1549
  if (this.completionCache.has(cacheKey)) {
1161
1550
  this.clearTimer();
1162
- const text = this.completionCache.get(cacheKey), snapshot = this.snapshot("complete", span);
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);
1163
1559
  if (text && snapshot) this.patch({ suggestion: { text, snapshot, kind: "complete" } });
1164
1560
  return;
1165
1561
  }
1166
1562
  if (this.timer !== void 0 && this.timerKey === cacheKey) return;
1167
1563
  this.clearTimer();
1168
- const delay = Math.max(this.state.settings.debounceMs, this.lastCompletionStart + this.state.settings.minIntervalMs - Date.now());
1564
+ const delay = Math.max(this.state.settings.debounceMs, this.lastCompletionStart + this.state.settings.minIntervalMs - Date.now(), this.completionRetryAt - Date.now());
1169
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");
1170
1567
  this.timer = setTimeout(() => {
1171
1568
  this.timer = void 0;
1172
1569
  this.timerKey = null;
1173
1570
  void this.requestSuggestion("complete");
1174
1571
  }, delay);
1175
1572
  }
1176
- async requestSuggestion(operation) {
1573
+ async requestSuggestion(operation, manual = false, deferOnce = true) {
1177
1574
  const binding = this.binding;
1178
1575
  if (!binding || !this.eligible()) return;
1576
+ if (!manual && !(operation === "next" ? this.state.settings.nextEnabled : this.state.settings.completionEnabled)) return;
1179
1577
  const span = binding.actions.captureInsertion(), snapshot = this.snapshot(operation, span);
1180
1578
  if (!snapshot || span.start !== span.end || span.end !== binding.input.draft.length) return;
1579
+ if (manual) snapshot.manual = true;
1181
1580
  if (operation === "complete") this.lastCompletionStart = Date.now();
1182
- const active = this.begin(snapshot), cacheKey = this.completionKey();
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;
1183
1584
  try {
1184
1585
  const result = await this.transport.call("generate", snapshot, active.controller.signal);
1185
1586
  if (this.active !== active || !this.isCurrent(snapshot, true)) return;
1186
1587
  if (!this.validResult(result, snapshot)) {
1187
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
+ }
1188
1599
  return;
1189
1600
  }
1190
1601
  if (operation === "complete") {
1191
1602
  this.completionCache.set(cacheKey, result.text);
1192
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);
1193
1607
  }
1194
1608
  if (operation === "next" && result.text) this.nextCache = { text: result.text, turn: snapshot.sourceTurnId, route: snapshot.routeKey, generation: snapshot.connectionGeneration };
1195
- if (result.text) this.patch({ suggestion: { text: result.text, snapshot, kind: operation } });
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");
1196
1611
  } catch {
1197
1612
  if (operation === "complete" && !active.controller.signal.aborted && this.active === active && this.isCurrent(snapshot, false)) {
1198
1613
  this.failedCompletionKey = cacheKey;
1199
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");
1200
1617
  } finally {
1201
1618
  this.recordLatency(active);
1202
- if (this.active === active) this.active = null;
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
+ }
1203
1632
  }
1204
1633
  }
1205
1634
  snapshot(operation, span, targetText) {
@@ -1209,7 +1638,7 @@ ${draft.text}` : draft.text;
1209
1638
  return {
1210
1639
  operation,
1211
1640
  sessionId: this.sessionId,
1212
- requestId: `${Date.now().toString(36)}-${++this.sequence}`,
1641
+ requestId: `${this.requestPrefix}-${++this.sequence}`,
1213
1642
  sourceTurnId: status.sourceTurnId,
1214
1643
  draft: binding.input.draft,
1215
1644
  draftRev: binding.input.draftRev,
@@ -1268,7 +1697,7 @@ ${draft.text}` : draft.text;
1268
1697
  }, checkCaret));
1269
1698
  }
1270
1699
  validResult(result, request) {
1271
- 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");
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);
1272
1701
  }
1273
1702
  sameCaret(span) {
1274
1703
  const current = this.binding?.actions.captureInsertion();
@@ -1277,6 +1706,11 @@ ${draft.text}` : draft.text;
1277
1706
  routeKey(route) {
1278
1707
  return route ? JSON.stringify([route.provider, route.model]) : "";
1279
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
+ }
1280
1714
  completionKey() {
1281
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]);
1282
1716
  }
@@ -1288,13 +1722,16 @@ ${draft.text}` : draft.text;
1288
1722
  this.nextCache = null;
1289
1723
  this.completionCache.clear();
1290
1724
  this.failedCompletionKey = null;
1725
+ this.failedNextKey = null;
1726
+ this.completionRetryAt = 0;
1291
1727
  }
1292
- this.patch({ suggestion: null });
1728
+ this.patch({ suggestion: null, ...cancelInFlight ? { preview: null } : {} });
1293
1729
  }
1294
1730
  clearTimer() {
1295
1731
  clearTimeout(this.timer);
1296
1732
  this.timer = void 0;
1297
1733
  this.timerKey = null;
1734
+ this.manualTimerKey = null;
1298
1735
  }
1299
1736
  cancelActive() {
1300
1737
  const active = this.active;
@@ -1305,6 +1742,7 @@ ${draft.text}` : draft.text;
1305
1742
  void this.transport.call("cancel", { sessionId: this.sessionId, requestId: active.snapshot.requestId }).catch(() => {
1306
1743
  });
1307
1744
  if (active.snapshot.operation === "optimize") this.patch({ busy: null });
1745
+ else this.suggestionStatus("idle", "");
1308
1746
  }
1309
1747
  recordLatency(active) {
1310
1748
  if (active.latencyRecorded) return;
@@ -1339,11 +1777,15 @@ var BrowserRuntime = class {
1339
1777
  pendingSettings = 0;
1340
1778
  settingsQueue = Promise.resolve();
1341
1779
  metrics = /* @__PURE__ */ new Map();
1780
+ undo = /* @__PURE__ */ new Map();
1342
1781
  transport;
1343
1782
  controllerFor(sessionId) {
1344
1783
  let entry = this.entries.get(sessionId);
1345
1784
  if (!entry) {
1346
- entry = { controller: new InputAssistantController(sessionId, this.transport, this.metrics.get(sessionId), (update) => this.saveSettings(update)), refs: 0 };
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 };
1347
1789
  this.entries.set(sessionId, entry);
1348
1790
  if (this.pendingSettings) entry.controller.beginSettingsUpdate();
1349
1791
  }
@@ -1351,19 +1793,50 @@ var BrowserRuntime = class {
1351
1793
  }
1352
1794
  retain(sessionId) {
1353
1795
  const controller = this.controllerFor(sessionId), entry = this.entries.get(sessionId);
1796
+ entry.waitForMutation?.();
1797
+ entry.waitForMutation = void 0;
1354
1798
  entry.refs++;
1355
1799
  controller.start();
1356
1800
  return () => {
1357
1801
  entry.refs--;
1358
1802
  queueMicrotask(() => {
1359
1803
  if (entry.refs === 0 && this.entries.get(sessionId) === entry) {
1360
- this.metrics.set(sessionId, { ...controller.getSnapshot().metrics });
1361
- controller.dispose();
1362
- this.entries.delete(sessionId);
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();
1363
1822
  }
1364
1823
  });
1365
1824
  };
1366
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
+ }
1367
1840
  applySettings(settings) {
1368
1841
  for (const entry of this.entries.values()) entry.controller.applySettings(settings);
1369
1842
  }
@@ -1393,49 +1866,364 @@ var BrowserRuntime = class {
1393
1866
  if (this.disposed) return;
1394
1867
  this.disposed = true;
1395
1868
  this.lifetime.abort();
1396
- for (const entry of this.entries.values()) entry.controller.dispose();
1869
+ for (const entry of this.entries.values()) {
1870
+ entry.waitForMutation?.();
1871
+ entry.controller.dispose();
1872
+ }
1397
1873
  this.entries.clear();
1398
1874
  this.metrics.clear();
1875
+ for (const record of this.undo.values()) clearTimeout(record.timer);
1876
+ this.undo.clear();
1399
1877
  }
1400
1878
  };
1401
1879
 
1402
1880
  // src/browser/styles.ts
1403
1881
  var STYLE_TEXT = `
1404
- .dsh-ia {--dsh-ia-fallback-bg:#fff;--dsh-ia-fallback-fg:#0f1115;--dsh-ia-fallback-muted:#61666b;--dsh-ia-fallback-border:#d7dce2;font-family:var(--dsw-font-family,system-ui,sans-serif);color:var(--dsw-alias-label-primary,var(--dsh-ia-fallback-fg));font-size:13px;line-height:1.5;box-sizing:border-box;color-scheme:light}
1405
- body[data-ds-dark-theme] .dsh-ia {--dsh-ia-fallback-bg:#232324;--dsh-ia-fallback-fg:#f3f4f6;--dsh-ia-fallback-muted:#adb2b8;--dsh-ia-fallback-border:#43454a;color-scheme:dark}
1406
- .dsh-ia * {box-sizing:border-box}
1407
- .dsh-ia button,.dsh-ia input,.dsh-ia select {font:inherit;color:inherit}
1408
- .dsh-ia button {border:1px solid var(--dsw-alias-border-l2,var(--dsh-ia-fallback-border));border-radius:7px;padding:5px 9px;background:transparent;cursor:pointer}
1409
- .dsh-ia button:hover:not(:disabled) {background:var(--dsw-alias-interactive-bg-hover,#8882)}
1410
- .dsh-ia button:disabled {opacity:.45;cursor:default}
1411
- .dsh-ia button:focus-visible,.dsh-ia input:focus-visible,.dsh-ia select:focus-visible {outline:2px solid var(--dsw-alias-state-business-primary,#4c88e4);outline-offset:2px}
1412
- .dsh-ia-root {display:flex;align-items:center;gap:3px;position:relative}
1413
- .dsh-ia-settings {width:100%;max-width:760px}
1414
- .dsh-ia-settings h2 {font-size:18px;margin:0 0 8px}.dsh-ia-settings h3 {font-size:14px;margin:0 0 10px}
1415
- .dsh-ia-settings fieldset {padding:0;margin:0;border:0;min-width:0}
1416
- .dsh-ia-settings-toggles {display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}
1417
- .dsh-ia-settings-toggles label {display:flex;align-items:center;gap:7px}
1418
- .dsh-ia-trigger {white-space:nowrap}
1419
- .dsh-ia-menu {position:fixed;width:min(440px,calc(100vw - 24px));max-height:calc(100dvh - 24px);overflow:auto;overscroll-behavior:contain;padding:14px;border:1px solid var(--dsw-alias-border-l2,var(--dsh-ia-fallback-border));border-radius:12px;background:var(--dsw-alias-bg-layer-2,var(--dsh-ia-fallback-bg));box-shadow:0 8px 32px #0003;z-index:1000}
1420
- .dsh-ia-row {display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:8px 0}
1421
- .dsh-ia-row label {display:flex;align-items:center;gap:6px}
1422
- .dsh-ia-field {display:flex;flex-direction:column;gap:4px;margin:8px 0}
1423
- .dsh-ia-field input,.dsh-ia select {border:1px solid var(--dsw-alias-border-l2,var(--dsh-ia-fallback-border));border-radius:6px;background:var(--dsw-alias-bg-layer-1,var(--dsh-ia-fallback-bg));padding:6px}
1424
- .dsh-ia-field input {width:100%}
1425
- .dsh-ia-muted {color:var(--dsw-alias-label-secondary,var(--dsh-ia-fallback-muted));font-size:12px}
1426
- .dsh-ia-notice {position:fixed;max-width:calc(100vw - 24px);overflow:auto;padding:8px 11px;background:var(--dsw-alias-bg-layer-2,var(--dsh-ia-fallback-bg));border:1px solid var(--dsw-alias-border-l2,var(--dsh-ia-fallback-border));border-radius:9px;box-shadow:0 4px 18px #0002;z-index:999}
1427
- .dsh-ia-menu .dsh-ia-notice {position:static;max-width:100%;margin:8px 0;box-shadow:none}
1428
- .dsh-ia-section {border-top:1px solid var(--dsw-alias-border-l2,var(--dsh-ia-fallback-border));margin-top:12px;padding-top:10px}
1429
- .dsh-ia-section h4 {font-size:13px;margin:0 0 8px}
1430
- .dsh-ia-pre {white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;font-size:12px;max-height:190px;overflow:auto;padding:9px;border:1px solid var(--dsw-alias-border-l2,var(--dsh-ia-fallback-border));border-radius:7px;background:var(--dsw-alias-bg-module-platform,#8881)}
1431
- .dsh-ia-diff ins {background:#27a35b26;text-decoration:none}.dsh-ia-diff del {background:#d9454526}
1432
- .dsh-ia-draft {border:1px solid var(--dsw-alias-border-l2,var(--dsh-ia-fallback-border));border-radius:8px;padding:8px;margin:7px 0}
1433
- .dsh-ia-draft summary {cursor:pointer;overflow-wrap:anywhere}
1434
- .dsh-ia-draft time {font-size:11px;opacity:.65}
1435
- .dsh-ia-candidate {position:fixed;display:flex;align-items:center;gap:8px;padding:9px 12px;overflow:auto;border:1px solid var(--dsw-alias-border-l2,var(--dsh-ia-fallback-border));border-radius:10px;background:var(--dsw-alias-bg-layer-2,var(--dsh-ia-fallback-bg));box-shadow:0 4px 16px #0002;pointer-events:auto;z-index:998}
1436
- .dsh-ia-candidate-text {flex:1;white-space:pre-wrap;overflow-wrap:anywhere}
1437
- .dsh-ia-ghost {position:fixed;white-space:pre-wrap;overflow-wrap:anywhere;overflow:hidden;max-height:3lh;pointer-events:none;user-select:none;color:var(--dsw-alias-label-tertiary,#76808d);opacity:.9;z-index:34}
1438
- [data-composer-card]:has([data-dsh-ia-empty-ghost]) [data-composer-placeholder] {visibility:hidden}
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; } }
1439
2227
  `;
1440
2228
 
1441
2229
  // src/browser/settings.tsx
@@ -1447,8 +2235,8 @@ var numeric = {
1447
2235
  minIntervalMs: [500, 3e4],
1448
2236
  maxHistoryChars: [1e3, 64e3],
1449
2237
  maxDraftChars: [100, 32e3],
1450
- maxNextChars: [20, 300],
1451
- maxCompletionChars: [10, 200],
2238
+ maxNextChars: [20, 1e3],
2239
+ maxCompletionChars: [10, 500],
1452
2240
  optimizeTimeoutMs: [1e3, 12e4],
1453
2241
  suggestionTimeoutMs: [1e3, 6e4]
1454
2242
  };
@@ -1460,6 +2248,7 @@ function validate(config) {
1460
2248
  }
1461
2249
  if (!["ghost", "panel"].includes(config.displayMode)) throw new AssistantError("CONFIG", "\u663E\u793A\u65B9\u5F0F\u5FC5\u987B\u662F ghost \u6216 panel");
1462
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");
1463
2252
  for (const [key, [min, max]] of Object.entries(numeric)) {
1464
2253
  const value = config[key];
1465
2254
  if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
@@ -1481,16 +2270,44 @@ function resolveConfig(input = {}) {
1481
2270
  // src/browser/settings.tsx
1482
2271
  var import_jsx_runtime2 = require("react/jsx-runtime");
1483
2272
  var NUMBERS = [
1484
- ["debounceMs", "\u7EED\u5199\u505C\u987F", "\u6BEB\u79D2", 200, 5e3],
1485
- ["minIntervalMs", "\u7EED\u5199\u8BF7\u6C42\u6700\u5C0F\u95F4\u9694", "\u6BEB\u79D2", 500, 3e4],
1486
- ["maxHistoryChars", "\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u4E0A\u9650", "\u5B57\u7B26", 1e3, 64e3],
1487
- ["maxDraftChars", "\u5F85\u5904\u7406\u8349\u7A3F\u4E0A\u9650", "\u5B57\u7B26", 100, 32e3],
1488
- ["maxNextChars", "\u4E0B\u4E00\u53E5\u957F\u5EA6\u4E0A\u9650", "\u5B57\u7B26", 20, 300],
1489
- ["maxCompletionChars", "\u7EED\u5199\u957F\u5EA6\u4E0A\u9650", "\u5B57\u7B26", 10, 200],
1490
- ["optimizeTimeoutMs", "\u4F18\u5316\u8D85\u65F6", "\u6BEB\u79D2", 1e3, 12e4],
1491
- ["suggestionTimeoutMs", "\u81EA\u52A8\u5EFA\u8BAE\u8D85\u65F6", "\u6BEB\u79D2", 1e3, 6e4]
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
+ } }
1492
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}`;
1493
2309
  function AssistantSettings({ runtime }) {
2310
+ const fieldsId = (0, import_react2.useId)();
1494
2311
  const generation = (0, import_react2.useSyncExternalStore)(
1495
2312
  runtime.connection.generation.subscribe.bind(runtime.connection.generation),
1496
2313
  () => runtime.transport.generation(),
@@ -1502,13 +2319,22 @@ function AssistantSettings({ runtime }) {
1502
2319
  const [saving, setSaving] = (0, import_react2.useState)(false);
1503
2320
  const [message, setMessage] = (0, import_react2.useState)("");
1504
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);
1505
2328
  (0, import_react2.useEffect)(() => {
1506
2329
  const abort = new AbortController();
1507
2330
  setLoading(true);
2331
+ setLoadFailed(false);
2332
+ setAccepted(null);
1508
2333
  setError("");
1509
2334
  setMessage("");
1510
2335
  if (generation === null) {
1511
2336
  setLoading(false);
2337
+ setLoadFailed(true);
1512
2338
  setError("\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u91CD\u8FDE\u540E\u52A0\u8F7D\u8BBE\u7F6E");
1513
2339
  return () => abort.abort();
1514
2340
  }
@@ -1521,11 +2347,34 @@ function AssistantSettings({ runtime }) {
1521
2347
  }).catch((reason) => {
1522
2348
  if (!abort.signal.aborted) {
1523
2349
  setLoading(false);
2350
+ setLoadFailed(true);
1524
2351
  setError(reason instanceof Error ? reason.message : "\u8BBE\u7F6E\u52A0\u8F7D\u5931\u8D25");
1525
2352
  }
1526
2353
  });
1527
2354
  return () => abort.abort();
1528
- }, [runtime, generation]);
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]);
1529
2378
  const change = (key, value) => {
1530
2379
  setForm((previous) => ({ ...previous, [key]: value }));
1531
2380
  setMessage("");
@@ -1558,15 +2407,41 @@ function AssistantSettings({ runtime }) {
1558
2407
  }
1559
2408
  };
1560
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;
1561
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) => {
1562
2413
  void save(event);
1563
2414
  }, children: [
1564
2415
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { children: "\u8F93\u5165\u52A9\u624B" }),
1565
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u5E2E\u52A9\u4F60\u5F62\u6210\u51C6\u786E\u3001\u53EF\u53D1\u9001\u7684\u8F93\u5165\u3002\u5404\u529F\u80FD\u72EC\u7ACB\u5F00\u5173\uFF0C\u91C7\u7EB3\u5185\u5BB9\u540E\u4ECD\u7531\u4F60\u53D1\u9001\u3002" }),
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" }),
1566
2417
  loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" }),
1567
2418
  message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: message }),
1568
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" }),
1569
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
+ ] }),
1570
2445
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "dsh-ia-section", children: [
1571
2446
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u529F\u80FD\u5F00\u5173" }),
1572
2447
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-ia-settings-toggles", children: [
@@ -1587,7 +2462,8 @@ function AssistantSettings({ runtime }) {
1587
2462
  "\u8349\u7A3F\u6682\u5B58"
1588
2463
  ] })
1589
2464
  ] }),
1590
- /* @__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" })
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" })
1591
2467
  ] }),
1592
2468
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "dsh-ia-section", children: [
1593
2469
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u4F18\u5316\u4E0E\u5EFA\u8BAE" }),
@@ -1599,7 +2475,16 @@ function AssistantSettings({ runtime }) {
1599
2475
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "deep", children: "\u6DF1\u5EA6\u68B3\u7406" })
1600
2476
  ] })
1601
2477
  ] }),
1602
- /* @__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\u5E94\u7528\u540E\u53EF\u67E5\u770B\u6539\u52A8\u3002" }),
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" }),
1603
2488
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
1604
2489
  "\u5EFA\u8BAE\u663E\u793A\u65B9\u5F0F",
1605
2490
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: form.displayMode, onChange: (event) => change("displayMode", event.target.value), children: [
@@ -1610,36 +2495,93 @@ function AssistantSettings({ runtime }) {
1610
2495
  ] }),
1611
2496
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "dsh-ia-section", children: [
1612
2497
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u8F85\u52A9\u6A21\u578B" }),
1613
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-ia-muted", children: "\u4E24\u9879\u540C\u65F6\u7559\u7A7A\u65F6\u8DDF\u968F\u8F93\u5165\u6846\u6A21\u578B\u3002\u8F85\u52A9\u8C03\u7528\u5355\u72EC\u6D88\u8017\u6A21\u578B\u7528\u91CF\u3002" }),
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" }),
1614
2499
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
1615
- "\u63D0\u4F9B\u5546",
1616
- /* @__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" })
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
+ ] })
1617
2517
  ] }),
1618
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
1619
- "\u6A21\u578B",
1620
- /* @__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" })
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
+ ] })
1621
2541
  ] })
1622
2542
  ] }),
1623
2543
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { className: "dsh-ia-section", children: [
1624
2544
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("summary", { children: "\u9AD8\u7EA7\u8BBE\u7F6E" }),
1625
- NUMBERS.map(([key, label, unit, min, max]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-ia-field", children: [
1626
- label,
1627
- "\uFF08",
1628
- unit,
1629
- "\uFF09",
1630
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1631
- "input",
1632
- {
1633
- type: "number",
1634
- "aria-label": `${label}\uFF08${unit}\uFF09`,
1635
- value: Number.isNaN(form[key]) ? "" : form[key],
1636
- min,
1637
- max,
1638
- step: 1,
1639
- required: true,
1640
- onChange: (event) => change(key, event.target.value === "" ? NaN : Number(event.target.value))
1641
- }
1642
- ),
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
+ ] }),
1643
2585
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-ia-muted", children: [
1644
2586
  min.toLocaleString(),
1645
2587
  "\u2013",
@@ -1647,7 +2589,7 @@ function AssistantSettings({ runtime }) {
1647
2589
  " ",
1648
2590
  unit
1649
2591
  ] })
1650
- ] }, key))
2592
+ ] }, key)) })
1651
2593
  ] }),
1652
2594
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-ia-row", children: [
1653
2595
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", disabled: !changed, children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u8BBE\u7F6E" }),