@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,73 @@
|
|
|
1
|
+
import { ark } from '@ark-ui/react/factory';
|
|
2
|
+
import { default as React } from 'react';
|
|
3
|
+
import { VariantProps } from 'tailwind-variants';
|
|
4
|
+
import { Avatar } from '@kanzo-tech/ui';
|
|
5
|
+
export type MessageRole = "user" | "assistant" | "system";
|
|
6
|
+
declare const messageVariants: import('tailwind-variants').TVReturnType<{
|
|
7
|
+
role: {
|
|
8
|
+
user: string;
|
|
9
|
+
assistant: string;
|
|
10
|
+
system: string;
|
|
11
|
+
};
|
|
12
|
+
}, undefined, string[], {
|
|
13
|
+
role: {
|
|
14
|
+
user: string;
|
|
15
|
+
assistant: string;
|
|
16
|
+
system: string;
|
|
17
|
+
};
|
|
18
|
+
}, undefined, import('tailwind-variants').TVReturnType<{
|
|
19
|
+
role: {
|
|
20
|
+
user: string;
|
|
21
|
+
assistant: string;
|
|
22
|
+
system: string;
|
|
23
|
+
};
|
|
24
|
+
}, undefined, string[], unknown, unknown, undefined>>;
|
|
25
|
+
export interface MessageProps extends Omit<React.ComponentProps<typeof ark.li>, "role">, VariantProps<typeof messageVariants> {
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One turn. The role is declared once, here, and every part below reads it off `data-role`
|
|
29
|
+
* through a group selector — repeating it as a prop on each part is how the two drift.
|
|
30
|
+
*
|
|
31
|
+
* `role` is the speaker and not the ARIA attribute, which is why the HTML one is omitted rather
|
|
32
|
+
* than merged: two meanings under one name, and the wrong one reaching the DOM, is worse than an
|
|
33
|
+
* `<li>` nobody can re-role.
|
|
34
|
+
*/
|
|
35
|
+
export declare const Message: (props: MessageProps) => React.JSX.Element;
|
|
36
|
+
/**
|
|
37
|
+
* **`role="list"` on top of the `<ul>`, because `list-none` takes the implicit one away.** WebKit
|
|
38
|
+
* drops list semantics from a list whose `list-style` is `none` — items included — so a styled
|
|
39
|
+
* transcript announces nothing in VoiceOver. jsdom does not model it, so the role queries passed
|
|
40
|
+
* throughout. See `DiagnosticList`, which carries the argument in full.
|
|
41
|
+
*/
|
|
42
|
+
export declare const MessageList: (props: React.ComponentProps<typeof ark.ul>) => React.JSX.Element;
|
|
43
|
+
export interface MessageAvatarProps extends React.ComponentProps<typeof Avatar> {
|
|
44
|
+
src?: string;
|
|
45
|
+
/** Names the image and, when there is no image, supplies the fallback's initials. */
|
|
46
|
+
name?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare const MessageAvatar: (props: MessageAvatarProps) => React.JSX.Element;
|
|
49
|
+
/**
|
|
50
|
+
* The turn's words. The two speakers are told apart by surface before they are read: the user gets
|
|
51
|
+
* a bubble and the assistant the page itself. A system note takes neither — it is not a speaker.
|
|
52
|
+
*
|
|
53
|
+
* **One treatment per speaker, not two.** The bubble was `bg-muted` plus a border, and it needed
|
|
54
|
+
* the border because `--muted` is one step off `--background` (#efefef on #fafafa) — a fill nobody
|
|
55
|
+
* could see, rescued by an outline. `--secondary` is the step that shows on its own, which is the
|
|
56
|
+
* fill the source uses for exactly this, and it takes the outline back out. The tucked corner went
|
|
57
|
+
* with it: a radius the rest of the library does not use, pointing at an avatar that is already
|
|
58
|
+
* beside it.
|
|
59
|
+
*
|
|
60
|
+
* **Two treatments, not three.** The system pill that used to be here is gone; see `Message`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const MessageContent: (props: React.ComponentProps<typeof ark.div>) => React.JSX.Element;
|
|
63
|
+
/**
|
|
64
|
+
* A row of affordances for the turn above it — copy, retry, a source. `basis-full` breaks it onto
|
|
65
|
+
* its own line, which is what the root's `flex-wrap` is there for.
|
|
66
|
+
*
|
|
67
|
+
* Hidden until the turn is pointed at, so a transcript is words rather than a column of buttons;
|
|
68
|
+
* revealed by focus as well as hover, and always present on a coarse pointer, where there is no
|
|
69
|
+
* hover to reveal it with.
|
|
70
|
+
*/
|
|
71
|
+
export declare const MessageActions: (props: React.ComponentProps<typeof ark.div>) => React.JSX.Element;
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../src/message.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAM,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAmC,MAAM,gBAAgB,CAAC;AAEzE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE1D,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;qDA6BnB,CAAC;AAEH,MAAM,WAAW,YACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,EACvD,YAAY,CAAC,OAAO,eAAe,CAAC;CAAG;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,YAAY,sBAW1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,sBAWrE,CAAC;AAUF,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,aAAa,GAAI,OAAO,kBAAkB,sBAsBtD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,sBAiBzE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,sBAiBzE,CAAC"}
|
package/dist/message.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { jsx as r, jsxs as g, Fragment as u } from "react/jsx-runtime";
|
|
2
|
+
import { ark as l } from "@ark-ui/react/factory";
|
|
3
|
+
import { tv as p } from "tailwind-variants";
|
|
4
|
+
import { cn as o, Avatar as d, AvatarImage as f, AvatarFallback as x } from "@kanzo-tech/ui";
|
|
5
|
+
const v = p({
|
|
6
|
+
base: [
|
|
7
|
+
"group/message",
|
|
8
|
+
"flex w-full flex-wrap items-start gap-3",
|
|
9
|
+
"text-sm"
|
|
10
|
+
],
|
|
11
|
+
variants: {
|
|
12
|
+
role: {
|
|
13
|
+
// Reversed rather than `justify-end`: the row packs against the reading end and the avatar
|
|
14
|
+
// goes with it, and `row-reverse` is direction-aware where a physical side is not.
|
|
15
|
+
user: "flex-row-reverse",
|
|
16
|
+
assistant: "",
|
|
17
|
+
// **No surface of its own, and that is the correction.** A system note used to be a centred
|
|
18
|
+
// `rounded-full` pill painted by `MessageContent` — a shape the library already has, spelled
|
|
19
|
+
// again, and one that cannot hold what it was given: a full-round pill around a wrapped
|
|
20
|
+
// sentence is a stadium with dead corners, and `Badge`, the component it was imitating, is
|
|
21
|
+
// `whitespace-nowrap` at a fixed `h-5` and could never have rendered it.
|
|
22
|
+
//
|
|
23
|
+
// AI Elements types `from` as the AI SDK's `UIMessage["role"]` — system included — and gives
|
|
24
|
+
// it no treatment at all: `is-user` or `is-assistant`, nothing else. So the role stays, the
|
|
25
|
+
// invented surface goes, and a caller who wants the note to read as an aside composes an
|
|
26
|
+
// `Alert` inside the row. That is a component with a wash, a border, an icon slot, an action
|
|
27
|
+
// slot, `role="status"` and the ability to wrap.
|
|
28
|
+
system: ""
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
role: "assistant"
|
|
33
|
+
}
|
|
34
|
+
}), M = (a) => {
|
|
35
|
+
const { role: s = "assistant", className: e, slot: t, ...n } = a;
|
|
36
|
+
return /* @__PURE__ */ r(
|
|
37
|
+
l.li,
|
|
38
|
+
{
|
|
39
|
+
className: o(v({ role: s }), e),
|
|
40
|
+
"data-role": s,
|
|
41
|
+
...n,
|
|
42
|
+
"data-slot": t ?? "message"
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}, b = (a) => {
|
|
46
|
+
const { className: s, slot: e, ...t } = a;
|
|
47
|
+
return /* @__PURE__ */ r(
|
|
48
|
+
l.ul,
|
|
49
|
+
{
|
|
50
|
+
className: o("flex w-full list-none flex-col gap-6", s),
|
|
51
|
+
role: "list",
|
|
52
|
+
...t,
|
|
53
|
+
"data-slot": e ?? "message-list"
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}, y = (a) => (a ?? "").split(/\s+/).filter(Boolean).slice(0, 2).map((s) => {
|
|
57
|
+
var e;
|
|
58
|
+
return ((e = s[0]) == null ? void 0 : e.toUpperCase()) ?? "";
|
|
59
|
+
}).join(""), j = (a) => {
|
|
60
|
+
const { src: s, name: e, className: t, children: n, size: i = "sm", slot: m, ...c } = a;
|
|
61
|
+
return /* @__PURE__ */ r(
|
|
62
|
+
d,
|
|
63
|
+
{
|
|
64
|
+
className: o(
|
|
65
|
+
"shrink-0",
|
|
66
|
+
"group-data-[role=system]/message:hidden",
|
|
67
|
+
t
|
|
68
|
+
),
|
|
69
|
+
size: i,
|
|
70
|
+
...c,
|
|
71
|
+
slot: m ?? "message-avatar",
|
|
72
|
+
children: n ?? /* @__PURE__ */ g(u, { children: [
|
|
73
|
+
s ? /* @__PURE__ */ r(f, { alt: e ?? "", src: s }) : null,
|
|
74
|
+
/* @__PURE__ */ r(x, { children: y(e) })
|
|
75
|
+
] })
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}, k = (a) => {
|
|
79
|
+
const { className: s, slot: e, ...t } = a;
|
|
80
|
+
return /* @__PURE__ */ r(
|
|
81
|
+
l.div,
|
|
82
|
+
{
|
|
83
|
+
className: o(
|
|
84
|
+
"flex min-w-0 flex-1 flex-col gap-2 leading-relaxed",
|
|
85
|
+
"group-data-[role=user]/message:max-w-[85%] group-data-[role=user]/message:flex-none",
|
|
86
|
+
"group-data-[role=user]/message:rounded-xl",
|
|
87
|
+
"group-data-[role=user]/message:bg-secondary group-data-[role=user]/message:text-secondary-foreground",
|
|
88
|
+
"group-data-[role=user]/message:px-3.5 group-data-[role=user]/message:py-2.5",
|
|
89
|
+
s
|
|
90
|
+
),
|
|
91
|
+
...t,
|
|
92
|
+
"data-slot": e ?? "message-content"
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}, C = (a) => {
|
|
96
|
+
const { className: s, slot: e, ...t } = a;
|
|
97
|
+
return /* @__PURE__ */ r(
|
|
98
|
+
l.div,
|
|
99
|
+
{
|
|
100
|
+
className: o(
|
|
101
|
+
"flex basis-full items-center gap-1",
|
|
102
|
+
"opacity-0 transition-opacity motion-reduce:transition-none!",
|
|
103
|
+
"group-hover/message:opacity-100 group-focus-within/message:opacity-100",
|
|
104
|
+
"pointer-coarse:opacity-100",
|
|
105
|
+
"group-data-[role=user]/message:justify-end",
|
|
106
|
+
s
|
|
107
|
+
),
|
|
108
|
+
...t,
|
|
109
|
+
"data-slot": e ?? "message-actions"
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
export {
|
|
114
|
+
M as Message,
|
|
115
|
+
C as MessageActions,
|
|
116
|
+
j as MessageAvatar,
|
|
117
|
+
k as MessageContent,
|
|
118
|
+
b as MessageList
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.js","sources":["../src/message.tsx"],"sourcesContent":["import { ark } from \"@ark-ui/react/factory\";\nimport type React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\nimport { Avatar, AvatarFallback, AvatarImage, cn } from \"@kanzo-tech/ui\";\n\nexport type MessageRole = \"user\" | \"assistant\" | \"system\";\n\nconst messageVariants = tv({\n base: [\n \"group/message\",\n \"flex w-full flex-wrap items-start gap-3\",\n \"text-sm\",\n ],\n variants: {\n role: {\n // Reversed rather than `justify-end`: the row packs against the reading end and the avatar\n // goes with it, and `row-reverse` is direction-aware where a physical side is not.\n user: \"flex-row-reverse\",\n assistant: \"\",\n // **No surface of its own, and that is the correction.** A system note used to be a centred\n // `rounded-full` pill painted by `MessageContent` — a shape the library already has, spelled\n // again, and one that cannot hold what it was given: a full-round pill around a wrapped\n // sentence is a stadium with dead corners, and `Badge`, the component it was imitating, is\n // `whitespace-nowrap` at a fixed `h-5` and could never have rendered it.\n //\n // AI Elements types `from` as the AI SDK's `UIMessage[\"role\"]` — system included — and gives\n // it no treatment at all: `is-user` or `is-assistant`, nothing else. So the role stays, the\n // invented surface goes, and a caller who wants the note to read as an aside composes an\n // `Alert` inside the row. That is a component with a wash, a border, an icon slot, an action\n // slot, `role=\"status\"` and the ability to wrap.\n system: \"\",\n },\n },\n defaultVariants: {\n role: \"assistant\",\n },\n});\n\nexport interface MessageProps\n extends Omit<React.ComponentProps<typeof ark.li>, \"role\">,\n VariantProps<typeof messageVariants> {}\n\n/**\n * One turn. The role is declared once, here, and every part below reads it off `data-role`\n * through a group selector — repeating it as a prop on each part is how the two drift.\n *\n * `role` is the speaker and not the ARIA attribute, which is why the HTML one is omitted rather\n * than merged: two meanings under one name, and the wrong one reaching the DOM, is worse than an\n * `<li>` nobody can re-role.\n */\nexport const Message = (props: MessageProps) => {\n const { role = \"assistant\", className, slot, ...rest } = props;\n\n return (\n <ark.li\n className={cn(messageVariants({ role }), className)}\n data-role={role}\n {...rest}\n data-slot={slot ?? \"message\"}\n />\n );\n};\n\n/**\n * **`role=\"list\"` on top of the `<ul>`, because `list-none` takes the implicit one away.** WebKit\n * drops list semantics from a list whose `list-style` is `none` — items included — so a styled\n * transcript announces nothing in VoiceOver. jsdom does not model it, so the role queries passed\n * throughout. See `DiagnosticList`, which carries the argument in full.\n */\nexport const MessageList = (props: React.ComponentProps<typeof ark.ul>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ark.ul\n className={cn(\"flex w-full list-none flex-col gap-6\", className)}\n role=\"list\"\n {...rest}\n data-slot={slot ?? \"message-list\"}\n />\n );\n};\n\nconst initials = (name?: string) =>\n (name ?? \"\")\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .map((word) => word[0]?.toUpperCase() ?? \"\")\n .join(\"\");\n\nexport interface MessageAvatarProps extends React.ComponentProps<typeof Avatar> {\n src?: string;\n /** Names the image and, when there is no image, supplies the fallback's initials. */\n name?: string;\n}\n\nexport const MessageAvatar = (props: MessageAvatarProps) => {\n const { src, name, className, children, size = \"sm\", slot, ...rest } = props;\n\n return (\n <Avatar\n className={cn(\n \"shrink-0\",\n \"group-data-[role=system]/message:hidden\",\n className\n )}\n size={size}\n {...rest}\n slot={slot ?? \"message-avatar\"}\n >\n {children ?? (\n <>\n {src ? <AvatarImage alt={name ?? \"\"} src={src} /> : null}\n <AvatarFallback>{initials(name)}</AvatarFallback>\n </>\n )}\n </Avatar>\n );\n};\n\n/**\n * The turn's words. The two speakers are told apart by surface before they are read: the user gets\n * a bubble and the assistant the page itself. A system note takes neither — it is not a speaker.\n *\n * **One treatment per speaker, not two.** The bubble was `bg-muted` plus a border, and it needed\n * the border because `--muted` is one step off `--background` (#efefef on #fafafa) — a fill nobody\n * could see, rescued by an outline. `--secondary` is the step that shows on its own, which is the\n * fill the source uses for exactly this, and it takes the outline back out. The tucked corner went\n * with it: a radius the rest of the library does not use, pointing at an avatar that is already\n * beside it.\n *\n * **Two treatments, not three.** The system pill that used to be here is gone; see `Message`.\n */\nexport const MessageContent = (props: React.ComponentProps<typeof ark.div>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ark.div\n className={cn(\n \"flex min-w-0 flex-1 flex-col gap-2 leading-relaxed\",\n \"group-data-[role=user]/message:max-w-[85%] group-data-[role=user]/message:flex-none\",\n \"group-data-[role=user]/message:rounded-xl\",\n \"group-data-[role=user]/message:bg-secondary group-data-[role=user]/message:text-secondary-foreground\",\n \"group-data-[role=user]/message:px-3.5 group-data-[role=user]/message:py-2.5\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"message-content\"}\n />\n );\n};\n\n/**\n * A row of affordances for the turn above it — copy, retry, a source. `basis-full` breaks it onto\n * its own line, which is what the root's `flex-wrap` is there for.\n *\n * Hidden until the turn is pointed at, so a transcript is words rather than a column of buttons;\n * revealed by focus as well as hover, and always present on a coarse pointer, where there is no\n * hover to reveal it with.\n */\nexport const MessageActions = (props: React.ComponentProps<typeof ark.div>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ark.div\n className={cn(\n \"flex basis-full items-center gap-1\",\n \"opacity-0 transition-opacity motion-reduce:transition-none!\",\n \"group-hover/message:opacity-100 group-focus-within/message:opacity-100\",\n \"pointer-coarse:opacity-100\",\n \"group-data-[role=user]/message:justify-end\",\n className\n )}\n {...rest}\n data-slot={slot ?? \"message-actions\"}\n />\n );\n};\n"],"names":["messageVariants","tv","Message","props","role","className","slot","rest","jsx","ark","cn","MessageList","initials","name","word","_a","MessageAvatar","src","children","size","Avatar","jsxs","Fragment","AvatarImage","AvatarFallback","MessageContent","MessageActions"],"mappings":";;;;AAOA,MAAMA,IAAkBC,EAAG;AAAA,EACzB,MAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF,UAAU;AAAA,IACR,MAAM;AAAA;AAAA;AAAA,MAGJ,MAAM;AAAA,MACN,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYX,QAAQ;AAAA,IAAA;AAAA,EACV;AAAA,EAEF,iBAAiB;AAAA,IACf,MAAM;AAAA,EAAA;AAEV,CAAC,GAcYC,IAAU,CAACC,MAAwB;AAC9C,QAAM,EAAE,MAAAC,IAAO,aAAa,WAAAC,GAAW,MAAAC,GAAM,GAAGC,MAASJ;AAEzD,SACE,gBAAAK;AAAA,IAACC,EAAI;AAAA,IAAJ;AAAA,MACC,WAAWC,EAAGV,EAAgB,EAAE,MAAAI,EAAA,CAAM,GAAGC,CAAS;AAAA,MAClD,aAAWD;AAAA,MACV,GAAGG;AAAA,MACJ,aAAWD,KAAQ;AAAA,IAAA;AAAA,EAAA;AAGzB,GAQaK,IAAc,CAACR,MAA+C;AACzE,QAAM,EAAE,WAAAE,GAAW,MAAAC,GAAM,GAAGC,MAASJ;AAErC,SACE,gBAAAK;AAAA,IAACC,EAAI;AAAA,IAAJ;AAAA,MACC,WAAWC,EAAG,wCAAwCL,CAAS;AAAA,MAC/D,MAAK;AAAA,MACJ,GAAGE;AAAA,MACJ,aAAWD,KAAQ;AAAA,IAAA;AAAA,EAAA;AAGzB,GAEMM,IAAW,CAACC,OACfA,KAAQ,IACN,MAAM,KAAK,EACX,OAAO,OAAO,EACd,MAAM,GAAG,CAAC,EACV,IAAI,CAACC,MAAA;;AAAS,WAAAC,IAAAD,EAAK,CAAC,MAAN,gBAAAC,EAAS,kBAAiB;AAAA,CAAE,EAC1C,KAAK,EAAE,GAQCC,IAAgB,CAACb,MAA8B;AAC1D,QAAM,EAAE,KAAAc,GAAK,MAAAJ,GAAM,WAAAR,GAAW,UAAAa,GAAU,MAAAC,IAAO,MAAM,MAAAb,GAAM,GAAGC,EAAA,IAASJ;AAEvE,SACE,gBAAAK;AAAA,IAACY;AAAA,IAAA;AAAA,MACC,WAAWV;AAAA,QACT;AAAA,QACA;AAAA,QACAL;AAAA,MAAA;AAAA,MAEF,MAAAc;AAAA,MACC,GAAGZ;AAAA,MACJ,MAAMD,KAAQ;AAAA,MAEb,eACC,gBAAAe,EAAAC,GAAA,EACG,UAAA;AAAA,QAAAL,sBAAOM,GAAA,EAAY,KAAKV,KAAQ,IAAI,KAAAI,GAAU,IAAK;AAAA,QACpD,gBAAAT,EAACgB,GAAA,EAAgB,UAAAZ,EAASC,CAAI,EAAA,CAAE;AAAA,MAAA,EAAA,CAClC;AAAA,IAAA;AAAA,EAAA;AAIR,GAeaY,IAAiB,CAACtB,MAAgD;AAC7E,QAAM,EAAE,WAAAE,GAAW,MAAAC,GAAM,GAAGC,MAASJ;AAErC,SACE,gBAAAK;AAAA,IAACC,EAAI;AAAA,IAAJ;AAAA,MACC,WAAWC;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACAL;AAAA,MAAA;AAAA,MAED,GAAGE;AAAA,MACJ,aAAWD,KAAQ;AAAA,IAAA;AAAA,EAAA;AAGzB,GAUaoB,IAAiB,CAACvB,MAAgD;AAC7E,QAAM,EAAE,WAAAE,GAAW,MAAAC,GAAM,GAAGC,MAASJ;AAErC,SACE,gBAAAK;AAAA,IAACC,EAAI;AAAA,IAAJ;AAAA,MACC,WAAWC;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACAL;AAAA,MAAA;AAAA,MAED,GAAGE;AAAA,MACJ,aAAWD,KAAQ;AAAA,IAAA;AAAA,EAAA;AAGzB;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ark } from '@ark-ui/react/factory';
|
|
2
|
+
import { default as React } from 'react';
|
|
3
|
+
import { InputGroupAddon, InputGroupButton, InputGroupTextarea } from '@kanzo-tech/ui';
|
|
4
|
+
import { AiStatus } from './use-ai.js';
|
|
5
|
+
/**
|
|
6
|
+
* The composer: the library's `InputGroup` as a form, so Enter and the button are one path.
|
|
7
|
+
*
|
|
8
|
+
* `asChild` rather than a wrapping `<form>`: `InputGroup`'s recipe selects its own direct children
|
|
9
|
+
* (`has-[>textarea]`, `has-[>[data-align=block-end]]`), and an element between the group and the
|
|
10
|
+
* field silently unsets half of it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PromptInput: (props: React.ComponentProps<typeof ark.form>) => React.JSX.Element;
|
|
13
|
+
export declare const PromptInputTextarea: (props: React.ComponentProps<typeof InputGroupTextarea>) => React.JSX.Element;
|
|
14
|
+
/**
|
|
15
|
+
* The strip of controls under the field — a model picker, an attachment, the ✨, the submit.
|
|
16
|
+
*
|
|
17
|
+
* `align="block-end"` is not decoration: a popover trigger living here anchors to the composer, so
|
|
18
|
+
* its surface opens over the text it writes. Hung off a label row above the field instead, it
|
|
19
|
+
* opened against the panel's edge, far from the value. Nothing here may become `overflow-hidden`
|
|
20
|
+
* for the same reason.
|
|
21
|
+
*
|
|
22
|
+
* It wraps rather than overflows, because everything in it is `shrink-0`: four controls in a narrow
|
|
23
|
+
* panel pushed the submit past the composer's edge instead of taking a second line.
|
|
24
|
+
*/
|
|
25
|
+
export declare const PromptInputToolbar: (props: React.ComponentProps<typeof InputGroupAddon>) => React.JSX.Element;
|
|
26
|
+
export interface PromptInputSubmitProps extends React.ComponentProps<typeof InputGroupButton> {
|
|
27
|
+
status?: AiStatus;
|
|
28
|
+
}
|
|
29
|
+
export declare const PromptInputSubmit: (props: PromptInputSubmitProps) => React.JSX.Element;
|
|
30
|
+
//# sourceMappingURL=prompt-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-input.d.ts","sourceRoot":"","sources":["../src/prompt-input.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAE5C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAGL,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,sBAUvE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,sBAuBzF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,sBAiBrF,CAAC;AAqDF,MAAM,WAAW,sBACf,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC;IACrD,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,eAAO,MAAM,iBAAiB,GAAI,OAAO,sBAAsB,sBA6B9D,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as a, jsxs as f } from "react/jsx-runtime";
|
|
3
|
+
import { ark as b } from "@ark-ui/react/factory";
|
|
4
|
+
import { RefreshCcwIcon as h, SquareIcon as x, SendIcon as I } from "lucide-react";
|
|
5
|
+
import { tv as N } from "tailwind-variants";
|
|
6
|
+
import { InputGroup as g, cn as l, InputGroupButton as S, InputGroupTextarea as v, InputGroupAddon as y, Spinner as w } from "@kanzo-tech/ui";
|
|
7
|
+
const j = (o) => {
|
|
8
|
+
const { className: e, slot: r, ...s } = o;
|
|
9
|
+
return (
|
|
10
|
+
// A composer is a panel rather than a one-line control, so it takes the next radius up and a
|
|
11
|
+
// shadow that puts it on top of the page instead of flush with it.
|
|
12
|
+
/* @__PURE__ */ a(g, { asChild: !0, className: l("rounded-xl shadow-xs/5", e), children: /* @__PURE__ */ a(b.form, { ...s, "data-slot": r ?? "prompt-input" }) })
|
|
13
|
+
);
|
|
14
|
+
}, q = (o) => {
|
|
15
|
+
const { className: e, onKeyDown: r, rows: s = 1, slot: n, ...i } = o;
|
|
16
|
+
return /* @__PURE__ */ a(
|
|
17
|
+
v,
|
|
18
|
+
{
|
|
19
|
+
className: l("max-h-48 min-h-16", e),
|
|
20
|
+
onKeyDown: (t) => {
|
|
21
|
+
var u;
|
|
22
|
+
r == null || r(t), !t.defaultPrevented && (t.key !== "Enter" || t.shiftKey || t.nativeEvent.isComposing || (t.preventDefault(), (u = t.currentTarget.form) == null || u.requestSubmit()));
|
|
23
|
+
},
|
|
24
|
+
rows: s,
|
|
25
|
+
...i,
|
|
26
|
+
slot: n ?? "prompt-input-textarea"
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}, B = (o) => {
|
|
30
|
+
const { className: e, slot: r, ...s } = o;
|
|
31
|
+
return /* @__PURE__ */ a(
|
|
32
|
+
y,
|
|
33
|
+
{
|
|
34
|
+
align: "block-end",
|
|
35
|
+
className: l("min-w-0 flex-wrap gap-1", e),
|
|
36
|
+
onClick: (n) => {
|
|
37
|
+
var i, t;
|
|
38
|
+
n.target.closest("button, a, input, textarea") || (t = (i = n.currentTarget.parentElement) == null ? void 0 : i.querySelector("textarea")) == null || t.focus();
|
|
39
|
+
},
|
|
40
|
+
...s,
|
|
41
|
+
slot: r ?? "prompt-input-toolbar"
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}, c = { icon: /* @__PURE__ */ a(I, {}), label: "Send" }, T = {
|
|
45
|
+
idle: c,
|
|
46
|
+
// An answer that has arrived leaves the composer ready for the next question, so `ready` and
|
|
47
|
+
// `idle` are one button. They are two states of the *stream*, not two states of this control.
|
|
48
|
+
ready: c,
|
|
49
|
+
loading: {
|
|
50
|
+
// The stop block inside the spinner it is stopping. A bare square changes only the glyph, and
|
|
51
|
+
// at a glance across a panel that reads as the same idle button; the motion is what says the
|
|
52
|
+
// answer is still arriving. `aria-hidden` on both — the accessible name already changed to
|
|
53
|
+
// "Stop", and a `role="status"` inside a named button announces the state twice.
|
|
54
|
+
icon: /* @__PURE__ */ f("span", { className: "relative flex size-4 items-center justify-center", children: [
|
|
55
|
+
/* @__PURE__ */ a(w, { "aria-hidden": !0, className: "absolute size-4" }),
|
|
56
|
+
/* @__PURE__ */ a(x, { "aria-hidden": !0, className: "size-2.5" })
|
|
57
|
+
] }),
|
|
58
|
+
label: "Stop"
|
|
59
|
+
},
|
|
60
|
+
error: { icon: /* @__PURE__ */ a(h, {}), label: "Retry" }
|
|
61
|
+
}, P = N({
|
|
62
|
+
// No transition of its own: `Button`'s base already carries `transition-all` and the
|
|
63
|
+
// `motion-reduce` escape, and a narrower `transition-colors` here would replace both.
|
|
64
|
+
base: ["ms-auto"],
|
|
65
|
+
variants: {
|
|
66
|
+
status: {
|
|
67
|
+
idle: "",
|
|
68
|
+
ready: "",
|
|
69
|
+
loading: "",
|
|
70
|
+
error: [
|
|
71
|
+
"bg-destructive/7 hover:bg-destructive/14",
|
|
72
|
+
"border-destructive/30",
|
|
73
|
+
"text-destructive-foreground",
|
|
74
|
+
"shadow-none"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
defaultVariants: {
|
|
79
|
+
status: "idle"
|
|
80
|
+
}
|
|
81
|
+
}), D = (o) => {
|
|
82
|
+
const {
|
|
83
|
+
status: e = "idle",
|
|
84
|
+
className: r,
|
|
85
|
+
children: s,
|
|
86
|
+
type: n = "submit",
|
|
87
|
+
variant: i = "default",
|
|
88
|
+
size: t = s === void 0 ? "icon-sm" : "sm",
|
|
89
|
+
slot: u,
|
|
90
|
+
...p
|
|
91
|
+
} = o, { icon: m, label: d } = T[e];
|
|
92
|
+
return /* @__PURE__ */ a(
|
|
93
|
+
S,
|
|
94
|
+
{
|
|
95
|
+
"aria-label": s === void 0 ? d : void 0,
|
|
96
|
+
className: l(P({ status: e }), r),
|
|
97
|
+
"data-status": e,
|
|
98
|
+
size: t,
|
|
99
|
+
type: n,
|
|
100
|
+
variant: i,
|
|
101
|
+
...p,
|
|
102
|
+
slot: u ?? "prompt-input-submit",
|
|
103
|
+
children: s ?? m
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
export {
|
|
108
|
+
j as PromptInput,
|
|
109
|
+
D as PromptInputSubmit,
|
|
110
|
+
q as PromptInputTextarea,
|
|
111
|
+
B as PromptInputToolbar
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=prompt-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-input.js","sources":["../src/prompt-input.tsx"],"sourcesContent":["\"use client\";\n\nimport { ark } from \"@ark-ui/react/factory\";\nimport { RefreshCcwIcon, SendIcon, SquareIcon } from \"lucide-react\";\nimport type React from \"react\";\nimport { tv } from \"tailwind-variants\";\nimport {\n cn,\n InputGroup,\n InputGroupAddon,\n InputGroupButton,\n InputGroupTextarea,\n Spinner,\n} from \"@kanzo-tech/ui\";\nimport type { AiStatus } from \"./use-ai.js\";\n\n/**\n * The composer: the library's `InputGroup` as a form, so Enter and the button are one path.\n *\n * `asChild` rather than a wrapping `<form>`: `InputGroup`'s recipe selects its own direct children\n * (`has-[>textarea]`, `has-[>[data-align=block-end]]`), and an element between the group and the\n * field silently unsets half of it.\n */\nexport const PromptInput = (props: React.ComponentProps<typeof ark.form>) => {\n const { className, slot, ...rest } = props;\n\n return (\n // A composer is a panel rather than a one-line control, so it takes the next radius up and a\n // shadow that puts it on top of the page instead of flush with it.\n <InputGroup asChild className={cn(\"rounded-xl shadow-xs/5\", className)}>\n <ark.form {...rest} data-slot={slot ?? \"prompt-input\"} />\n </InputGroup>\n );\n};\n\nexport const PromptInputTextarea = (props: React.ComponentProps<typeof InputGroupTextarea>) => {\n const { className, onKeyDown, rows = 1, slot, ...rest } = props;\n\n return (\n <InputGroupTextarea\n // `Textarea`'s base already sets `field-sizing-content`, so the field grows with what is\n // typed; the ceiling is what this adds. Without it a pasted document swallows the panel and\n // takes the transcript with it. Past 12rem the field scrolls and the composer stops moving.\n className={cn(\"max-h-48 min-h-16\", className)}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n // `isComposing` is the trap: an IME commits its candidate with Enter, and without this the\n // half-typed word is sent instead of finished.\n if (event.key !== \"Enter\" || event.shiftKey || event.nativeEvent.isComposing) return;\n event.preventDefault();\n event.currentTarget.form?.requestSubmit();\n }}\n rows={rows}\n {...rest}\n slot={slot ?? \"prompt-input-textarea\"}\n />\n );\n};\n\n/**\n * The strip of controls under the field — a model picker, an attachment, the ✨, the submit.\n *\n * `align=\"block-end\"` is not decoration: a popover trigger living here anchors to the composer, so\n * its surface opens over the text it writes. Hung off a label row above the field instead, it\n * opened against the panel's edge, far from the value. Nothing here may become `overflow-hidden`\n * for the same reason.\n *\n * It wraps rather than overflows, because everything in it is `shrink-0`: four controls in a narrow\n * panel pushed the submit past the composer's edge instead of taking a second line.\n */\nexport const PromptInputToolbar = (props: React.ComponentProps<typeof InputGroupAddon>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <InputGroupAddon\n align=\"block-end\"\n className={cn(\"min-w-0 flex-wrap gap-1\", className)}\n // The addon's own handler focuses an `input`, and a composer has a `textarea` — so the strip\n // carried `cursor-text` and gave no caret. Same guard: a press on a control is that control's.\n onClick={(event) => {\n if ((event.target as HTMLElement).closest(\"button, a, input, textarea\")) return;\n event.currentTarget.parentElement?.querySelector(\"textarea\")?.focus();\n }}\n {...rest}\n slot={slot ?? \"prompt-input-toolbar\"}\n />\n );\n};\n\nconst SEND = { icon: <SendIcon />, label: \"Send\" };\n\nconst SUBMIT: Record<AiStatus, { icon: React.ReactNode; label: string }> = {\n idle: SEND,\n // An answer that has arrived leaves the composer ready for the next question, so `ready` and\n // `idle` are one button. They are two states of the *stream*, not two states of this control.\n ready: SEND,\n loading: {\n // The stop block inside the spinner it is stopping. A bare square changes only the glyph, and\n // at a glance across a panel that reads as the same idle button; the motion is what says the\n // answer is still arriving. `aria-hidden` on both — the accessible name already changed to\n // \"Stop\", and a `role=\"status\"` inside a named button announces the state twice.\n icon: (\n <span className=\"relative flex size-4 items-center justify-center\">\n <Spinner aria-hidden className=\"absolute size-4\" />\n <SquareIcon aria-hidden className=\"size-2.5\" />\n </span>\n ),\n label: \"Stop\",\n },\n error: { icon: <RefreshCcwIcon />, label: \"Retry\" },\n};\n\n/**\n * One control in three looks, so the fill is what carries the state and the box never moves: same\n * slot, same size, same corner in all of them. `idle`, `ready` and `loading` share `Button`'s\n * `default` variant outright — the glyph is the whole difference — and `error` takes the wash\n * `Alert` uses for the same family, which is a button you can still press, not a disabled one.\n */\nconst promptInputSubmitVariants = tv({\n // No transition of its own: `Button`'s base already carries `transition-all` and the\n // `motion-reduce` escape, and a narrower `transition-colors` here would replace both.\n base: [\"ms-auto\"],\n variants: {\n status: {\n idle: \"\",\n ready: \"\",\n loading: \"\",\n error: [\n \"bg-destructive/7 hover:bg-destructive/14\",\n \"border-destructive/30\",\n \"text-destructive-foreground\",\n \"shadow-none\",\n ],\n },\n },\n defaultVariants: {\n status: \"idle\",\n },\n});\n\nexport interface PromptInputSubmitProps\n extends React.ComponentProps<typeof InputGroupButton> {\n status?: AiStatus;\n}\n\nexport const PromptInputSubmit = (props: PromptInputSubmitProps) => {\n const {\n status = \"idle\",\n className,\n children,\n type = \"submit\",\n variant = \"default\",\n size = children === undefined ? \"icon-sm\" : \"sm\",\n slot,\n ...rest\n } = props;\n const { icon, label } = SUBMIT[status];\n\n return (\n <InputGroupButton\n // Only where the button has no visible text of its own: a label over a legible one renames\n // it, which is worse than not having one.\n aria-label={children === undefined ? label : undefined}\n className={cn(promptInputSubmitVariants({ status }), className)}\n data-status={status}\n size={size}\n type={type}\n variant={variant}\n {...rest}\n slot={slot ?? \"prompt-input-submit\"}\n >\n {children ?? icon}\n </InputGroupButton>\n );\n};\n"],"names":[],"mappings":";;;;;;AAuBO;AACL;AAEA;AAAA;AAAA;AAAA;AAKE;AAEJ;AAGE;AAEA;AACE;AAAC;AAAA;AAI6C;;AAG1C;AAK0B;AAC5B;AACA;AACI;AACU;AAAA;AAGpB;AAcE;AAEA;AACE;AAAC;AAAA;AACO;AAC4C;;AAIhD;AAC8D;AAChE;AACI;AACU;AAAA;AAGpB;AAI2E;AACnE;AAAA;AAAA;AAGC;AACE;AAAA;AAAA;AAAA;AAAA;AAOH;AAAiD;AACJ;AAC/C;AAEK;AAAA;AAGX;AAQqC;AAAA;AAAA;AAGnB;AACN;AACA;AACA;AACC;AACE;AACF;AACL;AACA;AACA;AACA;AAAA;AACF;AACF;AAEe;AACP;AAEZ;AAQE;AAAM;AACK;AACT;AACA;AACO;AACG;AACkC;AAC5C;AACG;AAIL;AACE;AAAC;AAAA;AAG8C;AACiB;AACjD;AACb;AACA;AACA;AACI;AACU;AAED;AAAA;AAGnB;;;;;;;"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@kanzo-tech/ui';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export interface ReasoningProps extends React.ComponentProps<typeof Collapsible> {
|
|
4
|
+
/** The model is still thinking. */
|
|
5
|
+
streaming?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A model's thinking, folded away.
|
|
9
|
+
*
|
|
10
|
+
* The behaviour that *is* the component: it opens itself when the thinking starts and closes itself
|
|
11
|
+
* when it ends, so the reader watches it happen without being left with a wall of it afterwards.
|
|
12
|
+
* One exception, and it is the reason for the ref rather than a plain effect — **a reader's own
|
|
13
|
+
* toggle ends the automatic behaviour for good.** Anything else means a panel that reopens what
|
|
14
|
+
* somebody just closed, which is the version of this everyone has met.
|
|
15
|
+
*
|
|
16
|
+
* `group/reasoning` sits beside `Collapsible`'s own `group/collapsible`: the parts below key off
|
|
17
|
+
* `data-streaming`, which is this component's, not the machine's.
|
|
18
|
+
*/
|
|
19
|
+
export declare const Reasoning: (props: ReasoningProps) => React.JSX.Element;
|
|
20
|
+
/**
|
|
21
|
+
* The disclosure itself, sized to its own words rather than to the column: thinking is subordinate
|
|
22
|
+
* to the answer beside it, and a full-width bar with a chevron at the far end reads as a section
|
|
23
|
+
* heading — the one thing this is not.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ReasoningTrigger: (props: React.ComponentProps<typeof CollapsibleTrigger>) => React.JSX.Element;
|
|
26
|
+
/**
|
|
27
|
+
* The thinking, on a rail rather than in a panel — a quotation of the model to itself, and quieter
|
|
28
|
+
* than any message beside it. The rail takes the streaming tint so a wall of text that is still
|
|
29
|
+
* growing says so at the edge, where it does not move the words.
|
|
30
|
+
*/
|
|
31
|
+
export declare const ReasoningContent: (props: React.ComponentProps<typeof CollapsibleContent>) => React.JSX.Element;
|
|
32
|
+
//# sourceMappingURL=reasoning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoning.d.ts","sourceRoot":"","sources":["../src/reasoning.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAEL,WAAW,EACX,kBAAkB,EAElB,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AAexB,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,WAAW,CAAC;IAC9E,mCAAmC;IACnC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,cAAc,sBAmE9C,CAAC;AAkBF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,sBAwBtF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,sBAiBtF,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as r, jsxs as R } from "react/jsx-runtime";
|
|
3
|
+
import { ark as w } from "@ark-ui/react/factory";
|
|
4
|
+
import { BrainIcon as S } from "lucide-react";
|
|
5
|
+
import * as n from "react";
|
|
6
|
+
import { Collapsible as v, cn as f, CollapsibleContent as T, CollapsibleTrigger as I, Spinner as k, CollapsibleIndicator as z } from "@kanzo-tech/ui";
|
|
7
|
+
const p = n.createContext({
|
|
8
|
+
seconds: null,
|
|
9
|
+
streaming: !1
|
|
10
|
+
}), E = 1e3, M = 1e3, $ = (t) => {
|
|
11
|
+
const {
|
|
12
|
+
streaming: e = !1,
|
|
13
|
+
open: o,
|
|
14
|
+
defaultOpen: a = !1,
|
|
15
|
+
onOpenChange: i,
|
|
16
|
+
className: u,
|
|
17
|
+
children: l,
|
|
18
|
+
slot: x,
|
|
19
|
+
...h
|
|
20
|
+
} = t, [b, m] = n.useState(a), [N, C] = n.useState(null), g = n.useRef(!1), d = n.useRef(!1), s = n.useRef(null);
|
|
21
|
+
return n.useEffect(() => {
|
|
22
|
+
if (e) {
|
|
23
|
+
s.current ?? (s.current = Date.now());
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
s.current !== null && (C(Math.max(1, Math.ceil((Date.now() - s.current) / M))), s.current = null);
|
|
27
|
+
}, [e]), n.useEffect(() => {
|
|
28
|
+
if (e === d.current || (d.current = e, g.current)) return;
|
|
29
|
+
if (e) {
|
|
30
|
+
m(!0);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const c = setTimeout(() => m(!1), E);
|
|
34
|
+
return () => clearTimeout(c);
|
|
35
|
+
}, [e]), /* @__PURE__ */ r(p.Provider, { value: { seconds: N, streaming: e }, children: /* @__PURE__ */ r(
|
|
36
|
+
v,
|
|
37
|
+
{
|
|
38
|
+
className: f("group/reasoning w-full", u),
|
|
39
|
+
"data-streaming": e ? "" : void 0,
|
|
40
|
+
onOpenChange: (c) => {
|
|
41
|
+
g.current = !0, m(c.open), i == null || i(c);
|
|
42
|
+
},
|
|
43
|
+
open: o ?? b,
|
|
44
|
+
...h,
|
|
45
|
+
slot: x ?? "reasoning",
|
|
46
|
+
children: l
|
|
47
|
+
}
|
|
48
|
+
) });
|
|
49
|
+
}, O = (t, e) => t ? "Thinking…" : e === null ? "Reasoning" : `Thought for ${e} second${e === 1 ? "" : "s"}`, B = (t) => {
|
|
50
|
+
const { className: e, children: o, slot: a, ...i } = t, { seconds: u, streaming: l } = n.useContext(p);
|
|
51
|
+
return /* @__PURE__ */ R(
|
|
52
|
+
I,
|
|
53
|
+
{
|
|
54
|
+
className: f(
|
|
55
|
+
"-ms-1.5 inline-flex min-h-[24px] w-fit items-center gap-1.5 rounded-md px-1.5 py-1",
|
|
56
|
+
"text-muted-foreground text-xs",
|
|
57
|
+
"transition-colors hover:bg-accent hover:text-foreground motion-reduce:transition-none!",
|
|
58
|
+
"group-data-streaming/reasoning:text-info-foreground",
|
|
59
|
+
"[&_svg:not([class*='size-'])]:size-3.5",
|
|
60
|
+
e
|
|
61
|
+
),
|
|
62
|
+
...i,
|
|
63
|
+
slot: a ?? "reasoning-trigger",
|
|
64
|
+
children: [
|
|
65
|
+
l ? /* @__PURE__ */ r(k, { "aria-hidden": !0, className: "size-3.5" }) : /* @__PURE__ */ r(S, {}),
|
|
66
|
+
/* @__PURE__ */ r(w.span, { className: "text-start font-medium", children: o ?? O(l, u) }),
|
|
67
|
+
/* @__PURE__ */ r(z, { className: "opacity-64" })
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}, G = (t) => {
|
|
72
|
+
const { className: e, slot: o, ...a } = t;
|
|
73
|
+
return /* @__PURE__ */ r(
|
|
74
|
+
T,
|
|
75
|
+
{
|
|
76
|
+
className: f(
|
|
77
|
+
"mt-1 ms-2.5 whitespace-pre-wrap break-words",
|
|
78
|
+
"border-border border-s ps-3.5",
|
|
79
|
+
"text-muted-foreground text-sm leading-relaxed",
|
|
80
|
+
"group-data-streaming/reasoning:border-info",
|
|
81
|
+
"transition-colors motion-reduce:transition-none!",
|
|
82
|
+
e
|
|
83
|
+
),
|
|
84
|
+
...a,
|
|
85
|
+
slot: o ?? "reasoning-content"
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
export {
|
|
90
|
+
$ as Reasoning,
|
|
91
|
+
G as ReasoningContent,
|
|
92
|
+
B as ReasoningTrigger
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=reasoning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoning.js","sources":["../src/reasoning.tsx"],"sourcesContent":["\"use client\";\n\nimport { ark } from \"@ark-ui/react/factory\";\nimport { BrainIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport {\n cn,\n Collapsible,\n CollapsibleContent,\n CollapsibleIndicator,\n CollapsibleTrigger,\n Spinner,\n} from \"@kanzo-tech/ui\";\n\nconst Ctx = React.createContext<{ streaming: boolean; seconds: number | null }>({\n seconds: null,\n streaming: false,\n});\n\n/**\n * How long the thinking is left on screen after it stops. AI Elements' `AUTO_CLOSE_DELAY`, and the\n * reason is the last sentence: closing on the instant the stream ends takes the end of the thought\n * away from the reader who was reading it.\n */\nconst LINGER = 1000;\nconst MS_IN_S = 1000;\n\nexport interface ReasoningProps extends React.ComponentProps<typeof Collapsible> {\n /** The model is still thinking. */\n streaming?: boolean;\n}\n\n/**\n * A model's thinking, folded away.\n *\n * The behaviour that *is* the component: it opens itself when the thinking starts and closes itself\n * when it ends, so the reader watches it happen without being left with a wall of it afterwards.\n * One exception, and it is the reason for the ref rather than a plain effect — **a reader's own\n * toggle ends the automatic behaviour for good.** Anything else means a panel that reopens what\n * somebody just closed, which is the version of this everyone has met.\n *\n * `group/reasoning` sits beside `Collapsible`'s own `group/collapsible`: the parts below key off\n * `data-streaming`, which is this component's, not the machine's.\n */\nexport const Reasoning = (props: ReasoningProps) => {\n const {\n streaming = false,\n open,\n defaultOpen = false,\n onOpenChange,\n className,\n children,\n slot,\n ...rest\n } = props;\n const [selfOpen, setSelfOpen] = React.useState(defaultOpen);\n const [seconds, setSeconds] = React.useState<number | null>(null);\n const touched = React.useRef(false);\n const wasStreaming = React.useRef(false);\n const startedAt = React.useRef<number | null>(null);\n\n /**\n * **How long it thought, because the trigger says so once it is folded away.** Taken from AI\n * Elements, where the closed state reads *Thought for N seconds* rather than a static label — a\n * disclosure that says nothing about what is behind it gives the reader no reason to open it.\n *\n * Rounded up, and `null` until there is something to report: a block that never streamed has no\n * duration, which is not the same as a duration of zero.\n */\n React.useEffect(() => {\n if (streaming) {\n startedAt.current ??= Date.now();\n return;\n }\n if (startedAt.current === null) return;\n setSeconds(Math.max(1, Math.ceil((Date.now() - startedAt.current) / MS_IN_S)));\n startedAt.current = null;\n }, [streaming]);\n\n React.useEffect(() => {\n if (streaming === wasStreaming.current) return;\n wasStreaming.current = streaming;\n if (touched.current) return;\n if (streaming) {\n setSelfOpen(true);\n return;\n }\n // It lingers. Closing on the instant the last token lands snatches the end of the thought away\n // from somebody mid-sentence, and this is the one beat that makes the fold readable.\n const timer = setTimeout(() => setSelfOpen(false), LINGER);\n return () => clearTimeout(timer);\n }, [streaming]);\n\n return (\n <Ctx.Provider value={{ seconds, streaming }}>\n <Collapsible\n className={cn(\"group/reasoning w-full\", className)}\n data-streaming={streaming ? \"\" : undefined}\n onOpenChange={(details) => {\n touched.current = true;\n setSelfOpen(details.open);\n onOpenChange?.(details);\n }}\n open={open ?? selfOpen}\n {...rest}\n slot={slot ?? \"reasoning\"}\n >\n {children}\n </Collapsible>\n </Ctx.Provider>\n );\n};\n\n/**\n * What the trigger says, which is the only thing a folded-away thought tells anybody.\n *\n * **`Spinner` and not a shimmer, and that is a deliberate divergence.** AI Elements shimmers the\n * word *Thinking...* — a gradient swept across the glyphs with `background-clip: text`, on a\n * `motion/react` loop. It is a second vocabulary for *busy* beside the one this library already\n * has, and it costs an animation library to say what a `Spinner` says. Every other busy surface\n * here spins; the ✨ spins while a completion streams. One busy is a house principle, and AI\n * Elements is a source rather than a reference.\n */\nconst label = (streaming: boolean, seconds: number | null) => {\n if (streaming) return \"Thinking…\";\n if (seconds === null) return \"Reasoning\";\n return `Thought for ${seconds} second${seconds === 1 ? \"\" : \"s\"}`;\n};\n\n/**\n * The disclosure itself, sized to its own words rather than to the column: thinking is subordinate\n * to the answer beside it, and a full-width bar with a chevron at the far end reads as a section\n * heading — the one thing this is not.\n */\nexport const ReasoningTrigger = (props: React.ComponentProps<typeof CollapsibleTrigger>) => {\n const { className, children, slot, ...rest } = props;\n const { seconds, streaming } = React.useContext(Ctx);\n\n return (\n <CollapsibleTrigger\n className={cn(\n \"-ms-1.5 inline-flex min-h-[24px] w-fit items-center gap-1.5 rounded-md px-1.5 py-1\",\n \"text-muted-foreground text-xs\",\n \"transition-colors hover:bg-accent hover:text-foreground motion-reduce:transition-none!\",\n \"group-data-streaming/reasoning:text-info-foreground\",\n \"[&_svg:not([class*='size-'])]:size-3.5\",\n className\n )}\n {...rest}\n slot={slot ?? \"reasoning-trigger\"}\n >\n {streaming ? <Spinner aria-hidden className=\"size-3.5\" /> : <BrainIcon />}\n <ark.span className=\"text-start font-medium\">\n {children ?? label(streaming, seconds)}\n </ark.span>\n <CollapsibleIndicator className=\"opacity-64\" />\n </CollapsibleTrigger>\n );\n};\n\n/**\n * The thinking, on a rail rather than in a panel — a quotation of the model to itself, and quieter\n * than any message beside it. The rail takes the streaming tint so a wall of text that is still\n * growing says so at the edge, where it does not move the words.\n */\nexport const ReasoningContent = (props: React.ComponentProps<typeof CollapsibleContent>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <CollapsibleContent\n className={cn(\n \"mt-1 ms-2.5 whitespace-pre-wrap break-words\",\n \"border-border border-s ps-3.5\",\n \"text-muted-foreground text-sm leading-relaxed\",\n \"group-data-streaming/reasoning:border-info\",\n \"transition-colors motion-reduce:transition-none!\",\n className\n )}\n {...rest}\n slot={slot ?? \"reasoning-content\"}\n />\n );\n};\n"],"names":[],"mappings":";;;;;;AAcA;AAAgF;AACrE;AAEX;AA4BE;AAAM;AACQ;AACZ;AACc;AACd;AACA;AACA;AACA;AACG;AAgBL;AACE;AACE;AACA;AAAA;AAEF;AAEoB;AAMpB;AACA;AACE;AACA;AAAA;AAIF;AACA;AAA+B;AAK7B;AAAC;AAAA;AACkD;AAChB;AAE/B;AAEe;AACjB;AACc;AACV;AACU;AAEb;AAAA;AAIT;AAwBE;AAGA;AACE;AAAC;AAAA;AACY;AACT;AACA;AACA;AACA;AACA;AACA;AAAA;AAEE;AACU;AAEb;AAAsE;AAGvE;AAC6C;AAAA;AAAA;AAGnD;AAQE;AAEA;AACE;AAAC;AAAA;AACY;AACT;AACA;AACA;AACA;AACA;AACA;AAAA;AAEE;AACU;AAAA;AAGpB;;;;;;"}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-content:""}}}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:0}.inset-x-0{inset-inline:0}.bottom-4{bottom:calc(var(--spacing,.25rem) * 4)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.-ms-1\.5{margin-inline-start:calc(var(--spacing,.25rem) * -1.5)}.ms-0\.5{margin-inline-start:calc(var(--spacing,.25rem) * .5)}.ms-2\.5{margin-inline-start:calc(var(--spacing,.25rem) * 2.5)}.ms-auto{margin-inline-start:auto}.mt-1{margin-top:var(--spacing,.25rem)}.mt-1\.5{margin-top:calc(var(--spacing,.25rem) * 1.5)}.mb-1\.5{margin-bottom:calc(var(--spacing,.25rem) * 1.5)}.block{display:block}.flex{display:flex}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.field-sizing-content{field-sizing:content}.size-2\.5{width:calc(var(--spacing,.25rem) * 2.5);height:calc(var(--spacing,.25rem) * 2.5)}.size-3\.5{width:calc(var(--spacing,.25rem) * 3.5);height:calc(var(--spacing,.25rem) * 3.5)}.size-4{width:calc(var(--spacing,.25rem) * 4);height:calc(var(--spacing,.25rem) * 4)}.size-5{width:calc(var(--spacing,.25rem) * 5);height:calc(var(--spacing,.25rem) * 5)}.h-5{height:calc(var(--spacing,.25rem) * 5)}.h-\[1em\]{height:1em}.h-auto{height:auto}.max-h-48{max-height:calc(var(--spacing,.25rem) * 48)}.min-h-0{min-height:0}.min-h-8{min-height:calc(var(--spacing,.25rem) * 8)}.min-h-16{min-height:calc(var(--spacing,.25rem) * 16)}.min-h-\[24px\]{min-height:24px}.w-\(--size\){width:var(--size)}.w-\[0\.5ch\]{width:.5ch}.w-fit{width:fit-content}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl,48rem)}.max-w-md{max-width:var(--container-md,28rem)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.basis-full{flex-basis:100%}.animate-arrive{animation:.15s both arrive}.animate-pulse{animation:var(--animate-pulse,pulse 2s cubic-bezier(.4, 0, .6, 1) infinite)}.cursor-text{cursor:text}.list-none{list-style-type:none}.flex-col{flex-direction:column}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing,.25rem)}.gap-1\.5{gap:calc(var(--spacing,.25rem) * 1.5)}.gap-2{gap:calc(var(--spacing,.25rem) * 2)}.gap-2\.5{gap:calc(var(--spacing,.25rem) * 2.5)}.gap-3{gap:calc(var(--spacing,.25rem) * 3)}.gap-4{gap:calc(var(--spacing,.25rem) * 4)}.gap-6{gap:calc(var(--spacing,.25rem) * 6)}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:calc(var(--radius-field,.5rem) * 1)}.rounded-md{border-radius:calc(var(--radius-field,.5rem) * .75)}.rounded-xl{border-radius:calc(var(--radius-field,.5rem) * 1.5)}.border{border-style:var(--tw-border-style);border-width:1px}.border-s{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-border{border-color:var(--border)}.border-destructive\/30{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.border-destructive\/30{border-color:color-mix(in oklab, var(--destructive) 30%, transparent)}}.border-primary{border-color:var(--primary)}.bg-background{background-color:var(--background)}.bg-card{background-color:var(--card)}.bg-current{background-color:currentColor}.bg-destructive\/7{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.bg-destructive\/7{background-color:color-mix(in oklab, var(--destructive) 7%, transparent)}}.bg-field{background-color:var(--field,var(--muted))}.bg-muted,.bg-muted\/50{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/50{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.bg-primary\/17{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/17{background-color:color-mix(in oklab, var(--primary) 17%, transparent)}}.bg-transparent{background-color:#0000}.\[mask-image\:linear-gradient\(to_bottom\,\#000_calc\(100\%-0\.9lh\)\,transparent\)\]{-webkit-mask-image:linear-gradient(#000 calc(100% - .9lh),#0000);mask-image:linear-gradient(#000 calc(100% - .9lh),#0000)}.px-1\.5{padding-inline:calc(var(--spacing,.25rem) * 1.5)}.px-2{padding-inline:calc(var(--spacing,.25rem) * 2)}.px-3{padding-inline:calc(var(--spacing,.25rem) * 3)}.px-4{padding-inline:calc(var(--spacing,.25rem) * 4)}.py-1{padding-block:var(--spacing,.25rem)}.py-2{padding-block:calc(var(--spacing,.25rem) * 2)}.py-2\.5{padding-block:calc(var(--spacing,.25rem) * 2.5)}.py-3{padding-block:calc(var(--spacing,.25rem) * 3)}.py-6{padding-block:calc(var(--spacing,.25rem) * 6)}.py-12{padding-block:calc(var(--spacing,.25rem) * 12)}.ps-3\.5{padding-inline-start:calc(var(--spacing,.25rem) * 3.5)}.text-center{text-align:center}.text-start{text-align:start}.align-\[-0\.1em\]{vertical-align:-.1em}.font-mono{font-family:var(--font-mono,ui-monospace, SFMono-Regular, Menlo, monospace)}.text-sm{font-size:var(--text-sm,.875rem);line-height:var(--tw-leading,var(--text-sm--line-height,calc(1.25 / .875)))}.text-xs{font-size:var(--text-xs,.75rem);line-height:var(--tw-leading,var(--text-xs--line-height,calc(1 / .75)))}.leading-5{--tw-leading:calc(var(--spacing,.25rem) * 5);line-height:calc(var(--spacing,.25rem) * 5)}.leading-relaxed{--tw-leading:var(--leading-relaxed,1.625);line-height:var(--leading-relaxed,1.625)}.font-medium{--tw-font-weight:var(--font-weight-medium,500);font-weight:var(--font-weight-medium,500)}.tracking-wide{--tw-tracking:var(--tracking-wide,.025em);letter-spacing:var(--tracking-wide,.025em)}.text-balance{text-wrap:balance}.break-words{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-destructive-foreground{color:var(--destructive-foreground,var(--destructive))}.text-faint{color:var(--faint,var(--muted-foreground))}.text-info-foreground{color:var(--info-foreground,var(--info))}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-success-foreground{color:var(--success-foreground,var(--success))}.text-transparent{color:#0000}.uppercase{text-transform:uppercase}.opacity-0{opacity:0}.opacity-64{opacity:.64}.shadow-lg\/5{--tw-shadow-alpha:5%;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,oklab(0% 0 0/.05)), 0 4px 6px -4px var(--tw-shadow-color,oklab(0% 0 0/.05));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xs\/5{--tw-shadow-alpha:5%;--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,oklab(0% 0 0/.05));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-background{--tw-ring-color:var(--background)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function,cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function,cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function,cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function,cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}.group-focus-within\/message\:opacity-100:is(:where(.group\/message):focus-within *){opacity:1}@media (hover:hover){.group-hover\/message\:opacity-100:is(:where(.group\/message):hover *){opacity:1}}.group-data-streaming\/reasoning\:border-info:is(:where(.group\/reasoning)[data-streaming] *){border-color:var(--info)}.group-data-streaming\/reasoning\:text-info-foreground:is(:where(.group\/reasoning)[data-streaming] *){color:var(--info-foreground,var(--info))}.group-data-\[role\=system\]\/message\:hidden:is(:where(.group\/message)[data-role=system] *){display:none}.group-data-\[role\=user\]\/message\:max-w-\[85\%\]:is(:where(.group\/message)[data-role=user] *){max-width:85%}.group-data-\[role\=user\]\/message\:flex-none:is(:where(.group\/message)[data-role=user] *){flex:none}.group-data-\[role\=user\]\/message\:justify-end:is(:where(.group\/message)[data-role=user] *){justify-content:flex-end}.group-data-\[role\=user\]\/message\:rounded-xl:is(:where(.group\/message)[data-role=user] *){border-radius:calc(var(--radius-field,.5rem) * 1.5)}.group-data-\[role\=user\]\/message\:bg-secondary:is(:where(.group\/message)[data-role=user] *){background-color:var(--secondary)}.group-data-\[role\=user\]\/message\:px-3\.5:is(:where(.group\/message)[data-role=user] *){padding-inline:calc(var(--spacing,.25rem) * 3.5)}.group-data-\[role\=user\]\/message\:py-2\.5:is(:where(.group\/message)[data-role=user] *){padding-block:calc(var(--spacing,.25rem) * 2.5)}.group-data-\[role\=user\]\/message\:text-secondary-foreground:is(:where(.group\/message)[data-role=user] *){color:var(--secondary-foreground,var(--foreground))}.group-data-\[state\=failed\]\/task\:text-destructive-foreground:is(:where(.group\/task)[data-state=failed] *){color:var(--destructive-foreground,var(--destructive))}.group-data-\[state\=failed\]\/tool\:bg-destructive\/7:is(:where(.group\/tool)[data-state=failed] *){background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.group-data-\[state\=failed\]\/tool\:bg-destructive\/7:is(:where(.group\/tool)[data-state=failed] *){background-color:color-mix(in oklab, var(--destructive) 7%, transparent)}}.group-data-\[state\=failed\]\/tool\:text-destructive-foreground:is(:where(.group\/tool)[data-state=failed] *){color:var(--destructive-foreground,var(--destructive))}.group-data-\[state\=pending\]\/task\:text-muted-foreground:is(:where(.group\/task)[data-state=pending] *){color:var(--muted-foreground)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:start-2\.5:after{content:var(--tw-content);inset-inline-start:calc(var(--spacing,.25rem) * 2.5)}.after\:top-5:after{content:var(--tw-content);top:calc(var(--spacing,.25rem) * 5)}.after\:-bottom-2:after{content:var(--tw-content);bottom:calc(var(--spacing,.25rem) * -2)}.after\:w-px:after{content:var(--tw-content);width:1px}.after\:bg-border:after{content:var(--tw-content);background-color:var(--border)}.last\:after\:hidden:last-child:after{content:var(--tw-content);display:none}@media (hover:hover){.hover\:bg-accent:hover{background-color:var(--accent)}.hover\:bg-destructive\/14:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/14:hover{background-color:color-mix(in oklab, var(--destructive) 14%, transparent)}}.hover\:bg-primary\/30:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/30:hover{background-color:color-mix(in oklab, var(--primary) 30%, transparent)}}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-primary:hover{color:var(--primary)}}.data-\[state\=open\]\:text-primary[data-state=open]{color:var(--primary)}@media (prefers-reduced-motion:reduce){.motion-reduce\:animate-none{animation:none}.motion-reduce\:animate-none\!{animation:none!important}.motion-reduce\:transition-none\!{transition-property:none!important}}@media (pointer:coarse){.pointer-coarse\:opacity-100{opacity:1}}.\[\&_code\]\:font-mono code{font-family:var(--font-mono,ui-monospace, SFMono-Regular, Menlo, monospace)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3\.5 svg:not([class*=size-]){width:calc(var(--spacing,.25rem) * 3.5);height:calc(var(--spacing,.25rem) * 3.5)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing,.25rem) * 4);height:calc(var(--spacing,.25rem) * 4)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-6 svg:not([class*=size-]){width:calc(var(--spacing,.25rem) * 6);height:calc(var(--spacing,.25rem) * 6)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes pulse{50%{opacity:.5}}
|