@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.
- package/README.i18n.yaml +2 -2
- package/README.md +90 -24
- package/README.zh.md +91 -25
- package/lib/ConnectionIndicator.module.css +107 -0
- package/lib/DiffBlock.module.css +0 -6
- package/lib/DisclosureRow.module.css +21 -6
- package/lib/ReadBlock.module.css +0 -6
- package/lib/SearchBlock.module.css +0 -8
- package/lib/TerminalBlock.module.css +0 -6
- package/lib/Toast.module.css +7 -10
- package/lib/Tooltip.module.css +0 -7
- package/lib/WebBlock.module.css +0 -6
- package/lib/index.js +878 -334
- package/lib/markdown/CodeBlock.module.css +4 -2
- package/lib/markdown/MarkdownText.module.css +2 -2
- package/lib/types/ConnectionIndicator.d.ts +25 -0
- package/lib/types/DiffBlock.d.ts +28 -9
- package/lib/types/FishLogo.d.ts +7 -0
- package/lib/types/FoldToggle.d.ts +20 -0
- package/lib/types/HoverCard.d.ts +4 -4
- package/lib/types/JsonTree.d.ts +4 -6
- package/lib/types/Modal.d.ts +22 -18
- package/lib/types/OnboardingSurface.d.ts +2 -2
- package/lib/types/ReadBlock.d.ts +13 -1
- package/lib/types/ReferenceIcon.d.ts +16 -0
- package/lib/types/RiskConfirmation.d.ts +2 -1
- package/lib/types/SearchBlock.d.ts +14 -0
- package/lib/types/TerminalBlock.d.ts +4 -10
- package/lib/types/Toast.d.ts +9 -1
- package/lib/types/WebBlock.d.ts +13 -0
- package/lib/types/icons/index.d.ts +8 -0
- package/lib/types/index.d.ts +14 -8
- package/lib/types/markdown/CodeBlock.d.ts +11 -3
- package/lib/types/markdown/JsonBlock.d.ts +1 -1
- package/lib/types/markdown/MarkdownText.d.ts +8 -7
- package/lib/types/markdown/highlight.d.ts +44 -2
- package/lib/types/markdown/render.d.ts +9 -4
- package/lib/types/relative-time.d.ts +23 -0
- package/lib/types/useAnchoredPosition.d.ts +8 -6
- package/lib/types/useDismissOnOutsidePointer.d.ts +3 -1
- package/lib/types/user-text.d.ts +19 -0
- package/lib/user-text.module.css +35 -0
- package/package.json +4 -5
- package/lib/ConnectionBanner.module.css +0 -13
- 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 "./
|
|
16
|
-
import css$11 from "./
|
|
17
|
-
import css$12 from "./
|
|
18
|
-
import css$13 from "./
|
|
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$
|
|
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$
|
|
27
|
-
import css$
|
|
28
|
-
import 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$
|
|
41
|
+
import css$19 from "./markdown/CodeBlock.module.css";
|
|
41
42
|
import katex from "katex";
|
|
42
|
-
import css$
|
|
43
|
+
import css$20 from "./markdown/MarkdownText.module.css";
|
|
43
44
|
import "katex/dist/katex.min.css";
|
|
44
|
-
import css$
|
|
45
|
-
import css$
|
|
46
|
-
import 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
|
|
1759
|
-
* inside the viewport, and re-run on scroll (capture phase, so
|
|
1760
|
-
* inside the page are caught too), on resize, and on the
|
|
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 &&
|
|
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
|
|
1892
|
-
* @param props.copiedLabel - visible success label
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
2137
|
-
*
|
|
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/
|
|
2341
|
+
//#region lib/types/ConnectionIndicator.js
|
|
2213
2342
|
/**
|
|
2214
|
-
* Render
|
|
2215
|
-
* @param props.
|
|
2216
|
-
* @param props.
|
|
2217
|
-
*
|
|
2218
|
-
* @
|
|
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
|
|
2221
|
-
if (
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
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 *
|
|
2456
|
+
height: size * FISH_LOGO_VIEWBOX.height / FISH_LOGO_VIEWBOX.width,
|
|
2239
2457
|
className,
|
|
2240
|
-
viewBox:
|
|
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:
|
|
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$
|
|
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,
|
|
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$
|
|
2896
|
+
className: css$13.toast,
|
|
2554
2897
|
role: "alert",
|
|
2555
|
-
style:
|
|
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$
|
|
2903
|
+
className: css$13.icon,
|
|
2558
2904
|
"aria-hidden": true,
|
|
2559
2905
|
children: icon
|
|
2560
2906
|
}), jsx("span", {
|
|
2561
|
-
className: css$
|
|
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$
|
|
3007
|
+
className: css$14.keywordValue,
|
|
2628
3008
|
children: "null"
|
|
2629
3009
|
});
|
|
2630
3010
|
if (typeof value === "string") return jsx("span", {
|
|
2631
|
-
className: css$
|
|
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$
|
|
3015
|
+
className: css$14.numberValue,
|
|
2636
3016
|
children: String(value)
|
|
2637
3017
|
});
|
|
2638
3018
|
if (typeof value === "boolean") return jsx("span", {
|
|
2639
|
-
className: css$
|
|
3019
|
+
className: css$14.keywordValue,
|
|
2640
3020
|
children: String(value)
|
|
2641
3021
|
});
|
|
2642
3022
|
if (typeof value === "bigint") return jsx("span", {
|
|
2643
|
-
className: css$
|
|
3023
|
+
className: css$14.otherValue,
|
|
2644
3024
|
children: value.toString()
|
|
2645
3025
|
});
|
|
2646
3026
|
if (typeof value === "undefined") return jsx("span", {
|
|
2647
|
-
className: css$
|
|
3027
|
+
className: css$14.otherValue,
|
|
2648
3028
|
children: "undefined"
|
|
2649
3029
|
});
|
|
2650
3030
|
if (typeof value === "symbol") return jsx("span", {
|
|
2651
|
-
className: css$
|
|
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$
|
|
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$
|
|
3049
|
+
className: css$14.punctuation,
|
|
2670
3050
|
children: open
|
|
2671
3051
|
}),
|
|
2672
3052
|
depth >= PREVIEW_DEPTH_LIMIT ? jsx("span", {
|
|
2673
|
-
className: css$
|
|
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$
|
|
3057
|
+
className: css$14.punctuation,
|
|
2678
3058
|
children: ", "
|
|
2679
3059
|
}),
|
|
2680
3060
|
!array && jsxs(Fragment, { children: [jsx("span", {
|
|
2681
|
-
className: css$
|
|
3061
|
+
className: css$14.previewProperty,
|
|
2682
3062
|
children: key
|
|
2683
3063
|
}), jsx("span", {
|
|
2684
|
-
className: css$
|
|
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$
|
|
3070
|
+
className: css$14.previewEllipsis,
|
|
2691
3071
|
children: ", …"
|
|
2692
3072
|
}),
|
|
2693
3073
|
jsx("span", {
|
|
2694
|
-
className: css$
|
|
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$
|
|
3081
|
+
className: css$14.keywordValue,
|
|
2702
3082
|
children: "null"
|
|
2703
3083
|
});
|
|
2704
3084
|
if (typeof value === "string") return jsx("span", {
|
|
2705
|
-
className: css$
|
|
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$
|
|
3089
|
+
className: css$14.keywordValue,
|
|
2710
3090
|
children: String(value)
|
|
2711
3091
|
});
|
|
2712
3092
|
if (typeof value === "number") return jsx("span", {
|
|
2713
|
-
className: css$
|
|
3093
|
+
className: css$14.numberValue,
|
|
2714
3094
|
children: String(value)
|
|
2715
3095
|
});
|
|
2716
3096
|
if (typeof value === "bigint") return jsx("span", {
|
|
2717
|
-
className: css$
|
|
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$
|
|
3101
|
+
className: css$14.otherValue,
|
|
2722
3102
|
children: value.toISOString()
|
|
2723
3103
|
});
|
|
2724
3104
|
if (typeof value === "function") return jsxs("span", {
|
|
2725
|
-
className: css$
|
|
3105
|
+
className: css$14.otherValue,
|
|
2726
3106
|
children: ["function() ", "{ }"]
|
|
2727
3107
|
});
|
|
2728
3108
|
if (typeof value === "undefined") return jsx("span", {
|
|
2729
|
-
className: css$
|
|
3109
|
+
className: css$14.otherValue,
|
|
2730
3110
|
children: "undefined"
|
|
2731
3111
|
});
|
|
2732
3112
|
return jsx("span", {
|
|
2733
|
-
className: css$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3202
|
+
className: css$14.punctuation,
|
|
2823
3203
|
children: open
|
|
2824
3204
|
}),
|
|
2825
3205
|
jsx("span", {
|
|
2826
|
-
className: css$
|
|
3206
|
+
className: css$14.punctuation,
|
|
2827
3207
|
children: close
|
|
2828
3208
|
}),
|
|
2829
3209
|
!lastElement && jsx("span", {
|
|
2830
|
-
className: css$
|
|
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$
|
|
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$
|
|
3236
|
+
className: css$14.preview,
|
|
2857
3237
|
children: previewValue(value, 0)
|
|
2858
3238
|
}),
|
|
2859
3239
|
!lastElement && jsx("span", {
|
|
2860
|
-
className: css$
|
|
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$
|
|
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
|
|
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" ?
|
|
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$
|
|
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$
|
|
3414
|
+
className: css$14.expandedTopLevel,
|
|
3039
3415
|
children: [
|
|
3040
3416
|
jsx("div", {
|
|
3041
|
-
className: clsx(css$
|
|
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$
|
|
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$
|
|
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
|
|
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$
|
|
3448
|
+
className: clsx(css$14.row, css$14.topLevelBracket),
|
|
3073
3449
|
children: jsx("span", {
|
|
3074
|
-
className: css$
|
|
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$
|
|
3457
|
+
className: css$14.container,
|
|
3082
3458
|
role: "tree",
|
|
3083
3459
|
children: jsx(JsonTreeNode, {
|
|
3084
3460
|
value: data,
|
|
3085
3461
|
path: [],
|
|
3086
|
-
labels
|
|
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$
|
|
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$
|
|
3484
|
+
className: css$14.copyButton,
|
|
3109
3485
|
"data-json-copy-button": true,
|
|
3110
3486
|
"data-state": copyState,
|
|
3111
3487
|
"aria-label": copyTitle,
|
|
3112
|
-
title:
|
|
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(
|
|
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 =
|
|
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$
|
|
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$
|
|
4053
|
+
className: css$15.header,
|
|
3699
4054
|
children: [
|
|
3700
4055
|
jsxs("div", {
|
|
3701
|
-
className: css$
|
|
4056
|
+
className: css$15.prompt,
|
|
3702
4057
|
children: [jsx("span", {
|
|
3703
|
-
className: css$
|
|
4058
|
+
className: css$15.runStateLabel,
|
|
3704
4059
|
children: state.label
|
|
3705
4060
|
}), commandLines.map((line, index) => jsxs("div", {
|
|
3706
|
-
className: css$
|
|
4061
|
+
className: css$15.promptLine,
|
|
3707
4062
|
children: [
|
|
3708
4063
|
index === 0 && jsx(StateDot, {
|
|
3709
4064
|
state: state.state,
|
|
3710
|
-
className: css$
|
|
4065
|
+
className: css$15.runState
|
|
3711
4066
|
}),
|
|
3712
4067
|
jsx("span", {
|
|
3713
|
-
className: css$
|
|
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$
|
|
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$
|
|
4079
|
+
className: css$15.status,
|
|
3725
4080
|
children: status
|
|
3726
4081
|
}),
|
|
3727
4082
|
!running && !empty && jsx("button", {
|
|
3728
4083
|
type: "button",
|
|
3729
|
-
className: css$
|
|
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$
|
|
4090
|
+
className: css$15.empty,
|
|
3736
4091
|
children: copy.noOutput
|
|
3737
4092
|
}) : jsxs("div", {
|
|
3738
|
-
className: css$
|
|
4093
|
+
className: css$15.output,
|
|
3739
4094
|
children: [
|
|
3740
4095
|
(capped ? lines.slice(0, headLines) : lines).map((line, index) => jsx("div", {
|
|
3741
|
-
className: css$
|
|
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$
|
|
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$
|
|
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
|
-
*
|
|
4020
|
-
*
|
|
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$
|
|
4585
|
+
className: css$16.line,
|
|
4101
4586
|
children: [jsx("span", {
|
|
4102
|
-
className: css$
|
|
4587
|
+
className: css$16.gutter,
|
|
4103
4588
|
"aria-hidden": true,
|
|
4104
4589
|
children: line.number
|
|
4105
4590
|
}), jsx("span", {
|
|
4106
|
-
className: css$
|
|
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$
|
|
4597
|
+
className: clsx(css$16.block, className),
|
|
4113
4598
|
"data-read": "",
|
|
4114
4599
|
children: [jsxs("div", {
|
|
4115
|
-
className: css$
|
|
4600
|
+
className: css$16.banner,
|
|
4116
4601
|
children: [jsx("div", {
|
|
4117
|
-
className: css$
|
|
4602
|
+
className: css$16.label,
|
|
4118
4603
|
children: label ?? ""
|
|
4119
4604
|
}), jsxs("div", {
|
|
4120
|
-
className: css$
|
|
4605
|
+
className: css$16.action,
|
|
4121
4606
|
children: [
|
|
4122
4607
|
windowed && jsx("span", {
|
|
4123
|
-
className: css$
|
|
4124
|
-
children:
|
|
4608
|
+
className: css$16.count,
|
|
4609
|
+
children: labels.window(lines.length, totalLines)
|
|
4125
4610
|
}),
|
|
4126
4611
|
jsx("span", {
|
|
4127
|
-
className: css$
|
|
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$
|
|
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$
|
|
4624
|
+
className: css$16.body,
|
|
4140
4625
|
children: [
|
|
4141
4626
|
rows(capped ? paired.slice(0, headLines) : paired),
|
|
4142
|
-
hidden > 0 && jsx(
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
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$
|
|
4171
|
-
del: css$
|
|
4172
|
-
add: css$
|
|
4173
|
-
gap: css$
|
|
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.
|
|
4179
|
-
*
|
|
4180
|
-
*
|
|
4181
|
-
*
|
|
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
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
4787
|
+
className: css$17.body,
|
|
4297
4788
|
children: [
|
|
4298
4789
|
head.map((row, index) => jsx("div", {
|
|
4299
|
-
className: clsx(css$
|
|
4790
|
+
className: clsx(css$17.line, ROW_CLASS[row.kind]),
|
|
4300
4791
|
children: row.text
|
|
4301
4792
|
}, index)),
|
|
4302
|
-
hidden > 0 && jsx(
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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$
|
|
4943
|
+
className: css$18.line,
|
|
4457
4944
|
children: row.path
|
|
4458
4945
|
});
|
|
4459
4946
|
if (row.type === "match") return jsxs("div", {
|
|
4460
|
-
className: css$
|
|
4947
|
+
className: css$18.line,
|
|
4461
4948
|
children: [jsxs("span", {
|
|
4462
|
-
className: css$
|
|
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$
|
|
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$
|
|
4961
|
+
className: css$18.filePath,
|
|
4475
4962
|
children: row.path
|
|
4476
4963
|
}), jsx("span", {
|
|
4477
|
-
className: css$
|
|
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$
|
|
4970
|
+
className: clsx(css$18.block, className),
|
|
4484
4971
|
"data-search": props.kind,
|
|
4485
4972
|
children: [jsxs("div", {
|
|
4486
|
-
className: css$
|
|
4973
|
+
className: css$18.header,
|
|
4487
4974
|
children: [jsx("span", {
|
|
4488
|
-
className: css$
|
|
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$
|
|
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$
|
|
4498
|
-
children:
|
|
4984
|
+
className: css$18.empty,
|
|
4985
|
+
children: props.labels.noResults
|
|
4499
4986
|
}) : jsxs("div", {
|
|
4500
|
-
className: css$
|
|
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$
|
|
4992
|
+
className: css$18.expand,
|
|
4506
4993
|
"aria-expanded": expanded,
|
|
4507
|
-
"aria-label": expanded ?
|
|
4994
|
+
"aria-label": expanded ? props.labels.collapseAria : props.labels.expandAria(hidden),
|
|
4508
4995
|
onClick: onToggle,
|
|
4509
|
-
children: expanded ?
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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$
|
|
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$
|
|
5554
|
+
className: clsx(css$19.block, "md-code-block", className),
|
|
5016
5555
|
children: [jsx("div", {
|
|
5017
|
-
className: css$
|
|
5556
|
+
className: css$19.bannerWrap,
|
|
5018
5557
|
children: jsxs("div", {
|
|
5019
|
-
className: css$
|
|
5558
|
+
className: css$19.banner,
|
|
5020
5559
|
children: [jsx("div", {
|
|
5021
|
-
className: css$
|
|
5560
|
+
className: css$19.infostring,
|
|
5022
5561
|
children: lang ?? ""
|
|
5023
5562
|
}), jsx("div", {
|
|
5024
|
-
className: css$
|
|
5563
|
+
className: css$19.action,
|
|
5025
5564
|
children: jsx("button", {
|
|
5026
5565
|
type: "button",
|
|
5027
|
-
className: css$
|
|
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$
|
|
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
|
|
5283
|
-
|
|
5284
|
-
|
|
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$
|
|
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$
|
|
5932
|
+
className: css$20.imageAlt,
|
|
5393
5933
|
children: alt
|
|
5394
5934
|
}, key);
|
|
5395
5935
|
return jsx("img", {
|
|
5396
|
-
className: css$
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5529
|
-
constructor(
|
|
5530
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
5597
|
-
*
|
|
5598
|
-
*
|
|
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,
|
|
6150
|
+
const MarkdownText = memo(function MarkdownText({ text, streaming = false, labels, fileMentions }) {
|
|
5610
6151
|
const streamRef = useRef(null);
|
|
5611
|
-
const streamLabelsRef = useRef(
|
|
6152
|
+
const streamLabelsRef = useRef(labels);
|
|
5612
6153
|
const children = useMemo(() => {
|
|
5613
6154
|
if (!streaming) {
|
|
5614
6155
|
streamRef.current = null;
|
|
5615
|
-
return renderSettled(text,
|
|
6156
|
+
return renderSettled(text, labels, fileMentions);
|
|
5616
6157
|
}
|
|
5617
|
-
if (streamRef.current === null || streamLabelsRef.current !==
|
|
5618
|
-
streamRef.current = new StreamingRenderer(
|
|
5619
|
-
streamLabelsRef.current =
|
|
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
|
-
|
|
6166
|
+
labels,
|
|
5626
6167
|
fileMentions
|
|
5627
6168
|
]);
|
|
5628
6169
|
return jsx("div", {
|
|
5629
|
-
className: css$
|
|
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$
|
|
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$
|
|
6251
|
+
className: css$21.sourceLink
|
|
5711
6252
|
}),
|
|
5712
6253
|
source.snippet !== void 0 && source.snippet !== "" && jsx("div", {
|
|
5713
|
-
className: css$
|
|
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$
|
|
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$
|
|
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$
|
|
5737
|
-
children: jsx(MarkdownText, {
|
|
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$
|
|
5741
|
-
children:
|
|
6284
|
+
className: css$21.empty,
|
|
6285
|
+
children: labels.noResults
|
|
5742
6286
|
}) : jsx("ol", {
|
|
5743
|
-
className: css$
|
|
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$
|
|
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$
|
|
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$
|
|
6312
|
+
className: css$21.fetchUrl
|
|
5769
6313
|
}), jsxs("div", {
|
|
5770
|
-
className: css$
|
|
6314
|
+
className: css$21.fetchMeta,
|
|
5771
6315
|
children: [jsxs("span", {
|
|
5772
|
-
className: css$
|
|
5773
|
-
children: [
|
|
6316
|
+
className: css$21.status,
|
|
6317
|
+
children: [
|
|
6318
|
+
labels.http,
|
|
6319
|
+
" ",
|
|
6320
|
+
statusCode
|
|
6321
|
+
]
|
|
5774
6322
|
}), truncated && jsx("span", {
|
|
5775
|
-
className: css$
|
|
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
|
-
|
|
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$
|
|
6357
|
+
className: css$22.root,
|
|
5814
6358
|
children: [jsxs("button", {
|
|
5815
6359
|
type: "button",
|
|
5816
|
-
className: css$
|
|
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$
|
|
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$
|
|
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,
|
|
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
|