@deepseek-ai/dsh-client-ui-primitives 0.1.1-rc.2 → 0.1.2-alpha.2

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 (45) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +90 -24
  3. package/README.zh.md +91 -25
  4. package/lib/ConnectionIndicator.module.css +107 -0
  5. package/lib/DiffBlock.module.css +0 -6
  6. package/lib/DisclosureRow.module.css +21 -6
  7. package/lib/ReadBlock.module.css +0 -6
  8. package/lib/SearchBlock.module.css +0 -8
  9. package/lib/TerminalBlock.module.css +0 -6
  10. package/lib/Toast.module.css +7 -10
  11. package/lib/Tooltip.module.css +0 -7
  12. package/lib/WebBlock.module.css +0 -6
  13. package/lib/index.js +878 -334
  14. package/lib/markdown/CodeBlock.module.css +4 -2
  15. package/lib/markdown/MarkdownText.module.css +2 -2
  16. package/lib/types/ConnectionIndicator.d.ts +25 -0
  17. package/lib/types/DiffBlock.d.ts +28 -9
  18. package/lib/types/FishLogo.d.ts +7 -0
  19. package/lib/types/FoldToggle.d.ts +20 -0
  20. package/lib/types/HoverCard.d.ts +4 -4
  21. package/lib/types/JsonTree.d.ts +4 -6
  22. package/lib/types/Modal.d.ts +22 -18
  23. package/lib/types/OnboardingSurface.d.ts +2 -2
  24. package/lib/types/ReadBlock.d.ts +13 -1
  25. package/lib/types/ReferenceIcon.d.ts +16 -0
  26. package/lib/types/RiskConfirmation.d.ts +2 -1
  27. package/lib/types/SearchBlock.d.ts +14 -0
  28. package/lib/types/TerminalBlock.d.ts +4 -10
  29. package/lib/types/Toast.d.ts +9 -1
  30. package/lib/types/WebBlock.d.ts +13 -0
  31. package/lib/types/icons/index.d.ts +8 -0
  32. package/lib/types/index.d.ts +14 -8
  33. package/lib/types/markdown/CodeBlock.d.ts +11 -3
  34. package/lib/types/markdown/JsonBlock.d.ts +1 -1
  35. package/lib/types/markdown/MarkdownText.d.ts +8 -7
  36. package/lib/types/markdown/highlight.d.ts +44 -2
  37. package/lib/types/markdown/render.d.ts +9 -4
  38. package/lib/types/relative-time.d.ts +23 -0
  39. package/lib/types/useAnchoredPosition.d.ts +8 -6
  40. package/lib/types/useDismissOnOutsidePointer.d.ts +3 -1
  41. package/lib/types/user-text.d.ts +19 -0
  42. package/lib/user-text.module.css +35 -0
  43. package/package.json +4 -5
  44. package/lib/ConnectionBanner.module.css +0 -13
  45. package/lib/types/ConnectionBanner.d.ts +0 -12
package/lib/index.js CHANGED
@@ -12,20 +12,21 @@ import css$6 from "./HoverCard.module.css";
12
12
  import css$7 from "./Modal.module.css";
13
13
  import css$8 from "./OnboardingSurface.module.css";
14
14
  import css$9 from "./RiskConfirmation.module.css";
15
- import css$10 from "./ConnectionBanner.module.css";
16
- import css$11 from "./Tooltip.module.css";
17
- import css$12 from "./Toast.module.css";
18
- import css$13 from "./JsonTree.module.css";
15
+ import css$10 from "./ConnectionIndicator.module.css";
16
+ import css$11 from "./user-text.module.css";
17
+ import css$12 from "./Tooltip.module.css";
18
+ import css$13 from "./Toast.module.css";
19
+ import css$14 from "./JsonTree.module.css";
19
20
  import Anser from "anser";
20
- import css$14 from "./TerminalBlock.module.css";
21
+ import css$15 from "./TerminalBlock.module.css";
21
22
  import { createCssVariablesTheme, createHighlighterCoreSync } from "shiki/core";
22
23
  import { createJavaScriptRegexEngine, defaultJavaScriptRegexConstructor } from "shiki/engine/javascript";
23
24
  import langTs from "@shikijs/langs/typescript";
24
25
  import langBash from "@shikijs/langs/shellscript";
25
26
  import langJson from "@shikijs/langs/json";
26
- import css$15 from "./ReadBlock.module.css";
27
- import css$16 from "./DiffBlock.module.css";
28
- import css$17 from "./SearchBlock.module.css";
27
+ import css$16 from "./ReadBlock.module.css";
28
+ import css$17 from "./DiffBlock.module.css";
29
+ import css$18 from "./SearchBlock.module.css";
29
30
  import { fromMarkdown } from "mdast-util-from-markdown";
30
31
  import { gfmFromMarkdown } from "mdast-util-gfm";
31
32
  import { mathFromMarkdown } from "mdast-util-math";
@@ -37,13 +38,13 @@ import { classifyCharacter } from "micromark-util-classify-character";
37
38
  import { codes, constants, types } from "micromark-util-symbol";
38
39
  import { factorySpace } from "micromark-factory-space";
39
40
  import { normalizeUri } from "micromark-util-sanitize-uri";
40
- import css$18 from "./markdown/CodeBlock.module.css";
41
+ import css$19 from "./markdown/CodeBlock.module.css";
41
42
  import katex from "katex";
42
- import css$19 from "./markdown/MarkdownText.module.css";
43
+ import css$20 from "./markdown/MarkdownText.module.css";
43
44
  import "katex/dist/katex.min.css";
44
- import css$20 from "./WebBlock.module.css";
45
- import css$21 from "./markdown/JsonBlock.module.css";
46
- import css$22 from "./markdown/MessageText.module.css";
45
+ import css$21 from "./WebBlock.module.css";
46
+ import css$22 from "./markdown/JsonBlock.module.css";
47
+ import css$23 from "./markdown/MessageText.module.css";
47
48
  //#region lib/types/StateDot.js
48
49
  /** Outer 3x3 matrix cells (2px pixels on a 10px grid), clockwise from top-left. */
49
50
  const MATRIX_CELLS = [
@@ -618,6 +619,33 @@ const IconBrowseOutline16 = ({ size = 16, className }) => jsxs("svg", {
618
619
  })
619
620
  ]
620
621
  });
622
+ /** ic_ds_context_injection_outline_16 (figma extract): browse document frame with an open top and an arrow dropping in. */
623
+ const IconContextInjectionOutline16 = ({ size = 16, className }) => jsxs("svg", {
624
+ width: size,
625
+ height: size,
626
+ className,
627
+ viewBox: "0 0 16 16",
628
+ fill: "none",
629
+ xmlns: "http://www.w3.org/2000/svg",
630
+ children: [
631
+ jsx("path", {
632
+ d: "M11.9512 1.13281C12.401 1.20666 12.8093 1.34164 13.1738 1.60645C13.4282 1.79137 13.6521 2.01609 13.8369 2.27051C14.1574 2.71187 14.2892 3.21614 14.3506 3.78223C14.4105 4.33532 14.4102 5.02658 14.4102 5.87305V10.0273C14.4102 10.8738 14.4105 11.5651 14.3506 12.1182C14.2892 12.6843 14.1574 13.1885 13.8369 13.6299C13.652 13.8843 13.4282 14.109 13.1738 14.2939C12.7324 14.6146 12.2273 14.7462 11.6611 14.8076C11.1081 14.8675 10.4166 14.8672 9.57031 14.8672H6.43164C5.58533 14.8672 4.89387 14.8675 4.34082 14.8076C3.77474 14.7463 3.27046 14.6144 2.8291 14.2939C2.57453 14.109 2.35003 13.8844 2.16504 13.6299C1.84444 13.1885 1.71272 12.6844 1.65137 12.1182C1.59147 11.5651 1.5918 10.8738 1.5918 10.0273V5.87305C1.5918 5.02655 1.59146 4.33533 1.65137 3.78223C1.71272 3.21606 1.84443 2.71191 2.16504 2.27051C2.35003 2.01596 2.57453 1.79141 2.8291 1.60645C3.19332 1.34202 3.60062 1.20669 4.0498 1.13281V2.56445C3.87191 2.61154 3.74906 2.66836 3.65137 2.73926C3.51583 2.83777 3.3964 2.95726 3.29785 3.09277C3.1794 3.25581 3.09143 3.4856 3.04297 3.93262C2.9931 4.39287 2.99219 4.99529 2.99219 5.87305V10.0273C2.99219 10.905 2.99312 11.5075 3.04297 11.9678C3.09142 12.4147 3.17943 12.6446 3.29785 12.8076C3.3964 12.9431 3.51583 13.0626 3.65137 13.1611C3.81441 13.2795 4.04437 13.3676 4.49121 13.416C4.95142 13.4658 5.55411 13.4668 6.43164 13.4668H9.57031C10.4479 13.4668 11.0505 13.4659 11.5107 13.416C11.9576 13.3675 12.1876 13.2796 12.3506 13.1611C12.4861 13.0626 12.6056 12.9431 12.7041 12.8076C12.8224 12.6446 12.9106 12.4146 12.959 11.9678C13.0088 11.5075 13.0098 10.905 13.0098 10.0273V5.87305C13.0098 4.99532 13.0088 4.39286 12.959 3.93262C12.9105 3.48579 12.8225 3.2558 12.7041 3.09277C12.6056 2.95727 12.4861 2.83778 12.3506 2.73926C12.2527 2.66816 12.1296 2.61064 11.9512 2.56348V1.13281Z",
633
+ fill: "currentColor"
634
+ }),
635
+ jsx("path", {
636
+ d: "M9.32227 11.4141H4.95508V10.2148H9.32227V11.4141Z",
637
+ fill: "currentColor"
638
+ }),
639
+ jsx("path", {
640
+ d: "M11.0439 8.90039H4.95508V7.70117H11.0439V8.90039Z",
641
+ fill: "currentColor"
642
+ }),
643
+ jsx("path", {
644
+ d: "M8.59961 3.75781L9.70996 2.64746L10.5586 3.49609L8.49512 5.55957C8.22173 5.83266 7.77816 5.83285 7.50488 5.55957L5.44141 3.49512L6.28906 2.64746L7.40039 3.75781V1.09668H8.59961V3.75781Z",
645
+ fill: "currentColor"
646
+ })
647
+ ]
648
+ });
621
649
  /** ic_ds_link_outline_14 */
622
650
  const IconLinkOutline14 = ({ size = 14, className }) => jsxs("svg", {
623
651
  width: size,
@@ -1127,6 +1155,55 @@ const IconDataOutline16 = ({ size = 16, className }) => jsxs("svg", {
1127
1155
  fill: "currentColor"
1128
1156
  })]
1129
1157
  });
1158
+ /** IconDataOutline16 without its gear: a three-tier database cylinder. */
1159
+ const IconDatabaseOutline16 = ({ size = 16, className }) => jsxs("svg", {
1160
+ width: size,
1161
+ height: size,
1162
+ className,
1163
+ viewBox: "0 0 16 16",
1164
+ fill: "none",
1165
+ xmlns: "http://www.w3.org/2000/svg",
1166
+ children: [
1167
+ jsx("ellipse", {
1168
+ cx: "8",
1169
+ cy: "3.6",
1170
+ rx: "5.75",
1171
+ ry: "2.4",
1172
+ stroke: "currentColor",
1173
+ strokeWidth: "1.25"
1174
+ }),
1175
+ jsx("path", {
1176
+ d: "M2.25 3.6V12.3A5.75 2.4 0 0 0 13.75 12.3V3.6",
1177
+ stroke: "currentColor",
1178
+ strokeWidth: "1.25"
1179
+ }),
1180
+ jsx("path", {
1181
+ d: "M2.25 7.95A5.75 2.4 0 0 0 13.75 7.95",
1182
+ stroke: "currentColor",
1183
+ strokeWidth: "1.25"
1184
+ })
1185
+ ]
1186
+ });
1187
+ /** Thin-stroke clock: outlined dial with square-cut hour and minute hands. */
1188
+ const IconClockOutline16 = ({ size = 16, className }) => jsxs("svg", {
1189
+ width: size,
1190
+ height: size,
1191
+ className,
1192
+ viewBox: "0 0 16 16",
1193
+ fill: "none",
1194
+ xmlns: "http://www.w3.org/2000/svg",
1195
+ children: [jsx("circle", {
1196
+ cx: "8",
1197
+ cy: "8",
1198
+ r: "6.375",
1199
+ stroke: "currentColor",
1200
+ strokeWidth: "1.25"
1201
+ }), jsx("path", {
1202
+ d: "M8 4.4V8.3L10.7 9.85",
1203
+ stroke: "currentColor",
1204
+ strokeWidth: "1.25"
1205
+ })]
1206
+ });
1130
1207
  /** ic_send_outline_14 (figma extract): thin-stroke upward send arrow. */
1131
1208
  const IconSendOutline14 = ({ size = 14, className }) => jsx("svg", {
1132
1209
  width: size,
@@ -1318,6 +1395,56 @@ const IconQuestionOutline14 = ({ size = 14, className }) => jsxs("svg", {
1318
1395
  })
1319
1396
  ]
1320
1397
  });
1398
+ /** Alarm clock outline for active scheduled-task indicators. */
1399
+ const IconAlarmClockOutline16 = ({ size = 16, className }) => jsxs("svg", {
1400
+ "aria-hidden": "true",
1401
+ width: size,
1402
+ height: size,
1403
+ className,
1404
+ viewBox: "0 0 16 16",
1405
+ fill: "none",
1406
+ xmlns: "http://www.w3.org/2000/svg",
1407
+ children: [
1408
+ jsx("path", {
1409
+ d: "M3.5 2.5 1.75 4",
1410
+ stroke: "currentColor",
1411
+ strokeWidth: "1.25",
1412
+ strokeLinecap: "round"
1413
+ }),
1414
+ jsx("path", {
1415
+ d: "M12.5 2.5 14.25 4",
1416
+ stroke: "currentColor",
1417
+ strokeWidth: "1.25",
1418
+ strokeLinecap: "round"
1419
+ }),
1420
+ jsx("circle", {
1421
+ cx: "8",
1422
+ cy: "8.5",
1423
+ r: "4.75",
1424
+ stroke: "currentColor",
1425
+ strokeWidth: "1.25"
1426
+ }),
1427
+ jsx("path", {
1428
+ d: "M8 5.75V8.5L10 9.75",
1429
+ stroke: "currentColor",
1430
+ strokeWidth: "1.25",
1431
+ strokeLinecap: "round",
1432
+ strokeLinejoin: "round"
1433
+ }),
1434
+ jsx("path", {
1435
+ d: "m4.75 12.25-1 1.5",
1436
+ stroke: "currentColor",
1437
+ strokeWidth: "1.25",
1438
+ strokeLinecap: "round"
1439
+ }),
1440
+ jsx("path", {
1441
+ d: "m11.25 12.25 1 1.5",
1442
+ stroke: "currentColor",
1443
+ strokeWidth: "1.25",
1444
+ strokeLinecap: "round"
1445
+ })
1446
+ ]
1447
+ });
1321
1448
  /** ic_ds_archive_outline_20 (figma extract): lidded box + label slot. The export's
1322
1449
  * 0.11px stroke ring around the box contour is dropped — it restates the same
1323
1450
  * contour in the same ink, which currentColor already carries. */
