@bitbaum/chatkit 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/attachments.d.ts +72 -0
  4. package/dist/attachments.d.ts.map +1 -0
  5. package/dist/attachments.js +64 -0
  6. package/dist/attachments.js.map +1 -0
  7. package/dist/composer.d.ts +34 -0
  8. package/dist/composer.d.ts.map +1 -0
  9. package/dist/composer.js +41 -0
  10. package/dist/composer.js.map +1 -0
  11. package/dist/dictation.d.ts +60 -0
  12. package/dist/dictation.d.ts.map +1 -0
  13. package/dist/dictation.js +93 -0
  14. package/dist/dictation.js.map +1 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +11 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/markdown.d.ts +64 -0
  20. package/dist/markdown.d.ts.map +1 -0
  21. package/dist/markdown.js +129 -0
  22. package/dist/markdown.js.map +1 -0
  23. package/dist/react/Composer.d.ts +85 -0
  24. package/dist/react/Composer.d.ts.map +1 -0
  25. package/dist/react/Composer.js +132 -0
  26. package/dist/react/Composer.js.map +1 -0
  27. package/dist/react/Markdown.d.ts +27 -0
  28. package/dist/react/Markdown.d.ts.map +1 -0
  29. package/dist/react/Markdown.js +62 -0
  30. package/dist/react/Markdown.js.map +1 -0
  31. package/dist/react/Thread.d.ts +90 -0
  32. package/dist/react/Thread.d.ts.map +1 -0
  33. package/dist/react/Thread.js +64 -0
  34. package/dist/react/Thread.js.map +1 -0
  35. package/dist/react/hooks.d.ts +40 -0
  36. package/dist/react/hooks.d.ts.map +1 -0
  37. package/dist/react/hooks.js +99 -0
  38. package/dist/react/hooks.js.map +1 -0
  39. package/dist/react/icons.d.ts +12 -0
  40. package/dist/react/icons.d.ts.map +1 -0
  41. package/dist/react/icons.js +21 -0
  42. package/dist/react/icons.js.map +1 -0
  43. package/dist/react/index.d.ts +21 -0
  44. package/dist/react/index.d.ts.map +1 -0
  45. package/dist/react/index.js +16 -0
  46. package/dist/react/index.js.map +1 -0
  47. package/dist/react/use-attachments.d.ts +26 -0
  48. package/dist/react/use-attachments.d.ts.map +1 -0
  49. package/dist/react/use-attachments.js +202 -0
  50. package/dist/react/use-attachments.js.map +1 -0
  51. package/dist/react/use-dictation.d.ts +49 -0
  52. package/dist/react/use-dictation.d.ts.map +1 -0
  53. package/dist/react/use-dictation.js +365 -0
  54. package/dist/react/use-dictation.js.map +1 -0
  55. package/dist/scroll.d.ts +13 -0
  56. package/dist/scroll.d.ts.map +1 -0
  57. package/dist/scroll.js +11 -0
  58. package/dist/scroll.js.map +1 -0
  59. package/package.json +83 -0
  60. package/styles.css +689 -0
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Markdown, defaultRenderLink } from "./Markdown.js";
4
+ import { useClipboard, useStickToBottom } from "./hooks.js";
5
+ import { IconArrowDown, IconCheck, IconCopy, IconRetry, IconStop } from "./icons.js";
6
+ export const DEFAULT_THREAD_LABELS = {
7
+ copy: "Copy",
8
+ copied: "Copied",
9
+ retry: "Try again",
10
+ stop: "Stop",
11
+ working: "Working on it",
12
+ stopped: "Stopped. Send it again to retry.",
13
+ jump: "Jump to the latest message",
14
+ failed: "That did not go through.",
15
+ loading: "Loading conversation",
16
+ };
17
+ /**
18
+ * One turn. The asymmetry is deliberate: the person's own words get a pill —
19
+ * a short aside they already know — and the answer gets the full column with
20
+ * no box, because it is the thing being READ. Copy is on every answer; Retry
21
+ * only on the last one (retrying an older turn would fork the thread).
22
+ */
23
+ export function ChatMessage({ message, onRetry, renderLink = defaultRenderLink, showSpeaker = true, footer, last = false, labels = DEFAULT_THREAD_LABELS, }) {
24
+ const { copied, copy } = useClipboard();
25
+ if (message.role === "user") {
26
+ return (_jsx("div", { className: "ck-turn ck-turn-user", children: _jsx("div", { className: "ck-bubble", children: message.content }) }));
27
+ }
28
+ const cls = ["ck-turn", "ck-turn-answer"];
29
+ if (message.speaker?.id)
30
+ cls.push(`ck-from-${message.speaker.id}`);
31
+ // A failed turn and the latest answer never hide their buttons: Retry after
32
+ // a failure is the one thing the reader needs, and a hover it cannot do on
33
+ // a phone must not stand between them.
34
+ if (last || message.failed)
35
+ cls.push("ck-turn-pinned");
36
+ return (_jsxs("div", { className: cls.join(" "), children: [showSpeaker && message.speaker && _jsx("span", { className: "ck-speaker", children: message.speaker.name }), message.failed ? (_jsx("p", { className: "ck-failed", role: "status", children: message.content || labels.failed })) : (_jsx(Markdown, { text: message.content, citations: message.citations, renderLink: renderLink })), footer, _jsxs("div", { className: "ck-turn-actions", children: [!message.failed && (_jsxs("button", { type: "button", className: "ck-action", onClick: () => copy(message.content), "aria-label": copied ? labels.copied : labels.copy, children: [copied ? _jsx(IconCheck, {}) : _jsx(IconCopy, {}), _jsx("span", { children: copied ? labels.copied : labels.copy })] })), onRetry && (_jsxs("button", { type: "button", className: "ck-action", onClick: onRetry, children: [_jsx(IconRetry, {}), _jsx("span", { children: labels.retry })] }))] })] }));
37
+ }
38
+ /**
39
+ * The conversation. Follows new tokens only while the reader is at the
40
+ * bottom, with a button back down once they are not; a turn in flight can be
41
+ * stopped from where it is being written.
42
+ */
43
+ export function ChatThread({ messages, live = null, loading = false, stopped = false, onStop, onRetry, renderLink = defaultRenderLink, renderFooter, showSpeakers, empty, children, labels: labelOverrides, }) {
44
+ const labels = { ...DEFAULT_THREAD_LABELS, ...labelOverrides };
45
+ const { scrollRef, endRef, following, onScroll, jumpToBottom } = useStickToBottom([messages.length, live?.text, live?.status, stopped], messages.length);
46
+ if (loading) {
47
+ return (_jsx("div", { className: "ck-thread-loading", role: "status", children: labels.loading }));
48
+ }
49
+ if (messages.length === 0 && !live && !stopped)
50
+ return _jsx(_Fragment, { children: empty ?? null });
51
+ const speakers = new Set([...messages.map((m) => m.speaker?.name), live?.speaker?.name].filter(Boolean));
52
+ const named = showSpeakers ?? speakers.size > 1;
53
+ const lastAnswer = [...messages].reverse().find((m) => m.role === "assistant");
54
+ return (_jsxs("div", { className: "ck-thread-wrap", children: [_jsx("div", { ref: scrollRef, className: "ck-thread", onScroll: onScroll, children: _jsxs("div", { className: "ck-thread-inner", children: [messages.map((m) => (_jsx(ChatMessage, { message: m, onRetry: onRetry && m.id === lastAnswer?.id && !live ? onRetry : undefined, renderLink: renderLink, showSpeaker: named, footer: renderFooter?.(m), last: m.id === lastAnswer?.id, labels: labels }, m.id))), live && (_jsxs("div", { className: "ck-turn ck-turn-answer", children: [named && live.speaker && _jsx("span", { className: "ck-speaker", children: live.speaker.name }), live.text ? (_jsx(Markdown, { text: live.text, renderLink: renderLink })) : (_jsxs("p", { className: "ck-live", role: "status", "aria-live": "polite", children: [_jsxs("span", { className: "ck-dots", "aria-hidden": true, children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), live.status ?? labels.working] })), onStop && (_jsxs("button", { type: "button", className: "ck-stop", onClick: onStop, children: [_jsx(IconStop, {}), labels.stop] }))] })), stopped && !live && (_jsx("p", { className: "ck-stopped", role: "status", children: labels.stopped })), children, _jsx("div", { ref: endRef })] }) }), !following && (_jsx("button", { type: "button", className: "ck-jump", onClick: jumpToBottom, "aria-label": labels.jump, title: labels.jump, children: _jsx(IconArrowDown, {}) }))] }));
55
+ }
56
+ /** The empty state that starts a conversation. It disappears once one has. */
57
+ export function ChatStarters({ starters, onPick, title, }) {
58
+ return (_jsxs("div", { className: "ck-starters", children: [title && _jsx("div", { className: "ck-starters-title", children: title }), _jsx("div", { className: "ck-starters-list", children: starters.map((s, i) => {
59
+ const label = typeof s === "string" ? s : s.label;
60
+ const prompt = typeof s === "string" ? s : (s.prompt ?? s.label);
61
+ return (_jsx("button", { type: "button", className: "ck-starter", onClick: () => onPick(prompt), children: label }, i));
62
+ }) })] }));
63
+ }
64
+ //# sourceMappingURL=Thread.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Thread.js","sourceRoot":"","sources":["../../src/react/Thread.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { type DependencyList, type RefObject } from "react";
2
+ /**
3
+ * Copy + a checkmark that resets. Failures are swallowed on purpose (insecure
4
+ * origin, denied permission): the text stays selectable, so a failed copy
5
+ * blocks nothing — `copied` simply never flips.
6
+ */
7
+ export declare function useClipboard(resetMs?: number): {
8
+ copied: boolean;
9
+ copy: (text: string) => void;
10
+ };
11
+ /**
12
+ * Follow the conversation only while the reader is at the bottom, and offer a
13
+ * way back once they are not. Pinned in a layout effect so the view settles
14
+ * before the new token paints — in a passive effect the content lands first
15
+ * and the scroll catches up, which reads as a shudder on every chunk.
16
+ *
17
+ * `grew` should change when a NEW turn is added (message count): sending is an
18
+ * unambiguous "show me the reply", so it re-arms following.
19
+ */
20
+ export declare function useStickToBottom(changes: DependencyList, grew: number): {
21
+ scrollRef: RefObject<HTMLDivElement | null>;
22
+ endRef: RefObject<HTMLDivElement | null>;
23
+ following: boolean;
24
+ onScroll: () => void;
25
+ jumpToBottom: () => void;
26
+ };
27
+ /**
28
+ * Publishes the VISUAL viewport height as `--ck-viewport-height`.
29
+ *
30
+ * `100dvh` does not shrink when the Android soft keyboard opens; only
31
+ * `visualViewport` does. A chat column sized `100dvh` keeps its height while
32
+ * the keyboard covers the bottom of it, so the composer ends up behind the
33
+ * keyboard (orangecat `ViewportHeightSync`, #1094). Rounded down: sub-pixel
34
+ * heights leave a 1px seam under the composer on some devices.
35
+ */
36
+ export declare function useViewportHeight(target?: RefObject<HTMLElement | null>): void;
37
+ /** Grow a textarea to its content up to `max` px, keyed on the VALUE so a
38
+ * prefill, a dictated transcript or the clear after send resize it too. */
39
+ export declare function useAutoGrow(ref: RefObject<HTMLTextAreaElement | null>, value: string, max?: number): void;
40
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/react/hooks.ts"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAO;;iBAUhC,MAAM;EAYhB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM;;;;;;EA0BrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,QAoBvE;AAED;4EAC4E;AAC5E,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAM,QAO/F"}
@@ -0,0 +1,99 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react";
3
+ import { isFollowing } from "../scroll.js";
4
+ /**
5
+ * Copy + a checkmark that resets. Failures are swallowed on purpose (insecure
6
+ * origin, denied permission): the text stays selectable, so a failed copy
7
+ * blocks nothing — `copied` simply never flips.
8
+ */
9
+ export function useClipboard(resetMs = 1500) {
10
+ const [copied, setCopied] = useState(false);
11
+ const timer = useRef(null);
12
+ useEffect(() => () => {
13
+ if (timer.current)
14
+ clearTimeout(timer.current);
15
+ }, []);
16
+ const copy = useCallback((text) => {
17
+ void Promise.resolve(navigator.clipboard?.writeText(text))
18
+ .then(() => {
19
+ setCopied(true);
20
+ if (timer.current)
21
+ clearTimeout(timer.current);
22
+ timer.current = setTimeout(() => setCopied(false), resetMs);
23
+ })
24
+ .catch(() => { });
25
+ }, [resetMs]);
26
+ return { copied, copy };
27
+ }
28
+ /**
29
+ * Follow the conversation only while the reader is at the bottom, and offer a
30
+ * way back once they are not. Pinned in a layout effect so the view settles
31
+ * before the new token paints — in a passive effect the content lands first
32
+ * and the scroll catches up, which reads as a shudder on every chunk.
33
+ *
34
+ * `grew` should change when a NEW turn is added (message count): sending is an
35
+ * unambiguous "show me the reply", so it re-arms following.
36
+ */
37
+ export function useStickToBottom(changes, grew) {
38
+ const scrollRef = useRef(null);
39
+ const endRef = useRef(null);
40
+ const [following, setFollowing] = useState(true);
41
+ const onScroll = useCallback(() => {
42
+ const el = scrollRef.current;
43
+ if (el)
44
+ setFollowing(isFollowing(el));
45
+ }, []);
46
+ const jumpToBottom = useCallback(() => {
47
+ setFollowing(true);
48
+ endRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
49
+ }, []);
50
+ useLayoutEffect(() => {
51
+ if (following)
52
+ endRef.current?.scrollIntoView({ block: "end" });
53
+ }, [following, ...changes]);
54
+ const previous = useRef(grew);
55
+ useEffect(() => {
56
+ if (grew > previous.current)
57
+ setFollowing(true);
58
+ previous.current = grew;
59
+ }, [grew]);
60
+ return { scrollRef, endRef, following, onScroll, jumpToBottom };
61
+ }
62
+ /**
63
+ * Publishes the VISUAL viewport height as `--ck-viewport-height`.
64
+ *
65
+ * `100dvh` does not shrink when the Android soft keyboard opens; only
66
+ * `visualViewport` does. A chat column sized `100dvh` keeps its height while
67
+ * the keyboard covers the bottom of it, so the composer ends up behind the
68
+ * keyboard (orangecat `ViewportHeightSync`, #1094). Rounded down: sub-pixel
69
+ * heights leave a 1px seam under the composer on some devices.
70
+ */
71
+ export function useViewportHeight(target) {
72
+ useEffect(() => {
73
+ const vv = window.visualViewport;
74
+ const el = target?.current ?? document.documentElement;
75
+ const sync = () => el.style.setProperty("--ck-viewport-height", `${Math.floor(vv?.height ?? window.innerHeight)}px`);
76
+ sync();
77
+ vv?.addEventListener("resize", sync);
78
+ vv?.addEventListener("scroll", sync);
79
+ window.addEventListener("orientationchange", sync);
80
+ return () => {
81
+ vv?.removeEventListener("resize", sync);
82
+ vv?.removeEventListener("scroll", sync);
83
+ window.removeEventListener("orientationchange", sync);
84
+ el.style.removeProperty("--ck-viewport-height");
85
+ };
86
+ }, [target]);
87
+ }
88
+ /** Grow a textarea to its content up to `max` px, keyed on the VALUE so a
89
+ * prefill, a dictated transcript or the clear after send resize it too. */
90
+ export function useAutoGrow(ref, value, max = 240) {
91
+ useLayoutEffect(() => {
92
+ const el = ref.current;
93
+ if (!el)
94
+ return;
95
+ el.style.height = "auto";
96
+ el.style.height = `${Math.min(el.scrollHeight, max)}px`;
97
+ }, [ref, value, max]);
98
+ }
99
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/react/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export declare const IconArrowUp: () => import("react").JSX.Element;
2
+ export declare const IconArrowDown: () => import("react").JSX.Element;
3
+ export declare const IconMic: () => import("react").JSX.Element;
4
+ export declare const IconCheck: () => import("react").JSX.Element;
5
+ export declare const IconX: () => import("react").JSX.Element;
6
+ export declare const IconStop: () => import("react").JSX.Element;
7
+ export declare const IconSpinner: () => import("react").JSX.Element;
8
+ export declare const IconCopy: () => import("react").JSX.Element;
9
+ export declare const IconRetry: () => import("react").JSX.Element;
10
+ export declare const IconPaperclip: () => import("react").JSX.Element;
11
+ export declare const IconFile: () => import("react").JSX.Element;
12
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/react/icons.tsx"],"names":[],"mappings":"AAyBA,eAAO,MAAM,WAAW,mCAKvB,CAAC;AACF,eAAO,MAAM,aAAa,mCAKzB,CAAC;AACF,eAAO,MAAM,OAAO,mCAMnB,CAAC;AACF,eAAO,MAAM,SAAS,mCAIrB,CAAC;AACF,eAAO,MAAM,KAAK,mCAKjB,CAAC;AACF,eAAO,MAAM,QAAQ,mCAIpB,CAAC;AACF,eAAO,MAAM,WAAW,mCAIvB,CAAC;AACF,eAAO,MAAM,QAAQ,mCAKpB,CAAC;AACF,eAAO,MAAM,SAAS,mCAKrB,CAAC;AACF,eAAO,MAAM,aAAa,mCAIzB,CAAC;AACF,eAAO,MAAM,QAAQ,mCAKpB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The handful of icons a chat needs, inline — so installing chatkit never
4
+ * drags an icon library into an app that uses a different one. Paths follow
5
+ * Lucide (ISC), stroke = currentColor, so they take the button's colour.
6
+ */
7
+ function Svg(props) {
8
+ return (_jsx("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", ...props }));
9
+ }
10
+ export const IconArrowUp = () => (_jsxs(Svg, { children: [_jsx("path", { d: "m5 12 7-7 7 7" }), _jsx("path", { d: "M12 19V5" })] }));
11
+ export const IconArrowDown = () => (_jsxs(Svg, { children: [_jsx("path", { d: "M12 5v14" }), _jsx("path", { d: "m19 12-7 7-7-7" })] }));
12
+ export const IconMic = () => (_jsxs(Svg, { children: [_jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }), _jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), _jsx("path", { d: "M12 19v3" })] }));
13
+ export const IconCheck = () => (_jsx(Svg, { children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }));
14
+ export const IconX = () => (_jsxs(Svg, { children: [_jsx("path", { d: "M18 6 6 18" }), _jsx("path", { d: "m6 6 12 12" })] }));
15
+ export const IconStop = () => (_jsx(Svg, { fill: "currentColor", stroke: "none", children: _jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }));
16
+ export const IconSpinner = () => (_jsx(Svg, { className: "ck-spin", children: _jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
17
+ export const IconCopy = () => (_jsxs(Svg, { children: [_jsx("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2" }), _jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })] }));
18
+ export const IconRetry = () => (_jsxs(Svg, { children: [_jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }), _jsx("path", { d: "M3 3v5h5" })] }));
19
+ export const IconPaperclip = () => (_jsx(Svg, { children: _jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }));
20
+ export const IconFile = () => (_jsxs(Svg, { children: [_jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }), _jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })] }));
21
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","sourceRoot":"","sources":["../../src/react/icons.tsx"],"names":[],"mappings":";AAEA;;;;GAIG;AACH,SAAS,GAAG,CAAC,KAA8B;IACzC,OAAO,CACL,cACE,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,KAAK,EACX,MAAM,EAAC,KAAK,EACZ,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAE,CAAC,EACd,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,iBACV,MAAM,EAClB,SAAS,EAAC,OAAO,KACb,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,CAC/B,MAAC,GAAG,eACF,eAAM,CAAC,EAAC,eAAe,GAAG,EAC1B,eAAM,CAAC,EAAC,UAAU,GAAG,IACjB,CACP,CAAC;AACF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,CACjC,MAAC,GAAG,eACF,eAAM,CAAC,EAAC,UAAU,GAAG,EACrB,eAAM,CAAC,EAAC,gBAAgB,GAAG,IACvB,CACP,CAAC;AACF,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,CAC3B,MAAC,GAAG,eACF,eAAM,CAAC,EAAC,sDAAsD,GAAG,EACjE,eAAM,CAAC,EAAC,4BAA4B,GAAG,EACvC,eAAM,CAAC,EAAC,UAAU,GAAG,IACjB,CACP,CAAC;AACF,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,CAC7B,KAAC,GAAG,cACF,eAAM,CAAC,EAAC,iBAAiB,GAAG,GACxB,CACP,CAAC;AACF,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,CACzB,MAAC,GAAG,eACF,eAAM,CAAC,EAAC,YAAY,GAAG,EACvB,eAAM,CAAC,EAAC,YAAY,GAAG,IACnB,CACP,CAAC;AACF,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,CAC5B,KAAC,GAAG,IAAC,IAAI,EAAC,cAAc,EAAC,MAAM,EAAC,MAAM,YACpC,eAAM,CAAC,EAAC,GAAG,EAAC,CAAC,EAAC,GAAG,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAC,EAAE,EAAC,GAAG,GAAG,GAC9C,CACP,CAAC;AACF,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,CAC/B,KAAC,GAAG,IAAC,SAAS,EAAC,SAAS,YACtB,eAAM,CAAC,EAAC,6BAA6B,GAAG,GACpC,CACP,CAAC;AACF,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,CAC5B,MAAC,GAAG,eACF,eAAM,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAC,CAAC,EAAC,GAAG,EAAC,CAAC,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,GAAG,EAClD,eAAM,CAAC,EAAC,yDAAyD,GAAG,IAChE,CACP,CAAC;AACF,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,CAC7B,MAAC,GAAG,eACF,eAAM,CAAC,EAAC,mDAAmD,GAAG,EAC9D,eAAM,CAAC,EAAC,UAAU,GAAG,IACjB,CACP,CAAC;AACF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,CACjC,KAAC,GAAG,cACF,eAAM,CAAC,EAAC,iHAAiH,GAAG,GACxH,CACP,CAAC;AACF,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,CAC5B,MAAC,GAAG,eACF,eAAM,CAAC,EAAC,4DAA4D,GAAG,EACvE,eAAM,CAAC,EAAC,yBAAyB,GAAG,IAChC,CACP,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @bitbaum/chatkit/react — the chat every product uses.
3
+ *
4
+ * import { Composer, ChatThread, ChatStarters } from "@bitbaum/chatkit/react";
5
+ * import "@bitbaum/chatkit/styles.css";
6
+ *
7
+ * Behaviour is fixed here and identical everywhere; the look follows the
8
+ * app's tokens through `--ck-*` variables (see styles.css).
9
+ */
10
+ export { Composer, DEFAULT_COMPOSER_LABELS } from "./Composer.js";
11
+ export type { ComposerProps, ComposerLabels, ComposerLabelOverrides, ComposerVoice, } from "./Composer.js";
12
+ export { ChatThread, ChatMessage, ChatStarters, DEFAULT_THREAD_LABELS } from "./Thread.js";
13
+ export type { ChatMessageData, ChatSpeaker, LiveTurn, Starter, ThreadLabels } from "./Thread.js";
14
+ export { Markdown, defaultRenderLink } from "./Markdown.js";
15
+ export type { CitationMap, RenderLink } from "./Markdown.js";
16
+ export { useDictation } from "./use-dictation.js";
17
+ export type { DictationController, DictationStatus, UseDictationOptions } from "./use-dictation.js";
18
+ export { useAttachments } from "./use-attachments.js";
19
+ export type { AttachmentsController } from "./use-attachments.js";
20
+ export { useClipboard, useStickToBottom, useViewportHeight, useAutoGrow } from "./hooks.js";
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAClE,YAAY,EACV,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC3F,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC5D,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACpG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @bitbaum/chatkit/react — the chat every product uses.
3
+ *
4
+ * import { Composer, ChatThread, ChatStarters } from "@bitbaum/chatkit/react";
5
+ * import "@bitbaum/chatkit/styles.css";
6
+ *
7
+ * Behaviour is fixed here and identical everywhere; the look follows the
8
+ * app's tokens through `--ck-*` variables (see styles.css).
9
+ */
10
+ export { Composer, DEFAULT_COMPOSER_LABELS } from "./Composer.js";
11
+ export { ChatThread, ChatMessage, ChatStarters, DEFAULT_THREAD_LABELS } from "./Thread.js";
12
+ export { Markdown, defaultRenderLink } from "./Markdown.js";
13
+ export { useDictation } from "./use-dictation.js";
14
+ export { useAttachments } from "./use-attachments.js";
15
+ export { useClipboard, useStickToBottom, useViewportHeight, useAutoGrow } from "./hooks.js";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAOlE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE3F,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { type ClipboardEvent } from "react";
2
+ import { type Attachment, type AttachmentLimits, type AttachmentNotes, type StagedAttachment } from "../attachments.js";
3
+ export type AttachmentsController = {
4
+ attachments: StagedAttachment[];
5
+ /** Why something did not attach (too big, wrong type, too many). Never
6
+ * thrown — a rejected file must not lose the draft next to it. */
7
+ note: string | null;
8
+ clearNote: () => void;
9
+ addFiles: (files: FileList | File[] | null) => void;
10
+ /** Consumes the paste only when it carried an image. */
11
+ addFromPaste: (e: ClipboardEvent) => boolean;
12
+ remove: (key: string) => void;
13
+ clear: () => void;
14
+ toWire: () => Attachment[];
15
+ keyOf: (a: Attachment) => string;
16
+ full: boolean;
17
+ limits: AttachmentLimits;
18
+ };
19
+ /**
20
+ * Staging screenshots and text files for a composer — paste-to-attach
21
+ * included, because on a phone that is how a screenshot arrives. Preview URLs
22
+ * are revoked on remove, clear and unmount (a preview never revoked is a leak
23
+ * that survives every send). From loki `hooks/use-attachments.ts`.
24
+ */
25
+ export declare function useAttachments(limits?: Partial<AttachmentLimits>, notes?: Partial<AttachmentNotes>): AttachmentsController;
26
+ //# sourceMappingURL=use-attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-attachments.d.ts","sourceRoot":"","sources":["../../src/react/use-attachments.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4C,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AACtF,OAAO,EASL,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAiC3B,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC;uEACmE;IACnE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IACpD,wDAAwD;IACxD,YAAY,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC;IAC7C,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,UAAU,EAAE,CAAC;IAC3B,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM,EACtC,KAAK,GAAE,OAAO,CAAC,eAAe,CAAM,GACnC,qBAAqB,CAgLvB"}
@@ -0,0 +1,202 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { DEFAULT_ATTACHMENT_LIMITS, DEFAULT_ATTACHMENT_NOTES, attachmentKey, dataUrlBytes, fitWithin, isImageMime, stripDataUrlBase64, toWire, } from "../attachments.js";
4
+ /**
5
+ * Draw the image no larger than `maxEdge` and encode it as JPEG, stepping the
6
+ * quality down until it fits `maxBytes`. Null when it cannot be made to fit,
7
+ * or the browser cannot decode it (no `createImageBitmap`, no canvas).
8
+ */
9
+ async function shrink(file, maxEdge, maxBytes) {
10
+ if (typeof createImageBitmap !== "function" || typeof document === "undefined")
11
+ return null;
12
+ const bitmap = await createImageBitmap(file);
13
+ try {
14
+ const size = fitWithin(bitmap.width, bitmap.height, maxEdge);
15
+ const canvas = document.createElement("canvas");
16
+ canvas.width = size.width;
17
+ canvas.height = size.height;
18
+ const ctx = canvas.getContext("2d");
19
+ if (!ctx)
20
+ return null;
21
+ // JPEG has no alpha: paint white first so a transparent PNG does not turn black.
22
+ ctx.fillStyle = "#fff";
23
+ ctx.fillRect(0, 0, size.width, size.height);
24
+ ctx.drawImage(bitmap, 0, 0, size.width, size.height);
25
+ let quality = 0.82;
26
+ let dataUrl = canvas.toDataURL("image/jpeg", quality);
27
+ while (dataUrlBytes(dataUrl) > maxBytes && quality > 0.4) {
28
+ quality -= 0.15;
29
+ dataUrl = canvas.toDataURL("image/jpeg", quality);
30
+ }
31
+ return dataUrlBytes(dataUrl) > maxBytes ? null : dataUrl;
32
+ }
33
+ finally {
34
+ bitmap.close();
35
+ }
36
+ }
37
+ /**
38
+ * Staging screenshots and text files for a composer — paste-to-attach
39
+ * included, because on a phone that is how a screenshot arrives. Preview URLs
40
+ * are revoked on remove, clear and unmount (a preview never revoked is a leak
41
+ * that survives every send). From loki `hooks/use-attachments.ts`.
42
+ */
43
+ export function useAttachments(limits = {}, notes = {}) {
44
+ const lim = { ...DEFAULT_ATTACHMENT_LIMITS, ...limits };
45
+ const { maxFiles, maxImageBytes, maxTextChars, maxImageEdge } = lim;
46
+ // Mirrored in a ref so a new `notes` object every render does not rebuild
47
+ // every callback below.
48
+ const say = useRef(DEFAULT_ATTACHMENT_NOTES);
49
+ useEffect(() => {
50
+ say.current = { ...DEFAULT_ATTACHMENT_NOTES, ...notes };
51
+ });
52
+ const [attachments, setAttachments] = useState([]);
53
+ const [note, setNote] = useState(null);
54
+ const previews = useRef(new Set());
55
+ const count = useRef(0);
56
+ useEffect(() => {
57
+ count.current = attachments.length;
58
+ }, [attachments.length]);
59
+ useEffect(() => {
60
+ const urls = previews.current;
61
+ return () => {
62
+ for (const url of urls)
63
+ URL.revokeObjectURL(url);
64
+ urls.clear();
65
+ };
66
+ }, []);
67
+ const add = useCallback((item) => setAttachments((prev) => {
68
+ if (prev.length >= maxFiles)
69
+ return prev;
70
+ if (prev.some((p) => attachmentKey(p) === attachmentKey(item)))
71
+ return prev;
72
+ return [...prev, item];
73
+ }), [maxFiles]);
74
+ const stageImage = useCallback((file) => {
75
+ const name = file.name || "screenshot.png";
76
+ if (!isImageMime(file.type)) {
77
+ setNote(say.current.wrongType(name));
78
+ return;
79
+ }
80
+ const tooLarge = () => setNote(say.current.imageTooLarge(name, Math.round(maxImageBytes / 1_000_000)));
81
+ if (maxImageEdge > 0) {
82
+ // Shrink first, then judge the size: a 4 MB screenshot is fine once it
83
+ // is the 300 KB it needed to be.
84
+ shrink(file, maxImageEdge, maxImageBytes)
85
+ .then((dataUrl) => {
86
+ if (!dataUrl) {
87
+ tooLarge();
88
+ return;
89
+ }
90
+ const previewUrl = URL.createObjectURL(file);
91
+ previews.current.add(previewUrl);
92
+ add({
93
+ kind: "image",
94
+ name: name.replace(/\.(png|gif|webp|jpe?g)$/i, "") + ".jpg",
95
+ mimeType: "image/jpeg",
96
+ dataBase64: stripDataUrlBase64(dataUrl),
97
+ previewUrl,
98
+ });
99
+ })
100
+ .catch(() => setNote(say.current.unreadable(name)));
101
+ return;
102
+ }
103
+ if (file.size > maxImageBytes) {
104
+ tooLarge();
105
+ return;
106
+ }
107
+ const reader = new FileReader();
108
+ reader.onload = () => {
109
+ const previewUrl = URL.createObjectURL(file);
110
+ previews.current.add(previewUrl);
111
+ add({
112
+ kind: "image",
113
+ name,
114
+ mimeType: file.type,
115
+ dataBase64: stripDataUrlBase64(String(reader.result ?? "")),
116
+ previewUrl,
117
+ });
118
+ };
119
+ reader.onerror = () => setNote(say.current.unreadable(name));
120
+ reader.readAsDataURL(file);
121
+ }, [add, maxImageBytes, maxImageEdge]);
122
+ const stageText = useCallback((file) => {
123
+ if (file.size > maxTextChars) {
124
+ setNote(say.current.textTooLarge(file.name, Math.round(maxTextChars / 1000)));
125
+ return;
126
+ }
127
+ const reader = new FileReader();
128
+ reader.onload = () => add({
129
+ kind: "text",
130
+ name: file.name,
131
+ content: String(reader.result ?? "").slice(0, maxTextChars),
132
+ });
133
+ reader.onerror = () => setNote(say.current.unreadable(file.name));
134
+ reader.readAsText(file);
135
+ }, [add, maxTextChars]);
136
+ const addFiles = useCallback((files) => {
137
+ if (!files)
138
+ return;
139
+ setNote(null);
140
+ const room = maxFiles - count.current;
141
+ if (room <= 0) {
142
+ setNote(say.current.tooMany(maxFiles));
143
+ return;
144
+ }
145
+ for (const file of Array.from(files).slice(0, room)) {
146
+ if (isImageMime(file.type))
147
+ stageImage(file);
148
+ else
149
+ stageText(file);
150
+ }
151
+ }, [maxFiles, stageImage, stageText]);
152
+ const addFromPaste = useCallback((e) => {
153
+ const items = e.clipboardData?.items;
154
+ if (!items)
155
+ return false;
156
+ const images = Array.from(items).filter((i) => i.type.startsWith("image/"));
157
+ if (images.length === 0)
158
+ return false;
159
+ setNote(null);
160
+ for (const item of images) {
161
+ const file = item.getAsFile();
162
+ if (file)
163
+ stageImage(file);
164
+ }
165
+ return true;
166
+ }, [stageImage]);
167
+ const revoke = (a) => {
168
+ if (!a.previewUrl)
169
+ return;
170
+ URL.revokeObjectURL(a.previewUrl);
171
+ previews.current.delete(a.previewUrl);
172
+ };
173
+ const remove = useCallback((key) => {
174
+ setAttachments((prev) => {
175
+ const target = prev.find((a) => attachmentKey(a) === key);
176
+ if (target)
177
+ revoke(target);
178
+ return prev.filter((a) => attachmentKey(a) !== key);
179
+ });
180
+ }, []);
181
+ const clear = useCallback(() => {
182
+ setAttachments((prev) => {
183
+ prev.forEach(revoke);
184
+ return [];
185
+ });
186
+ setNote(null);
187
+ }, []);
188
+ return {
189
+ attachments,
190
+ note,
191
+ clearNote: () => setNote(null),
192
+ addFiles,
193
+ addFromPaste,
194
+ remove,
195
+ clear,
196
+ toWire: () => toWire(attachments),
197
+ keyOf: attachmentKey,
198
+ full: attachments.length >= maxFiles,
199
+ limits: lim,
200
+ };
201
+ }
202
+ //# sourceMappingURL=use-attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-attachments.js","sourceRoot":"","sources":["../../src/react/use-attachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { type DictationPreference, type DictationProblem } from "../dictation.js";
2
+ export type DictationStatus = "idle" | "listening" | "transcribing";
3
+ export type UseDictationOptions = {
4
+ /** Receives what was said, once, when the take ends. */
5
+ onText: (text: string) => void;
6
+ /** BCP-47 language for the browser recogniser. Defaults to the page's
7
+ * `<html lang>`, then the browser's. The server leg receives its first part
8
+ * as `locale` and may detect the language itself. */
9
+ lang?: string;
10
+ /** Which path first. Default `browser` (free, instant); `server` for one
11
+ * model everywhere. Each falls back to the other when it cannot work. */
12
+ prefer?: DictationPreference;
13
+ /** Server leg: POST multipart `{ audio, locale }` → `{ text }`. */
14
+ transcribeUrl?: string;
15
+ /** Or bring your own server leg (a widget token, a different body). */
16
+ transcribe?: (audio: Blob, locale: string) => Promise<string>;
17
+ maxRecordingMs?: number;
18
+ /** Where a dead recogniser is remembered (localStorage). `null` to never
19
+ * store anything on the device. */
20
+ rememberKey?: string | null;
21
+ };
22
+ export type DictationController = {
23
+ /** False only when neither path can work here — the mic is then hidden. */
24
+ supported: boolean;
25
+ status: DictationStatus;
26
+ /** When the current take began (ms epoch), for the timer. */
27
+ startedAt: number | null;
28
+ problem: DictationProblem | null;
29
+ clearProblem: () => void;
30
+ start: () => void;
31
+ /** End the take and deliver what was said. */
32
+ stop: () => void;
33
+ /** End the take and throw it away. */
34
+ cancel: () => void;
35
+ toggle: () => void;
36
+ };
37
+ /**
38
+ * Speaking instead of typing — with a microphone that is never a dead button.
39
+ *
40
+ * Browser recogniser first (unless `prefer: "server"`); when it is missing, or
41
+ * accepts `start()` and then says nothing (heidi measured nine silent seconds
42
+ * on a real Chromium), the same press RECORDS and the server transcribes. A
43
+ * failure always ends as a `problem` the composer shows in words.
44
+ *
45
+ * Every microphone track is stopped on every exit path. A mic left open on
46
+ * someone's device because a request failed is the worst bug a chat can have.
47
+ */
48
+ export declare function useDictation(opts: UseDictationOptions): DictationController;
49
+ //# sourceMappingURL=use-dictation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-dictation.d.ts","sourceRoot":"","sources":["../../src/react/use-dictation.ts"],"names":[],"mappings":"AAGA,OAAO,EASL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAwCzB,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,GAAG,cAAc,CAAC;AAEpE,MAAM,MAAM,mBAAmB,GAAG;IAChC,wDAAwD;IACxD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B;;0DAEsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;8EAC0E;IAC1E,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;wCACoC;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;IACxB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AA2CF;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,mBAAmB,CAoS3E"}