@kanzo-tech/ai 0.1.0
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/dist/ai-mark.d.ts +54 -0
- package/dist/ai-mark.d.ts.map +1 -0
- package/dist/ai-mark.js +68 -0
- package/dist/ai-mark.js.map +1 -0
- package/dist/complete.d.ts +66 -0
- package/dist/complete.d.ts.map +1 -0
- package/dist/complete.js +259 -0
- package/dist/complete.js.map +1 -0
- package/dist/conversation.d.ts +38 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/conversation.js +118 -0
- package/dist/conversation.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +50 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +36 -0
- package/dist/markdown.js.map +1 -0
- package/dist/message-part.d.ts +122 -0
- package/dist/message-part.d.ts.map +1 -0
- package/dist/message-part.js +8 -0
- package/dist/message-part.js.map +1 -0
- package/dist/message-text.d.ts +44 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +49 -0
- package/dist/message-text.js.map +1 -0
- package/dist/message.d.ts +73 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +120 -0
- package/dist/message.js.map +1 -0
- package/dist/prompt-input.d.ts +30 -0
- package/dist/prompt-input.d.ts.map +1 -0
- package/dist/prompt-input.js +113 -0
- package/dist/prompt-input.js.map +1 -0
- package/dist/reasoning.d.ts +32 -0
- package/dist/reasoning.d.ts.map +1 -0
- package/dist/reasoning.js +94 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/suggest.d.ts +75 -0
- package/dist/suggest.d.ts.map +1 -0
- package/dist/suggest.js +123 -0
- package/dist/suggest.js.map +1 -0
- package/dist/task.d.ts +48 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +91 -0
- package/dist/task.js.map +1 -0
- package/dist/tool.d.ts +56 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +122 -0
- package/dist/tool.js.map +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/use-ai.d.ts +170 -0
- package/dist/use-ai.d.ts.map +1 -0
- package/dist/use-ai.js +186 -0
- package/dist/use-ai.js.map +1 -0
- package/package.json +111 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as c } from "react/jsx-runtime";
|
|
3
|
+
import { ark as f } from "@ark-ui/react/factory";
|
|
4
|
+
import { ArrowDownIcon as R } from "lucide-react";
|
|
5
|
+
import * as l from "react";
|
|
6
|
+
import { cn as m, Button as b } from "@kanzo-tech/ui";
|
|
7
|
+
const w = l.createContext(null), h = (n) => {
|
|
8
|
+
const e = l.useContext(w);
|
|
9
|
+
if (!e) throw new Error(`${n} must render inside <Conversation>`);
|
|
10
|
+
return e;
|
|
11
|
+
}, y = 24, k = (n) => {
|
|
12
|
+
const { className: e, children: o, slot: s, ...a } = n, [t, u] = l.useState(null), [i, C] = l.useState(null), [g, x] = l.useState(!0), d = l.useRef(!0), N = l.useCallback(() => {
|
|
13
|
+
t && (d.current = !0, x(!0), t.scrollTop = t.scrollHeight);
|
|
14
|
+
}, [t]);
|
|
15
|
+
return l.useEffect(() => {
|
|
16
|
+
if (!t) return;
|
|
17
|
+
const v = () => {
|
|
18
|
+
const p = t.scrollHeight - t.scrollTop - t.clientHeight <= y;
|
|
19
|
+
d.current = p, x(p);
|
|
20
|
+
};
|
|
21
|
+
v(), t.addEventListener("scroll", v, { passive: !0 });
|
|
22
|
+
const E = () => {
|
|
23
|
+
d.current && (t.scrollTop = t.scrollHeight);
|
|
24
|
+
}, r = typeof ResizeObserver > "u" ? null : new ResizeObserver(E);
|
|
25
|
+
return r == null || r.observe(t), i && (r == null || r.observe(i)), () => {
|
|
26
|
+
t.removeEventListener("scroll", v), r == null || r.disconnect();
|
|
27
|
+
};
|
|
28
|
+
}, [t, i]), /* @__PURE__ */ c(
|
|
29
|
+
w.Provider,
|
|
30
|
+
{
|
|
31
|
+
value: { viewportRef: u, contentRef: C, pinned: g, scrollToEnd: N },
|
|
32
|
+
children: /* @__PURE__ */ c(
|
|
33
|
+
f.div,
|
|
34
|
+
{
|
|
35
|
+
className: m("relative flex min-h-0 flex-1 flex-col", e),
|
|
36
|
+
...a,
|
|
37
|
+
"data-slot": s ?? "conversation",
|
|
38
|
+
children: o
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}, B = (n) => {
|
|
44
|
+
const { className: e, children: o, slot: s, ...a } = n, t = h("ConversationContent");
|
|
45
|
+
return /* @__PURE__ */ c(
|
|
46
|
+
f.div,
|
|
47
|
+
{
|
|
48
|
+
className: m(
|
|
49
|
+
"flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain",
|
|
50
|
+
"px-4 py-6",
|
|
51
|
+
e
|
|
52
|
+
),
|
|
53
|
+
ref: t.viewportRef,
|
|
54
|
+
role: "log",
|
|
55
|
+
...a,
|
|
56
|
+
"data-slot": s ?? "conversation-content",
|
|
57
|
+
children: /* @__PURE__ */ c(
|
|
58
|
+
f.div,
|
|
59
|
+
{
|
|
60
|
+
className: "mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6",
|
|
61
|
+
ref: t.contentRef,
|
|
62
|
+
children: o
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}, P = (n) => {
|
|
68
|
+
const { className: e, slot: o, ...s } = n;
|
|
69
|
+
return /* @__PURE__ */ c(
|
|
70
|
+
f.div,
|
|
71
|
+
{
|
|
72
|
+
className: m(
|
|
73
|
+
"flex flex-1 flex-col items-center justify-center gap-3",
|
|
74
|
+
"mx-auto max-w-md px-4 py-12",
|
|
75
|
+
"text-balance text-center text-muted-foreground text-sm",
|
|
76
|
+
"[&_svg:not([class*='size-'])]:size-6",
|
|
77
|
+
e
|
|
78
|
+
),
|
|
79
|
+
...s,
|
|
80
|
+
"data-slot": o ?? "conversation-empty"
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}, j = (n) => {
|
|
84
|
+
const { className: e, children: o, onClick: s, slot: a, ...t } = n, u = h("ConversationScrollButton");
|
|
85
|
+
return u.pinned ? null : /* @__PURE__ */ c(
|
|
86
|
+
b,
|
|
87
|
+
{
|
|
88
|
+
"aria-label": o === void 0 ? "Scroll to the latest message" : void 0,
|
|
89
|
+
className: m(
|
|
90
|
+
"absolute inset-x-0 bottom-4 z-10 mx-auto",
|
|
91
|
+
// `inset-x-0` is what centres it, and it also stretches it, so the width has to be stated.
|
|
92
|
+
// The icon size states its own — `w-(--size)`, square — and a `w-fit` here would win the
|
|
93
|
+
// merge and hand it the icon's width against the control's height.
|
|
94
|
+
o === void 0 ? void 0 : "w-fit",
|
|
95
|
+
"bg-background shadow-lg/5",
|
|
96
|
+
"fade-in-0 zoom-in-95 slide-in-from-bottom-2 animate-in",
|
|
97
|
+
"motion-reduce:animate-none!",
|
|
98
|
+
e
|
|
99
|
+
),
|
|
100
|
+
onClick: (i) => {
|
|
101
|
+
s == null || s(i), i.defaultPrevented || u.scrollToEnd();
|
|
102
|
+
},
|
|
103
|
+
pill: !0,
|
|
104
|
+
size: o === void 0 ? "icon-sm" : "sm",
|
|
105
|
+
variant: "outline",
|
|
106
|
+
...t,
|
|
107
|
+
slot: a ?? "conversation-scroll-button",
|
|
108
|
+
children: o ?? /* @__PURE__ */ c(R, {})
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
export {
|
|
113
|
+
k as Conversation,
|
|
114
|
+
B as ConversationContent,
|
|
115
|
+
P as ConversationEmpty,
|
|
116
|
+
j as ConversationScrollButton
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=conversation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation.js","sources":["../src/conversation.tsx"],"sourcesContent":["\"use client\";\n\nimport { ark } from \"@ark-ui/react/factory\";\nimport { ArrowDownIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button, cn } from \"@kanzo-tech/ui\";\n\ninterface ConversationCtx {\n viewportRef: (el: HTMLDivElement | null) => void;\n contentRef: (el: HTMLDivElement | null) => void;\n pinned: boolean;\n scrollToEnd: () => void;\n}\n\nconst Ctx = React.createContext<ConversationCtx | null>(null);\nconst useCtx = (part: string) => {\n const c = React.useContext(Ctx);\n if (!c) throw new Error(`${part} must render inside <Conversation>`);\n return c;\n};\n\n/** A scroll position is fractional and rarely lands on the end exactly; this is the allowance. */\nconst AT_END = 24;\n\n/**\n * The transcript's scroll container, and the pin is the whole component.\n *\n * **The naive version, which both real consumers wrote:** an effect on the message array calling\n * `scrollTo({ top: scrollHeight, behavior: \"smooth\" })`. It follows the tail unconditionally, so a\n * reader who scrolled up to re-read something is dragged back down by the next token, every token,\n * for as long as the answer is streaming. Following is only correct while the reader is *already*\n * at the tail: scrolling up releases the pin, and `ConversationScrollButton` is how they take it\n * back. Roughly forty lines of listener and observer, and no dependency.\n */\nexport const Conversation = (props: React.ComponentProps<typeof ark.div>) => {\n const { className, children, slot, ...rest } = props;\n const [viewport, setViewport] = React.useState<HTMLDivElement | null>(null);\n const [content, setContent] = React.useState<HTMLDivElement | null>(null);\n const [pinned, setPinned] = React.useState(true);\n const pinnedRef = React.useRef(true);\n\n const scrollToEnd = React.useCallback(() => {\n if (!viewport) return;\n pinnedRef.current = true;\n setPinned(true);\n viewport.scrollTop = viewport.scrollHeight;\n }, [viewport]);\n\n React.useEffect(() => {\n if (!viewport) return;\n\n const sync = () => {\n const atEnd = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight <= AT_END;\n pinnedRef.current = atEnd;\n setPinned(atEnd);\n };\n sync();\n viewport.addEventListener(\"scroll\", sync, { passive: true });\n\n // A stream produces *growth*, not scrolling: the content gets taller under a stationary\n // `scrollTop`, and no scroll event is fired for that. Observing the viewport alone never sees\n // it either — its own box did not change — which is why the inner element is observed too.\n const follow = () => {\n if (pinnedRef.current) viewport.scrollTop = viewport.scrollHeight;\n };\n const observer = typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(follow);\n observer?.observe(viewport);\n if (content) observer?.observe(content);\n\n return () => {\n viewport.removeEventListener(\"scroll\", sync);\n observer?.disconnect();\n };\n }, [viewport, content]);\n\n return (\n <Ctx.Provider\n value={{ viewportRef: setViewport, contentRef: setContent, pinned, scrollToEnd }}\n >\n <ark.div\n className={cn(\"relative flex min-h-0 flex-1 flex-col\", className)}\n {...rest}\n data-slot={slot ?? \"conversation\"}\n >\n {children}\n </ark.div>\n </Ctx.Provider>\n );\n};\n\n/**\n * The scrolling region.\n *\n * ARIA: `role=\"log\"` carries an implicit `aria-live=\"polite\"`, so a message appended while the\n * reader is somewhere else is announced after what they are on rather than over it. Nothing inside\n * declares a role — a message is content, not a widget — so there is no keyboard contract to owe.\n */\nexport const ConversationContent = (props: React.ComponentProps<typeof ark.div>) => {\n const { className, children, slot, ...rest } = props;\n const ctx = useCtx(\"ConversationContent\");\n\n return (\n <ark.div\n className={cn(\n \"flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain\",\n \"px-4 py-6\",\n className\n )}\n ref={ctx.viewportRef}\n role=\"log\"\n {...rest}\n data-slot={slot ?? \"conversation-content\"}\n >\n {/* `flex-1` gives the inner element the viewport's height when the transcript is short, which\n is what lets `ConversationEmpty` centre itself in the panel rather than sit at the top. */}\n <ark.div\n className=\"mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6\"\n ref={ctx.contentRef}\n >\n {children}\n </ark.div>\n </ark.div>\n );\n};\n\n/**\n * The stage for the zero state, not the zero state itself: a centred column with a reading measure,\n * which `Item` and its parts are then composed into. There is deliberately no `title` / `description`\n * / `icon` prop — `EmptyState` was exactly that and it is a tombstone in `@kanzo-tech/ui`.\n */\nexport const ConversationEmpty = (props: React.ComponentProps<typeof ark.div>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ark.div\n className={cn(\n \"flex flex-1 flex-col items-center justify-center gap-3\",\n \"mx-auto max-w-md px-4 py-12\",\n \"text-balance text-center text-muted-foreground text-sm\",\n \"[&_svg:not([class*='size-'])]:size-6\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"conversation-empty\"}\n />\n );\n};\n\n/**\n * Present only while the pin is released, and pressing it takes the pin back.\n *\n * It floats *over* a transcript that is still growing underneath it, which is why it enters rather\n * than appears and why it paints its own ground: `variant=\"outline\"` is `bg-transparent` in light\n * mode, so without `bg-background` the message it covers reads straight through it. Dark keeps the\n * variant's own `bg-field`.\n */\nexport const ConversationScrollButton = (props: React.ComponentProps<typeof Button>) => {\n const { className, children, onClick, slot, ...rest } = props;\n const ctx = useCtx(\"ConversationScrollButton\");\n\n if (ctx.pinned) return null;\n\n return (\n <Button\n aria-label={children === undefined ? \"Scroll to the latest message\" : undefined}\n className={cn(\n \"absolute inset-x-0 bottom-4 z-10 mx-auto\",\n // `inset-x-0` is what centres it, and it also stretches it, so the width has to be stated.\n // The icon size states its own — `w-(--size)`, square — and a `w-fit` here would win the\n // merge and hand it the icon's width against the control's height.\n children === undefined ? undefined : \"w-fit\",\n \"bg-background shadow-lg/5\",\n \"fade-in-0 zoom-in-95 slide-in-from-bottom-2 animate-in\",\n \"motion-reduce:animate-none!\",\n className\n )}\n onClick={(event) => {\n onClick?.(event);\n if (!event.defaultPrevented) ctx.scrollToEnd();\n }}\n pill\n // Icon-only by default; give it words and it becomes a pill wide enough to hold them, the way\n // `PromptInputSubmit` does.\n size={children === undefined ? \"icon-sm\" : \"sm\"}\n variant=\"outline\"\n {...rest}\n slot={slot ?? \"conversation-scroll-button\"}\n >\n {children ?? <ArrowDownIcon />}\n </Button>\n );\n};\n"],"names":[],"mappings":";;;;;;AAcA;AAEE;AACA;AACA;AACF;AAgBE;AAOE;AAG8B;AAGhC;AACE;AAEA;AACE;AACA;AACe;AAEjB;AAMA;AACE;AAAqD;AAGvD;AAIE;AACU;AACZ;AAIA;AAAK;AAAJ;AACoE;AAEnE;AAAK;AAAJ;AACiE;AAC5D;AACe;AAElB;AAAA;AAAA;AACH;AAGN;AAUE;AAGA;AACE;AAAK;AAAJ;AACY;AACT;AACA;AACA;AAAA;AAEO;AACJ;AACD;AACe;AAInB;AAAK;AAAJ;AACW;AACD;AAER;AAAA;AAAA;AACH;AAGN;AAQE;AAEA;AACE;AAAK;AAAJ;AACY;AACT;AACA;AACA;AACA;AACA;AAAA;AAEE;AACe;AAAA;AAGzB;AAWE;AAGA;AAGE;AAAC;AAAA;AACuE;AAC3D;AACT;AAAA;AAAA;AAAA;AAIqC;AACrC;AACA;AACA;AACA;AAAA;AAGA;AACiC;AACnC;AACI;AAGuC;AACnC;AACJ;AACU;AAEc;AAAA;AAGlC;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { Conversation, ConversationContent, ConversationEmpty, ConversationScrollButton, } from './conversation.js';
|
|
2
|
+
export { Message, MessageActions, MessageAvatar, MessageContent, MessageList, } from './message.js';
|
|
3
|
+
export { MessageText } from './message-text.js';
|
|
4
|
+
export type { MessageProps, MessageAvatarProps, MessageRole } from './message.js';
|
|
5
|
+
export type { MessageTextProps } from './message-text.js';
|
|
6
|
+
export { PromptInput, PromptInputSubmit, PromptInputTextarea, PromptInputToolbar, } from './prompt-input.js';
|
|
7
|
+
export type { PromptInputSubmitProps } from './prompt-input.js';
|
|
8
|
+
export { Reasoning, ReasoningContent, ReasoningTrigger } from './reasoning.js';
|
|
9
|
+
export type { ReasoningProps } from './reasoning.js';
|
|
10
|
+
export { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from './tool.js';
|
|
11
|
+
export type { ToolProps, ToolIoProps } from './tool.js';
|
|
12
|
+
export { Task, TaskList, TaskStatus, TaskTitle } from './task.js';
|
|
13
|
+
export type { TaskProps, RunState } from './task.js';
|
|
14
|
+
export { CompleteRoot, CompleteTextarea, CompleteGhost, CompleteHint, CompleteKeys, CompleteError, CompleteMark, } from './complete.js';
|
|
15
|
+
export { AiMark } from './ai-mark.js';
|
|
16
|
+
export type { AiMarkProps } from './ai-mark.js';
|
|
17
|
+
export { SuggestRoot, SuggestMark, SuggestList } from './suggest.js';
|
|
18
|
+
export type { SuggestRootProps, SuggestListProps, SuggestTrigger } from './suggest.js';
|
|
19
|
+
export type { CompleteRootProps } from './complete.js';
|
|
20
|
+
export { useAiStream, useInlineCompletion, useSuggestions, cleanGhost } from './use-ai.js';
|
|
21
|
+
export type { AiStatus, AiStream, InlineCompletion, InlineCompletionRequest, InlineCompletionTrigger, SuggestionsController, UseInlineCompletionOptions, UseSuggestionsOptions, } from './use-ai.js';
|
|
22
|
+
export type { Candidate } from './types.js';
|
|
23
|
+
export { isTextPart, isReasoningPart, isToolPart, isTaskPart } from './message-part.js';
|
|
24
|
+
export type { AiMessage, AiMessagePart, AiReasoningPart, AiTaskPart, AiTextPart, AiToolPart, StreamState, } from './message-part.js';
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,OAAO,EACP,cAAc,EACd,aAAa,EACb,cAAc,EACd,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAClF,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAGhE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjF,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAClE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrD,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,GACb,MAAM,eAAe,CAAC;AAIvB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACrE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvF,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC3F,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACxF,YAAY,EACV,SAAS,EACT,aAAa,EACb,eAAe,EACf,UAAU,EACV,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Conversation as e, ConversationContent as r, ConversationEmpty as s, ConversationScrollButton as a } from "./conversation.js";
|
|
2
|
+
import { Message as p, MessageActions as m, MessageAvatar as i, MessageContent as g, MessageList as l } from "./message.js";
|
|
3
|
+
import { MessageText as u } from "./message-text.js";
|
|
4
|
+
import { PromptInput as C, PromptInputSubmit as f, PromptInputTextarea as M, PromptInputToolbar as k } from "./prompt-input.js";
|
|
5
|
+
import { Reasoning as S, ReasoningContent as I, ReasoningTrigger as R } from "./reasoning.js";
|
|
6
|
+
import { Tool as A, ToolContent as c, ToolHeader as L, ToolInput as b, ToolOutput as h } from "./tool.js";
|
|
7
|
+
import { Task as E, TaskList as G, TaskStatus as H, TaskTitle as d } from "./task.js";
|
|
8
|
+
import { CompleteError as K, CompleteGhost as O, CompleteHint as j, CompleteKeys as q, CompleteMark as w, CompleteRoot as z, CompleteTextarea as D } from "./complete.js";
|
|
9
|
+
import { AiMark as J } from "./ai-mark.js";
|
|
10
|
+
import { SuggestList as Q, SuggestMark as U, SuggestRoot as V } from "./suggest.js";
|
|
11
|
+
import { cleanGhost as X, useAiStream as Y, useInlineCompletion as Z, useSuggestions as _ } from "./use-ai.js";
|
|
12
|
+
import { isReasoningPart as oo, isTaskPart as to, isTextPart as eo, isToolPart as ro } from "./message-part.js";
|
|
13
|
+
export {
|
|
14
|
+
J as AiMark,
|
|
15
|
+
K as CompleteError,
|
|
16
|
+
O as CompleteGhost,
|
|
17
|
+
j as CompleteHint,
|
|
18
|
+
q as CompleteKeys,
|
|
19
|
+
w as CompleteMark,
|
|
20
|
+
z as CompleteRoot,
|
|
21
|
+
D as CompleteTextarea,
|
|
22
|
+
e as Conversation,
|
|
23
|
+
r as ConversationContent,
|
|
24
|
+
s as ConversationEmpty,
|
|
25
|
+
a as ConversationScrollButton,
|
|
26
|
+
p as Message,
|
|
27
|
+
m as MessageActions,
|
|
28
|
+
i as MessageAvatar,
|
|
29
|
+
g as MessageContent,
|
|
30
|
+
l as MessageList,
|
|
31
|
+
u as MessageText,
|
|
32
|
+
C as PromptInput,
|
|
33
|
+
f as PromptInputSubmit,
|
|
34
|
+
M as PromptInputTextarea,
|
|
35
|
+
k as PromptInputToolbar,
|
|
36
|
+
S as Reasoning,
|
|
37
|
+
I as ReasoningContent,
|
|
38
|
+
R as ReasoningTrigger,
|
|
39
|
+
Q as SuggestList,
|
|
40
|
+
U as SuggestMark,
|
|
41
|
+
V as SuggestRoot,
|
|
42
|
+
E as Task,
|
|
43
|
+
G as TaskList,
|
|
44
|
+
H as TaskStatus,
|
|
45
|
+
d as TaskTitle,
|
|
46
|
+
A as Tool,
|
|
47
|
+
c as ToolContent,
|
|
48
|
+
L as ToolHeader,
|
|
49
|
+
b as ToolInput,
|
|
50
|
+
h as ToolOutput,
|
|
51
|
+
X as cleanGhost,
|
|
52
|
+
oo as isReasoningPart,
|
|
53
|
+
to as isTaskPart,
|
|
54
|
+
eo as isTextPart,
|
|
55
|
+
ro as isToolPart,
|
|
56
|
+
Y as useAiStream,
|
|
57
|
+
Z as useInlineCompletion,
|
|
58
|
+
_ as useSuggestions
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { StreamdownProps } from 'streamdown';
|
|
2
|
+
/**
|
|
3
|
+
* **`MessageText`, for a source that streams markdown.**
|
|
4
|
+
*
|
|
5
|
+
* `MessageText` takes a plain string and renders it as text, which is right for a model answering
|
|
6
|
+
* in prose and wrong the moment one emits a `**bold`, a list or a fence. The hard part is not the
|
|
7
|
+
* markdown — it is the *incomplete* markdown: a stream delivers `**bo`, then `**bold`, then
|
|
8
|
+
* `**bold**`, and a parser that renders each of those honestly makes the answer flicker between
|
|
9
|
+
* literal asterisks and emphasis as it completes. Streamdown exists to close those, and closing
|
|
10
|
+
* them well is a parser's worth of work rather than a component's.
|
|
11
|
+
*
|
|
12
|
+
* ## Why a subpath, and it is a one-way door
|
|
13
|
+
*
|
|
14
|
+
* **Measured before it was decided:** `streamdown` bundles to **495 kB minified, 128 kB brotli**
|
|
15
|
+
* on its own — `parse5` 123 kB for `rehype-raw`'s HTML, `entities` 51 kB, `marked` 40 kB, the
|
|
16
|
+
* unified stack behind them. `@kanzo-tech/ai`'s whole root barrel is budgeted at 20 kB and measures
|
|
17
|
+
* 8.34. A static import from `index.ts` would take every host that renders plain prose from 8 kB to
|
|
18
|
+
* roughly 136, for a parser it never calls.
|
|
19
|
+
*
|
|
20
|
+
* So it lives here, behind `@kanzo-tech/ai/markdown`, with `streamdown` as an **optional peer** —
|
|
21
|
+
* the same door `@kanzo-tech/ui/editor` stands behind for CodeMirror and `@kanzo-tech/graph/duckdb`
|
|
22
|
+
* for Mosaic, and `scripts/smoke-install.mjs` is what holds it shut: it packs the real tarball,
|
|
23
|
+
* installs it *without* the optional peers, and imports the root barrel.
|
|
24
|
+
*
|
|
25
|
+
* ## The animation is Streamdown's now, not ours
|
|
26
|
+
*
|
|
27
|
+
* `MessageText` hand-rolls a per-word cascade whose four rules are all read off Streamdown's
|
|
28
|
+
* `animate.ts` — its docblock says so and names each one. Here the original is present, so it does
|
|
29
|
+
* the work: `animated` with `sep: "word"` is the same idea from the source it was copied from, and
|
|
30
|
+
* two implementations of one cascade is one too many. The numbers below are `MessageText`'s, so a
|
|
31
|
+
* transcript that mixes the two components does not visibly change rhythm mid-conversation.
|
|
32
|
+
*
|
|
33
|
+
* **A message that mounts complete did not arrive**, so `streaming` also decides whether anything
|
|
34
|
+
* animates at all — a transcript loaded from history must not replay itself, which is the one rule
|
|
35
|
+
* of `MessageText` that has no counterpart in a prop and has to be spelled here.
|
|
36
|
+
*/
|
|
37
|
+
export interface MessageMarkdownProps extends Omit<StreamdownProps, "children" | "mode" | "animated"> {
|
|
38
|
+
/** The answer so far, as markdown. Grows; never rewritten. */
|
|
39
|
+
children: string;
|
|
40
|
+
/** More is coming: closes the incomplete markdown, animates the arrival, draws the caret. */
|
|
41
|
+
streaming?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Renames the part. Declared here and nowhere else in the package: every other component takes
|
|
44
|
+
* `slot` for free from `React.HTMLAttributes`, and `StreamdownProps` is react-markdown's
|
|
45
|
+
* `Options` rather than the attributes of an element, so it carries no `slot` to inherit.
|
|
46
|
+
*/
|
|
47
|
+
slot?: string;
|
|
48
|
+
}
|
|
49
|
+
export declare const MessageMarkdown: (props: MessageMarkdownProps) => import("react").JSX.Element;
|
|
50
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAG9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,WAAW,oBACf,SAAQ,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;IAC/D,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,6FAA6F;IAC7F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAKD,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,gCA6B1D,CAAC"}
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { Streamdown as n } from "streamdown";
|
|
3
|
+
import { cn as d } from "@kanzo-tech/ui";
|
|
4
|
+
const i = 18, p = (r) => {
|
|
5
|
+
const { children: s, streaming: a = !1, className: o, slot: t, ...m } = r;
|
|
6
|
+
return (
|
|
7
|
+
// The element exists so the part has one. `Streamdown` renders a `div` of its own but drops
|
|
8
|
+
// every prop it does not consume — measured, not assumed, and `markdown.test.tsx` is what keeps
|
|
9
|
+
// it measured: `data-slot` and `data-streaming` handed to it reach no attribute at all. So the
|
|
10
|
+
// two things a consumer selects on are written here, on an element we own, and only
|
|
11
|
+
// Streamdown's own options are passed down.
|
|
12
|
+
/* @__PURE__ */ e(
|
|
13
|
+
"div",
|
|
14
|
+
{
|
|
15
|
+
className: d("min-w-0 break-words", o),
|
|
16
|
+
"data-streaming": a || void 0,
|
|
17
|
+
"data-slot": t ?? "message-markdown",
|
|
18
|
+
children: /* @__PURE__ */ e(
|
|
19
|
+
n,
|
|
20
|
+
{
|
|
21
|
+
animated: a ? { sep: "word", stagger: i } : !1,
|
|
22
|
+
caret: a ? "block" : void 0,
|
|
23
|
+
mode: a ? "streaming" : "static",
|
|
24
|
+
parseIncompleteMarkdown: a,
|
|
25
|
+
...m,
|
|
26
|
+
children: s
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
export {
|
|
34
|
+
p as MessageMarkdown
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sources":["../src/markdown.tsx"],"sourcesContent":["// No `\"use client\"` here, and `client-boundary.test.ts` is what caught the first version that had\n// one: this module holds no state, no effect and no handler of its own. `streamdown/dist/index.js`\n// opens with the directive itself, so importing it establishes the boundary — a second one here\n// would opt the wrapper out of server rendering for nothing.\nimport { Streamdown, type StreamdownProps } from \"streamdown\";\nimport { cn } from \"@kanzo-tech/ui\";\n\n/**\n * **`MessageText`, for a source that streams markdown.**\n *\n * `MessageText` takes a plain string and renders it as text, which is right for a model answering\n * in prose and wrong the moment one emits a `**bold`, a list or a fence. The hard part is not the\n * markdown — it is the *incomplete* markdown: a stream delivers `**bo`, then `**bold`, then\n * `**bold**`, and a parser that renders each of those honestly makes the answer flicker between\n * literal asterisks and emphasis as it completes. Streamdown exists to close those, and closing\n * them well is a parser's worth of work rather than a component's.\n *\n * ## Why a subpath, and it is a one-way door\n *\n * **Measured before it was decided:** `streamdown` bundles to **495 kB minified, 128 kB brotli**\n * on its own — `parse5` 123 kB for `rehype-raw`'s HTML, `entities` 51 kB, `marked` 40 kB, the\n * unified stack behind them. `@kanzo-tech/ai`'s whole root barrel is budgeted at 20 kB and measures\n * 8.34. A static import from `index.ts` would take every host that renders plain prose from 8 kB to\n * roughly 136, for a parser it never calls.\n *\n * So it lives here, behind `@kanzo-tech/ai/markdown`, with `streamdown` as an **optional peer** —\n * the same door `@kanzo-tech/ui/editor` stands behind for CodeMirror and `@kanzo-tech/graph/duckdb`\n * for Mosaic, and `scripts/smoke-install.mjs` is what holds it shut: it packs the real tarball,\n * installs it *without* the optional peers, and imports the root barrel.\n *\n * ## The animation is Streamdown's now, not ours\n *\n * `MessageText` hand-rolls a per-word cascade whose four rules are all read off Streamdown's\n * `animate.ts` — its docblock says so and names each one. Here the original is present, so it does\n * the work: `animated` with `sep: \"word\"` is the same idea from the source it was copied from, and\n * two implementations of one cascade is one too many. The numbers below are `MessageText`'s, so a\n * transcript that mixes the two components does not visibly change rhythm mid-conversation.\n *\n * **A message that mounts complete did not arrive**, so `streaming` also decides whether anything\n * animates at all — a transcript loaded from history must not replay itself, which is the one rule\n * of `MessageText` that has no counterpart in a prop and has to be spelled here.\n */\nexport interface MessageMarkdownProps\n extends Omit<StreamdownProps, \"children\" | \"mode\" | \"animated\"> {\n /** The answer so far, as markdown. Grows; never rewritten. */\n children: string;\n /** More is coming: closes the incomplete markdown, animates the arrival, draws the caret. */\n streaming?: boolean;\n /**\n * Renames the part. Declared here and nowhere else in the package: every other component takes\n * `slot` for free from `React.HTMLAttributes`, and `StreamdownProps` is react-markdown's\n * `Options` rather than the attributes of an element, so it carries no `slot` to inherit.\n */\n slot?: string;\n}\n\n/** `MessageText`'s cascade, so the two components keep the same rhythm. See its docblock. */\nconst STAGGER = 18;\n\nexport const MessageMarkdown = (props: MessageMarkdownProps) => {\n const { children, streaming = false, className, slot, ...rest } = props;\n\n return (\n // The element exists so the part has one. `Streamdown` renders a `div` of its own but drops\n // every prop it does not consume — measured, not assumed, and `markdown.test.tsx` is what keeps\n // it measured: `data-slot` and `data-streaming` handed to it reach no attribute at all. So the\n // two things a consumer selects on are written here, on an element we own, and only\n // Streamdown's own options are passed down.\n <div\n className={cn(\"min-w-0 break-words\", className)}\n data-streaming={streaming || undefined}\n data-slot={slot ?? \"message-markdown\"}\n >\n <Streamdown\n animated={streaming ? { sep: \"word\", stagger: STAGGER } : false}\n // Streamdown's own caret, rather than the `<span>` `MessageText` draws: it belongs at the\n // end of the last *rendered* block, which only the renderer knows — a caret appended by us\n // would sit after a table rather than inside the paragraph the model is still writing.\n caret={streaming ? \"block\" : undefined}\n mode={streaming ? \"streaming\" : \"static\"}\n // The whole point. `false` renders `**bo` as two asterisks and a `bo`.\n parseIncompleteMarkdown={streaming}\n {...rest}\n >\n {children}\n </Streamdown>\n </div>\n );\n};\n"],"names":["STAGGER","MessageMarkdown","props","children","streaming","className","slot","rest","jsx","cn","Streamdown"],"mappings":";;;AAyDA,MAAMA,IAAU,IAEHC,IAAkB,CAACC,MAAgC;AAC9D,QAAM,EAAE,UAAAC,GAAU,WAAAC,IAAY,IAAO,WAAAC,GAAW,MAAAC,GAAM,GAAGC,MAASL;AAElE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAME,gBAAAM;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAWC,EAAG,uBAAuBJ,CAAS;AAAA,QAC9C,kBAAgBD,KAAa;AAAA,QAC7B,aAAWE,KAAQ;AAAA,QAEnB,UAAA,gBAAAE;AAAA,UAACE;AAAA,UAAA;AAAA,YACC,UAAUN,IAAY,EAAE,KAAK,QAAQ,SAASJ,MAAY;AAAA,YAI1D,OAAOI,IAAY,UAAU;AAAA,YAC7B,MAAMA,IAAY,cAAc;AAAA,YAEhC,yBAAyBA;AAAA,YACxB,GAAGG;AAAA,YAEH,UAAAJ;AAAA,UAAA;AAAA,QAAA;AAAA,MACH;AAAA,IAAA;AAAA;AAGN;"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { MessageRole } from './message.js';
|
|
2
|
+
import { RunState } from './task.js';
|
|
3
|
+
/**
|
|
4
|
+
* A turn as a **list of parts**, and the join every component in this package was missing.
|
|
5
|
+
*
|
|
6
|
+
* `Message`, `MessageText`, `Reasoning`, `Tool` and `Task` were each correct and none of them was
|
|
7
|
+
* wired to the next: a host held its own shape and translated into five sets of props by hand.
|
|
8
|
+
* keasy's `AskMessage` is the worked example — it carries `sql`, `reasoning`, `explanation` and a
|
|
9
|
+
* `phase` of `'generating' | 'executing' | 'explaining' | 'done'`, which is a tool part, a reasoning
|
|
10
|
+
* part, a text part and a state, hand-rolled. **That is the second consumer, and it existed before
|
|
11
|
+
* this file did.** So this is not a protocol invented ahead of its need.
|
|
12
|
+
*
|
|
13
|
+
* The shape mirrors the AI SDK's `UIMessage`, deliberately and without depending on it — the four
|
|
14
|
+
* properties that make theirs work are the four copied here:
|
|
15
|
+
*
|
|
16
|
+
* 1. **A message is a list of parts, not a string.** One `.map` with one `switch` on `part.type`
|
|
17
|
+
* renders a whole turn.
|
|
18
|
+
* 2. **State lives on the part**, so a renderer is told what to draw rather than inferring it.
|
|
19
|
+
* 3. **Generic in metadata and tools**, so a product extends the union instead of forking it.
|
|
20
|
+
* 4. **Transport stays separate.** Ours already is, and is smaller than theirs: a caller passes a
|
|
21
|
+
* function returning an `AsyncIterable`. Nothing here replaces that seam.
|
|
22
|
+
*
|
|
23
|
+
* ## Four variants, not nine
|
|
24
|
+
*
|
|
25
|
+
* Theirs has nine. `file`, `data-*`, `step-start` and `dynamic-tool` have no call site here, and
|
|
26
|
+
* `source` is **declined rather than deferred**: discovery does not cite a document, it runs a
|
|
27
|
+
* query, and provenance that is a statement plus rows is a tool call. It arrives the day an answer
|
|
28
|
+
* rests on a text somebody wrote.
|
|
29
|
+
*
|
|
30
|
+
* ## The two state vocabularies are the ones already in the package
|
|
31
|
+
*
|
|
32
|
+
* `RunState` — `pending` · `running` · `done` · `failed` — is `Task`'s and `Tool`'s, from one table
|
|
33
|
+
* with one label map and one icon family. It is not restated here, and the reference's
|
|
34
|
+
* `input-streaming | input-available | output-available | output-error` is not adopted: four states
|
|
35
|
+
* under two spellings is the drift this repo keeps deleting.
|
|
36
|
+
*
|
|
37
|
+
* `StreamState` is the other one, and it is two values because text and reasoning have two.
|
|
38
|
+
*
|
|
39
|
+
* ## What this file is not
|
|
40
|
+
*
|
|
41
|
+
* **It renders nothing.** There is no `<Message parts={…} />` here and that is the shape rather than
|
|
42
|
+
* an omission: which part a product renders, in what order, with what chrome around it, is the
|
|
43
|
+
* thing that differs per product — and a component that owned the `switch` would own those answers
|
|
44
|
+
* too. The `switch` belongs to the host; what this gives it is a union exhaustive enough that
|
|
45
|
+
* TypeScript tells the host when a case is missing.
|
|
46
|
+
*/
|
|
47
|
+
/** Where a streamed part has got to. Two values, because text and reasoning have two. */
|
|
48
|
+
export type StreamState = "streaming" | "done";
|
|
49
|
+
/** Prose the model wrote. `MessageText` draws it. */
|
|
50
|
+
export interface AiTextPart {
|
|
51
|
+
type: "text";
|
|
52
|
+
text: string;
|
|
53
|
+
state?: StreamState;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The model's own working, folded away. `Reasoning` draws it.
|
|
57
|
+
*
|
|
58
|
+
* **No duration here**, and that is checked rather than assumed: `Reasoning` times itself off the
|
|
59
|
+
* `streaming` transition and renders *Thought for N seconds* from its own state. A `seconds` on the
|
|
60
|
+
* part would be a second measurement of one fact, and the one no component reads.
|
|
61
|
+
*/
|
|
62
|
+
export interface AiReasoningPart {
|
|
63
|
+
type: "reasoning";
|
|
64
|
+
text: string;
|
|
65
|
+
state?: StreamState;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* One call the model made. `Tool` draws it.
|
|
69
|
+
*
|
|
70
|
+
* `input` and `output` are `unknown` here and **children** at the component: `ToolInput` and
|
|
71
|
+
* `ToolOutput` take what you render rather than a JSON blob, because we always know what the tool
|
|
72
|
+
* was. The part still carries the values, because a host that wants the JSON tree has to have them.
|
|
73
|
+
*
|
|
74
|
+
* `NAME` is the axis a product extends along. Give it a union of your tool names and a `switch` on
|
|
75
|
+
* `part.name` narrows `input` and `output` if you declare them per name.
|
|
76
|
+
*/
|
|
77
|
+
export interface AiToolPart<NAME extends string = string> {
|
|
78
|
+
type: "tool";
|
|
79
|
+
name: NAME;
|
|
80
|
+
state?: RunState;
|
|
81
|
+
input?: unknown;
|
|
82
|
+
output?: unknown;
|
|
83
|
+
/** Set when `state` is `failed`. */
|
|
84
|
+
errorText?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* One step of work, ours and with no equivalent in the reference. `Task` draws it.
|
|
88
|
+
*
|
|
89
|
+
* Two fields, because `Task` has two parts: `TaskTitle` takes the words and `TaskStatus` reads the
|
|
90
|
+
* state off the root and says it. There is no list of sub-lines on the part because there is none
|
|
91
|
+
* on the component — a step that wants children is a step whose host renders them.
|
|
92
|
+
*/
|
|
93
|
+
export interface AiTaskPart {
|
|
94
|
+
type: "task";
|
|
95
|
+
title: string;
|
|
96
|
+
state?: RunState;
|
|
97
|
+
}
|
|
98
|
+
export type AiMessagePart<NAME extends string = string> = AiTextPart | AiReasoningPart | AiToolPart<NAME> | AiTaskPart;
|
|
99
|
+
/**
|
|
100
|
+
* One turn.
|
|
101
|
+
*
|
|
102
|
+
* `METADATA` is where a product hangs what only it knows — a request id, a cost, who asked. It is
|
|
103
|
+
* unconstrained on purpose: constraining it is how a transport type ends up describing one product.
|
|
104
|
+
*/
|
|
105
|
+
export interface AiMessage<METADATA = unknown, NAME extends string = string> {
|
|
106
|
+
id: string;
|
|
107
|
+
role: MessageRole;
|
|
108
|
+
metadata?: METADATA;
|
|
109
|
+
parts: AiMessagePart<NAME>[];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The guards, because they are what make a host's `switch` readable.
|
|
113
|
+
*
|
|
114
|
+
* Each is a type predicate, so `parts.filter(isToolPart)` is an `AiToolPart[]` and not a cast. That
|
|
115
|
+
* is the whole reason they exist rather than the host writing `part.type === "tool"`: the equality
|
|
116
|
+
* narrows inside a `switch` and does not survive a `.filter`.
|
|
117
|
+
*/
|
|
118
|
+
export declare const isTextPart: (part: AiMessagePart) => part is AiTextPart;
|
|
119
|
+
export declare const isReasoningPart: (part: AiMessagePart) => part is AiReasoningPart;
|
|
120
|
+
export declare const isToolPart: <NAME extends string>(part: AiMessagePart<NAME>) => part is AiToolPart<NAME>;
|
|
121
|
+
export declare const isTaskPart: (part: AiMessagePart) => part is AiTaskPart;
|
|
122
|
+
//# sourceMappingURL=message-part.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-part.d.ts","sourceRoot":"","sources":["../src/message-part.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,MAAM,CAAC;AAE/C,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAClD,UAAU,GACV,eAAe,GACf,UAAU,CAAC,IAAI,CAAC,GAChB,UAAU,CAAC;AAEf;;;;;GAKG;AACH,MAAM,WAAW,SAAS,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,SAAS,MAAM,GAAG,MAAM;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,aAAa,KAAG,IAAI,IAAI,UAAkC,CAAC;AAE5F,eAAO,MAAM,eAAe,GAAI,MAAM,aAAa,KAAG,IAAI,IAAI,eACnC,CAAC;AAE5B,eAAO,MAAM,UAAU,GAAI,IAAI,SAAS,MAAM,EAC5C,MAAM,aAAa,CAAC,IAAI,CAAC,KACxB,IAAI,IAAI,UAAU,CAAC,IAAI,CAAyB,CAAC;AAEpD,eAAO,MAAM,UAAU,GAAI,MAAM,aAAa,KAAG,IAAI,IAAI,UAAkC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const s = (t) => t.type === "text", o = (t) => t.type === "reasoning", e = (t) => t.type === "tool", a = (t) => t.type === "task";
|
|
2
|
+
export {
|
|
3
|
+
o as isReasoningPart,
|
|
4
|
+
a as isTaskPart,
|
|
5
|
+
s as isTextPart,
|
|
6
|
+
e as isToolPart
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=message-part.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-part.js","sources":["../src/message-part.ts"],"sourcesContent":["import type { MessageRole } from \"./message.js\";\nimport type { RunState } from \"./task.js\";\n\n/**\n * A turn as a **list of parts**, and the join every component in this package was missing.\n *\n * `Message`, `MessageText`, `Reasoning`, `Tool` and `Task` were each correct and none of them was\n * wired to the next: a host held its own shape and translated into five sets of props by hand.\n * keasy's `AskMessage` is the worked example — it carries `sql`, `reasoning`, `explanation` and a\n * `phase` of `'generating' | 'executing' | 'explaining' | 'done'`, which is a tool part, a reasoning\n * part, a text part and a state, hand-rolled. **That is the second consumer, and it existed before\n * this file did.** So this is not a protocol invented ahead of its need.\n *\n * The shape mirrors the AI SDK's `UIMessage`, deliberately and without depending on it — the four\n * properties that make theirs work are the four copied here:\n *\n * 1. **A message is a list of parts, not a string.** One `.map` with one `switch` on `part.type`\n * renders a whole turn.\n * 2. **State lives on the part**, so a renderer is told what to draw rather than inferring it.\n * 3. **Generic in metadata and tools**, so a product extends the union instead of forking it.\n * 4. **Transport stays separate.** Ours already is, and is smaller than theirs: a caller passes a\n * function returning an `AsyncIterable`. Nothing here replaces that seam.\n *\n * ## Four variants, not nine\n *\n * Theirs has nine. `file`, `data-*`, `step-start` and `dynamic-tool` have no call site here, and\n * `source` is **declined rather than deferred**: discovery does not cite a document, it runs a\n * query, and provenance that is a statement plus rows is a tool call. It arrives the day an answer\n * rests on a text somebody wrote.\n *\n * ## The two state vocabularies are the ones already in the package\n *\n * `RunState` — `pending` · `running` · `done` · `failed` — is `Task`'s and `Tool`'s, from one table\n * with one label map and one icon family. It is not restated here, and the reference's\n * `input-streaming | input-available | output-available | output-error` is not adopted: four states\n * under two spellings is the drift this repo keeps deleting.\n *\n * `StreamState` is the other one, and it is two values because text and reasoning have two.\n *\n * ## What this file is not\n *\n * **It renders nothing.** There is no `<Message parts={…} />` here and that is the shape rather than\n * an omission: which part a product renders, in what order, with what chrome around it, is the\n * thing that differs per product — and a component that owned the `switch` would own those answers\n * too. The `switch` belongs to the host; what this gives it is a union exhaustive enough that\n * TypeScript tells the host when a case is missing.\n */\n\n/** Where a streamed part has got to. Two values, because text and reasoning have two. */\nexport type StreamState = \"streaming\" | \"done\";\n\n/** Prose the model wrote. `MessageText` draws it. */\nexport interface AiTextPart {\n type: \"text\";\n text: string;\n state?: StreamState;\n}\n\n/**\n * The model's own working, folded away. `Reasoning` draws it.\n *\n * **No duration here**, and that is checked rather than assumed: `Reasoning` times itself off the\n * `streaming` transition and renders *Thought for N seconds* from its own state. A `seconds` on the\n * part would be a second measurement of one fact, and the one no component reads.\n */\nexport interface AiReasoningPart {\n type: \"reasoning\";\n text: string;\n state?: StreamState;\n}\n\n/**\n * One call the model made. `Tool` draws it.\n *\n * `input` and `output` are `unknown` here and **children** at the component: `ToolInput` and\n * `ToolOutput` take what you render rather than a JSON blob, because we always know what the tool\n * was. The part still carries the values, because a host that wants the JSON tree has to have them.\n *\n * `NAME` is the axis a product extends along. Give it a union of your tool names and a `switch` on\n * `part.name` narrows `input` and `output` if you declare them per name.\n */\nexport interface AiToolPart<NAME extends string = string> {\n type: \"tool\";\n name: NAME;\n state?: RunState;\n input?: unknown;\n output?: unknown;\n /** Set when `state` is `failed`. */\n errorText?: string;\n}\n\n/**\n * One step of work, ours and with no equivalent in the reference. `Task` draws it.\n *\n * Two fields, because `Task` has two parts: `TaskTitle` takes the words and `TaskStatus` reads the\n * state off the root and says it. There is no list of sub-lines on the part because there is none\n * on the component — a step that wants children is a step whose host renders them.\n */\nexport interface AiTaskPart {\n type: \"task\";\n title: string;\n state?: RunState;\n}\n\nexport type AiMessagePart<NAME extends string = string> =\n | AiTextPart\n | AiReasoningPart\n | AiToolPart<NAME>\n | AiTaskPart;\n\n/**\n * One turn.\n *\n * `METADATA` is where a product hangs what only it knows — a request id, a cost, who asked. It is\n * unconstrained on purpose: constraining it is how a transport type ends up describing one product.\n */\nexport interface AiMessage<METADATA = unknown, NAME extends string = string> {\n id: string;\n role: MessageRole;\n metadata?: METADATA;\n parts: AiMessagePart<NAME>[];\n}\n\n/**\n * The guards, because they are what make a host's `switch` readable.\n *\n * Each is a type predicate, so `parts.filter(isToolPart)` is an `AiToolPart[]` and not a cast. That\n * is the whole reason they exist rather than the host writing `part.type === \"tool\"`: the equality\n * narrows inside a `switch` and does not survive a `.filter`.\n */\nexport const isTextPart = (part: AiMessagePart): part is AiTextPart => part.type === \"text\";\n\nexport const isReasoningPart = (part: AiMessagePart): part is AiReasoningPart =>\n part.type === \"reasoning\";\n\nexport const isToolPart = <NAME extends string>(\n part: AiMessagePart<NAME>,\n): part is AiToolPart<NAME> => part.type === \"tool\";\n\nexport const isTaskPart = (part: AiMessagePart): part is AiTaskPart => part.type === \"task\";\n"],"names":["isTextPart","part","isReasoningPart","isToolPart","isTaskPart"],"mappings":"AAkIO,MAAMA,IAAa,CAACC,MAA4CA,EAAK,SAAS,QAExEC,IAAkB,CAACD,MAC9BA,EAAK,SAAS,aAEHE,IAAa,CACxBF,MAC6BA,EAAK,SAAS,QAEhCG,IAAa,CAACH,MAA4CA,EAAK,SAAS;"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ark } from '@ark-ui/react/factory';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* A streamed answer, arriving a word at a time.
|
|
5
|
+
*
|
|
6
|
+
* The shape is Streamdown's, which is what AI Elements renders an answer and a reasoning block
|
|
7
|
+
* with — `packages/streamdown/lib/animate.ts` and its `styles.css`. Four things are taken from it
|
|
8
|
+
* and each one is a bug this component would otherwise have:
|
|
9
|
+
*
|
|
10
|
+
* 1. **A word, not a chunk.** A model emits tokens and tokens cut words in half; animating what
|
|
11
|
+
* arrives makes half a word fade in and the other half a frame later. Streamdown splits the
|
|
12
|
+
* same way and glues the trailing whitespace onto the word before it, so a partial answer never
|
|
13
|
+
* ends on a naked space either.
|
|
14
|
+
* 2. **Only what is new animates.** Words already on screen are not re-run. Here that falls out of
|
|
15
|
+
* the key: each word is keyed by its absolute character offset, so React keeps the elements it
|
|
16
|
+
* already mounted. Keyed by the word itself, the last one remounts on every frame while its own
|
|
17
|
+
* characters are still arriving — its arrival restarts each time and it flashes instead of
|
|
18
|
+
* settling. The text only ever grows at the end, which is what makes the offset stable.
|
|
19
|
+
* 3. **A stagger, so a batch cascades instead of flashing.** A stream does not deliver one word per
|
|
20
|
+
* frame; it delivers twenty at once and then nothing for 200ms. Without a per-word delay the
|
|
21
|
+
* twenty fade in together, which is a flash — the thing the animation exists to avoid.
|
|
22
|
+
* 4. **A budget on the cascade.** {@link MAX_CASCADE} is Streamdown's `MAX_ANIMATION_BACKLOG_MS`.
|
|
23
|
+
* A stagger applied naively to a fast stream schedules words further and further ahead of the
|
|
24
|
+
* text, so an unbounded queue of `opacity: 0` words builds up behind a stream that has already
|
|
25
|
+
* finished. Compressing the step to fit the budget — floored at {@link MIN_STEP}, so the batch
|
|
26
|
+
* still cascades rather than collapsing to zero — is what keeps the paint caught up.
|
|
27
|
+
*
|
|
28
|
+
* What is NOT taken, and it is the gap to know about: Streamdown exists mostly to render
|
|
29
|
+
* **incomplete markdown**, closing an unterminated `**bold` or a half-arrived code fence so the
|
|
30
|
+
* answer does not flicker as it completes. This takes a plain string and renders it as text, which
|
|
31
|
+
* has no such problem and also no markdown. A source that streams markdown into this will want
|
|
32
|
+
* that, and it is a dependency rather than a rewrite.
|
|
33
|
+
*
|
|
34
|
+
* Reduced motion is `motion-reduce:animate-none`: with the animation off the words are already at
|
|
35
|
+
* their settled opacity, so there is nothing to unwind.
|
|
36
|
+
*/
|
|
37
|
+
export interface MessageTextProps extends Omit<React.ComponentProps<typeof ark.div>, "children"> {
|
|
38
|
+
/** The answer so far. Grows; never rewritten. */
|
|
39
|
+
children: string;
|
|
40
|
+
/** More is coming. Draws the caret, and nothing else — the arrival is per word. */
|
|
41
|
+
streaming?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare const MessageText: (props: MessageTextProps) => React.JSX.Element;
|
|
44
|
+
//# sourceMappingURL=message-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-text.d.ts","sourceRoot":"","sources":["../src/message-text.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC9D,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAYD,eAAO,MAAM,WAAW,GAAI,OAAO,gBAAgB,sBAsDlD,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as M, jsx as l } from "react/jsx-runtime";
|
|
3
|
+
import { ark as v } from "@ark-ui/react/factory";
|
|
4
|
+
import * as d from "react";
|
|
5
|
+
import { cn as r } from "@kanzo-tech/ui";
|
|
6
|
+
const w = /\S+\s*/g, h = 18, A = 320, N = 4, b = (p) => {
|
|
7
|
+
const { children: a, streaming: n = !1, className: u, slot: g, ...f } = p, o = d.useRef(n ? 0 : a.length), s = [...a.matchAll(w)], e = s.findIndex((t) => t.index >= o.current), i = e < 0 ? 0 : s.length - e, x = i > 1 ? Math.max(N, Math.min(h, A / (i - 1))) : h;
|
|
8
|
+
return d.useLayoutEffect(() => {
|
|
9
|
+
o.current = a.length;
|
|
10
|
+
}), /* @__PURE__ */ M(
|
|
11
|
+
v.div,
|
|
12
|
+
{
|
|
13
|
+
className: r("min-w-0 whitespace-pre-wrap break-words", u),
|
|
14
|
+
"data-streaming": n || void 0,
|
|
15
|
+
...f,
|
|
16
|
+
"data-slot": g ?? "message-text",
|
|
17
|
+
children: [
|
|
18
|
+
s.map((t, m) => {
|
|
19
|
+
const c = e >= 0 && m >= e;
|
|
20
|
+
return /* @__PURE__ */ l(
|
|
21
|
+
"span",
|
|
22
|
+
{
|
|
23
|
+
className: r(c && "animate-arrive motion-reduce:animate-none"),
|
|
24
|
+
style: c ? { animationDelay: `${Math.round((m - e) * x)}ms` } : void 0,
|
|
25
|
+
children: t[0]
|
|
26
|
+
},
|
|
27
|
+
t.index
|
|
28
|
+
);
|
|
29
|
+
}),
|
|
30
|
+
/* @__PURE__ */ l(
|
|
31
|
+
"span",
|
|
32
|
+
{
|
|
33
|
+
"aria-hidden": !0,
|
|
34
|
+
className: r(
|
|
35
|
+
"ms-0.5 inline-block h-[1em] w-[0.5ch] align-[-0.1em] bg-current",
|
|
36
|
+
"animate-pulse motion-reduce:animate-none",
|
|
37
|
+
!n && "hidden"
|
|
38
|
+
),
|
|
39
|
+
"data-slot": "message-caret"
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
export {
|
|
47
|
+
b as MessageText
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=message-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-text.js","sources":["../src/message-text.tsx"],"sourcesContent":["\"use client\";\n\nimport { ark } from \"@ark-ui/react/factory\";\nimport * as React from \"react\";\nimport { cn } from \"@kanzo-tech/ui\";\n\n/**\n * A streamed answer, arriving a word at a time.\n *\n * The shape is Streamdown's, which is what AI Elements renders an answer and a reasoning block\n * with — `packages/streamdown/lib/animate.ts` and its `styles.css`. Four things are taken from it\n * and each one is a bug this component would otherwise have:\n *\n * 1. **A word, not a chunk.** A model emits tokens and tokens cut words in half; animating what\n * arrives makes half a word fade in and the other half a frame later. Streamdown splits the\n * same way and glues the trailing whitespace onto the word before it, so a partial answer never\n * ends on a naked space either.\n * 2. **Only what is new animates.** Words already on screen are not re-run. Here that falls out of\n * the key: each word is keyed by its absolute character offset, so React keeps the elements it\n * already mounted. Keyed by the word itself, the last one remounts on every frame while its own\n * characters are still arriving — its arrival restarts each time and it flashes instead of\n * settling. The text only ever grows at the end, which is what makes the offset stable.\n * 3. **A stagger, so a batch cascades instead of flashing.** A stream does not deliver one word per\n * frame; it delivers twenty at once and then nothing for 200ms. Without a per-word delay the\n * twenty fade in together, which is a flash — the thing the animation exists to avoid.\n * 4. **A budget on the cascade.** {@link MAX_CASCADE} is Streamdown's `MAX_ANIMATION_BACKLOG_MS`.\n * A stagger applied naively to a fast stream schedules words further and further ahead of the\n * text, so an unbounded queue of `opacity: 0` words builds up behind a stream that has already\n * finished. Compressing the step to fit the budget — floored at {@link MIN_STEP}, so the batch\n * still cascades rather than collapsing to zero — is what keeps the paint caught up.\n *\n * What is NOT taken, and it is the gap to know about: Streamdown exists mostly to render\n * **incomplete markdown**, closing an unterminated `**bold` or a half-arrived code fence so the\n * answer does not flicker as it completes. This takes a plain string and renders it as text, which\n * has no such problem and also no markdown. A source that streams markdown into this will want\n * that, and it is a dependency rather than a rewrite.\n *\n * Reduced motion is `motion-reduce:animate-none`: with the animation off the words are already at\n * their settled opacity, so there is nothing to unwind.\n */\nexport interface MessageTextProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"children\"> {\n /** The answer so far. Grows; never rewritten. */\n children: string;\n /** More is coming. Draws the caret, and nothing else — the arrival is per word. */\n streaming?: boolean;\n}\n\n/** A run of non-space plus the space that follows it, so the whitespace travels with its word. */\nconst WORD = /\\S+\\s*/g;\n\n/** Milliseconds between two words of one batch, before the budget below compresses it. */\nconst STAGGER = 18;\n/** How far ahead of the text a batch may schedule. Streamdown's `MAX_ANIMATION_BACKLOG_MS`. */\nconst MAX_CASCADE = 320;\n/** The step never falls below this, so a compressed batch still cascades. Streamdown's floor. */\nconst MIN_STEP = 4;\n\nexport const MessageText = (props: MessageTextProps) => {\n const { children, streaming = false, className, slot, ...rest } = props;\n\n // What was on screen when the last render committed. **A message that mounts complete did not\n // arrive**, so one that is not streaming at mount starts with all of it settled and animates\n // nothing — a transcript loaded from history should not replay itself.\n const settled = React.useRef(streaming ? 0 : children.length);\n\n const words = [...children.matchAll(WORD)];\n const from = words.findIndex((word) => word.index >= settled.current);\n const fresh = from < 0 ? 0 : words.length - from;\n const step =\n fresh > 1 ? Math.max(MIN_STEP, Math.min(STAGGER, MAX_CASCADE / (fresh - 1))) : STAGGER;\n\n // After paint, not during render: a ref written in the render body is wrong under StrictMode's\n // double invoke, which is the same hazard Streamdown answers with `mark`/`rewind`.\n React.useLayoutEffect(() => {\n settled.current = children.length;\n });\n\n return (\n <ark.div\n className={cn(\"min-w-0 whitespace-pre-wrap break-words\", className)}\n data-streaming={streaming || undefined}\n {...rest}\n data-slot={slot ?? \"message-text\"}\n >\n {words.map((word, index) => {\n const arriving = from >= 0 && index >= from;\n return (\n <span\n className={cn(arriving && \"animate-arrive motion-reduce:animate-none\")}\n key={word.index}\n style={arriving ? { animationDelay: `${Math.round((index - from) * step)}ms` } : undefined}\n >\n {word[0]}\n </span>\n );\n })}\n {/* Always rendered, hidden rather than unmounted: a component that renders one shape while\n streaming and another when done re-mounts every word at the moment it finishes, and the\n whole answer arrives a second time. `aria-hidden` because a caret is not content — the\n live region that announces the turn is the caller's. */}\n <span\n aria-hidden\n className={cn(\n \"ms-0.5 inline-block h-[1em] w-[0.5ch] align-[-0.1em] bg-current\",\n \"animate-pulse motion-reduce:animate-none\",\n !streaming && \"hidden\",\n )}\n data-slot=\"message-caret\"\n />\n </ark.div>\n );\n};\n"],"names":[],"mappings":";;;;;AAiDA;AAUE;AAeA;AACE;AAA2B;AAI3B;AAAK;AAAJ;AACmE;AACrC;AACzB;AACe;AAElB;AACC;AACA;AACE;AAAC;AAAA;AACsE;AAEY;AAE1E;AAAA;AAHG;AAAA;AAMf;AAKD;AAAC;AAAA;AACY;AACA;AACT;AACA;AACc;AAAA;AAEN;AAAA;AAAA;AACZ;AAAA;AAGN;;;;"}
|