@@ -1755,19 +1882,19 @@ function useAnchoredMaxHeight(ref, cap, signal) {
1755
1882
  *
1756
1883
  * A portaled panel is positioned from its anchor's viewport rect, which stops
1757
1884
  * being true the moment anything scrolls or the window resizes. This owns that
1758
- * one concern: measure the anchor, offset the panel below it, clamp the result
1759
- * inside the viewport, and re-run on scroll (capture phase, so scrollers nested
1760
- * inside the page are caught too), on resize, and on the panel's own size
1761
- * changes while the element is open.
1885
+ * one concern: measure the anchor, offset the panel below or above it, clamp
1886
+ * the result inside the viewport, and re-run on scroll (capture phase, so
1887
+ * scrollers nested inside the page are caught too), on resize, and on the
1888
+ * panel's own size changes while the element is open.
1762
1889
  * @module @deepseek-ai/dsh-client-ui-primitives/useAnchoredPosition
1763
1890
  */
1764
1891
  /**
1765
1892
  * Track an anchor and return the panel's fixed coordinates.
1766
- * @param options - the open state, the two refs, and the gap/margin distances.
1893
+ * @param options - the open state, the two refs, the placement side, and the gap/margin distances.
1767
1894
  * @returns `left`/`top` for the panel, or `null` before the first measurement.
1768
1895
  */
1769
1896
  function useAnchoredPosition(options) {
1770
- const { open, anchorRef, panelRef, gap, margin } = options;
1897
+ const { open, anchorRef, panelRef, side = "bottom", gap, margin } = options;
1771
1898
  const [position, setPosition] = useState(null);
1772
1899
  useLayoutEffect(() => {
1773
1900
  if (!open) {
@@ -1784,7 +1911,7 @@ function useAnchoredPosition(options) {
1784
1911
  const width = panel?.offsetWidth ?? 0;
1785
1912
  const height = panel?.offsetHeight ?? 0;
1786
1913
  let left = rect.left;
1787
- let top = rect.bottom + gap;
1914
+ let top = side === "top" ? rect.top - gap - height : rect.bottom + gap;
1788
1915
  if (width > 0) left = Math.min(Math.max(left, margin), window.innerWidth - width - margin);
1789
1916
  if (height > 0) top = Math.min(Math.max(top, margin), window.innerHeight - height - margin);
1790
1917
  /* v8 ignore stop */
@@ -1811,6 +1938,7 @@ function useAnchoredPosition(options) {
1811
1938
  open,
1812
1939
  anchorRef,
1813
1940
  panelRef,
1941
+ side,
1814
1942
  gap,
1815
1943
  margin
1816
1944
  ]);
@@ -1827,12 +1955,14 @@ function useAnchoredPosition(options) {
1827
1955
  * @param root - element containing both the trigger and the open surface.
1828
1956
  * @param open - whether the surface is showing; false detaches the listener.
1829
1957
  * @param setOpen - state setter invoked with false on an outside pointerdown.
1958
+ * @param portal - surface portaled outside the root (a `document.body` dialog)
1959
+ * that also counts as inside; omit when the root contains the whole popover.
1830
1960
  */
1831
- function useDismissOnOutsidePointer(root, open, setOpen) {
1961
+ function useDismissOnOutsidePointer(root, open, setOpen, portal) {
1832
1962
  useEffect(() => {
1833
1963
  if (!open) return;
1834
1964
  const closeOutside = (event) => {
1835
- if (event.target instanceof Node && !root.current?.contains(event.target)) setOpen(false);
1965
+ if (event.target instanceof Node && root.current?.contains(event.target) !== true && portal?.current?.contains(event.target) !== true) setOpen(false);
1836
1966
  };
1837
1967
  document.addEventListener("pointerdown", closeOutside);
1838
1968
  return () => {
@@ -1841,7 +1971,8 @@ function useDismissOnOutsidePointer(root, open, setOpen) {
1841
1971
  }, [
1842
1972
  root,
1843
1973
  open,
1844
- setOpen
1974
+ setOpen,
1975
+ portal
1845
1976
  ]);
1846
1977
  }
1847
1978
  //#endregion
@@ -1888,11 +2019,11 @@ async function writeClipboard(text) {
1888
2019
  * @param props.disabled - suppress opening; turning true closes an open card.
1889
2020
  * @param props.copyText - optional primary value copied by activation and
1890
2021
  * included in the card's accessible name.
1891
- * @param props.copyLabel - accessible activation-label prefix (default "复制").
1892
- * @param props.copiedLabel - visible success label (default "复制成功").
2022
+ * @param props.copyLabel - localized accessible activation-label prefix.
2023
+ * @param props.copiedLabel - localized visible success label.
1893
2024
  * @returns anchor wrapper with the conditional portaled card.
1894
2025
  */
1895
- function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyText, copyLabel = "复制", copiedLabel = "复制成功" }) {
2026
+ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyText, copyLabel, copiedLabel }) {
1896
2027
  const rootRef = useRef(null);
1897
2028
  const cardRef = useRef(null);
1898
2029
  const timerRef = useRef(null);
@@ -2058,23 +2189,20 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
2058
2189
  //#endregion
2059
2190
  //#region lib/types/Modal.js
2060
2191
  /**
2061
- * Render a centered modal over a blurred page mask.
2192
+ * Render a centered, body-portaled modal over a blurred page mask.
2062
2193
  * @param props.open - whether the dialog is showing.
2063
2194
  * @param props.onClose - Escape or mask click.
2064
2195
  * @param props.title - dialog heading (aria-label in every mode).
2065
- * @param props.closeLabel - accessible close-button label.
2196
+ * @param props.closeLabel - localized accessible close-button label.
2066
2197
  * @param props.description - optional supporting sentence under the title.
2067
2198
  * @param props.children - body (inputs, etc.).
2068
2199
  * @param props.footer - action row (Cancel / Create).
2069
2200
  * @param props.contentClassName - optional class for a scrollable content region.
2070
2201
  * @param props.headless - render children directly in the card (no default
2071
- * header/close/body chrome) for dialogs whose figma frame owns its own
2072
- * header structure; mask, card, Escape, and aria-label remain.
2073
- * @param props.closeLabel - close-button aria label; the owner passes
2074
- * localized copy (this package is cordis-free, so copy arrives via props).
2202
+ * header/close/body chrome); mask, card, Escape, and aria-label remain.
2075
2203
  * @returns null when closed; otherwise the overlay tree.
2076
2204
  */
2077
- function Modal({ open, onClose, title, closeLabel = "Close", description, children, footer, className, contentClassName, headless = false }) {
2205
+ function Modal({ open, onClose, title, closeLabel, description, children, footer, className, contentClassName, headless = false }) {
2078
2206
  useEffect(() => {
2079
2207
  if (!open) return;
2080
2208
  const onKeyDown = (e) => {
@@ -2133,8 +2261,8 @@ function Modal({ open, onClose, title, closeLabel = "Close", description, childr
2133
2261
  //#endregion
2134
2262
  //#region lib/types/OnboardingSurface.js
2135
2263
  /**
2136
- * Render the onboarding takeover chrome (mask + opaque stage) around one
2137
- * step's content and keep the application root inert while mounted.
2264
+ * Render a body-portaled onboarding stage and keep the application root inert
2265
+ * while mounted.
2138
2266
  * @param props.children - the step's page content, centered on the stage.
2139
2267
  * @returns the body-portaled overlay tree.
2140
2268
  */
@@ -2169,11 +2297,12 @@ function OnboardingSurface({ children }) {
2169
2297
  * Render one in-page confirmation whose primary action is unavailable until
2170
2298
  * the caller-controlled acknowledgement is checked.
2171
2299
  */
2172
- function RiskConfirmation({ open, title, description, acknowledgeLabel, cancelLabel, confirmLabel, acknowledged, disabled = false, onAcknowledgedChange, onCancel, onConfirm }) {
2300
+ function RiskConfirmation({ open, title, description, acknowledgeLabel, cancelLabel, closeLabel, confirmLabel, acknowledged, disabled = false, onAcknowledgedChange, onCancel, onConfirm }) {
2173
2301
  return jsxs(Modal, {
2174
2302
  open,
2175
2303
  onClose: onCancel,
2176
2304
  title,
2305
+ closeLabel,
2177
2306
  className: css$9.confirmation ?? "",
2178
2307
  contentClassName: css$9.confirmationContent ?? "",
2179
2308
  footer: jsxs(Fragment, { children: [jsx(Button, {
@@ -2209,23 +2338,112 @@ function RiskConfirmation({ open, title, description, acknowledgeLabel, cancelLa
2209
2338
  });
2210
2339
  }
2211
2340
  //#endregion
2212
- //#region lib/types/ConnectionBanner.js
2341
+ //#region lib/types/ConnectionIndicator.js
2213
2342
  /**
2214
- * Render the reconnecting banner.
2215
- * @param props.reconnecting - true while the connection is in backoff/retry.
2216
- * @param props.label - banner text; the owner passes localized copy (this
2217
- * package is cordis-free, so copy arrives via props).
2218
- * @returns the banner, or null when connected.
2343
+ * Render an inline connection-recovery control.
2344
+ * @param props.state - visible outage, retry-attempt, or recovered state.
2345
+ * @param props.disconnectedLabel - localized outage text.
2346
+ * @param props.reconnectLabel - localized action text shown on hover or focus.
2347
+ * @param props.connectingLabel - localized retry text followed by the attempt dots.
2348
+ * @param props.recoveredLabel - localized recovery confirmation.
2349
+ * @param props.reconnectActionLabel - accessible label for the outage action.
2350
+ * @param props.restartActionLabel - accessible label for replacing an active attempt.
2351
+ * @param props.onReconnect - request an immediate reconnect attempt.
2352
+ * @returns the indicator, or null when no connection feedback is active.
2219
2353
  */
2220
- function ConnectionBanner({ reconnecting, label = "连接已断开,正在重连…" }) {
2221
- if (!reconnecting) return null;
2222
- return jsx("div", {
2223
- className: css$10.banner,
2224
- children: label
2354
+ function ConnectionIndicator({ state, disconnectedLabel, reconnectLabel, connectingLabel, recoveredLabel, reconnectActionLabel, restartActionLabel, onReconnect }) {
2355
+ if (state === void 0) return null;
2356
+ const sizeLabels = jsxs(Fragment, { children: [
2357
+ jsx("span", {
2358
+ className: css$10.sizeLabel,
2359
+ "aria-hidden": "true",
2360
+ children: disconnectedLabel
2361
+ }),
2362
+ jsx("span", {
2363
+ className: css$10.sizeLabel,
2364
+ "aria-hidden": "true",
2365
+ children: reconnectLabel
2366
+ }),
2367
+ jsxs("span", {
2368
+ className: css$10.sizeLabel,
2369
+ "aria-hidden": "true",
2370
+ children: [connectingLabel, jsx("span", {
2371
+ className: css$10.dots,
2372
+ children: "..."
2373
+ })]
2374
+ }),
2375
+ jsx("span", {
2376
+ className: css$10.sizeLabel,
2377
+ "aria-hidden": "true",
2378
+ children: recoveredLabel
2379
+ })
2380
+ ] });
2381
+ if (state === "recovered") return jsxs("div", {
2382
+ className: `${css$10.indicator} ${css$10.success}`,
2383
+ role: "status",
2384
+ "aria-label": recoveredLabel,
2385
+ children: [jsx("span", {
2386
+ className: css$10.icon,
2387
+ "aria-hidden": "true",
2388
+ children: jsx(IconCheckOutline16, { size: 14 })
2389
+ }), jsxs("span", {
2390
+ className: css$10.label,
2391
+ children: [sizeLabels, jsx("span", {
2392
+ className: css$10.stateLabel,
2393
+ children: recoveredLabel
2394
+ })]
2395
+ })]
2396
+ });
2397
+ const connecting = state === "connecting";
2398
+ return jsxs("button", {
2399
+ type: "button",
2400
+ className: `${css$10.indicator} ${css$10.warning}`,
2401
+ "data-phase": state,
2402
+ "aria-label": connecting ? restartActionLabel : reconnectActionLabel,
2403
+ onClick: onReconnect,
2404
+ children: [jsx("span", {
2405
+ className: css$10.icon,
2406
+ "aria-hidden": "true",
2407
+ children: jsx(IconWarningOutline16, { size: 14 })
2408
+ }), jsxs("span", {
2409
+ className: css$10.label,
2410
+ children: [
2411
+ sizeLabels,
2412
+ jsx("span", {
2413
+ className: css$10.stateLabel,
2414
+ children: connecting ? jsxs(Fragment, { children: [connectingLabel, jsxs("span", {
2415
+ className: css$10.dots,
2416
+ "aria-hidden": "true",
2417
+ children: [
2418
+ jsx("span", { children: "." }),
2419
+ jsx("span", {
2420
+ className: css$10.secondDot,
2421
+ children: "."
2422
+ }),
2423
+ jsx("span", {
2424
+ className: css$10.thirdDot,
2425
+ children: "."
2426
+ })
2427
+ ]
2428
+ })] }) : disconnectedLabel
2429
+ }),
2430
+ jsx("span", {
2431
+ className: css$10.hoverLabel,
2432
+ children: reconnectLabel
2433
+ })
2434
+ ]
2435
+ })]
2225
2436
  });
2226
2437
  }
2227
2438
  //#endregion
2228
2439
  //#region lib/types/FishLogo.js
2440
+ /** Native viewBox of {@link FISH_LOGO_PATH} (width and height in user units). */
2441
+ const FISH_LOGO_VIEWBOX = {
2442
+ width: 23.16,
2443
+ height: 17.04
2444
+ };
2445
+ /** The fish silhouette path data, exported for consumers that compose their own svg (entrance effects, masks) around the same geometry. */
2446
+ const FISH_LOGO_PATH = "M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018ZM11.1749 14.4736C9.15936 12.889 8.18184 12.3675 7.77832 12.39C7.40081 12.4125 7.46881 12.8445 7.55182 13.126C7.63882 13.404 7.75182 13.5955 7.91033 13.8396C8.01983 14.0011 8.09533 14.2411 7.80083 14.4216C7.15181 14.8231 6.02327 14.2866 5.97027 14.2601C4.65673 13.4865 3.5587 12.4655 2.78467 11.069C2.03715 9.72493 1.60314 8.28289 1.53164 6.74384C1.51264 6.37233 1.62214 6.24082 1.99215 6.17332C2.47916 6.08332 2.98118 6.06432 3.46769 6.13582C5.52476 6.43633 7.27581 7.35586 8.74385 8.8129C9.58188 9.64243 10.2159 10.634 10.8689 11.6025C11.5634 12.631 12.3105 13.611 13.262 14.4146C13.598 14.6961 13.866 14.9101 14.1225 15.0681C13.349 15.1546 12.058 15.1731 11.1749 14.4746L11.1749 14.4736ZM12.141 8.25988C12.141 8.09488 12.273 7.96338 12.439 7.96338C12.4765 7.96338 12.5105 7.97088 12.541 7.98188C12.5825 7.99688 12.6205 8.01938 12.6505 8.05338C12.7035 8.10588 12.7335 8.18088 12.7335 8.25988C12.7335 8.42489 12.6015 8.55639 12.4355 8.55639C12.2695 8.55639 12.141 8.42489 12.141 8.25988ZM15.1415 9.79893C14.949 9.87793 14.7565 9.94544 14.5715 9.95294C14.2845 9.96794 13.9715 9.85143 13.8015 9.70893C13.5375 9.48742 13.3485 9.36342 13.2695 8.97691C13.2355 8.8119 13.2545 8.55639 13.2845 8.40989C13.3525 8.09438 13.277 7.89187 13.0545 7.70787C12.8735 7.55786 12.643 7.51636 12.39 7.51636C12.2955 7.51636 12.209 7.47486 12.1445 7.44136C12.039 7.38886 11.9519 7.25735 12.035 7.09585C12.0615 7.04335 12.19 6.91584 12.22 6.89334C12.5635 6.69784 12.9595 6.76184 13.326 6.90834C13.6655 7.04735 13.9225 7.30236 14.292 7.66287C14.6695 8.09838 14.7375 8.21838 14.9525 8.54539C15.1225 8.8009 15.277 9.06341 15.3831 9.36392C15.4471 9.55142 15.3641 9.70493 15.1415 9.79893Z";
2229
2447
  /**
2230
2448
  * Render the fish logo.
2231
2449
  * @param props.size - width in px (default 24; height keeps the 23.16:17.04 ratio).
@@ -2235,13 +2453,13 @@ function ConnectionBanner({ reconnecting, label = "连接已断开,正在重
2235
2453
  function FishLogo({ size = 24, className }) {
2236
2454
  return jsx("svg", {
2237
2455
  width: size,
2238
- height: size * 17.04 / 23.16,
2456
+ height: size * FISH_LOGO_VIEWBOX.height / FISH_LOGO_VIEWBOX.width,
2239
2457
  className,
2240
- viewBox: "0 0 23.16 17.04",
2458
+ viewBox: `0 0 ${FISH_LOGO_VIEWBOX.width} ${FISH_LOGO_VIEWBOX.height}`,
2241
2459
  fill: "none",
2242
2460
  "aria-hidden": "true",
2243
2461
  children: jsx("path", {
2244
- d: "M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018ZM11.1749 14.4736C9.15936 12.889 8.18184 12.3675 7.77832 12.39C7.40081 12.4125 7.46881 12.8445 7.55182 13.126C7.63882 13.404 7.75182 13.5955 7.91033 13.8396C8.01983 14.0011 8.09533 14.2411 7.80083 14.4216C7.15181 14.8231 6.02327 14.2866 5.97027 14.2601C4.65673 13.4865 3.5587 12.4655 2.78467 11.069C2.03715 9.72493 1.60314 8.28289 1.53164 6.74384C1.51264 6.37233 1.62214 6.24082 1.99215 6.17332C2.47916 6.08332 2.98118 6.06432 3.46769 6.13582C5.52476 6.43633 7.27581 7.35586 8.74385 8.8129C9.58188 9.64243 10.2159 10.634 10.8689 11.6025C11.5634 12.631 12.3105 13.611 13.262 14.4146C13.598 14.6961 13.866 14.9101 14.1225 15.0681C13.349 15.1546 12.058 15.1731 11.1749 14.4746L11.1749 14.4736ZM12.141 8.25988C12.141 8.09488 12.273 7.96338 12.439 7.96338C12.4765 7.96338 12.5105 7.97088 12.541 7.98188C12.5825 7.99688 12.6205 8.01938 12.6505 8.05338C12.7035 8.10588 12.7335 8.18088 12.7335 8.25988C12.7335 8.42489 12.6015 8.55639 12.4355 8.55639C12.2695 8.55639 12.141 8.42489 12.141 8.25988ZM15.1415 9.79893C14.949 9.87793 14.7565 9.94544 14.5715 9.95294C14.2845 9.96794 13.9715 9.85143 13.8015 9.70893C13.5375 9.48742 13.3485 9.36342 13.2695 8.97691C13.2355 8.8119 13.2545 8.55639 13.2845 8.40989C13.3525 8.09438 13.277 7.89187 13.0545 7.70787C12.8735 7.55786 12.643 7.51636 12.39 7.51636C12.2955 7.51636 12.209 7.47486 12.1445 7.44136C12.039 7.38886 11.9519 7.25735 12.035 7.09585C12.0615 7.04335 12.19 6.91584 12.22 6.89334C12.5635 6.69784 12.9595 6.76184 13.326 6.90834C13.6655 7.04735 13.9225 7.30236 14.292 7.66287C14.6695 8.09838 14.7375 8.21838 14.9525 8.54539C15.1225 8.8009 15.277 9.06341 15.3831 9.36392C15.4471 9.55142 15.3641 9.70493 15.1415 9.79893Z",
2462
+ d: FISH_LOGO_PATH,
2245
2463
  fill: "currentColor"
2246
2464
  })
2247
2465
  });
@@ -2369,6 +2587,120 @@ function BrandWordmark({ size = 24, className, includeMark = true }) {
2369
2587
  });
2370
2588
  }
2371
2589
  //#endregion
2590
+ //#region lib/types/ReferenceIcon.js
2591
+ /**
2592
+ * Render the icon that identifies one inline reference domain.
2593
+ * @param props - Reference kind, optional size, and optional CSS class.
2594
+ * @returns The corresponding current-color SVG glyph.
2595
+ */
2596
+ function ReferenceIcon({ kind, size = 16, className }) {
2597
+ switch (kind) {
2598
+ case "session": return jsx("svg", {
2599
+ width: size,
2600
+ height: size,
2601
+ className,
2602
+ viewBox: "0 0 16 16",
2603
+ fill: "none",
2604
+ "aria-hidden": true,
2605
+ children: jsx("path", {
2606
+ d: "M8 0.597656C3.91296 0.597656 0.599716 3.91103 0.599609 7.99805C0.599609 9.13171 0.854567 10.2079 1.31152 11.1699L1.59277 11.7607L2.77441 11.1992L2.49414 10.6084L2.36035 10.3076C2.06865 9.59612 1.90723 8.81645 1.90723 7.99805C1.90733 4.63362 4.63554 1.90625 8 1.90625C11.3644 1.90635 14.0917 4.63368 14.0918 7.99805C14.0918 11.3625 11.3644 14.0907 8 14.0908C7.311 14.0908 6.80642 14.0414 6.35938 13.918C5.919 13.7963 5.50105 13.5929 5.00098 13.2441C4.26805 12.7329 3.21756 12.5526 2.35156 13.0996L2.33789 13.1084L2.32422 13.1182L1.74805 13.5234L2.18164 14.8184L3.05957 14.2002C3.37505 14.0068 3.84248 14.0319 4.25195 14.3174C4.84447 14.7307 5.39718 15.009 6.01172 15.1787C6.61963 15.3465 7.25579 15.3984 8 15.3984C12.087 15.3983 15.4004 12.0851 15.4004 7.99805C15.4003 3.9111 12.087 0.59776 8 0.597656ZM4.56836 8.50977V9.80371H8.12402V8.50977H4.56836ZM4.56836 7.30078H11.4619V6.00684H4.56836V7.30078Z",
2607
+ fill: "currentColor"
2608
+ })
2609
+ });
2610
+ case "file": return jsx(IconBrowseOutline16, {
2611
+ size,
2612
+ className
2613
+ });
2614
+ case "folder": return jsx(IconFolderClose16, {
2615
+ size,
2616
+ className
2617
+ });
2618
+ }
2619
+ }
2620
+ //#endregion
2621
+ //#region lib/types/user-text.js
2622
+ /** The wire form a session chip serializes to; label is the display text. */
2623
+ const SESSION_WIRE_RE = /@\[([^\]\n]+)\]\(dsh-session:[^)\s]+\)/gu;
2624
+ /**
2625
+ * Split one sent text into inline plain runs and reference chips.
2626
+ * @param text - the logged model text of the message or queue row.
2627
+ * @param sessionLabels - exact session mention labels associated by an adjacent recall.
2628
+ * @returns inline nodes covering the whole text.
2629
+ */
2630
+ function projectUserText(text, sessionLabels) {
2631
+ const ranges = [];
2632
+ SESSION_WIRE_RE.lastIndex = 0;
2633
+ let wire;
2634
+ while ((wire = SESSION_WIRE_RE.exec(text)) !== null) ranges.push({
2635
+ start: wire.index,
2636
+ end: wire.index + wire[0].length,
2637
+ label: wire[0],
2638
+ kind: "session",
2639
+ display: wire[1]
2640
+ });
2641
+ for (const rawLabel of [...new Set(sessionLabels)].sort((a, b) => b.length - a.length)) {
2642
+ const label = `@${rawLabel}`;
2643
+ let start = text.indexOf(label);
2644
+ while (start >= 0) {
2645
+ ranges.push({
2646
+ start,
2647
+ end: start + label.length,
2648
+ label,
2649
+ kind: "session"
2650
+ });
2651
+ start = text.indexOf(label, start + label.length);
2652
+ }
2653
+ }
2654
+ const re = /(^|\s)(\/[\w-]+|@"[^"\n]+"|@[^\s]+)/gu;
2655
+ let m;
2656
+ while ((m = re.exec(text)) !== null) {
2657
+ const tokenStart = m.index + m[1].length;
2658
+ const rawLabel = m[2];
2659
+ const label = rawLabel.startsWith("@\"") ? rawLabel : rawLabel.replace(/[.,;:!?,。;:!?]+$/gu, "");
2660
+ if (label.length <= 1) continue;
2661
+ ranges.push({
2662
+ start: tokenStart,
2663
+ end: tokenStart + label.length,
2664
+ label,
2665
+ kind: "plain"
2666
+ });
2667
+ }
2668
+ const rankOf = (range) => range.kind === "session" ? 0 : 1;
2669
+ ranges.sort((a, b) => a.start - b.start || rankOf(a) - rankOf(b) || b.end - a.end);
2670
+ const parts = [];
2671
+ let cursor = 0;
2672
+ const pushPlain = (from, to) => {
2673
+ parts.push(jsx("span", {
2674
+ className: css$11.plainRun,
2675
+ children: text.slice(from, to)
2676
+ }, `t${from}`));
2677
+ };
2678
+ for (const range of ranges) {
2679
+ if (range.start < cursor) continue;
2680
+ const { start: tokenStart, end, label, kind } = range;
2681
+ if (tokenStart > cursor) pushPlain(cursor, tokenStart);
2682
+ const referenceKind = kind === "session" ? "session" : label.startsWith("@") ? label.endsWith("/") ? "folder" : "file" : void 0;
2683
+ const displayLabel = range.display ?? (referenceKind === void 0 ? label : referenceKind === "session" ? label.slice(1) : label.slice(1).replace(/^"|"$/gu, "").split(/[\\/]/u).filter(Boolean).at(-1) ?? label.slice(1));
2684
+ parts.push(jsxs("span", {
2685
+ className: css$11.refChip,
2686
+ "data-ref-chip": referenceKind ?? "skill",
2687
+ title: label,
2688
+ children: [referenceKind !== void 0 && jsx(ReferenceIcon, {
2689
+ kind: referenceKind,
2690
+ size: 16,
2691
+ className: css$11.refIcon
2692
+ }), displayLabel]
2693
+ }, tokenStart));
2694
+ cursor = end;
2695
+ }
2696
+ if (parts.length === 0) return jsx("span", {
2697
+ className: css$11.plainRun,
2698
+ children: text
2699
+ });
2700
+ if (cursor < text.length) pushPlain(cursor, text.length);
2701
+ return jsx(Fragment, { children: parts });
2702
+ }
2703
+ //#endregion
2372
2704
  //#region lib/types/Tooltip.js
2373
2705
  /**
2374
2706
  * Attach a hover/focus tooltip to an anchor element.
@@ -2500,7 +2832,7 @@ function Tooltip({ label, side = "right", delayMs = 0, disabled = false, maxWidt
2500
2832
  }
2501
2833
  }), pos !== null && jsx("span", {
2502
2834
  ref: bubble,
2503
- className: css$11.bubble,
2835
+ className: css$12.bubble,
2504
2836
  "data-side": placement,
2505
2837
  style: {
2506
2838
  left: pos.x,
@@ -2513,6 +2845,10 @@ function Tooltip({ label, side = "right", delayMs = 0, disabled = false, maxWidt
2513
2845
  }
2514
2846
  //#endregion
2515
2847
  //#region lib/types/Toast.js
2848
+ /** Full-opacity hold before the fade starts, when the owner names none. */
2849
+ const HOLD_MS = 3e3;
2850
+ /** Fade duration. Must agree with the stylesheet's toast-fade duration. */
2851
+ const FADE_MS = 1e3;
2516
2852
  /**
2517
2853
  * Transient top-center banner: slides in, holds at full opacity, fades out,
2518
2854
  * then reports done so the owner can unmount it. Re-showing the same text
@@ -2521,21 +2857,28 @@ function Tooltip({ label, side = "right", delayMs = 0, disabled = false, maxWidt
2521
2857
  * transformed or filtered ancestor cannot trap the fixed banner in that
2522
2858
  * ancestor's box.
2523
2859
  *
2860
+ * The hold is the owner's to set, because how long a banner has to stay
2861
+ * depends on how much there is to read: a one-line limit lands in the default
2862
+ * window, while a failure that names what broke does not. One value drives
2863
+ * both the unmount timer and the stylesheet's fade delay — the stylesheet
2864
+ * reads it as a custom property — so the two can no longer disagree and leave
2865
+ * the banner unmounting mid-fade.
2524
2866
  * @param props.text - resolved banner copy; the owner passes localized text.
2525
2867
  * @param props.icon - optional leading glyph (e.g. a warning icon).
2868
+ * @param props.holdMs - full-opacity hold before the fade; defaults to 3000.
2526
2869
  * @param props.anchor - optional element whose horizontal center the banner
2527
2870
  * follows (e.g. the composer card, so the banner centers over the chat column
2528
2871
  * rather than the whole window); omitted, it centers on the viewport.
2529
2872
  * @param props.onDone - called once the fade completes; unmount the toast here.
2530
2873
  * @returns the floating banner.
2531
2874
  */
2532
- function Toast({ text, icon, anchor, onDone }) {
2875
+ function Toast({ text, icon, anchor, holdMs = HOLD_MS, onDone }) {
2533
2876
  useEffect(() => {
2534
- const timer = setTimeout(onDone, 4e3);
2877
+ const timer = setTimeout(onDone, holdMs + FADE_MS);
2535
2878
  return () => {
2536
2879
  clearTimeout(timer);
2537
2880
  };
2538
- }, [onDone]);
2881
+ }, [holdMs, onDone]);
2539
2882
  const [left, setLeft] = useState(null);
2540
2883
  useLayoutEffect(() => {
2541
2884
  if (anchor == null) return;
@@ -2550,36 +2893,73 @@ function Toast({ text, icon, anchor, onDone }) {
2550
2893
  };
2551
2894
  }, [anchor]);
2552
2895
  return createPortal(jsxs("div", {
2553
- className: css$12.toast,
2896
+ className: css$13.toast,
2554
2897
  role: "alert",
2555
- style: left === null ? void 0 : { left },
2898
+ style: {
2899
+ ...left === null ? {} : { left },
2900
+ "--dsh-toast-hold": `${String(holdMs)}ms`
2901
+ },
2556
2902
  children: [icon !== void 0 && jsx("span", {
2557
- className: css$12.icon,
2903
+ className: css$13.icon,
2558
2904
  "aria-hidden": true,
2559
2905
  children: icon
2560
2906
  }), jsx("span", {
2561
- className: css$12.text,
2907
+ className: css$13.text,
2562
2908
  children: text
2563
2909
  })]
2564
2910
  }), document.body);
2565
2911
  }
2566
2912
  //#endregion
2913
+ //#region lib/types/relative-time.js
2914
+ /**
2915
+ * Compact relative-time bucketing shared by every surface that dates a
2916
+ * session. Bucketing is here so two surfaces naming the same session agree;
2917
+ * the words stay in each plugin's own dictionary, per locale-owned copy.
2918
+ *
2919
+ * @module @deepseek-ai/dsh-client-ui-primitives/relative-time
2920
+ */
2921
+ /**
2922
+ * Compact relative time, as a structured bucket the renderer localizes
2923
+ * ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
2924
+ * @param at - epoch ms of the dated moment.
2925
+ * @param now - current epoch ms (injected for pure rendering).
2926
+ * @returns the row's trailing time bucket and magnitude.
2927
+ */
2928
+ function relativeTime(at, now) {
2929
+ const MIN = 6e4;
2930
+ const HOUR = 36e5;
2931
+ const DAY = 864e5;
2932
+ const diff = Math.max(0, now - at);
2933
+ if (diff < MIN) return {
2934
+ unit: "now",
2935
+ n: 0
2936
+ };
2937
+ if (diff < HOUR) return {
2938
+ unit: "minutes",
2939
+ n: Math.floor(diff / MIN)
2940
+ };
2941
+ if (diff < DAY) return {
2942
+ unit: "hours",
2943
+ n: Math.floor(diff / HOUR)
2944
+ };
2945
+ if (diff < 30 * DAY) return {
2946
+ unit: "days",
2947
+ n: Math.floor(diff / DAY)
2948
+ };
2949
+ if (diff < 365 * DAY) return {
2950
+ unit: "months",
2951
+ n: Math.floor(diff / (30 * DAY))
2952
+ };
2953
+ return {
2954
+ unit: "years",
2955
+ n: Math.floor(diff / (365 * DAY))
2956
+ };
2957
+ }
2958
+ //#endregion
2567
2959
  //#region lib/types/JsonTree.js
2568
2960
  const OBJECT_PREVIEW_LIMIT = 4;
2569
2961
  const ARRAY_PREVIEW_LIMIT = 5;
2570
2962
  const PREVIEW_DEPTH_LIMIT = 2;
2571
- const DEFAULT_LABELS$1 = {
2572
- copyValue: "Copy value",
2573
- copyJson: "Copy JSON",
2574
- copyPath: "Copy property path",
2575
- copyPrettyJson: "Copy pretty JSON",
2576
- copyCompactJson: "Copy compact JSON",
2577
- copied: "Copied",
2578
- copyFailed: "Copy failed",
2579
- collapseNode: "Collapse JSON node",
2580
- expandNode: "Expand JSON node",
2581
- copyButtonTitle: (action) => `${action}; right-click for copy options`
2582
- };
2583
2963
  function valueCopyMenuItems(labels) {
2584
2964
  return [
2585
2965
  {
@@ -2624,35 +3004,35 @@ function bracketOf(value) {
2624
3004
  }
2625
3005
  function previewPrimitive(value) {
2626
3006
  if (value === null) return jsx("span", {
2627
- className: css$13.keywordValue,
3007
+ className: css$14.keywordValue,
2628
3008
  children: "null"
2629
3009
  });
2630
3010
  if (typeof value === "string") return jsx("span", {
2631
- className: css$13.stringValue,
3011
+ className: css$14.stringValue,
2632
3012
  children: JSON.stringify(value)
2633
3013
  });
2634
3014
  if (typeof value === "number") return jsx("span", {
2635
- className: css$13.numberValue,
3015
+ className: css$14.numberValue,
2636
3016
  children: String(value)
2637
3017
  });
2638
3018
  if (typeof value === "boolean") return jsx("span", {
2639
- className: css$13.keywordValue,
3019
+ className: css$14.keywordValue,
2640
3020
  children: String(value)
2641
3021
  });
2642
3022
  if (typeof value === "bigint") return jsx("span", {
2643
- className: css$13.otherValue,
3023
+ className: css$14.otherValue,
2644
3024
  children: value.toString()
2645
3025
  });
2646
3026
  if (typeof value === "undefined") return jsx("span", {
2647
- className: css$13.otherValue,
3027
+ className: css$14.otherValue,
2648
3028
  children: "undefined"
2649
3029
  });
2650
3030
  if (typeof value === "symbol") return jsx("span", {
2651
- className: css$13.otherValue,
3031
+ className: css$14.otherValue,
2652
3032
  children: value.description ?? "Symbol"
2653
3033
  });
2654
3034
  if (typeof value === "function") return jsx("span", {
2655
- className: css$13.otherValue,
3035
+ className: css$14.otherValue,
2656
3036
  children: value.name || "Function"
2657
3037
  });
2658
3038
  return null;
@@ -2666,71 +3046,71 @@ function previewValue(value, depth) {
2666
3046
  const [open, close] = bracketOf(value);
2667
3047
  return jsxs(Fragment, { children: [
2668
3048
  jsx("span", {
2669
- className: css$13.punctuation,
3049
+ className: css$14.punctuation,
2670
3050
  children: open
2671
3051
  }),
2672
3052
  depth >= PREVIEW_DEPTH_LIMIT ? jsx("span", {
2673
- className: css$13.previewEllipsis,
3053
+ className: css$14.previewEllipsis,
2674
3054
  children: "…"
2675
3055
  }) : visible.map(([key, item], index) => jsxs("span", { children: [
2676
3056
  index > 0 && jsx("span", {
2677
- className: css$13.punctuation,
3057
+ className: css$14.punctuation,
2678
3058
  children: ", "
2679
3059
  }),
2680
3060
  !array && jsxs(Fragment, { children: [jsx("span", {
2681
- className: css$13.previewProperty,
3061
+ className: css$14.previewProperty,
2682
3062
  children: key
2683
3063
  }), jsx("span", {
2684
- className: css$13.punctuation,
3064
+ className: css$14.punctuation,
2685
3065
  children: ": "
2686
3066
  })] }),
2687
3067
  previewValue(item, depth + 1)
2688
3068
  ] }, key)),
2689
3069
  depth < PREVIEW_DEPTH_LIMIT && entries.length > limit && jsx("span", {
2690
- className: css$13.previewEllipsis,
3070
+ className: css$14.previewEllipsis,
2691
3071
  children: ", …"
2692
3072
  }),
2693
3073
  jsx("span", {
2694
- className: css$13.punctuation,
3074
+ className: css$14.punctuation,
2695
3075
  children: close
2696
3076
  })
2697
3077
  ] });
2698
3078
  }
2699
3079
  function primitiveValue(value) {
2700
3080
  if (value === null) return jsx("span", {
2701
- className: css$13.keywordValue,
3081
+ className: css$14.keywordValue,
2702
3082
  children: "null"
2703
3083
  });
2704
3084
  if (typeof value === "string") return jsx("span", {
2705
- className: css$13.stringValue,
3085
+ className: css$14.stringValue,
2706
3086
  children: JSON.stringify(value)
2707
3087
  });
2708
3088
  if (typeof value === "boolean") return jsx("span", {
2709
- className: css$13.keywordValue,
3089
+ className: css$14.keywordValue,
2710
3090
  children: String(value)
2711
3091
  });
2712
3092
  if (typeof value === "number") return jsx("span", {
2713
- className: css$13.numberValue,
3093
+ className: css$14.numberValue,
2714
3094
  children: String(value)
2715
3095
  });
2716
3096
  if (typeof value === "bigint") return jsx("span", {
2717
- className: css$13.numberValue,
3097
+ className: css$14.numberValue,
2718
3098
  children: `${value.toString()}n`
2719
3099
  });
2720
3100
  if (value instanceof Date) return jsx("span", {
2721
- className: css$13.otherValue,
3101
+ className: css$14.otherValue,
2722
3102
  children: value.toISOString()
2723
3103
  });
2724
3104
  if (typeof value === "function") return jsxs("span", {
2725
- className: css$13.otherValue,
3105
+ className: css$14.otherValue,
2726
3106
  children: ["function() ", "{ }"]
2727
3107
  });
2728
3108
  if (typeof value === "undefined") return jsx("span", {
2729
- className: css$13.otherValue,
3109
+ className: css$14.otherValue,
2730
3110
  children: "undefined"
2731
3111
  });
2732
3112
  return jsx("span", {
2733
- className: css$13.otherValue,
3113
+ className: css$14.otherValue,
2734
3114
  children: value.toString()
2735
3115
  });
2736
3116
  }
@@ -2758,7 +3138,7 @@ function moveFocus(button, direction) {
2758
3138
  function NodeField({ field, expandable, onToggle }) {
2759
3139
  if (field === void 0) return null;
2760
3140
  return jsxs("span", {
2761
- className: clsx(css$13.label, expandable && css$13.clickableLabel),
3141
+ className: clsx(css$14.label, expandable && css$14.clickableLabel),
2762
3142
  onClick: expandable ? onToggle : void 0,
2763
3143
  children: [fieldText(field), ":"]
2764
3144
  });
@@ -2787,7 +3167,7 @@ function JsonTreeNode({ field, initialExpanded, labels, lastElement, onClaimTabS
2787
3167
  }
2788
3168
  };
2789
3169
  const row = (children, ariaExpanded) => jsx("div", {
2790
- className: css$13.row,
3170
+ className: css$14.row,
2791
3171
  role: "treeitem",
2792
3172
  "aria-expanded": ariaExpanded,
2793
3173
  onMouseOver: (event) => {
@@ -2807,7 +3187,7 @@ function JsonTreeNode({ field, initialExpanded, labels, lastElement, onClaimTabS
2807
3187
  }),
2808
3188
  primitiveValue(value),
2809
3189
  !lastElement && jsx("span", {
2810
- className: css$13.punctuation,
3190
+ className: css$14.punctuation,
2811
3191
  children: ","
2812
3192
  })
2813
3193
  ] }));
@@ -2819,22 +3199,22 @@ function JsonTreeNode({ field, initialExpanded, labels, lastElement, onClaimTabS
2819
3199
  onToggle: toggle
2820
3200
  }),
2821
3201
  jsx("span", {
2822
- className: css$13.punctuation,
3202
+ className: css$14.punctuation,
2823
3203
  children: open
2824
3204
  }),
2825
3205
  jsx("span", {
2826
- className: css$13.punctuation,
3206
+ className: css$14.punctuation,
2827
3207
  children: close
2828
3208
  }),
2829
3209
  !lastElement && jsx("span", {
2830
- className: css$13.punctuation,
3210
+ className: css$14.punctuation,
2831
3211
  children: ","
2832
3212
  })
2833
3213
  ] }));
2834
3214
  return row(jsxs(Fragment, { children: [
2835
3215
  jsx("span", {
2836
3216
  ref: expanderRef,
2837
- className: clsx(css$13.expander, expanded ? css$13.collapseIcon : css$13.expandIcon),
3217
+ className: clsx(css$14.expander, expanded ? css$14.collapseIcon : css$14.expandIcon),
2838
3218
  "data-json-expander": true,
2839
3219
  role: "button",
2840
3220
  "aria-label": expanded ? labels.collapseNode : labels.expandNode,
@@ -2853,17 +3233,17 @@ function JsonTreeNode({ field, initialExpanded, labels, lastElement, onClaimTabS
2853
3233
  onToggle: toggle
2854
3234
  }),
2855
3235
  jsx("span", {
2856
- className: css$13.preview,
3236
+ className: css$14.preview,
2857
3237
  children: previewValue(value, 0)
2858
3238
  }),
2859
3239
  !lastElement && jsx("span", {
2860
- className: css$13.punctuation,
3240
+ className: css$14.punctuation,
2861
3241
  children: ","
2862
3242
  }),
2863
3243
  expanded && jsx("ul", {
2864
3244
  id: contentsId,
2865
3245
  role: "group",
2866
- className: css$13.children,
3246
+ className: css$14.children,
2867
3247
  children: entries.map(([key, item], index) => jsx(JsonTreeNode, {
2868
3248
  field: key,
2869
3249
  value: item,
@@ -2900,11 +3280,7 @@ function copyText$2(target, mode) {
2900
3280
  * @param props - Parsed data, accessible label, and display options.
2901
3281
  * @returns A read-only JSON tree with an optionally fixed-open top level.
2902
3282
  */
2903
- function JsonTree({ data, label = "JSON", className, copyable = true, expandTopLevel = true, labels }) {
2904
- const copyLabels = useMemo(() => labels === void 0 ? DEFAULT_LABELS$1 : {
2905
- ...DEFAULT_LABELS$1,
2906
- ...labels
2907
- }, [labels]);
3283
+ function JsonTree({ data, label, className, copyable = true, expandTopLevel = true, labels }) {
2908
3284
  const rootEntries = entriesOf(data);
2909
3285
  const firstExpandableIndex = rootEntries.findIndex(([, value]) => isExpandableValue(value) && entriesOf(value).length > 0);
2910
3286
  const firstExpandableEntry = rootEntries[firstExpandableIndex];
@@ -3025,20 +3401,20 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
3025
3401
  const [rootOpen, rootClose] = bracketOf(data);
3026
3402
  const copyTargetIsObject = typeof copyTarget?.value === "object" && copyTarget.value !== null;
3027
3403
  const defaultCopyMode = copyTargetIsObject ? "prettyJson" : "value";
3028
- const copyTitle = copyState === "copied" ? copyLabels.copied : copyState === "failed" ? copyLabels.copyFailed : copyTargetIsObject ? copyLabels.copyPrettyJson : copyLabels.copyValue;
3404
+ const copyTitle = copyState === "copied" ? labels.copied : copyState === "failed" ? labels.copyFailed : copyTargetIsObject ? labels.copyPrettyJson : labels.copyValue;
3029
3405
  return jsxs("div", {
3030
3406
  ref: rootRef,
3031
- className: clsx(css$13.root, className),
3407
+ className: clsx(css$14.root, className),
3032
3408
  onMouseOver: handleRootMouseOver,
3033
3409
  onMouseLeave: () => {
3034
3410
  if (!copyMenuOpenRef.current) clearCopyTarget();
3035
3411
  },
3036
3412
  onScroll: handleScroll,
3037
3413
  children: [expandTopLevel ? jsxs("div", {
3038
- className: css$13.expandedTopLevel,
3414
+ className: css$14.expandedTopLevel,
3039
3415
  children: [
3040
3416
  jsx("div", {
3041
- className: clsx(css$13.row, css$13.topLevelBracket),
3417
+ className: clsx(css$14.row, css$14.topLevelBracket),
3042
3418
  "data-json-root-row": true,
3043
3419
  onMouseOver: (event) => {
3044
3420
  event.stopPropagation();
@@ -3048,19 +3424,19 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
3048
3424
  });
3049
3425
  },
3050
3426
  children: jsx("span", {
3051
- className: css$13.punctuation,
3427
+ className: css$14.punctuation,
3052
3428
  children: rootOpen
3053
3429
  })
3054
3430
  }),
3055
3431
  jsx("div", {
3056
3432
  "aria-label": label,
3057
- className: clsx(css$13.container, css$13.expandedTopLevelContainer),
3433
+ className: clsx(css$14.container, css$14.expandedTopLevelContainer),
3058
3434
  role: "tree",
3059
3435
  children: rootEntries.map(([key, value], index) => jsx(JsonTreeNode, {
3060
3436
  field: key,
3061
3437
  value,
3062
3438
  path: [Array.isArray(data) ? index : key],
3063
- labels: copyLabels,
3439
+ labels,
3064
3440
  lastElement: index === rootEntries.length - 1,
3065
3441
  initialExpanded: false,
3066
3442
  tabStopId,
@@ -3069,21 +3445,21 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
3069
3445
  }, key))
3070
3446
  }),
3071
3447
  jsx("div", {
3072
- className: clsx(css$13.row, css$13.topLevelBracket),
3448
+ className: clsx(css$14.row, css$14.topLevelBracket),
3073
3449
  children: jsx("span", {
3074
- className: css$13.punctuation,
3450
+ className: css$14.punctuation,
3075
3451
  children: rootClose
3076
3452
  })
3077
3453
  })
3078
3454
  ]
3079
3455
  }) : jsx("div", {
3080
3456
  "aria-label": label,
3081
- className: css$13.container,
3457
+ className: css$14.container,
3082
3458
  role: "tree",
3083
3459
  children: jsx(JsonTreeNode, {
3084
3460
  value: data,
3085
3461
  path: [],
3086
- labels: copyLabels,
3462
+ labels,
3087
3463
  lastElement: true,
3088
3464
  initialExpanded: true,
3089
3465
  tabStopId,
@@ -3091,7 +3467,7 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
3091
3467
  onRowHover: handleRowHover
3092
3468
  })
3093
3469
  }), copyTarget !== void 0 && jsx("span", {
3094
- className: css$13.copyAnchor,
3470
+ className: css$14.copyAnchor,
3095
3471
  style: {
3096
3472
  left: copyTarget.left,
3097
3473
  top: copyTarget.top
@@ -3105,11 +3481,11 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
3105
3481
  anchor: jsx("button", {
3106
3482
  ref: copyButtonRef,
3107
3483
  type: "button",
3108
- className: css$13.copyButton,
3484
+ className: css$14.copyButton,
3109
3485
  "data-json-copy-button": true,
3110
3486
  "data-state": copyState,
3111
3487
  "aria-label": copyTitle,
3112
- title: copyLabels.copyButtonTitle(copyTitle),
3488
+ title: labels.copyButtonTitle(copyTitle),
3113
3489
  onClick: () => void copy(defaultCopyMode),
3114
3490
  onContextMenu: (event) => {
3115
3491
  event.preventDefault();
@@ -3119,7 +3495,7 @@ function JsonTree({ data, label = "JSON", className, copyable = true, expandTopL
3119
3495
  },
3120
3496
  children: copyState === "copied" ? jsx(IconCheckOutline16, { size: 12 }) : jsx(IconCopyOutline16, { size: 12 })
3121
3497
  }),
3122
- items: copyTargetIsObject ? objectCopyMenuItems(copyLabels) : valueCopyMenuItems(copyLabels),
3498
+ items: copyTargetIsObject ? objectCopyMenuItems(labels) : valueCopyMenuItems(labels),
3123
3499
  onSelect: (id) => {
3124
3500
  copy(id);
3125
3501
  copyMenuOpenRef.current = false;
@@ -3572,26 +3948,8 @@ function useCopyFeedback(text) {
3572
3948
  }
3573
3949
  //#endregion
3574
3950
  //#region lib/types/TerminalBlock.js
3575
- /**
3576
- * Output lines shown before the height cap collapses the middle. Matches the
3577
- * TUI transcript's default tool-output budget so both front ends cut a long
3578
- * command's output at the same place.
3579
- */
3951
+ /** Output lines shown before the height cap collapses the middle. */
3580
3952
  const DEFAULT_TERMINAL_MAX_LINES = 16;
3581
- const DEFAULT_LABELS = {
3582
- signal: (signal) => `信号 ${signal}`,
3583
- exitCode: (exitCode) => `退出码 ${exitCode}`,
3584
- running: "运行中",
3585
- failed: "失败",
3586
- done: "已完成",
3587
- copy: "复制",
3588
- copied: "复制成功",
3589
- noOutput: "无输出",
3590
- collapseAria: "收起输出",
3591
- collapse: "收起",
3592
- expandAria: (hidden) => `展开其余 ${hidden} 行输出`,
3593
- expand: (hidden) => `… 其余 ${hidden} 行`
3594
- };
3595
3953
  /**
3596
3954
  * Prompt label for a working directory: `~` for the home directory itself,
3597
3955
  * otherwise the path's last segment (both separators accepted, trailing
@@ -3668,10 +4026,7 @@ function renderLine(line) {
3668
4026
  * @returns the terminal block element.
3669
4027
  */
3670
4028
  function TerminalBlock({ command, cwd, home, output, exitCode, signal, running = false, maxLines = 16, className, labels }) {
3671
- const copy = useMemo(() => labels === void 0 ? DEFAULT_LABELS : {
3672
- ...DEFAULT_LABELS,
3673
- ...labels
3674
- }, [labels]);
4029
+ const copy = labels;
3675
4030
  const text = output ?? "";
3676
4031
  const lines = useMemo(() => {
3677
4032
  const parsed = parseAnsiLines(text);
@@ -3691,66 +4046,66 @@ function TerminalBlock({ command, cwd, home, output, exitCode, signal, running =
3691
4046
  const empty = lines.every((line) => line.every((span) => span.text.trim() === ""));
3692
4047
  const { hidden, capped, headLines, tailLines } = headTailCap(lines.length, maxLines, expanded);
3693
4048
  return jsxs("div", {
3694
- className: clsx(css$14.block, className),
4049
+ className: clsx(css$15.block, className),
3695
4050
  "data-terminal": "",
3696
4051
  "data-running": running ? "" : void 0,
3697
4052
  children: [jsxs("div", {
3698
- className: css$14.header,
4053
+ className: css$15.header,
3699
4054
  children: [
3700
4055
  jsxs("div", {
3701
- className: css$14.prompt,
4056
+ className: css$15.prompt,
3702
4057
  children: [jsx("span", {
3703
- className: css$14.runStateLabel,
4058
+ className: css$15.runStateLabel,
3704
4059
  children: state.label
3705
4060
  }), commandLines.map((line, index) => jsxs("div", {
3706
- className: css$14.promptLine,
4061
+ className: css$15.promptLine,
3707
4062
  children: [
3708
4063
  index === 0 && jsx(StateDot, {
3709
4064
  state: state.state,
3710
- className: css$14.runState
4065
+ className: css$15.runState
3711
4066
  }),
3712
4067
  jsx("span", {
3713
- className: css$14.cwd,
4068
+ className: css$15.cwd,
3714
4069
  children: index > 0 || cwd === void 0 ? "$" : promptLabel(cwd, home)
3715
4070
  }),
3716
4071
  jsx("span", {
3717
- className: css$14.command,
4072
+ className: css$15.command,
3718
4073
  children: line
3719
4074
  })
3720
4075
  ]
3721
4076
  }, index))]
3722
4077
  }),
3723
4078
  status !== void 0 && jsx(Pill, {
3724
- className: css$14.status,
4079
+ className: css$15.status,
3725
4080
  children: status
3726
4081
  }),
3727
4082
  !running && !empty && jsx("button", {
3728
4083
  type: "button",
3729
- className: css$14.copyButton,
4084
+ className: css$15.copyButton,
3730
4085
  onClick: onCopy,
3731
4086
  children: copied ? copy.copied : copy.copy
3732
4087
  })
3733
4088
  ]
3734
4089
  }), !running && (empty ? jsx("div", {
3735
- className: css$14.empty,
4090
+ className: css$15.empty,
3736
4091
  children: copy.noOutput
3737
4092
  }) : jsxs("div", {
3738
- className: css$14.output,
4093
+ className: css$15.output,
3739
4094
  children: [
3740
4095
  (capped ? lines.slice(0, headLines) : lines).map((line, index) => jsx("div", {
3741
- className: css$14.line,
4096
+ className: css$15.line,
3742
4097
  children: renderLine(line)
3743
4098
  }, index)),
3744
4099
  hidden > 0 && jsx("button", {
3745
4100
  type: "button",
3746
- className: css$14.expand,
4101
+ className: css$15.expand,
3747
4102
  "aria-expanded": expanded,
3748
4103
  "aria-label": expanded ? copy.collapseAria : copy.expandAria(hidden),
3749
4104
  onClick: onToggle,
3750
4105
  children: expanded ? copy.collapse : copy.expand(hidden)
3751
4106
  }),
3752
4107
  capped && lines.slice(lines.length - tailLines).map((line, index) => jsx("div", {
3753
- className: css$14.line,
4108
+ className: css$15.line,
3754
4109
  children: renderLine(line)
3755
4110
  }, index))
3756
4111
  ]
@@ -3758,6 +4113,23 @@ function TerminalBlock({ command, cwd, home, output, exitCode, signal, running =
3758
4113
  });
3759
4114
  }
3760
4115
  //#endregion
4116
+ //#region lib/types/FoldToggle.js
4117
+ /**
4118
+ * Render the shared head-tail fold control with caller-owned localized copy.
4119
+ * @param props - Fold state, localized labels, and toggle callback.
4120
+ * @returns The accessible expand or collapse button.
4121
+ */
4122
+ function FoldToggle({ className, expanded, hidden, labels, onToggle }) {
4123
+ return jsx("button", {
4124
+ type: "button",
4125
+ className,
4126
+ "aria-expanded": expanded,
4127
+ "aria-label": expanded ? labels.collapseAria : labels.expandAria(hidden),
4128
+ onClick: onToggle,
4129
+ children: expanded ? labels.collapse : labels.expand(hidden)
4130
+ });
4131
+ }
4132
+ //#endregion
3761
4133
  //#region lib/types/markdown/highlight.js
3762
4134
  /**
3763
4135
  * The client's ONE syntax highlighter: a synchronous fine-grained shiki core
@@ -4008,6 +4380,130 @@ function highlightToHtml(code, lang) {
4008
4380
  theme: "css-variables"
4009
4381
  });
4010
4382
  }
4383
+ /** vscode-textmate FontStyle bits shiki folds into `text-decoration` values. */
4384
+ const DECORATION_BITS = [[4, "underline"], [8, "line-through"]];
4385
+ /**
4386
+ * The inline style shiki's HTML arm assigns one token (`getTokenStyleObject`
4387
+ * mirrored onto React style keys): the css-variables color plus the
4388
+ * vscode-textmate font-style bits the theme lets through — italic (1), bold
4389
+ * (2), and the {@link DECORATION_BITS} decorations (the theme injects bold,
4390
+ * italic, and underline rules for markup scopes, so markdown fences carry
4391
+ * them). The theme has no per-scope backgrounds, so `background-color` never
4392
+ * occurs; the arm-parity tests fail loud if a shiki upgrade changes that.
4393
+ */
4394
+ function spanStyle(token) {
4395
+ const style = { color: token.color };
4396
+ /* v8 ignore next -- fontStyle is optional in ThemedToken's type; tokenizeWithTheme always stamps it. */
4397
+ const bits = token.fontStyle ?? 0;
4398
+ if ((bits & 1) !== 0) style.fontStyle = "italic";
4399
+ if ((bits & 2) !== 0) style.fontWeight = "bold";
4400
+ const decorations = DECORATION_BITS.filter(([bit]) => (bits & bit) !== 0);
4401
+ if (decorations.length > 0) style.textDecoration = decorations.map(([, value]) => value).join(" ");
4402
+ return style;
4403
+ }
4404
+ /**
4405
+ * Narrow one tokenized line to the runs a `<span style>` renders, folding a
4406
+ * whitespace-only run into the token that follows it — shiki's default
4407
+ * `mergeWhitespaces` HTML behavior — with each run styled through
4408
+ * {@link spanStyle}, so the streaming spans and the settled `codeToHtml`
4409
+ * swap render one identical span tree. shiki exempts underlined/struck
4410
+ * whitespace from the fold; under the css-variables theme that case cannot
4411
+ * occur — its only underline rule styles inline-link scopes, whose spaced
4412
+ * text tokenizes as one run, and it injects no strikethrough rule — so the
4413
+ * unconditional fold here stays equivalent (the markdown arm-parity test
4414
+ * pins it). A line-trailing whitespace-only run has no follower and keeps
4415
+ * its own span, as in shiki.
4416
+ */
4417
+ function lineSpans(line) {
4418
+ const spans = [];
4419
+ let pendingWhitespace = "";
4420
+ for (const [index, token] of line.entries()) {
4421
+ if (/^\s+$/.test(token.content) && index + 1 < line.length) {
4422
+ pendingWhitespace += token.content;
4423
+ continue;
4424
+ }
4425
+ spans.push({
4426
+ text: pendingWhitespace + token.content,
4427
+ style: spanStyle(token)
4428
+ });
4429
+ pendingWhitespace = "";
4430
+ }
4431
+ return spans;
4432
+ }
4433
+ /**
4434
+ * Incremental highlighter for one growing streaming fence. TextMate
4435
+ * tokenization is line-based and forward-only — a line's tokens depend only on
4436
+ * its own text and the grammar state entering it — so appended text never
4437
+ * changes a completed line's tokens. The session caches the spans of every
4438
+ * completed line together with the grammar state after them; each
4439
+ * {@link update} tokenizes newly completed text from that state, plus the
4440
+ * still-growing last line. Per-call cost therefore excludes the completed
4441
+ * prefix, and the result equals a from-scratch tokenization of the same code.
4442
+ * Non-append input and a change of resolved grammar reset the cache and
4443
+ * re-tokenize fully, so any input stays correct.
4444
+ */
4445
+ var StreamingHighlightSession = class {
4446
+ /** Grammar id the cache was built with; a different resolution resets it. */
4447
+ resolved;
4448
+ /** Newline-terminated source prefix covered by {@link spans}. */
4449
+ prefix = "";
4450
+ /** Cached spans, one entry per completed line of {@link prefix}. */
4451
+ spans = [];
4452
+ /** Grammar state after {@link prefix}; undefined = the grammar's initial state. */
4453
+ state;
4454
+ lastCode;
4455
+ lastLang;
4456
+ lastResult;
4457
+ reset(resolved) {
4458
+ this.resolved = resolved;
4459
+ this.prefix = "";
4460
+ this.spans = [];
4461
+ this.state = void 0;
4462
+ }
4463
+ /** Tokenize `text` with `resolved`, resuming from the cached grammar state when one exists. */
4464
+ tokenize(resolved, text) {
4465
+ return highlighter().codeToTokensBase(text, {
4466
+ lang: resolved,
4467
+ theme: "css-variables",
4468
+ ...this.state === void 0 ? {} : { grammarState: this.state }
4469
+ });
4470
+ }
4471
+ /**
4472
+ * Tokenize the fence's current text into per-line highlighted runs;
4473
+ * `undefined` means the caller renders its plain fallback. Idempotent per
4474
+ * (`code`, `lang`) input — repeated calls return the identical result array —
4475
+ * and a retained line keeps its span-array identity across growing calls, so
4476
+ * a React caller can reuse cached line elements. A lazy grammar not yet
4477
+ * loaded returns `undefined` and loads in the background exactly as
4478
+ * {@link highlightToHtml} does; the next call after it registers highlights.
4479
+ * @param code - the fence text accumulated so far (display-trimmed, no synthetic trailing newline).
4480
+ * @param lang - the language hint (a markdown fence info string).
4481
+ * @returns one entry per line of `code` (each an array of runs), or `undefined` for unknown or not-yet-loaded languages.
4482
+ */
4483
+ update(code, lang) {
4484
+ if (code === this.lastCode && lang === this.lastLang && this.lastResult !== void 0) return this.lastResult;
4485
+ this.lastCode = code;
4486
+ this.lastLang = lang;
4487
+ const resolved = lang === void 0 ? void 0 : LANG_ALIASES.get(lang.toLowerCase());
4488
+ if (resolved === void 0 || !ensureGrammar(resolved)) {
4489
+ this.reset(void 0);
4490
+ this.lastResult = void 0;
4491
+ return;
4492
+ }
4493
+ if (resolved !== this.resolved || !code.startsWith(this.prefix)) this.reset(resolved);
4494
+ const rest = code.slice(this.prefix.length);
4495
+ const lastNewline = rest.lastIndexOf("\n");
4496
+ if (lastNewline >= 0) {
4497
+ const grownEnd = rest[lastNewline - 1] === "\r" ? lastNewline - 1 : lastNewline;
4498
+ const tokens = this.tokenize(resolved, rest.slice(0, grownEnd));
4499
+ for (const line of tokens) this.spans.push(lineSpans(line));
4500
+ this.state = highlighter().getLastGrammarState(tokens);
4501
+ this.prefix = code.slice(0, this.prefix.length + lastNewline + 1);
4502
+ }
4503
+ this.lastResult = [...this.spans, ...this.tokenize(resolved, rest.slice(lastNewline + 1)).map(lineSpans)];
4504
+ return this.lastResult;
4505
+ }
4506
+ };
4011
4507
  /**
4012
4508
  * Tokenize `code` into per-line highlighted runs when `lang` maps to a
4013
4509
  * registered grammar; `undefined` means the caller renders its plain fallback.
@@ -4016,8 +4512,9 @@ function highlightToHtml(code, lang) {
4016
4512
  * so this returns shiki's own 2D line/token structure narrowed to what a run
4017
4513
  * renders. Each run's color is a `--shiki-*` custom property, keeping token
4018
4514
  * colors on the theme package's sheets exactly as the HTML path does; the
4019
- * css-variables theme carries no font-style bits, matching that path's
4020
- * color-only output. The trailing newline shiki appends as a final empty line
4515
+ * markup font-style bits the theme lets through (bold/italic/underline in
4516
+ * markdown scopes) are dropped — the line-numbered file view renders
4517
+ * color-only runs. The trailing newline shiki appends as a final empty line
4021
4518
  * is dropped so the run count matches the caller's own line array.
4022
4519
  * @param code - the source text.
4023
4520
  * @param lang - the language hint (a file-extension-derived language id).
@@ -4045,13 +4542,6 @@ function highlightLines(code, lang) {
4045
4542
  * same place in the same flow.
4046
4543
  */
4047
4544
  const DEFAULT_READ_MAX_LINES = 16;
4048
- /**
4049
- * Render one line's highlighted runs. The css-variables theme colors every run,
4050
- * so each run is a styled span; a line with no highlighting at all takes the
4051
- * bare-text path in the caller instead (an unknown or absent language).
4052
- * @param spans - the line's styled runs.
4053
- * @returns the line's children.
4054
- */
4055
4545
  function renderSpans(spans) {
4056
4546
  return spans.map((span, index) => jsx("span", {
4057
4547
  style: span.style,
@@ -4064,7 +4554,7 @@ function renderSpans(spans) {
4064
4554
  * @param props - see {@link ReadBlockProps}.
4065
4555
  * @returns the read block element.
4066
4556
  */
4067
- function ReadBlock({ label, lines, totalLines, lang, maxLines = 16, className }) {
4557
+ function ReadBlock({ label, labels, lines, totalLines, lang, maxLines = 16, className }) {
4068
4558
  const raw = useMemo(() => lines.map((line) => line.text).join("\n"), [lines]);
4069
4559
  const highlighted = useMemo(() => highlightLines(raw, lang), [
4070
4560
  raw,
@@ -4091,61 +4581,55 @@ function ReadBlock({ label, lines, totalLines, lang, maxLines = 16, className })
4091
4581
  const headLines = Math.ceil(maxLines / 2);
4092
4582
  const tailLines = maxLines - headLines;
4093
4583
  const windowed = lines.length < totalLines;
4094
- /**
4095
- * Render a slice of the line array as gutter-numbered rows.
4096
- * @param slice - the lines to draw, each with its aligned run array.
4097
- * @returns the row elements.
4098
- */
4099
4584
  const rows = (slice) => slice.map(([line, spans]) => jsxs("div", {
4100
- className: css$15.line,
4585
+ className: css$16.line,
4101
4586
  children: [jsx("span", {
4102
- className: css$15.gutter,
4587
+ className: css$16.gutter,
4103
4588
  "aria-hidden": true,
4104
4589
  children: line.number
4105
4590
  }), jsx("span", {
4106
- className: css$15.content,
4591
+ className: css$16.content,
4107
4592
  children: spans === void 0 ? line.text : renderSpans(spans)
4108
4593
  })]
4109
4594
  }, line.number));
4110
4595
  const paired = lines.map((line, index) => [line, highlighted?.[index]]);
4111
4596
  return jsxs("div", {
4112
- className: clsx(css$15.block, className),
4597
+ className: clsx(css$16.block, className),
4113
4598
  "data-read": "",
4114
4599
  children: [jsxs("div", {
4115
- className: css$15.banner,
4600
+ className: css$16.banner,
4116
4601
  children: [jsx("div", {
4117
- className: css$15.label,
4602
+ className: css$16.label,
4118
4603
  children: label ?? ""
4119
4604
  }), jsxs("div", {
4120
- className: css$15.action,
4605
+ className: css$16.action,
4121
4606
  children: [
4122
4607
  windowed && jsx("span", {
4123
- className: css$15.count,
4124
- children: `显示 ${lines.length} / ${totalLines} 行`
4608
+ className: css$16.count,
4609
+ children: labels.window(lines.length, totalLines)
4125
4610
  }),
4126
4611
  jsx("span", {
4127
- className: css$15.lang,
4612
+ className: css$16.lang,
4128
4613
  children: lang ?? ""
4129
4614
  }),
4130
4615
  lines.length > 0 && jsx("button", {
4131
4616
  type: "button",
4132
- className: css$15.copyButton,
4617
+ className: css$16.copyButton,
4133
4618
  onClick: onCopy,
4134
- children: copied ? "复制成功" : "复制"
4619
+ children: copied ? labels.copied : labels.copy
4135
4620
  })
4136
4621
  ]
4137
4622
  })]
4138
4623
  }), jsxs("div", {
4139
- className: css$15.body,
4624
+ className: css$16.body,
4140
4625
  children: [
4141
4626
  rows(capped ? paired.slice(0, headLines) : paired),
4142
- hidden > 0 && jsx("button", {
4143
- type: "button",
4144
- className: css$15.expand,
4145
- "aria-expanded": expanded,
4146
- "aria-label": expanded ? "收起内容" : `展开其余 ${hidden} 行`,
4147
- onClick: onToggle,
4148
- children: expanded ? "收起" : `… 其余 ${hidden} 行`
4627
+ hidden > 0 && jsx(FoldToggle, {
4628
+ className: css$16.expand,
4629
+ expanded,
4630
+ hidden,
4631
+ labels,
4632
+ onToggle
4149
4633
  }),
4150
4634
  capped && rows(paired.slice(paired.length - tailLines))
4151
4635
  ]
@@ -4154,11 +4638,7 @@ function ReadBlock({ label, lines, totalLines, lang, maxLines = 16, className })
4154
4638
  }
4155
4639
  //#endregion
4156
4640
  //#region lib/types/DiffBlock.js
4157
- /**
4158
- * Output lines shown before the height cap collapses the middle. Matches
4159
- * {@link DEFAULT_TERMINAL_MAX_LINES} so a diff card and a terminal card cut a
4160
- * long body at the same place.
4161
- */
4641
+ /** Output lines shown before the height cap collapses the middle. */
4162
4642
  const DEFAULT_DIFF_MAX_LINES = 16;
4163
4643
  /** Local exhaustiveness helper — this package does not depend on `dsh-llm`. */
4164
4644
  /* v8 ignore next 3 -- closed-union backstop; only reached if a row kind is forged */
@@ -4167,26 +4647,44 @@ function assertNever(value) {
4167
4647
  }
4168
4648
  /** The dim class per row kind (path/gap chrome vs the diff's own +/- colors). */
4169
4649
  const ROW_CLASS = {
4170
- path: css$16.path,
4171
- del: css$16.del,
4172
- add: css$16.add,
4173
- gap: css$16.gap
4650
+ path: css$17.path,
4651
+ del: css$17.del,
4652
+ add: css$17.add,
4653
+ gap: css$17.gap
4174
4654
  };
4175
4655
  /**
4656
+ * Total added/removed line counts across hunks — the same numbers the footer
4657
+ * prints, exported so a summary row can show them without rebuilding the body.
4658
+ * Every old-side line counts toward `removed` and every new-side line toward
4659
+ * `added`, under {@link contentLines}'s terminator rule.
4660
+ * @param diffs - the hunks to count.
4661
+ * @returns the +/- totals.
4662
+ */
4663
+ function diffTotals(diffs) {
4664
+ let added = 0;
4665
+ let removed = 0;
4666
+ for (const diff of diffs) {
4667
+ if (diff.oldText !== null) removed += contentLines(diff.oldText).length;
4668
+ added += contentLines(diff.newText).length;
4669
+ }
4670
+ return {
4671
+ added,
4672
+ removed
4673
+ };
4674
+ }
4675
+ /**
4176
4676
  * Flatten the hunks into the body's rows plus the footer counts. A path header
4177
4677
  * opens each new file; a same-file second hunk (a scattered edit) opens with a
4178
- * `⋯` gap instead of repeating the path. Every old-side line counts toward
4179
- * `removed` and every new-side line toward `added`. The file count is of
4180
- * DISTINCT paths, matching the TUI diff card's footer, so two hunks in one file
4181
- * read as `1 file` on both front ends.
4678
+ * `⋯` gap instead of repeating the path. The +/- totals are
4679
+ * {@link diffTotals}'s. The file count is of DISTINCT paths, matching the TUI
4680
+ * diff card's footer, so two hunks in one file read as `1 file` on both front
4681
+ * ends.
4182
4682
  * @param diffs - the hunks to render.
4183
4683
  * @returns the body rows, the +/- totals, and the distinct-file count.
4184
4684
  */
4185
4685
  function buildRows(diffs) {
4186
4686
  const rows = [];
4187
4687
  const paths = /* @__PURE__ */ new Set();
4188
- let added = 0;
4189
- let removed = 0;
4190
4688
  let prevPath;
4191
4689
  for (const diff of diffs) {
4192
4690
  paths.add(diff.path);
@@ -4199,25 +4697,18 @@ function buildRows(diffs) {
4199
4697
  text: "⋯"
4200
4698
  });
4201
4699
  prevPath = diff.path;
4202
- if (diff.oldText !== null) for (const line of contentLines(diff.oldText)) {
4203
- rows.push({
4204
- kind: "del",
4205
- text: line
4206
- });
4207
- removed++;
4208
- }
4209
- for (const line of contentLines(diff.newText)) {
4210
- rows.push({
4211
- kind: "add",
4212
- text: line
4213
- });
4214
- added++;
4215
- }
4700
+ if (diff.oldText !== null) for (const line of contentLines(diff.oldText)) rows.push({
4701
+ kind: "del",
4702
+ text: line
4703
+ });
4704
+ for (const line of contentLines(diff.newText)) rows.push({
4705
+ kind: "add",
4706
+ text: line
4707
+ });
4216
4708
  }
4217
4709
  return {
4218
4710
  rows,
4219
- added,
4220
- removed,
4711
+ ...diffTotals(diffs),
4221
4712
  files: paths.size
4222
4713
  };
4223
4714
  }
@@ -4258,7 +4749,7 @@ function copyText$1(rows) {
4258
4749
  * @param props - see {@link DiffBlockProps}.
4259
4750
  * @returns the diff block element.
4260
4751
  */
4261
- function DiffBlock({ diffs, maxLines = 16, className }) {
4752
+ function DiffBlock({ diffs, labels, maxLines = 16, className }) {
4262
4753
  const { rows, added, removed, files } = useMemo(() => buildRows(diffs), [diffs]);
4263
4754
  const [expanded, setExpanded] = useState(false);
4264
4755
  const [copied, setCopied] = useState(false);
@@ -4283,47 +4774,44 @@ function DiffBlock({ diffs, maxLines = 16, className }) {
4283
4774
  const head = capped ? rows.slice(0, headLines) : rows;
4284
4775
  const tail = capped ? rows.slice(rows.length - tailLines) : [];
4285
4776
  return jsxs("div", {
4286
- className: clsx(css$16.block, className),
4777
+ className: clsx(css$17.block, className),
4287
4778
  "data-diff": "",
4288
4779
  children: [
4289
4780
  jsx("button", {
4290
4781
  type: "button",
4291
- className: css$16.copyButton,
4782
+ className: css$17.copyButton,
4292
4783
  onClick: onCopy,
4293
- children: copied ? "复制成功" : "复制"
4784
+ children: copied ? labels.copied : labels.copy
4294
4785
  }),
4295
4786
  jsxs("div", {
4296
- className: css$16.body,
4787
+ className: css$17.body,
4297
4788
  children: [
4298
4789
  head.map((row, index) => jsx("div", {
4299
- className: clsx(css$16.line, ROW_CLASS[row.kind]),
4790
+ className: clsx(css$17.line, ROW_CLASS[row.kind]),
4300
4791
  children: row.text
4301
4792
  }, index)),
4302
- hidden > 0 && jsx("button", {
4303
- type: "button",
4304
- className: css$16.expand,
4305
- "aria-expanded": expanded,
4306
- "aria-label": expanded ? "收起差异" : `展开其余 ${hidden} 行差异`,
4307
- onClick: onToggle,
4308
- children: expanded ? "收起" : `… 其余 ${hidden} 行`
4793
+ hidden > 0 && jsx(FoldToggle, {
4794
+ className: css$17.expand,
4795
+ expanded,
4796
+ hidden,
4797
+ labels,
4798
+ onToggle
4309
4799
  }),
4310
4800
  tail.map((row, index) => jsx("div", {
4311
- className: clsx(css$16.line, ROW_CLASS[row.kind]),
4801
+ className: clsx(css$17.line, ROW_CLASS[row.kind]),
4312
4802
  children: row.text
4313
4803
  }, index))
4314
4804
  ]
4315
4805
  }),
4316
4806
  jsxs("div", {
4317
- className: css$16.footer,
4807
+ className: css$17.footer,
4318
4808
  children: [
4319
4809
  "└ +",
4320
4810
  added,
4321
4811
  " -",
4322
4812
  removed,
4323
4813
  " · ",
4324
- files,
4325
- " file",
4326
- files === 1 ? "" : "s"
4814
+ labels.files(files)
4327
4815
  ]
4328
4816
  })
4329
4817
  ]
@@ -4371,8 +4859,7 @@ function shownCount(props) {
4371
4859
  * @returns the summary text.
4372
4860
  */
4373
4861
  function summaryText(props, shown, truncated, total) {
4374
- const count = truncated ? `显示 ${shown} / 共 ${total}` : `${shown}`;
4375
- return props.kind === "paths" ? `${count} 个路径` : `${count} 处匹配 · ${props.files.length} 个文件`;
4862
+ return props.kind === "paths" ? props.labels.pathsSummary(shown, total, truncated) : props.labels.matchesSummary(shown, total, props.files.length, truncated);
4376
4863
  }
4377
4864
  /**
4378
4865
  * Flatten a card's shape into its render rows, dropping a collapsed file
@@ -4453,60 +4940,60 @@ function SearchBlock(props) {
4453
4940
  const tail = tailHeader === void 0 ? naturalTail : naturalTail.slice(1);
4454
4941
  const renderRow = (row) => {
4455
4942
  if (row.type === "path") return jsx("div", {
4456
- className: css$17.line,
4943
+ className: css$18.line,
4457
4944
  children: row.path
4458
4945
  });
4459
4946
  if (row.type === "match") return jsxs("div", {
4460
- className: css$17.line,
4947
+ className: css$18.line,
4461
4948
  children: [jsxs("span", {
4462
- className: css$17.lineNumber,
4949
+ className: css$18.lineNumber,
4463
4950
  children: [row.lineNumber, ": "]
4464
4951
  }), row.line]
4465
4952
  });
4466
4953
  return jsxs("button", {
4467
4954
  type: "button",
4468
- className: css$17.fileHeader,
4955
+ className: css$18.fileHeader,
4469
4956
  "aria-expanded": !row.collapsed,
4470
4957
  onClick: () => {
4471
4958
  toggleFile(row.index);
4472
4959
  },
4473
4960
  children: [jsx("span", {
4474
- className: css$17.filePath,
4961
+ className: css$18.filePath,
4475
4962
  children: row.path
4476
4963
  }), jsx("span", {
4477
- className: css$17.fileCount,
4964
+ className: css$18.fileCount,
4478
4965
  children: row.count
4479
4966
  })]
4480
4967
  });
4481
4968
  };
4482
4969
  return jsxs("div", {
4483
- className: clsx(css$17.block, className),
4970
+ className: clsx(css$18.block, className),
4484
4971
  "data-search": props.kind,
4485
4972
  children: [jsxs("div", {
4486
- className: css$17.header,
4973
+ className: css$18.header,
4487
4974
  children: [jsx("span", {
4488
- className: css$17.summary,
4975
+ className: css$18.summary,
4489
4976
  children: summaryText(props, shown, truncated, total)
4490
4977
  }), !empty && jsx("button", {
4491
4978
  type: "button",
4492
- className: css$17.copyButton,
4979
+ className: css$18.copyButton,
4493
4980
  onClick: onCopy,
4494
- children: copied ? "复制成功" : "复制"
4981
+ children: copied ? props.labels.copied : props.labels.copy
4495
4982
  })]
4496
4983
  }), empty ? jsx("div", {
4497
- className: css$17.empty,
4498
- children: "无结果"
4984
+ className: css$18.empty,
4985
+ children: props.labels.noResults
4499
4986
  }) : jsxs("div", {
4500
- className: css$17.body,
4987
+ className: css$18.body,
4501
4988
  children: [
4502
4989
  head.map((row) => jsx("div", { children: renderRow(row) }, rowKey(row))),
4503
4990
  hidden > 0 && jsx("button", {
4504
4991
  type: "button",
4505
- className: css$17.expand,
4992
+ className: css$18.expand,
4506
4993
  "aria-expanded": expanded,
4507
- "aria-label": expanded ? "收起结果" : `展开其余 ${hidden} 行结果`,
4994
+ "aria-label": expanded ? props.labels.collapseAria : props.labels.expandAria(hidden),
4508
4995
  onClick: onToggle,
4509
- children: expanded ? "收起" : `… 其余 ${hidden} 行`
4996
+ children: expanded ? props.labels.collapse : props.labels.expand(hidden)
4510
4997
  }),
4511
4998
  tailHeader !== void 0 && jsx("div", { children: renderRow(tailHeader) }, `tailHeader:${rowKey(tailHeader)}`),
4512
4999
  tail.map((row) => jsx("div", { children: renderRow(row) }, rowKey(row)))
@@ -4987,12 +5474,64 @@ function parseGfmWithMath(text) {
4987
5474
  }
4988
5475
  //#endregion
4989
5476
  //#region lib/types/markdown/CodeBlock.js
4990
- function CodeBlock({ code, lang, className, copyLabel = "复制", copiedLabel = "复制成功" }) {
5477
+ /**
5478
+ * The `pre` attributes shiki's HTML arm emits for the css-variables theme,
5479
+ * mirrored so the streaming arm's tree is interchangeable with the settled
5480
+ * swap (`tests/streaming-code-block.client.spec.tsx` pins the two arms'
5481
+ * parity).
5482
+ */
5483
+ const SHIKI_PRE_PROPS = {
5484
+ className: "shiki css-variables",
5485
+ style: {
5486
+ backgroundColor: "var(--shiki-background)",
5487
+ color: "var(--shiki-foreground)"
5488
+ },
5489
+ tabIndex: 0
5490
+ };
5491
+ function CodeBlock({ code, lang, streaming, className, copyLabel, copiedLabel }) {
4991
5492
  const trimmed = code.endsWith("\n") ? code.slice(0, -1) : code;
4992
- const html = useMemo(() => highlightToHtml(trimmed, lang), [
5493
+ const loaded = useSyncExternalStore(subscribeGrammarLoaded, grammarLoadCount, grammarLoadCount);
5494
+ const html = useMemo(() => streaming === true ? void 0 : highlightToHtml(trimmed, lang), [
5495
+ streaming,
4993
5496
  trimmed,
4994
5497
  lang,
4995
- useSyncExternalStore(subscribeGrammarLoaded, grammarLoadCount, grammarLoadCount)
5498
+ loaded
5499
+ ]);
5500
+ const sessionRef = useRef(null);
5501
+ const lineCacheRef = useRef(null);
5502
+ const streamedBody = useMemo(() => {
5503
+ if (streaming !== true) {
5504
+ sessionRef.current = null;
5505
+ lineCacheRef.current = null;
5506
+ return;
5507
+ }
5508
+ sessionRef.current ??= new StreamingHighlightSession();
5509
+ const lines = sessionRef.current.update(trimmed, lang);
5510
+ if (lines === void 0) {
5511
+ lineCacheRef.current = null;
5512
+ return;
5513
+ }
5514
+ const previous = lineCacheRef.current;
5515
+ const elements = lines.map((line, index) => previous !== null && previous.lines[index] === line ? previous.elements[index] : jsxs(Fragment$1, { children: [index > 0 && "\n", jsx("span", {
5516
+ className: "line",
5517
+ children: line.map((span, spanIndex) => jsx("span", {
5518
+ style: span.style,
5519
+ children: span.text
5520
+ }, spanIndex))
5521
+ })] }, index));
5522
+ lineCacheRef.current = {
5523
+ lines,
5524
+ elements
5525
+ };
5526
+ return jsx("pre", {
5527
+ ...SHIKI_PRE_PROPS,
5528
+ children: jsx("code", { children: elements })
5529
+ });
5530
+ }, [
5531
+ streaming,
5532
+ trimmed,
5533
+ lang,
5534
+ loaded
4996
5535
  ]);
4997
5536
  const rootRef = useRef(null);
4998
5537
  const [copied, setCopied] = useState(false);
@@ -5006,25 +5545,25 @@ function CodeBlock({ code, lang, className, copyLabel = "复制", copiedLabel =
5006
5545
  }, 1e3);
5007
5546
  });
5008
5547
  }, [copied, trimmed]);
5009
- const body = html === void 0 ? jsx("pre", {
5010
- className: css$18.plain,
5548
+ const body = streamedBody !== void 0 ? streamedBody : html === void 0 ? jsx("pre", {
5549
+ className: css$19.plain,
5011
5550
  children: jsx("code", { children: trimmed })
5012
5551
  }) : jsx("div", { dangerouslySetInnerHTML: { __html: html } });
5013
5552
  return jsxs("div", {
5014
5553
  ref: rootRef,
5015
- className: clsx(css$18.block, "md-code-block", className),
5554
+ className: clsx(css$19.block, "md-code-block", className),
5016
5555
  children: [jsx("div", {
5017
- className: css$18.bannerWrap,
5556
+ className: css$19.bannerWrap,
5018
5557
  children: jsxs("div", {
5019
- className: css$18.banner,
5558
+ className: css$19.banner,
5020
5559
  children: [jsx("div", {
5021
- className: css$18.infostring,
5560
+ className: css$19.infostring,
5022
5561
  children: lang ?? ""
5023
5562
  }), jsx("div", {
5024
- className: css$18.action,
5563
+ className: css$19.action,
5025
5564
  children: jsx("button", {
5026
5565
  type: "button",
5027
- className: css$18.copyButton,
5566
+ className: css$19.copyButton,
5028
5567
  onClick: onCopy,
5029
5568
  children: copied ? copiedLabel : copyLabel
5030
5569
  })
@@ -5244,7 +5783,7 @@ function renderNode(node, key, context) {
5244
5783
  const mention = context.inLink === true ? void 0 : context.fileMentions?.resolve(value);
5245
5784
  if (mention !== void 0) return jsx("code", { children: jsx("button", {
5246
5785
  type: "button",
5247
- className: css$19.fileMention,
5786
+ className: css$20.fileMention,
5248
5787
  title: mention.title,
5249
5788
  "aria-label": mention.label,
5250
5789
  onClick: mention.open,
@@ -5279,9 +5818,10 @@ function renderCode(node, key, context) {
5279
5818
  if (!context.streaming && lang === "math") return jsx(Fragment$1, { children: renderTexToReact(`${node.value}\n`, true) }, key);
5280
5819
  return jsx(CodeBlock, {
5281
5820
  code: `${node.value}\n`,
5282
- lang: context.streaming ? void 0 : lang,
5283
- copyLabel: context.codeLabels?.copyLabel,
5284
- copiedLabel: context.codeLabels?.copiedLabel
5821
+ lang,
5822
+ streaming: context.streaming,
5823
+ copyLabel: context.labels.code.copyLabel,
5824
+ copiedLabel: context.labels.code.copiedLabel
5285
5825
  }, key);
5286
5826
  }
5287
5827
  /** A list is loose when it or any of its items is spread; every item then keeps its paragraphs. */
@@ -5338,7 +5878,7 @@ function renderTable(node, key, context) {
5338
5878
  const [headRow, ...bodyRows] = node.children;
5339
5879
  const wide = (align === null ? headRow?.children.length ?? 0 : align.length) >= 4 && context.inBlockquote !== true;
5340
5880
  return jsx("div", {
5341
- className: clsx(css$19.tableScroll, wide ? "md-table-wide" : css$19.tableFill),
5881
+ className: clsx(css$20.tableScroll, wide ? "md-table-wide" : css$20.tableFill),
5342
5882
  tabIndex: wide ? 0 : void 0,
5343
5883
  children: jsxs("table", { children: [headRow !== void 0 && jsx("thead", { children: renderTableRow(headRow, "th", align, 0, context) }), bodyRows.length > 0 && jsx("tbody", { children: bodyRows.map((row, index) => renderTableRow(row, "td", align, index + 1, context)) })] })
5344
5884
  }, key);
@@ -5389,11 +5929,11 @@ function inlineCodeHttpUrl(value) {
5389
5929
  function renderImage(url, alt, key) {
5390
5930
  const imageSrc = remoteImageUrl(sanitizeUrl(normalizeUri(url)));
5391
5931
  if (imageSrc === void 0) return jsx("span", {
5392
- className: css$19.imageAlt,
5932
+ className: css$20.imageAlt,
5393
5933
  children: alt
5394
5934
  }, key);
5395
5935
  return jsx("img", {
5396
- className: css$19.image,
5936
+ className: css$20.image,
5397
5937
  src: imageSrc,
5398
5938
  alt,
5399
5939
  loading: "lazy",
@@ -5466,7 +6006,7 @@ function renderFootnoteSection(context) {
5466
6006
  children: [jsx("h2", {
5467
6007
  id: "footnote-label",
5468
6008
  className: "sr-only",
5469
- children: "Footnotes"
6009
+ children: context.labels.footnotes
5470
6010
  }), jsx("ol", { children: items })]
5471
6011
  }, "footnotes");
5472
6012
  }
@@ -5485,13 +6025,13 @@ function renderFootnoteSection(context) {
5485
6025
  * full parse self-heals it.
5486
6026
  */
5487
6027
  /** One settled full render: parse with math, resolve references, append the footnote section. */
5488
- function renderSettled(text, codeLabels, fileMentions) {
6028
+ function renderSettled(text, labels, fileMentions) {
5489
6029
  const root = parseGfmWithMath(text);
5490
6030
  const targets = createReferenceTargets();
5491
6031
  collectReferenceTargets(root.children, targets);
5492
6032
  const context = {
5493
6033
  streaming: false,
5494
- codeLabels,
6034
+ labels,
5495
6035
  fileMentions,
5496
6036
  targets,
5497
6037
  footnoteOrder: [],
@@ -5515,7 +6055,7 @@ function renderSettled(text, codeLabels, fileMentions) {
5515
6055
  * final, so the tail continues from a copy of it each frame).
5516
6056
  */
5517
6057
  var StreamingRenderer = class {
5518
- codeLabels;
6058
+ labels;
5519
6059
  parser = new IncrementalMarkdownParser(parseGfm);
5520
6060
  generation = -1;
5521
6061
  frozenCount = 0;
@@ -5525,9 +6065,9 @@ var StreamingRenderer = class {
5525
6065
  frozenFootnoteCounts = /* @__PURE__ */ new Map();
5526
6066
  lastText = null;
5527
6067
  lastRendered = [];
5528
- /** @param codeLabels - Fence copy labels baked into cached elements; the owner replaces the renderer when they change. */
5529
- constructor(codeLabels) {
5530
- this.codeLabels = codeLabels;
6068
+ /** @param labels - Localized Markdown chrome baked into cached elements; the owner replaces the renderer when it changes. */
6069
+ constructor(labels) {
6070
+ this.labels = labels;
5531
6071
  }
5532
6072
  /**
5533
6073
  * Render the current accumulated text. Idempotent per text value, so React
@@ -5556,7 +6096,7 @@ var StreamingRenderer = class {
5556
6096
  if (newlyFrozen.length > 0) {
5557
6097
  const frozenContext = {
5558
6098
  streaming: true,
5559
- codeLabels: this.codeLabels,
6099
+ labels: this.labels,
5560
6100
  fileMentions: void 0,
5561
6101
  targets: frameTargets,
5562
6102
  footnoteOrder: this.frozenFootnoteOrder,
@@ -5572,7 +6112,7 @@ var StreamingRenderer = class {
5572
6112
  }
5573
6113
  const tailContext = {
5574
6114
  streaming: true,
5575
- codeLabels: this.codeLabels,
6115
+ labels: this.labels,
5576
6116
  fileMentions: void 0,
5577
6117
  targets: frameTargets,
5578
6118
  footnoteOrder: [...this.frozenFootnoteOrder],
@@ -5593,9 +6133,10 @@ var StreamingRenderer = class {
5593
6133
  /**
5594
6134
  * Render untrusted assistant-authored Markdown as semantic React elements.
5595
6135
  * @param props - Markdown source text preserved by the session projection;
5596
- * `streaming` renders fences and TeX plain (highlighting and KaTeX land on
5597
- * the finalize swap) and parses incrementally across chunks; `codeLabels`
5598
- * forwards localized copy-button labels to fence CodeBlocks — pass a
6136
+ * `streaming` parses incrementally across chunks and highlights fences as
6137
+ * they grow (each fence re-tokenizes only appended text; TeX stays literal
6138
+ * until the finalize swap so incomplete formulae never flash errors);
6139
+ * `labels` forwards localized fence and footnote chrome — pass a
5599
6140
  * reference-stable object (memoized per locale revision), because a new
5600
6141
  * identity discards the streaming render cache mid-message. `fileMentions`
5601
6142
  * links inline-code tokens its resolver recognizes as real files; this is
@@ -5606,27 +6147,27 @@ var StreamingRenderer = class {
5606
6147
  * relative links, and unsafe protocols are disabled, while absolute HTTP(S)
5607
6148
  * images render directly.
5608
6149
  */
5609
- const MarkdownText = memo(function MarkdownText({ text, streaming = false, codeLabels, fileMentions }) {
6150
+ const MarkdownText = memo(function MarkdownText({ text, streaming = false, labels, fileMentions }) {
5610
6151
  const streamRef = useRef(null);
5611
- const streamLabelsRef = useRef(codeLabels);
6152
+ const streamLabelsRef = useRef(labels);
5612
6153
  const children = useMemo(() => {
5613
6154
  if (!streaming) {
5614
6155
  streamRef.current = null;
5615
- return renderSettled(text, codeLabels, fileMentions);
6156
+ return renderSettled(text, labels, fileMentions);
5616
6157
  }
5617
- if (streamRef.current === null || streamLabelsRef.current !== codeLabels) {
5618
- streamRef.current = new StreamingRenderer(codeLabels);
5619
- streamLabelsRef.current = codeLabels;
6158
+ if (streamRef.current === null || streamLabelsRef.current !== labels) {
6159
+ streamRef.current = new StreamingRenderer(labels);
6160
+ streamLabelsRef.current = labels;
5620
6161
  }
5621
6162
  return streamRef.current.render(text);
5622
6163
  }, [
5623
6164
  text,
5624
6165
  streaming,
5625
- codeLabels,
6166
+ labels,
5626
6167
  fileMentions
5627
6168
  ]);
5628
6169
  return jsx("div", {
5629
- className: css$19.markdown,
6170
+ className: css$20.markdown,
5630
6171
  children
5631
6172
  });
5632
6173
  });
@@ -5701,20 +6242,20 @@ function SafeLink({ url, label, className }) {
5701
6242
  */
5702
6243
  function SourceItem({ source, ordinal }) {
5703
6244
  return jsxs("li", {
5704
- className: css$20.source,
6245
+ className: css$21.source,
5705
6246
  value: ordinal,
5706
6247
  children: [
5707
6248
  jsx(SafeLink, {
5708
6249
  url: source.url,
5709
6250
  label: linkLabel(source.url, source.title),
5710
- className: css$20.sourceLink
6251
+ className: css$21.sourceLink
5711
6252
  }),
5712
6253
  source.snippet !== void 0 && source.snippet !== "" && jsx("div", {
5713
- className: css$20.snippet,
6254
+ className: css$21.snippet,
5714
6255
  children: source.snippet
5715
6256
  }),
5716
6257
  source.publishedAt !== void 0 && source.publishedAt !== "" && jsx("div", {
5717
- className: css$20.published,
6258
+ className: css$21.published,
5718
6259
  children: source.publishedAt
5719
6260
  })
5720
6261
  ]
@@ -5726,29 +6267,32 @@ function SourceItem({ source, ordinal }) {
5726
6267
  * @param props - see {@link WebSearchBlockProps}.
5727
6268
  * @returns the search card element.
5728
6269
  */
5729
- function WebSearchBlock({ answer, sources, truncated, className }) {
6270
+ function WebSearchBlock({ answer, sources, truncated, labels, className }) {
5730
6271
  const empty = (answer === void 0 || answer === "") && sources.length === 0;
5731
6272
  return jsxs("div", {
5732
- className: clsx(css$20.block, className),
6273
+ className: clsx(css$21.block, className),
5733
6274
  "data-web": "search",
5734
6275
  children: [
5735
6276
  answer !== void 0 && answer !== "" && jsx("div", {
5736
- className: css$20.answer,
5737
- children: jsx(MarkdownText, { text: answer })
6277
+ className: css$21.answer,
6278
+ children: jsx(MarkdownText, {
6279
+ text: answer,
6280
+ labels: labels.markdown
6281
+ })
5738
6282
  }),
5739
6283
  empty ? jsx("div", {
5740
- className: css$20.empty,
5741
- children: "未找到结果"
6284
+ className: css$21.empty,
6285
+ children: labels.noResults
5742
6286
  }) : jsx("ol", {
5743
- className: css$20.sources,
6287
+ className: css$21.sources,
5744
6288
  children: sources.map((source, index) => jsx(SourceItem, {
5745
6289
  source,
5746
6290
  ordinal: index + 1
5747
6291
  }, index))
5748
6292
  }),
5749
6293
  truncated && jsx("div", {
5750
- className: css$20.truncated,
5751
- children: "来源列表已截断"
6294
+ className: css$21.truncated,
6295
+ children: labels.sourcesTruncated
5752
6296
  })
5753
6297
  ]
5754
6298
  });
@@ -5758,22 +6302,26 @@ function WebSearchBlock({ answer, sources, truncated, className }) {
5758
6302
  * @param props - see {@link WebFetchBlockProps}.
5759
6303
  * @returns the fetch card element.
5760
6304
  */
5761
- function WebFetchBlock({ url, statusCode, truncated, className }) {
6305
+ function WebFetchBlock({ url, statusCode, truncated, labels, className }) {
5762
6306
  return jsxs("div", {
5763
- className: clsx(css$20.block, css$20.fetch, className),
6307
+ className: clsx(css$21.block, css$21.fetch, className),
5764
6308
  "data-web": "fetch",
5765
6309
  children: [jsx(SafeLink, {
5766
6310
  url,
5767
6311
  label: url,
5768
- className: css$20.fetchUrl
6312
+ className: css$21.fetchUrl
5769
6313
  }), jsxs("div", {
5770
- className: css$20.fetchMeta,
6314
+ className: css$21.fetchMeta,
5771
6315
  children: [jsxs("span", {
5772
- className: css$20.status,
5773
- children: ["HTTP ", statusCode]
6316
+ className: css$21.status,
6317
+ children: [
6318
+ labels.http,
6319
+ " ",
6320
+ statusCode
6321
+ ]
5774
6322
  }), truncated && jsx("span", {
5775
- className: css$20.truncated,
5776
- children: "内容已截断"
6323
+ className: css$21.truncated,
6324
+ children: labels.contentTruncated
5777
6325
  })]
5778
6326
  })]
5779
6327
  });
@@ -5789,11 +6337,7 @@ function WebBlock(props) {
5789
6337
  //#endregion
5790
6338
  //#region lib/types/markdown/JsonBlock.js
5791
6339
  const MAX_CHARS = 2e4;
5792
- /** Default truncation footer; the owner passes a localized formatter. */
5793
- function defaultTruncatedLabel(total) {
5794
- return `… 已截断,共 ${total} 字符`;
5795
- }
5796
- function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel = defaultTruncatedLabel }) {
6340
+ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel }) {
5797
6341
  const [open, setOpen] = useState(defaultOpen);
5798
6342
  const body = useMemo(() => {
5799
6343
  if (!open) return "";
@@ -5810,10 +6354,10 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel = defau
5810
6354
  truncatedLabel
5811
6355
  ]);
5812
6356
  return jsxs("div", {
5813
- className: css$21.root,
6357
+ className: css$22.root,
5814
6358
  children: [jsxs("button", {
5815
6359
  type: "button",
5816
- className: css$21.toggle,
6360
+ className: css$22.toggle,
5817
6361
  onClick: () => {
5818
6362
  setOpen((v) => !v);
5819
6363
  },
@@ -5823,7 +6367,7 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel = defau
5823
6367
  label
5824
6368
  ]
5825
6369
  }), open && jsx("pre", {
5826
- className: css$21.body,
6370
+ className: css$22.body,
5827
6371
  children: body
5828
6372
  })]
5829
6373
  });
@@ -5832,7 +6376,7 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel = defau
5832
6376
  //#region lib/types/markdown/MessageText.js
5833
6377
  function MessageText({ text }) {
5834
6378
  return jsx("div", {
5835
- className: css$22.text,
6379
+ className: css$23.text,
5836
6380
  children: text
5837
6381
  });
5838
6382
  }
@@ -5908,6 +6452,6 @@ function extractMarkdownPlainText(markdown, options = {}) {
5908
6452
  }
5909
6453
  }
5910
6454
  //#endregion
5911
- export { BrandWordmark, Button, CodeBlock, ConnectionBanner, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DiffBlock, DisclosureRow, FishLogo, HoverCard, IconAgentPresetOutline16, IconApiOutline14, IconArchiveOutline20, IconBranchOutline16, IconBrowseOutline16, IconCheckOutline14, IconCheckOutline16, IconChecklistOutline14, IconChevronDownOutline14, IconChevronLeftOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconCloseFill14, IconCloseOutline16, IconCodeOutline16, IconCopyOutline16, IconCordisPluginOutline14, IconDarkOutline16, IconDataOutline16, IconDislikeFill16, IconDislikeOutline16, IconDownloadOutline16, IconEditOutline16, IconEllipsisOutline16, IconEnhanceOutline16, IconFolderClose16, IconFolderOpen16, IconFolderOpenOutline16, IconFollowsystemOutline16, IconFullscreenOutline16, IconGlobeOutline14, IconGoalOutline16, IconInspectOutline12, IconLightOutline16, IconLikeFill16, IconLikeOutline16, IconLinkOutline14, IconLinkOutline16, IconListPenOutline16, IconLoadingOutline16, IconNewChatOutline16, IconPanelLeftOutline16, IconPaperclipOutline16, IconPauseOutline16, IconPersonalizationOutline16, IconPlayOutline16, IconPlusOutline16, IconProjectAddOutline16, IconQuestionOutline14, IconQueueOutline14, IconRefreshOutline14, IconRefreshOutline16, IconRightUpOutline14, IconRightUpOutline16, IconSearchOutline16, IconSendOutline14, IconSendOutline16, IconSettingsOutline14, IconSettingsOutline16, IconShareOutline16, IconSkillOutline16, IconSparkle16, IconStopFill16, IconThinkOutline14, IconThinkOutline16, IconTrashOutline16, IconTreeCorner8x10, IconTriangleRightFill14, IconUserOutline16, IconWarningOutline16, Input, JsonBlock, JsonTree, MarkdownText, Menu, MessageText, Modal, OnboardingSurface, Pill, ReadBlock, RiskConfirmation, SearchBlock, StateDot, TerminalBlock, Toast, Tooltip, WebBlock, extractMarkdownPlainText, useAnchoredMaxHeight, useAnchoredPosition, useDismissOnOutsidePointer, writeClipboard };
6455
+ export { BrandWordmark, Button, CodeBlock, ConnectionIndicator, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DiffBlock, DisclosureRow, FISH_LOGO_PATH, FISH_LOGO_VIEWBOX, FishLogo, HoverCard, IconAgentPresetOutline16, IconAlarmClockOutline16, IconApiOutline14, IconArchiveOutline20, IconBranchOutline16, IconBrowseOutline16, IconCheckOutline14, IconCheckOutline16, IconChecklistOutline14, IconChevronDownOutline14, IconChevronLeftOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconClockOutline16, IconCloseFill14, IconCloseOutline16, IconCodeOutline16, IconContextInjectionOutline16, IconCopyOutline16, IconCordisPluginOutline14, IconDarkOutline16, IconDataOutline16, IconDatabaseOutline16, IconDislikeFill16, IconDislikeOutline16, IconDownloadOutline16, IconEditOutline16, IconEllipsisOutline16, IconEnhanceOutline16, IconFolderClose16, IconFolderOpen16, IconFolderOpenOutline16, IconFollowsystemOutline16, IconFullscreenOutline16, IconGlobeOutline14, IconGoalOutline16, IconInspectOutline12, IconLightOutline16, IconLikeFill16, IconLikeOutline16, IconLinkOutline14, IconLinkOutline16, IconListPenOutline16, IconLoadingOutline16, IconNewChatOutline16, IconPanelLeftOutline16, IconPaperclipOutline16, IconPauseOutline16, IconPersonalizationOutline16, IconPlayOutline16, IconPlusOutline16, IconProjectAddOutline16, IconQuestionOutline14, IconQueueOutline14, IconRefreshOutline14, IconRefreshOutline16, IconRightUpOutline14, IconRightUpOutline16, IconSearchOutline16, IconSendOutline14, IconSendOutline16, IconSettingsOutline14, IconSettingsOutline16, IconShareOutline16, IconSkillOutline16, IconSparkle16, IconStopFill16, IconThinkOutline14, IconThinkOutline16, IconTrashOutline16, IconTreeCorner8x10, IconTriangleRightFill14, IconUserOutline16, IconWarningOutline16, Input, JsonBlock, JsonTree, MarkdownText, Menu, MessageText, Modal, OnboardingSurface, Pill, ReadBlock, ReferenceIcon, RiskConfirmation, SearchBlock, StateDot, TerminalBlock, Toast, Tooltip, WebBlock, diffTotals, extractMarkdownPlainText, projectUserText, relativeTime, useAnchoredMaxHeight, useAnchoredPosition, useDismissOnOutsidePointer, writeClipboard };
5912
6456
 
5913
6457
  //# sourceMappingURL=index.js.map