@tanstack/ai-react 0.22.3 → 0.23.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/README.md +11 -15
- package/dist/esm/chat-ui/chat-input.d.ts +57 -0
- package/dist/esm/chat-ui/chat-input.js +124 -0
- package/dist/esm/chat-ui/chat-input.js.map +1 -0
- package/dist/esm/chat-ui/chat-message.d.ts +91 -0
- package/dist/esm/chat-ui/chat-message.js +157 -0
- package/dist/esm/chat-ui/chat-message.js.map +1 -0
- package/dist/esm/chat-ui/chat-messages.d.ts +33 -0
- package/dist/esm/chat-ui/chat-messages.js +44 -0
- package/dist/esm/chat-ui/chat-messages.js.map +1 -0
- package/dist/esm/chat-ui/chat.d.ts +53 -0
- package/dist/esm/chat-ui/chat.js +57 -0
- package/dist/esm/chat-ui/chat.js.map +1 -0
- package/dist/esm/chat-ui/create-chat-hook.d.ts +48 -0
- package/dist/esm/chat-ui/create-chat-hook.js +57 -0
- package/dist/esm/chat-ui/create-chat-hook.js.map +1 -0
- package/dist/esm/chat-ui/create-ui-contexts.d.ts +29 -0
- package/dist/esm/chat-ui/create-ui-contexts.js +36 -0
- package/dist/esm/chat-ui/create-ui-contexts.js.map +1 -0
- package/dist/esm/chat-ui/create-ui.d.ts +153 -0
- package/dist/esm/chat-ui/create-ui.js +294 -0
- package/dist/esm/chat-ui/create-ui.js.map +1 -0
- package/dist/esm/chat-ui/markdown-plugins.d.ts +15 -0
- package/dist/esm/chat-ui/markdown-plugins.js +28 -0
- package/dist/esm/chat-ui/markdown-plugins.js.map +1 -0
- package/dist/esm/chat-ui/text-part.d.ts +57 -0
- package/dist/esm/chat-ui/text-part.js +46 -0
- package/dist/esm/chat-ui/text-part.js.map +1 -0
- package/dist/esm/chat-ui/thinking-part.d.ts +41 -0
- package/dist/esm/chat-ui/thinking-part.js +73 -0
- package/dist/esm/chat-ui/thinking-part.js.map +1 -0
- package/dist/esm/chat-ui/tool-approval.d.ts +52 -0
- package/dist/esm/chat-ui/tool-approval.js +83 -0
- package/dist/esm/chat-ui/tool-approval.js.map +1 -0
- package/dist/esm/chat-ui/tool-result-content.d.ts +18 -0
- package/dist/esm/chat-ui/tool-result-content.js +19 -0
- package/dist/esm/chat-ui/tool-result-content.js.map +1 -0
- package/dist/esm/create-chat-hook.d.ts +35 -0
- package/dist/esm/create-chat-hook.js +37 -0
- package/dist/esm/create-chat-hook.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/ui.d.ts +10 -0
- package/dist/esm/ui.js +11 -0
- package/package.json +18 -4
- package/src/chat-ui/chat-input.tsx +177 -0
- package/src/chat-ui/chat-message.tsx +271 -0
- package/src/chat-ui/chat-messages.tsx +89 -0
- package/src/chat-ui/chat.tsx +98 -0
- package/src/chat-ui/create-chat-hook.tsx +103 -0
- package/src/chat-ui/create-ui-contexts.tsx +57 -0
- package/src/chat-ui/create-ui.tsx +761 -0
- package/src/chat-ui/markdown-plugins.ts +48 -0
- package/src/chat-ui/text-part.tsx +97 -0
- package/src/chat-ui/thinking-part.tsx +83 -0
- package/src/chat-ui/tool-approval.tsx +132 -0
- package/src/chat-ui/tool-result-content.ts +32 -0
- package/src/create-chat-hook.ts +67 -0
- package/src/index.ts +1 -0
- package/src/ui.ts +43 -0
package/README.md
CHANGED
|
@@ -61,12 +61,8 @@ framework bindings as your app needs them.
|
|
|
61
61
|
|
|
62
62
|
- [Overview](https://tanstack.com/ai/latest/docs/getting-started/overview) -
|
|
63
63
|
what TanStack AI is and how the packages fit together.
|
|
64
|
-
- [Quick Start
|
|
65
|
-
add streaming chat
|
|
66
|
-
- [Quick Start: Vue](https://tanstack.com/ai/latest/docs/getting-started/quick-start-vue) -
|
|
67
|
-
build with Vue composables.
|
|
68
|
-
- [Quick Start: Svelte](https://tanstack.com/ai/latest/docs/getting-started/quick-start-svelte) -
|
|
69
|
-
build with Svelte 5 runes.
|
|
64
|
+
- [Quick Start](https://tanstack.com/ai/latest/docs/getting-started/quick-start) -
|
|
65
|
+
add streaming chat. Pick your framework at the top of the page.
|
|
70
66
|
- [Quick Start: Server Only](https://tanstack.com/ai/latest/docs/getting-started/quick-start-server) -
|
|
71
67
|
use TanStack AI from a server endpoint, script, or backend service.
|
|
72
68
|
- [TanStack AI vs Vercel AI SDK](https://tanstack.com/ai/latest/docs/comparison/vercel-ai-sdk) -
|
|
@@ -226,15 +222,15 @@ on.
|
|
|
226
222
|
|
|
227
223
|
## Framework Packages
|
|
228
224
|
|
|
229
|
-
| Package
|
|
230
|
-
|
|
|
231
|
-
| [`@tanstack/ai-client`](https://tanstack.com/ai/latest/docs/api/ai-client)
|
|
232
|
-
| [`@tanstack/ai-react`](https://tanstack.com/ai/latest/docs/api/ai-react)
|
|
233
|
-
| [`@tanstack/ai-solid`](https://tanstack.com/ai/latest/docs/api/ai-solid)
|
|
234
|
-
| [`@tanstack/ai-vue`](https://tanstack.com/ai/latest/docs/api/ai-vue)
|
|
235
|
-
| [`@tanstack/ai-svelte`](https://tanstack.com/ai/latest/docs/api/ai-svelte)
|
|
236
|
-
| [`@tanstack/ai-preact`](https://tanstack.com/ai/latest/docs/api/ai-preact)
|
|
237
|
-
| `@tanstack/ai-react
|
|
225
|
+
| Package | What it provides |
|
|
226
|
+
| ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
227
|
+
| [`@tanstack/ai-client`](https://tanstack.com/ai/latest/docs/api/ai-client) | Headless chat, realtime, and generation clients |
|
|
228
|
+
| [`@tanstack/ai-react`](https://tanstack.com/ai/latest/docs/api/ai-react) | React hooks including `useChat`, `useRealtimeChat`, and generation hooks |
|
|
229
|
+
| [`@tanstack/ai-solid`](https://tanstack.com/ai/latest/docs/api/ai-solid) | Solid hooks for chat and generations |
|
|
230
|
+
| [`@tanstack/ai-vue`](https://tanstack.com/ai/latest/docs/api/ai-vue) | Vue composables for chat and generations |
|
|
231
|
+
| [`@tanstack/ai-svelte`](https://tanstack.com/ai/latest/docs/api/ai-svelte) | Svelte 5 factories for chat and generations |
|
|
232
|
+
| [`@tanstack/ai-preact`](https://tanstack.com/ai/latest/docs/api/ai-preact) | Preact hooks for chat |
|
|
233
|
+
| `@tanstack/ai-react/ui`, `@tanstack/ai-solid/ui`, `@tanstack/ai-vue/ui`, `@tanstack/ai-svelte/ui` | Headless UI components for chat interfaces. `@tanstack/ai-react-ui`, `@tanstack/ai-solid-ui`, and `@tanstack/ai-vue-ui` are deprecated. |
|
|
238
234
|
|
|
239
235
|
## Advanced Docs
|
|
240
236
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ReactNode, RefObject } from 'react';
|
|
2
|
+
export interface ChatInputRenderProps {
|
|
3
|
+
/** Current input value */
|
|
4
|
+
value: string;
|
|
5
|
+
/** Set input value */
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
/** Submit the message */
|
|
8
|
+
onSubmit: () => void;
|
|
9
|
+
/** Is the chat currently loading */
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
/** Is input disabled */
|
|
12
|
+
disabled: boolean;
|
|
13
|
+
/** Ref to the input element */
|
|
14
|
+
inputRef: RefObject<HTMLInputElement | HTMLTextAreaElement | null>;
|
|
15
|
+
}
|
|
16
|
+
export interface ChatInputProps {
|
|
17
|
+
/** Render prop for full control */
|
|
18
|
+
children?: (props: ChatInputRenderProps) => ReactNode;
|
|
19
|
+
/** CSS class name */
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Placeholder text */
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
/** Disable input */
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
/** Submit on Enter (Shift+Enter for new line) */
|
|
26
|
+
submitOnEnter?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated Use `createChatUI()` and an application-owned input component.
|
|
30
|
+
* Deprecated in 0.9.0. Removed in 1.0.0.
|
|
31
|
+
*
|
|
32
|
+
* Chat input component - handles message input and submission
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - Auto-growing textarea
|
|
36
|
+
* - Submit on Enter (Shift+Enter for new line)
|
|
37
|
+
* - Loading state management
|
|
38
|
+
* - Full render prop support for custom UIs
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <Chat.Input placeholder="Type your message..." />
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Custom UI with render prop
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <Chat.Input>
|
|
48
|
+
* {({ value, onChange, onSubmit, isLoading }) => (
|
|
49
|
+
* <div>
|
|
50
|
+
* <textarea value={value} onChange={(e) => onChange(e.target.value)} />
|
|
51
|
+
* <button onClick={onSubmit} disabled={isLoading}>Send</button>
|
|
52
|
+
* </div>
|
|
53
|
+
* )}
|
|
54
|
+
* </Chat.Input>
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function ChatInput({ children, className, placeholder, disabled: disabledProp, submitOnEnter, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useChatContext } from "./chat.js";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/chat-ui/chat-input.tsx
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Use `createChatUI()` and an application-owned input component.
|
|
7
|
+
* Deprecated in 0.9.0. Removed in 1.0.0.
|
|
8
|
+
*
|
|
9
|
+
* Chat input component - handles message input and submission
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Auto-growing textarea
|
|
13
|
+
* - Submit on Enter (Shift+Enter for new line)
|
|
14
|
+
* - Loading state management
|
|
15
|
+
* - Full render prop support for custom UIs
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <Chat.Input placeholder="Type your message..." />
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example Custom UI with render prop
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <Chat.Input>
|
|
25
|
+
* {({ value, onChange, onSubmit, isLoading }) => (
|
|
26
|
+
* <div>
|
|
27
|
+
* <textarea value={value} onChange={(e) => onChange(e.target.value)} />
|
|
28
|
+
* <button onClick={onSubmit} disabled={isLoading}>Send</button>
|
|
29
|
+
* </div>
|
|
30
|
+
* )}
|
|
31
|
+
* </Chat.Input>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function ChatInput({ children, className, placeholder = "Type a message...", disabled: disabledProp, submitOnEnter = true }) {
|
|
35
|
+
const { sendMessage, isLoading } = useChatContext();
|
|
36
|
+
const [value, setValue] = useState("");
|
|
37
|
+
const inputRef = useRef(null);
|
|
38
|
+
const disabled = disabledProp || isLoading;
|
|
39
|
+
const handleSubmit = () => {
|
|
40
|
+
if (!value.trim() || disabled) return;
|
|
41
|
+
sendMessage(value);
|
|
42
|
+
setValue("");
|
|
43
|
+
};
|
|
44
|
+
const renderProps = {
|
|
45
|
+
value,
|
|
46
|
+
onChange: setValue,
|
|
47
|
+
onSubmit: handleSubmit,
|
|
48
|
+
isLoading,
|
|
49
|
+
disabled,
|
|
50
|
+
inputRef
|
|
51
|
+
};
|
|
52
|
+
if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(renderProps) });
|
|
53
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
54
|
+
className,
|
|
55
|
+
"data-chat-input": true,
|
|
56
|
+
style: {
|
|
57
|
+
display: "flex",
|
|
58
|
+
gap: "0.75rem",
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
width: "100%"
|
|
61
|
+
},
|
|
62
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
63
|
+
ref: inputRef,
|
|
64
|
+
type: "text",
|
|
65
|
+
value,
|
|
66
|
+
onChange: (e) => setValue(e.target.value),
|
|
67
|
+
onKeyDown: (e) => {
|
|
68
|
+
if (submitOnEnter && e.key === "Enter") {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
handleSubmit();
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
placeholder,
|
|
74
|
+
disabled,
|
|
75
|
+
"data-chat-textarea": true,
|
|
76
|
+
style: {
|
|
77
|
+
flex: 1,
|
|
78
|
+
padding: "0.75rem 1rem",
|
|
79
|
+
fontSize: "0.875rem",
|
|
80
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
81
|
+
borderRadius: "0.75rem",
|
|
82
|
+
backgroundColor: "rgba(31, 41, 55, 0.5)",
|
|
83
|
+
color: "white",
|
|
84
|
+
outline: "none",
|
|
85
|
+
transition: "all 0.2s"
|
|
86
|
+
},
|
|
87
|
+
onFocus: (e) => {
|
|
88
|
+
e.target.style.borderColor = "rgba(249, 115, 22, 0.4)";
|
|
89
|
+
e.target.style.boxShadow = "0 0 0 2px rgba(249, 115, 22, 0.2)";
|
|
90
|
+
},
|
|
91
|
+
onBlur: (e) => {
|
|
92
|
+
e.target.style.borderColor = "rgba(255, 255, 255, 0.1)";
|
|
93
|
+
e.target.style.boxShadow = "none";
|
|
94
|
+
}
|
|
95
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
96
|
+
onClick: handleSubmit,
|
|
97
|
+
disabled: disabled || !value.trim(),
|
|
98
|
+
"data-chat-submit": true,
|
|
99
|
+
style: {
|
|
100
|
+
padding: "0.75rem 1.5rem",
|
|
101
|
+
fontSize: "0.875rem",
|
|
102
|
+
fontWeight: 500,
|
|
103
|
+
color: "white",
|
|
104
|
+
backgroundColor: disabled || !value.trim() ? "rgba(107, 114, 128, 0.5)" : "rgb(249, 115, 22)",
|
|
105
|
+
border: "none",
|
|
106
|
+
borderRadius: "0.75rem",
|
|
107
|
+
cursor: disabled || !value.trim() ? "not-allowed" : "pointer",
|
|
108
|
+
transition: "all 0.2s",
|
|
109
|
+
whiteSpace: "nowrap"
|
|
110
|
+
},
|
|
111
|
+
onMouseEnter: (e) => {
|
|
112
|
+
if (!disabled && value.trim()) e.target.style.backgroundColor = "rgb(234, 88, 12)";
|
|
113
|
+
},
|
|
114
|
+
onMouseLeave: (e) => {
|
|
115
|
+
if (!disabled && value.trim()) e.target.style.backgroundColor = "rgb(249, 115, 22)";
|
|
116
|
+
},
|
|
117
|
+
children: isLoading ? "Sending..." : "Send"
|
|
118
|
+
})]
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { ChatInput };
|
|
123
|
+
|
|
124
|
+
//# sourceMappingURL=chat-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-input.js","names":[],"sources":["../../../src/chat-ui/chat-input.tsx"],"sourcesContent":["import { useRef, useState } from 'react'\nimport { useChatContext } from './chat'\nimport type { ReactNode, RefObject } from 'react'\n\nexport interface ChatInputRenderProps {\n /** Current input value */\n value: string\n /** Set input value */\n onChange: (value: string) => void\n /** Submit the message */\n onSubmit: () => void\n /** Is the chat currently loading */\n isLoading: boolean\n /** Is input disabled */\n disabled: boolean\n /** Ref to the input element */\n inputRef: RefObject<HTMLInputElement | HTMLTextAreaElement | null>\n}\n\nexport interface ChatInputProps {\n /** Render prop for full control */\n children?: (props: ChatInputRenderProps) => ReactNode\n /** CSS class name */\n className?: string\n /** Placeholder text */\n placeholder?: string\n /** Disable input */\n disabled?: boolean\n /** Submit on Enter (Shift+Enter for new line) */\n submitOnEnter?: boolean\n}\n\n/**\n * @deprecated Use `createChatUI()` and an application-owned input component.\n * Deprecated in 0.9.0. Removed in 1.0.0.\n *\n * Chat input component - handles message input and submission\n *\n * Features:\n * - Auto-growing textarea\n * - Submit on Enter (Shift+Enter for new line)\n * - Loading state management\n * - Full render prop support for custom UIs\n *\n * @example\n * ```tsx\n * <Chat.Input placeholder=\"Type your message...\" />\n * ```\n *\n * @example Custom UI with render prop\n * ```tsx\n * <Chat.Input>\n * {({ value, onChange, onSubmit, isLoading }) => (\n * <div>\n * <textarea value={value} onChange={(e) => onChange(e.target.value)} />\n * <button onClick={onSubmit} disabled={isLoading}>Send</button>\n * </div>\n * )}\n * </Chat.Input>\n * ```\n */\nexport function ChatInput({\n children,\n className,\n placeholder = 'Type a message...',\n disabled: disabledProp,\n submitOnEnter = true,\n}: ChatInputProps) {\n const { sendMessage, isLoading } = useChatContext()\n const [value, setValue] = useState('')\n const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null)\n\n const disabled = disabledProp || isLoading\n\n const handleSubmit = () => {\n if (!value.trim() || disabled) return\n void sendMessage(value)\n setValue('')\n }\n\n const renderProps: ChatInputRenderProps = {\n value,\n onChange: setValue,\n onSubmit: handleSubmit,\n isLoading,\n disabled,\n inputRef,\n }\n\n // Render prop pattern\n if (children) {\n return <>{children(renderProps)}</>\n }\n\n // Default implementation\n return (\n <div\n className={className}\n data-chat-input\n style={{\n display: 'flex',\n gap: '0.75rem',\n alignItems: 'center',\n width: '100%',\n }}\n >\n <input\n ref={inputRef as React.RefObject<HTMLInputElement>}\n type=\"text\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (submitOnEnter && e.key === 'Enter') {\n e.preventDefault()\n handleSubmit()\n }\n }}\n placeholder={placeholder}\n disabled={disabled}\n data-chat-textarea\n style={{\n flex: 1,\n padding: '0.75rem 1rem',\n fontSize: '0.875rem',\n border: '1px solid rgba(255, 255, 255, 0.1)',\n borderRadius: '0.75rem',\n backgroundColor: 'rgba(31, 41, 55, 0.5)',\n color: 'white',\n outline: 'none',\n transition: 'all 0.2s',\n }}\n onFocus={(e) => {\n e.target.style.borderColor = 'rgba(249, 115, 22, 0.4)'\n e.target.style.boxShadow = '0 0 0 2px rgba(249, 115, 22, 0.2)'\n }}\n onBlur={(e) => {\n e.target.style.borderColor = 'rgba(255, 255, 255, 0.1)'\n e.target.style.boxShadow = 'none'\n }}\n />\n <button\n onClick={handleSubmit}\n disabled={disabled || !value.trim()}\n data-chat-submit\n style={{\n padding: '0.75rem 1.5rem',\n fontSize: '0.875rem',\n fontWeight: 500,\n color: 'white',\n backgroundColor:\n disabled || !value.trim()\n ? 'rgba(107, 114, 128, 0.5)'\n : 'rgb(249, 115, 22)',\n border: 'none',\n borderRadius: '0.75rem',\n cursor: disabled || !value.trim() ? 'not-allowed' : 'pointer',\n transition: 'all 0.2s',\n whiteSpace: 'nowrap',\n }}\n onMouseEnter={(e) => {\n if (!disabled && value.trim()) {\n ;(e.target as HTMLButtonElement).style.backgroundColor =\n 'rgb(234, 88, 12)'\n }\n }}\n onMouseLeave={(e) => {\n if (!disabled && value.trim()) {\n ;(e.target as HTMLButtonElement).style.backgroundColor =\n 'rgb(249, 115, 22)'\n }\n }}\n >\n {isLoading ? 'Sending...' : 'Send'}\n </button>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,SAAgB,UAAU,EACxB,UACA,WACA,cAAc,qBACd,UAAU,cACV,gBAAgB,QACC;CACjB,MAAM,EAAE,aAAa,cAAc,eAAe;CAClD,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CACrC,MAAM,WAAW,OAA+C,IAAI;CAEpE,MAAM,WAAW,gBAAgB;CAEjC,MAAM,qBAAqB;EACzB,IAAI,CAAC,MAAM,KAAK,KAAK,UAAU;EAC/B,YAAiB,KAAK;EACtB,SAAS,EAAE;CACb;CAEA,MAAM,cAAoC;EACxC;EACA,UAAU;EACV,UAAU;EACV;EACA;EACA;CACF;CAGA,IAAI,UACF,OAAO,oBAAA,UAAA,EAAA,UAAG,SAAS,WAAW,EAAI,CAAA;CAIpC,OACE,qBAAC,OAAD;EACa;EACX,mBAAA;EACA,OAAO;GACL,SAAS;GACT,KAAK;GACL,YAAY;GACZ,OAAO;EACT;EARF,UAAA,CAUE,oBAAC,SAAD;GACE,KAAK;GACL,MAAK;GACE;GACP,WAAW,MAAM,SAAS,EAAE,OAAO,KAAK;GACxC,YAAY,MAAM;IAChB,IAAI,iBAAiB,EAAE,QAAQ,SAAS;KACtC,EAAE,eAAe;KACjB,aAAa;IACf;GACF;GACa;GACH;GACV,sBAAA;GACA,OAAO;IACL,MAAM;IACN,SAAS;IACT,UAAU;IACV,QAAQ;IACR,cAAc;IACd,iBAAiB;IACjB,OAAO;IACP,SAAS;IACT,YAAY;GACd;GACA,UAAU,MAAM;IACd,EAAE,OAAO,MAAM,cAAc;IAC7B,EAAE,OAAO,MAAM,YAAY;GAC7B;GACA,SAAS,MAAM;IACb,EAAE,OAAO,MAAM,cAAc;IAC7B,EAAE,OAAO,MAAM,YAAY;GAC7B;EACD,CAAA,GACD,oBAAC,UAAD;GACE,SAAS;GACT,UAAU,YAAY,CAAC,MAAM,KAAK;GAClC,oBAAA;GACA,OAAO;IACL,SAAS;IACT,UAAU;IACV,YAAY;IACZ,OAAO;IACP,iBACE,YAAY,CAAC,MAAM,KAAK,IACpB,6BACA;IACN,QAAQ;IACR,cAAc;IACd,QAAQ,YAAY,CAAC,MAAM,KAAK,IAAI,gBAAgB;IACpD,YAAY;IACZ,YAAY;GACd;GACA,eAAe,MAAM;IACnB,IAAI,CAAC,YAAY,MAAM,KAAK,GACzB,EAAG,OAA6B,MAAM,kBACrC;GAEN;GACA,eAAe,MAAM;IACnB,IAAI,CAAC,YAAY,MAAM,KAAK,GACzB,EAAG,OAA6B,MAAM,kBACrC;GAEN;GAEC,UAAA,YAAY,eAAe;EACtB,CAAA,CACL;;AAET"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { UIMessage } from '../types.js';
|
|
3
|
+
export interface ToolCallRenderProps {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
arguments: string;
|
|
7
|
+
state: string;
|
|
8
|
+
approval?: any;
|
|
9
|
+
output?: any;
|
|
10
|
+
}
|
|
11
|
+
export interface ChatMessageProps {
|
|
12
|
+
/** The message to render */
|
|
13
|
+
message: UIMessage;
|
|
14
|
+
/** Base CSS class name */
|
|
15
|
+
className?: string;
|
|
16
|
+
/** Additional className for user messages */
|
|
17
|
+
userClassName?: string;
|
|
18
|
+
/** Additional className for assistant messages */
|
|
19
|
+
assistantClassName?: string;
|
|
20
|
+
/** Custom renderer for text parts */
|
|
21
|
+
textPartRenderer?: (props: {
|
|
22
|
+
content: string;
|
|
23
|
+
}) => ReactNode;
|
|
24
|
+
/** Custom renderer for thinking parts */
|
|
25
|
+
thinkingPartRenderer?: (props: {
|
|
26
|
+
content: string;
|
|
27
|
+
isComplete?: boolean;
|
|
28
|
+
}) => ReactNode;
|
|
29
|
+
/** Named tool renderers - use the tool name as the key */
|
|
30
|
+
toolsRenderer?: Record<string, (props: ToolCallRenderProps) => ReactNode>;
|
|
31
|
+
/** Default tool renderer when tool name not found in toolsRenderer */
|
|
32
|
+
defaultToolRenderer?: (props: ToolCallRenderProps) => ReactNode;
|
|
33
|
+
/** Custom renderer for tool result parts */
|
|
34
|
+
toolResultRenderer?: (props: {
|
|
35
|
+
toolCallId: string;
|
|
36
|
+
content: string;
|
|
37
|
+
state: string;
|
|
38
|
+
}) => ReactNode;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Use `createChatUI()` Message instead. Deprecated in 0.9.0. Removed in 1.0.0.
|
|
42
|
+
*
|
|
43
|
+
* Message component - renders a single message with all its parts
|
|
44
|
+
*
|
|
45
|
+
* This component natively understands TanStack AI's parts-based message format:
|
|
46
|
+
* - thinking parts: rendered as collapsible thinking/reasoning sections (auto-collapses when complete)
|
|
47
|
+
* - text parts: rendered as content
|
|
48
|
+
* - tool-call parts: rendered with state, approvals, etc.
|
|
49
|
+
* - tool-result parts: rendered with results
|
|
50
|
+
*
|
|
51
|
+
* @example Basic usage
|
|
52
|
+
* ```tsx
|
|
53
|
+
* <Chat.Message message={message} />
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example With role-based styling
|
|
57
|
+
* ```tsx
|
|
58
|
+
* <ChatMessage
|
|
59
|
+
* message={message}
|
|
60
|
+
* className="flex"
|
|
61
|
+
* userClassName="justify-end"
|
|
62
|
+
* assistantClassName="justify-start"
|
|
63
|
+
* />
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @example With custom thinking renderer
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <ChatMessage
|
|
69
|
+
* message={message}
|
|
70
|
+
* thinkingPartRenderer={({ content, isComplete }) => (
|
|
71
|
+
* <details open={!isComplete}>
|
|
72
|
+
* <summary>💭 Thinking...</summary>
|
|
73
|
+
* <pre>{content}</pre>
|
|
74
|
+
* </details>
|
|
75
|
+
* )}
|
|
76
|
+
* />
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example With named tool renderers
|
|
80
|
+
* ```tsx
|
|
81
|
+
* <ChatMessage
|
|
82
|
+
* message={message}
|
|
83
|
+
* toolsRenderer={{
|
|
84
|
+
* recommendGuitar: ({ id, arguments: args }) => <GuitarCard {...JSON.parse(args)} />,
|
|
85
|
+
* weatherLookup: ({ id, arguments: args }) => <WeatherWidget {...JSON.parse(args)} />,
|
|
86
|
+
* }}
|
|
87
|
+
* defaultToolRenderer={() => null}
|
|
88
|
+
* />
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function ChatMessage({ message, className, userClassName, assistantClassName, textPartRenderer, thinkingPartRenderer, toolsRenderer, defaultToolRenderer, toolResultRenderer, }: ChatMessageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { ThinkingPart } from "./thinking-part.js";
|
|
2
|
+
import { toolResultContentToString } from "./tool-result-content.js";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/chat-ui/chat-message.tsx
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Use `createChatUI()` Message instead. Deprecated in 0.9.0. Removed in 1.0.0.
|
|
7
|
+
*
|
|
8
|
+
* Message component - renders a single message with all its parts
|
|
9
|
+
*
|
|
10
|
+
* This component natively understands TanStack AI's parts-based message format:
|
|
11
|
+
* - thinking parts: rendered as collapsible thinking/reasoning sections (auto-collapses when complete)
|
|
12
|
+
* - text parts: rendered as content
|
|
13
|
+
* - tool-call parts: rendered with state, approvals, etc.
|
|
14
|
+
* - tool-result parts: rendered with results
|
|
15
|
+
*
|
|
16
|
+
* @example Basic usage
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <Chat.Message message={message} />
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example With role-based styling
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <ChatMessage
|
|
24
|
+
* message={message}
|
|
25
|
+
* className="flex"
|
|
26
|
+
* userClassName="justify-end"
|
|
27
|
+
* assistantClassName="justify-start"
|
|
28
|
+
* />
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example With custom thinking renderer
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <ChatMessage
|
|
34
|
+
* message={message}
|
|
35
|
+
* thinkingPartRenderer={({ content, isComplete }) => (
|
|
36
|
+
* <details open={!isComplete}>
|
|
37
|
+
* <summary>💭 Thinking...</summary>
|
|
38
|
+
* <pre>{content}</pre>
|
|
39
|
+
* </details>
|
|
40
|
+
* )}
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example With named tool renderers
|
|
45
|
+
* ```tsx
|
|
46
|
+
* <ChatMessage
|
|
47
|
+
* message={message}
|
|
48
|
+
* toolsRenderer={{
|
|
49
|
+
* recommendGuitar: ({ id, arguments: args }) => <GuitarCard {...JSON.parse(args)} />,
|
|
50
|
+
* weatherLookup: ({ id, arguments: args }) => <WeatherWidget {...JSON.parse(args)} />,
|
|
51
|
+
* }}
|
|
52
|
+
* defaultToolRenderer={() => null}
|
|
53
|
+
* />
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function ChatMessage({ message, className = "", userClassName = "", assistantClassName = "", textPartRenderer, thinkingPartRenderer, toolsRenderer, defaultToolRenderer, toolResultRenderer }) {
|
|
57
|
+
const combinedClassName = [className, message.role === "user" ? userClassName : assistantClassName].filter(Boolean).join(" ");
|
|
58
|
+
return /* @__PURE__ */ jsx("div", {
|
|
59
|
+
className: combinedClassName || void 0,
|
|
60
|
+
"data-message-id": message.id,
|
|
61
|
+
"data-message-role": message.role,
|
|
62
|
+
"data-message-created": message.createdAt?.toISOString(),
|
|
63
|
+
children: message.parts.map((part, index) => {
|
|
64
|
+
const isThinkingComplete = part.type === "thinking" && message.parts.slice(index + 1).some((p) => p.type === "text");
|
|
65
|
+
return /* @__PURE__ */ jsx(MessagePart, {
|
|
66
|
+
part,
|
|
67
|
+
isThinkingComplete,
|
|
68
|
+
textPartRenderer,
|
|
69
|
+
thinkingPartRenderer,
|
|
70
|
+
toolsRenderer,
|
|
71
|
+
defaultToolRenderer,
|
|
72
|
+
toolResultRenderer
|
|
73
|
+
}, `${message.id}-part-${index}`);
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function MessagePart({ part, isThinkingComplete, textPartRenderer, thinkingPartRenderer, toolsRenderer, defaultToolRenderer, toolResultRenderer }) {
|
|
78
|
+
if (part.type === "text") {
|
|
79
|
+
if (textPartRenderer) return /* @__PURE__ */ jsx(Fragment, { children: textPartRenderer({ content: part.content }) });
|
|
80
|
+
return /* @__PURE__ */ jsx("div", {
|
|
81
|
+
"data-part-type": "text",
|
|
82
|
+
"data-part-content": true,
|
|
83
|
+
children: part.content
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (part.type === "thinking") {
|
|
87
|
+
if (thinkingPartRenderer) return /* @__PURE__ */ jsx(Fragment, { children: thinkingPartRenderer({
|
|
88
|
+
content: part.content,
|
|
89
|
+
isComplete: isThinkingComplete
|
|
90
|
+
}) });
|
|
91
|
+
return /* @__PURE__ */ jsx(ThinkingPart, {
|
|
92
|
+
content: part.content,
|
|
93
|
+
isComplete: isThinkingComplete
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (part.type === "tool-call") {
|
|
97
|
+
const toolProps = {
|
|
98
|
+
id: part.id,
|
|
99
|
+
name: part.name,
|
|
100
|
+
arguments: part.arguments,
|
|
101
|
+
state: part.state,
|
|
102
|
+
approval: part.approval,
|
|
103
|
+
output: part.output
|
|
104
|
+
};
|
|
105
|
+
if (toolsRenderer?.[part.name]) return /* @__PURE__ */ jsx(Fragment, { children: toolsRenderer[part.name]?.(toolProps) });
|
|
106
|
+
if (defaultToolRenderer) return /* @__PURE__ */ jsx(Fragment, { children: defaultToolRenderer(toolProps) });
|
|
107
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
108
|
+
"data-part-type": "tool-call",
|
|
109
|
+
"data-tool-name": part.name,
|
|
110
|
+
"data-tool-state": part.state,
|
|
111
|
+
"data-tool-id": part.id,
|
|
112
|
+
children: [
|
|
113
|
+
/* @__PURE__ */ jsxs("div", {
|
|
114
|
+
"data-tool-header": true,
|
|
115
|
+
children: [/* @__PURE__ */ jsx("strong", { children: part.name }), /* @__PURE__ */ jsx("span", {
|
|
116
|
+
"data-tool-state-badge": true,
|
|
117
|
+
children: part.state
|
|
118
|
+
})]
|
|
119
|
+
}),
|
|
120
|
+
part.arguments && /* @__PURE__ */ jsx("div", {
|
|
121
|
+
"data-tool-arguments": true,
|
|
122
|
+
children: /* @__PURE__ */ jsx("pre", { children: part.arguments })
|
|
123
|
+
}),
|
|
124
|
+
part.approval && /* @__PURE__ */ jsx("div", {
|
|
125
|
+
"data-tool-approval": true,
|
|
126
|
+
children: part.approval.approved !== void 0 ? part.approval.approved ? "✓ Approved" : "✗ Denied" : "⏳ Awaiting approval..."
|
|
127
|
+
}),
|
|
128
|
+
part.output && /* @__PURE__ */ jsx("div", {
|
|
129
|
+
"data-tool-output": true,
|
|
130
|
+
children: /* @__PURE__ */ jsx("pre", { children: JSON.stringify(part.output, null, 2) })
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (part.type === "tool-result") {
|
|
136
|
+
const toolResultContent = toolResultContentToString(part.content);
|
|
137
|
+
if (toolResultRenderer) return /* @__PURE__ */ jsx(Fragment, { children: toolResultRenderer({
|
|
138
|
+
toolCallId: part.toolCallId,
|
|
139
|
+
content: toolResultContent,
|
|
140
|
+
state: part.state
|
|
141
|
+
}) });
|
|
142
|
+
return /* @__PURE__ */ jsx("div", {
|
|
143
|
+
"data-part-type": "tool-result",
|
|
144
|
+
"data-tool-call-id": part.toolCallId,
|
|
145
|
+
"data-tool-result-state": part.state,
|
|
146
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
147
|
+
"data-tool-result-content": true,
|
|
148
|
+
children: toolResultContent
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
export { ChatMessage };
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=chat-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-message.js","names":[],"sources":["../../../src/chat-ui/chat-message.tsx"],"sourcesContent":["import { ThinkingPart } from './thinking-part'\nimport { toolResultContentToString } from './tool-result-content'\nimport type { ReactNode } from 'react'\nimport type { UIMessage } from '../types'\n\nexport interface ToolCallRenderProps {\n id: string\n name: string\n arguments: string\n state: string\n approval?: any\n output?: any\n}\n\nexport interface ChatMessageProps {\n /** The message to render */\n message: UIMessage\n /** Base CSS class name */\n className?: string\n /** Additional className for user messages */\n userClassName?: string\n /** Additional className for assistant messages */\n assistantClassName?: string\n /** Custom renderer for text parts */\n textPartRenderer?: (props: { content: string }) => ReactNode\n /** Custom renderer for thinking parts */\n thinkingPartRenderer?: (props: {\n content: string\n isComplete?: boolean\n }) => ReactNode\n /** Named tool renderers - use the tool name as the key */\n toolsRenderer?: Record<string, (props: ToolCallRenderProps) => ReactNode>\n /** Default tool renderer when tool name not found in toolsRenderer */\n defaultToolRenderer?: (props: ToolCallRenderProps) => ReactNode\n /** Custom renderer for tool result parts */\n toolResultRenderer?: (props: {\n toolCallId: string\n content: string\n state: string\n }) => ReactNode\n}\n\n/**\n * @deprecated Use `createChatUI()` Message instead. Deprecated in 0.9.0. Removed in 1.0.0.\n *\n * Message component - renders a single message with all its parts\n *\n * This component natively understands TanStack AI's parts-based message format:\n * - thinking parts: rendered as collapsible thinking/reasoning sections (auto-collapses when complete)\n * - text parts: rendered as content\n * - tool-call parts: rendered with state, approvals, etc.\n * - tool-result parts: rendered with results\n *\n * @example Basic usage\n * ```tsx\n * <Chat.Message message={message} />\n * ```\n *\n * @example With role-based styling\n * ```tsx\n * <ChatMessage\n * message={message}\n * className=\"flex\"\n * userClassName=\"justify-end\"\n * assistantClassName=\"justify-start\"\n * />\n * ```\n *\n * @example With custom thinking renderer\n * ```tsx\n * <ChatMessage\n * message={message}\n * thinkingPartRenderer={({ content, isComplete }) => (\n * <details open={!isComplete}>\n * <summary>💭 Thinking...</summary>\n * <pre>{content}</pre>\n * </details>\n * )}\n * />\n * ```\n *\n * @example With named tool renderers\n * ```tsx\n * <ChatMessage\n * message={message}\n * toolsRenderer={{\n * recommendGuitar: ({ id, arguments: args }) => <GuitarCard {...JSON.parse(args)} />,\n * weatherLookup: ({ id, arguments: args }) => <WeatherWidget {...JSON.parse(args)} />,\n * }}\n * defaultToolRenderer={() => null}\n * />\n * ```\n */\nexport function ChatMessage({\n message,\n className = '',\n userClassName = '',\n assistantClassName = '',\n textPartRenderer,\n thinkingPartRenderer,\n toolsRenderer,\n defaultToolRenderer,\n toolResultRenderer,\n}: ChatMessageProps) {\n // Combine classes based on role\n const roleClassName =\n message.role === 'user' ? userClassName : assistantClassName\n\n const combinedClassName = [className, roleClassName].filter(Boolean).join(' ')\n\n return (\n <div\n className={combinedClassName || undefined}\n data-message-id={message.id}\n data-message-role={message.role}\n data-message-created={message.createdAt?.toISOString()}\n >\n {message.parts.map((part, index) => {\n // Check if thinking is complete (if there's a text part after this thinking part)\n const isThinkingComplete =\n part.type === 'thinking' &&\n message.parts.slice(index + 1).some((p) => p.type === 'text')\n\n return (\n <MessagePart\n key={`${message.id}-part-${index}`}\n part={part}\n isThinkingComplete={isThinkingComplete}\n textPartRenderer={textPartRenderer}\n thinkingPartRenderer={thinkingPartRenderer}\n toolsRenderer={toolsRenderer}\n defaultToolRenderer={defaultToolRenderer}\n toolResultRenderer={toolResultRenderer}\n />\n )\n })}\n </div>\n )\n}\n\nfunction MessagePart({\n part,\n isThinkingComplete,\n textPartRenderer,\n thinkingPartRenderer,\n toolsRenderer,\n defaultToolRenderer,\n toolResultRenderer,\n}: {\n part: UIMessage['parts'][number]\n isThinkingComplete?: boolean\n textPartRenderer?: ChatMessageProps['textPartRenderer']\n thinkingPartRenderer?: ChatMessageProps['thinkingPartRenderer']\n toolsRenderer?: ChatMessageProps['toolsRenderer']\n defaultToolRenderer?: ChatMessageProps['defaultToolRenderer']\n toolResultRenderer?: ChatMessageProps['toolResultRenderer']\n}) {\n // Text part\n if (part.type === 'text') {\n if (textPartRenderer) {\n return <>{textPartRenderer({ content: part.content })}</>\n }\n return (\n <div data-part-type=\"text\" data-part-content>\n {part.content}\n </div>\n )\n }\n\n // Thinking part\n if (part.type === 'thinking') {\n if (thinkingPartRenderer) {\n return (\n <>\n {thinkingPartRenderer({\n content: part.content,\n isComplete: isThinkingComplete,\n })}\n </>\n )\n }\n return (\n <ThinkingPart content={part.content} isComplete={isThinkingComplete} />\n )\n }\n\n // Tool call part\n if (part.type === 'tool-call') {\n const toolProps: ToolCallRenderProps = {\n id: part.id,\n name: part.name,\n arguments: part.arguments,\n state: part.state,\n approval: part.approval,\n output: part.output,\n }\n\n // Check if there's a specific renderer for this tool\n if (toolsRenderer?.[part.name]) {\n return <>{toolsRenderer[part.name]?.(toolProps)}</>\n }\n\n // Use default tool renderer if provided\n if (defaultToolRenderer) {\n return <>{defaultToolRenderer(toolProps)}</>\n }\n\n // Fallback to built-in default renderer\n return (\n <div\n data-part-type=\"tool-call\"\n data-tool-name={part.name}\n data-tool-state={part.state}\n data-tool-id={part.id}\n >\n <div data-tool-header>\n <strong>{part.name}</strong>\n <span data-tool-state-badge>{part.state}</span>\n </div>\n {part.arguments && (\n <div data-tool-arguments>\n <pre>{part.arguments}</pre>\n </div>\n )}\n {part.approval && (\n <div data-tool-approval>\n {part.approval.approved !== undefined\n ? part.approval.approved\n ? '✓ Approved'\n : '✗ Denied'\n : '⏳ Awaiting approval...'}\n </div>\n )}\n {part.output && (\n <div data-tool-output>\n <pre>{JSON.stringify(part.output, null, 2)}</pre>\n </div>\n )}\n </div>\n )\n }\n\n // Tool result part\n if (part.type === 'tool-result') {\n const toolResultContent = toolResultContentToString(part.content)\n\n if (toolResultRenderer) {\n return (\n <>\n {toolResultRenderer({\n toolCallId: part.toolCallId,\n content: toolResultContent,\n state: part.state,\n })}\n </>\n )\n }\n\n return (\n <div\n data-part-type=\"tool-result\"\n data-tool-call-id={part.toolCallId}\n data-tool-result-state={part.state}\n >\n <div data-tool-result-content>{toolResultContent}</div>\n </div>\n )\n }\n\n return null\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { UIMessage } from '../types.js';
|
|
3
|
+
/** @deprecated Use `createChatUI()` Messages instead. Deprecated in 0.9.0. Removed in 1.0.0. */
|
|
4
|
+
export interface ChatMessagesProps {
|
|
5
|
+
/** Custom render function for each message */
|
|
6
|
+
children?: (message: UIMessage, index: number) => ReactNode;
|
|
7
|
+
/** CSS class name */
|
|
8
|
+
className?: string;
|
|
9
|
+
/** Element to show when there are no messages */
|
|
10
|
+
emptyState?: ReactNode;
|
|
11
|
+
/** Element to show while loading the first message */
|
|
12
|
+
loadingState?: ReactNode;
|
|
13
|
+
/** Custom error renderer */
|
|
14
|
+
errorState?: (props: {
|
|
15
|
+
error: Error;
|
|
16
|
+
reload: () => Promise<void>;
|
|
17
|
+
}) => ReactNode;
|
|
18
|
+
/** Auto-scroll to bottom on new messages */
|
|
19
|
+
autoScroll?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use `createChatUI()` Messages instead. Deprecated in 0.9.0. Removed in 1.0.0.
|
|
23
|
+
*
|
|
24
|
+
* Messages container - renders all messages in the conversation
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <Chat.Messages>
|
|
29
|
+
* {(message) => <Chat.Message message={message} />}
|
|
30
|
+
* </Chat.Messages>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function ChatMessages({ children, className, emptyState, loadingState, errorState, autoScroll, }: ChatMessagesProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useChatContext } from "./chat.js";
|
|
2
|
+
import { ChatMessage } from "./chat-message.js";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/chat-ui/chat-messages.tsx
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Use `createChatUI()` Messages instead. Deprecated in 0.9.0. Removed in 1.0.0.
|
|
8
|
+
*
|
|
9
|
+
* Messages container - renders all messages in the conversation
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <Chat.Messages>
|
|
14
|
+
* {(message) => <Chat.Message message={message} />}
|
|
15
|
+
* </Chat.Messages>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
function ChatMessages({ children, className, emptyState, loadingState, errorState, autoScroll = true }) {
|
|
19
|
+
const { messages, isLoading, error, reload } = useChatContext();
|
|
20
|
+
const containerRef = useRef(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (autoScroll && containerRef.current) containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
23
|
+
}, [messages, autoScroll]);
|
|
24
|
+
if (error && errorState) return /* @__PURE__ */ jsx(Fragment, { children: errorState({
|
|
25
|
+
error,
|
|
26
|
+
reload
|
|
27
|
+
}) });
|
|
28
|
+
if (isLoading && messages.length === 0 && loadingState) return /* @__PURE__ */ jsx(Fragment, { children: loadingState });
|
|
29
|
+
if (messages.length === 0 && emptyState) return /* @__PURE__ */ jsx(Fragment, { children: emptyState });
|
|
30
|
+
return /* @__PURE__ */ jsx("div", {
|
|
31
|
+
ref: containerRef,
|
|
32
|
+
className,
|
|
33
|
+
"data-chat-messages": true,
|
|
34
|
+
"data-message-count": messages.length,
|
|
35
|
+
children: messages.map((message, index) => children ? /* @__PURE__ */ jsx("div", {
|
|
36
|
+
"data-message-id": message.id,
|
|
37
|
+
children: children(message, index)
|
|
38
|
+
}, message.id) : /* @__PURE__ */ jsx(ChatMessage, { message }, message.id))
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { ChatMessages };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=chat-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-messages.js","names":[],"sources":["../../../src/chat-ui/chat-messages.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport { useChatContext } from './chat'\nimport { ChatMessage } from './chat-message'\nimport type { ReactNode } from 'react'\nimport type { UIMessage } from '../types'\n\n/** @deprecated Use `createChatUI()` Messages instead. Deprecated in 0.9.0. Removed in 1.0.0. */\nexport interface ChatMessagesProps {\n /** Custom render function for each message */\n children?: (message: UIMessage, index: number) => ReactNode\n /** CSS class name */\n className?: string\n /** Element to show when there are no messages */\n emptyState?: ReactNode\n /** Element to show while loading the first message */\n loadingState?: ReactNode\n /** Custom error renderer */\n errorState?: (props: {\n error: Error\n reload: () => Promise<void>\n }) => ReactNode\n /** Auto-scroll to bottom on new messages */\n autoScroll?: boolean\n}\n\n/**\n * @deprecated Use `createChatUI()` Messages instead. Deprecated in 0.9.0. Removed in 1.0.0.\n *\n * Messages container - renders all messages in the conversation\n *\n * @example\n * ```tsx\n * <Chat.Messages>\n * {(message) => <Chat.Message message={message} />}\n * </Chat.Messages>\n * ```\n */\nexport function ChatMessages({\n children,\n className,\n emptyState,\n loadingState,\n errorState,\n autoScroll = true,\n}: ChatMessagesProps) {\n const { messages, isLoading, error, reload } = useChatContext()\n const containerRef = useRef<HTMLDivElement>(null)\n\n // Auto-scroll to bottom on new messages\n useEffect(() => {\n if (autoScroll && containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight\n }\n }, [messages, autoScroll])\n\n // Error state\n if (error && errorState) {\n return <>{errorState({ error, reload })}</>\n }\n\n // Loading state (only show if no messages yet)\n if (isLoading && messages.length === 0 && loadingState) {\n return <>{loadingState}</>\n }\n\n // Empty state\n if (messages.length === 0 && emptyState) {\n return <>{emptyState}</>\n }\n\n return (\n <div\n ref={containerRef}\n className={className}\n data-chat-messages\n data-message-count={messages.length}\n >\n {messages.map((message, index) =>\n children ? (\n <div key={message.id} data-message-id={message.id}>\n {children(message, index)}\n </div>\n ) : (\n <ChatMessage key={message.id} message={message} />\n ),\n )}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAqCA,SAAgB,aAAa,EAC3B,UACA,WACA,YACA,cACA,YACA,aAAa,QACO;CACpB,MAAM,EAAE,UAAU,WAAW,OAAO,WAAW,eAAe;CAC9D,MAAM,eAAe,OAAuB,IAAI;CAGhD,gBAAgB;EACd,IAAI,cAAc,aAAa,SAC7B,aAAa,QAAQ,YAAY,aAAa,QAAQ;CAE1D,GAAG,CAAC,UAAU,UAAU,CAAC;CAGzB,IAAI,SAAS,YACX,OAAO,oBAAA,UAAA,EAAA,UAAG,WAAW;EAAE;EAAO;CAAO,CAAC,EAAI,CAAA;CAI5C,IAAI,aAAa,SAAS,WAAW,KAAK,cACxC,OAAO,oBAAA,UAAA,EAAA,UAAG,aAAe,CAAA;CAI3B,IAAI,SAAS,WAAW,KAAK,YAC3B,OAAO,oBAAA,UAAA,EAAA,UAAG,WAAa,CAAA;CAGzB,OACE,oBAAC,OAAD;EACE,KAAK;EACM;EACX,sBAAA;EACA,sBAAoB,SAAS;EAE5B,UAAA,SAAS,KAAK,SAAS,UACtB,WACE,oBAAC,OAAD;GAAsB,mBAAiB,QAAQ;GAC5C,UAAA,SAAS,SAAS,KAAK;EACrB,GAFK,QAAQ,EAEb,IAEL,oBAAC,aAAD,EAAuC,QAAU,GAA/B,QAAQ,EAAuB,CAErD;CACG,CAAA;AAET"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ConnectionAdapter } from '@tanstack/ai-client';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { UIMessage, UseChatReturn } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Hook to access chat context
|
|
6
|
+
* @throws Error if used outside of Chat component
|
|
7
|
+
*/
|
|
8
|
+
/** @deprecated Use `createChatUI().useChatContext()` instead. Deprecated in 0.9.0. Removed in 1.0.0. */
|
|
9
|
+
export declare function useChatContext(): UseChatReturn;
|
|
10
|
+
/** @deprecated Use `createChatUI()` Chat/Provider instead. Deprecated in 0.9.0. Removed in 1.0.0. */
|
|
11
|
+
export interface ChatProps {
|
|
12
|
+
/** Child components (Chat.Messages, Chat.Input, etc.) */
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
/** CSS class name for the root element */
|
|
15
|
+
className?: string;
|
|
16
|
+
/** Connection adapter for communicating with your API */
|
|
17
|
+
connection: ConnectionAdapter;
|
|
18
|
+
/** Initial messages to display */
|
|
19
|
+
initialMessages?: Array<UIMessage>;
|
|
20
|
+
/** Custom message ID generator */
|
|
21
|
+
id?: string;
|
|
22
|
+
/** Additional body data to send with requests */
|
|
23
|
+
body?: any;
|
|
24
|
+
/** Callback when a response is received */
|
|
25
|
+
onResponse?: (response?: Response) => void | Promise<void>;
|
|
26
|
+
/** Callback when each chunk arrives */
|
|
27
|
+
onChunk?: (chunk: any) => void;
|
|
28
|
+
/** Callback when a message is complete */
|
|
29
|
+
onFinish?: (message: UIMessage) => void;
|
|
30
|
+
/** Callback when an error occurs */
|
|
31
|
+
onError?: (error: Error) => void;
|
|
32
|
+
/** Custom tool components registry */
|
|
33
|
+
tools?: Record<string, React.ComponentType<{
|
|
34
|
+
input: any;
|
|
35
|
+
output?: any;
|
|
36
|
+
}>>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Since 0.9.0. Use `createChatHook()` from `@tanstack/ai-react/ui` instead.
|
|
40
|
+
* See https://tanstack.com/ai/latest/docs/ui/react
|
|
41
|
+
* Removed in 1.0.0.
|
|
42
|
+
*
|
|
43
|
+
* Root Chat component - provides context for all chat subcomponents
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <Chat connection={fetchServerSentEvents("/api/chat")}>
|
|
48
|
+
* <Chat.Messages />
|
|
49
|
+
* <Chat.Input />
|
|
50
|
+
* </Chat>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function Chat({ children, className, connection, initialMessages, id, body, onResponse, onChunk, onFinish, onError, }: ChatProps): import("react/jsx-runtime").JSX.Element;
|