@tanstack/ai-react 0.22.4 → 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.
Files changed (60) hide show
  1. package/README.md +11 -15
  2. package/dist/esm/chat-ui/chat-input.d.ts +57 -0
  3. package/dist/esm/chat-ui/chat-input.js +124 -0
  4. package/dist/esm/chat-ui/chat-input.js.map +1 -0
  5. package/dist/esm/chat-ui/chat-message.d.ts +91 -0
  6. package/dist/esm/chat-ui/chat-message.js +157 -0
  7. package/dist/esm/chat-ui/chat-message.js.map +1 -0
  8. package/dist/esm/chat-ui/chat-messages.d.ts +33 -0
  9. package/dist/esm/chat-ui/chat-messages.js +44 -0
  10. package/dist/esm/chat-ui/chat-messages.js.map +1 -0
  11. package/dist/esm/chat-ui/chat.d.ts +53 -0
  12. package/dist/esm/chat-ui/chat.js +57 -0
  13. package/dist/esm/chat-ui/chat.js.map +1 -0
  14. package/dist/esm/chat-ui/create-chat-hook.d.ts +48 -0
  15. package/dist/esm/chat-ui/create-chat-hook.js +57 -0
  16. package/dist/esm/chat-ui/create-chat-hook.js.map +1 -0
  17. package/dist/esm/chat-ui/create-ui-contexts.d.ts +29 -0
  18. package/dist/esm/chat-ui/create-ui-contexts.js +36 -0
  19. package/dist/esm/chat-ui/create-ui-contexts.js.map +1 -0
  20. package/dist/esm/chat-ui/create-ui.d.ts +153 -0
  21. package/dist/esm/chat-ui/create-ui.js +294 -0
  22. package/dist/esm/chat-ui/create-ui.js.map +1 -0
  23. package/dist/esm/chat-ui/markdown-plugins.d.ts +15 -0
  24. package/dist/esm/chat-ui/markdown-plugins.js +28 -0
  25. package/dist/esm/chat-ui/markdown-plugins.js.map +1 -0
  26. package/dist/esm/chat-ui/text-part.d.ts +57 -0
  27. package/dist/esm/chat-ui/text-part.js +46 -0
  28. package/dist/esm/chat-ui/text-part.js.map +1 -0
  29. package/dist/esm/chat-ui/thinking-part.d.ts +41 -0
  30. package/dist/esm/chat-ui/thinking-part.js +73 -0
  31. package/dist/esm/chat-ui/thinking-part.js.map +1 -0
  32. package/dist/esm/chat-ui/tool-approval.d.ts +52 -0
  33. package/dist/esm/chat-ui/tool-approval.js +83 -0
  34. package/dist/esm/chat-ui/tool-approval.js.map +1 -0
  35. package/dist/esm/chat-ui/tool-result-content.d.ts +18 -0
  36. package/dist/esm/chat-ui/tool-result-content.js +19 -0
  37. package/dist/esm/chat-ui/tool-result-content.js.map +1 -0
  38. package/dist/esm/create-chat-hook.d.ts +35 -0
  39. package/dist/esm/create-chat-hook.js +37 -0
  40. package/dist/esm/create-chat-hook.js.map +1 -0
  41. package/dist/esm/index.d.ts +1 -0
  42. package/dist/esm/index.js +2 -1
  43. package/dist/esm/ui.d.ts +10 -0
  44. package/dist/esm/ui.js +11 -0
  45. package/package.json +18 -4
  46. package/src/chat-ui/chat-input.tsx +177 -0
  47. package/src/chat-ui/chat-message.tsx +271 -0
  48. package/src/chat-ui/chat-messages.tsx +89 -0
  49. package/src/chat-ui/chat.tsx +98 -0
  50. package/src/chat-ui/create-chat-hook.tsx +103 -0
  51. package/src/chat-ui/create-ui-contexts.tsx +57 -0
  52. package/src/chat-ui/create-ui.tsx +761 -0
  53. package/src/chat-ui/markdown-plugins.ts +48 -0
  54. package/src/chat-ui/text-part.tsx +97 -0
  55. package/src/chat-ui/thinking-part.tsx +83 -0
  56. package/src/chat-ui/tool-approval.tsx +132 -0
  57. package/src/chat-ui/tool-result-content.ts +32 -0
  58. package/src/create-chat-hook.ts +67 -0
  59. package/src/index.ts +1 -0
  60. package/src/ui.ts +43 -0
@@ -0,0 +1,294 @@
1
+ import { defaultChatUIContexts } from "./create-ui-contexts.js";
2
+ import { createContext, memo, useContext } from "react";
3
+ import { Fragment, jsx } from "react/jsx-runtime";
4
+ import { automaticPartsForMessage, collectInlineToolNames, resolveInterruptComponent, selectChatUI, selectMessageUI } from "@tanstack/ai-client/ui";
5
+ //#region src/chat-ui/create-ui.tsx
6
+ function createWarnOnce() {
7
+ const seen = /* @__PURE__ */ new Set();
8
+ return (key, message) => {
9
+ if (process.env.NODE_ENV === "production") return;
10
+ if (seen.has(key)) return;
11
+ seen.add(key);
12
+ console.warn(message);
13
+ };
14
+ }
15
+ function readMessages(chat) {
16
+ return chat.messages;
17
+ }
18
+ function readInterrupts(chat) {
19
+ return chat.interrupts ?? [];
20
+ }
21
+ function queueItemEqual(prev, next) {
22
+ return prev.item.id === next.item.id && prev.item.createdAt === next.item.createdAt && prev.item.content === next.item.content && prev.cancelQueued === next.cancelQueued;
23
+ }
24
+ function selectedPartPropsEqual(prev, next) {
25
+ if (prev.selected.key !== next.selected.key) return false;
26
+ if (prev.selected.part !== next.selected.part) return false;
27
+ if (prev.selected.key === "toolCall" && next.selected.key === "toolCall") return prev.selected.result === next.selected.result && prev.selected.interrupt === next.selected.interrupt;
28
+ if (prev.selected.key === "toolResult" && next.selected.key === "toolResult") return prev.selected.matched === next.selected.matched;
29
+ return true;
30
+ }
31
+ function isSelectedPart(value) {
32
+ return "key" in value && "part" in value;
33
+ }
34
+ function bindMap(map, bind) {
35
+ const out = {};
36
+ for (const [key, component] of Object.entries(map ?? {})) if (component) out[key] = bind(component);
37
+ return out;
38
+ }
39
+ /**
40
+ * Bind chat options and UI widgets once at module scope. This matches Form
41
+ * `createFormHook` and Table `createTableHook`: widgets register here, mix
42
+ * onto Part / Interrupt / the UI kit, and automatic dispatch still walks the
43
+ * message list.
44
+ *
45
+ * `chatOptions` is type-only at runtime. Pass optional `chatContext`,
46
+ * `partContext`, and `interruptContext` from {@link createChatHookContexts}
47
+ * when widgets live in other files or when nested chats need isolation.
48
+ */
49
+ function createChatUI(options, config) {
50
+ const { context: contextOption, components, partsComponents: parts, toolsComponents: tools, interruptsComponents: interrupts } = config;
51
+ const { layout: Layout, message: MessageComponent, input: InputComponent, queue: QueueItemComponent } = components;
52
+ const { chatContext: chatContextOption, partContext: partContextOption, interruptContext: interruptContextOption } = contextOption ?? {};
53
+ const warn = createWarnOnce();
54
+ const ChatContext = chatContextOption ?? defaultChatUIContexts.chatContext;
55
+ const PartContext = partContextOption ?? defaultChatUIContexts.partContext;
56
+ const InterruptContext = interruptContextOption ?? defaultChatUIContexts.interruptContext;
57
+ const inlineToolNames = collectInlineToolNames(interrupts?.tools, Object.keys(tools ?? {}));
58
+ function useChatContext() {
59
+ const chat = useContext(ChatContext);
60
+ if (!chat) throw new Error("`useChatContext` must be used within `UI.Provider` or `UI.Chat`.");
61
+ return chat;
62
+ }
63
+ function usePartContext() {
64
+ const selected = useContext(PartContext);
65
+ if (!selected) throw new Error("`usePartContext` must be used within `UI.Part` or an automatic part.");
66
+ return selected;
67
+ }
68
+ function useInterruptContext() {
69
+ const interrupt = useContext(InterruptContext);
70
+ if (!interrupt) throw new Error("`useInterruptContext` must be used within `UI.Interrupt`.");
71
+ return interrupt;
72
+ }
73
+ function bindPart(Component) {
74
+ return function BoundPart() {
75
+ const selected = usePartContext();
76
+ return /* @__PURE__ */ jsx(Component, { part: selected.part });
77
+ };
78
+ }
79
+ function bindTool(Component) {
80
+ return function BoundTool() {
81
+ const selected = usePartContext();
82
+ if (selected.key !== "toolCall") return null;
83
+ return /* @__PURE__ */ jsx(Component, {
84
+ part: selected.part,
85
+ result: selected.result,
86
+ interrupt: selected.interrupt
87
+ });
88
+ };
89
+ }
90
+ function bindInterrupt(Component) {
91
+ return function BoundInterrupt() {
92
+ const interrupt = useInterruptContext();
93
+ return /* @__PURE__ */ jsx(Component, { interrupt });
94
+ };
95
+ }
96
+ const partMixins = bindMap(parts, bindPart);
97
+ const toolMixins = bindMap(tools, bindTool);
98
+ const interruptMixins = {
99
+ ...bindMap(interrupts?.generic, bindInterrupt),
100
+ ...bindMap(interrupts?.tools, bindInterrupt)
101
+ };
102
+ function mixPart(selected) {
103
+ return Object.assign({}, selected, partMixins, toolMixins, { Render: BoundRender });
104
+ }
105
+ function mixInterrupt(interrupt) {
106
+ return Object.assign({}, interrupt, interruptMixins, { Render: BoundInterruptRender });
107
+ }
108
+ function Provider({ chat, children }) {
109
+ return /* @__PURE__ */ jsx(ChatContext.Provider, {
110
+ value: chat,
111
+ children
112
+ });
113
+ }
114
+ function MissingInput() {
115
+ warn("input", "[tanstack-ai-ui] Rendered <Input /> but no `input` component is registered.");
116
+ return null;
117
+ }
118
+ const LayoutSlots = {
119
+ Messages,
120
+ Interrupts,
121
+ Queue,
122
+ Input: InputComponent ?? MissingInput
123
+ };
124
+ function Chat({ chat }) {
125
+ return /* @__PURE__ */ jsx(Provider, {
126
+ chat,
127
+ children: /* @__PURE__ */ jsx(Layout, { ...LayoutSlots })
128
+ });
129
+ }
130
+ function Queue() {
131
+ const chat = useChatContext();
132
+ if (!QueueItemComponent) return null;
133
+ const items = chat.queue;
134
+ return /* @__PURE__ */ jsx(Fragment, { children: items.map((item) => /* @__PURE__ */ jsx(QueueItemView, {
135
+ cancelQueued: chat.cancelQueued,
136
+ item
137
+ }, item.id)) });
138
+ }
139
+ const QueueItemView = memo(function QueueItemView({ item, cancelQueued }) {
140
+ if (!QueueItemComponent) return null;
141
+ return /* @__PURE__ */ jsx(QueueItemComponent, { item: {
142
+ ...item,
143
+ cancelQueued: () => {
144
+ cancelQueued(item.id);
145
+ }
146
+ } });
147
+ }, queueItemEqual);
148
+ function Messages({ children } = {}) {
149
+ const chat = useChatContext();
150
+ const messages = readMessages(chat);
151
+ const interrupts = readInterrupts(chat);
152
+ if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(messages) });
153
+ return /* @__PURE__ */ jsx(Fragment, { children: messages.map((message) => /* @__PURE__ */ jsx(MessageView, {
154
+ inlineToolNames,
155
+ interrupts,
156
+ message
157
+ }, message.id)) });
158
+ }
159
+ const MessageRenderContext = createContext(null);
160
+ function Parts() {
161
+ const scope = useContext(MessageRenderContext);
162
+ if (!scope) throw new Error("`Parts` must be rendered by a `message` component.");
163
+ return /* @__PURE__ */ jsx(AutomaticParts, {
164
+ inlineToolNames: scope.inlineToolNames,
165
+ interrupts: scope.interrupts,
166
+ message: scope.message
167
+ });
168
+ }
169
+ const MessageView = memo(function MessageView({ message, interrupts, inlineToolNames: names, children }) {
170
+ const selected = selectMessageUI(message, {
171
+ interrupts,
172
+ inlineToolNames: names
173
+ });
174
+ if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(selected.parts) });
175
+ return /* @__PURE__ */ jsx(MessageRenderContext.Provider, {
176
+ value: {
177
+ message,
178
+ interrupts,
179
+ inlineToolNames: names
180
+ },
181
+ children: /* @__PURE__ */ jsx(MessageComponent, {
182
+ message,
183
+ Parts
184
+ })
185
+ });
186
+ });
187
+ function Message({ message, children }) {
188
+ const chat = useChatContext();
189
+ return /* @__PURE__ */ jsx(MessageView, {
190
+ children,
191
+ inlineToolNames,
192
+ interrupts: readInterrupts(chat),
193
+ message
194
+ });
195
+ }
196
+ const AutomaticParts = memo(function AutomaticParts({ message, interrupts, inlineToolNames: names }) {
197
+ const selected = selectMessageUI(message, {
198
+ interrupts,
199
+ inlineToolNames: names
200
+ });
201
+ return /* @__PURE__ */ jsx(Fragment, { children: automaticPartsForMessage(selected).map((part, index) => /* @__PURE__ */ jsx(SelectedPartView, { selected: part }, `${message.id}-${index}`)) });
202
+ });
203
+ const SelectedPartInner = memo(function SelectedPartInner({ selected }) {
204
+ if (selected.key === "toolCall") {
205
+ const name = selected.part.name;
206
+ const Tool = tools?.[name];
207
+ if (!Tool) {
208
+ warn(`tool:${name}`, `[tanstack-ai-ui] Missing tools.${name} component`);
209
+ return null;
210
+ }
211
+ return /* @__PURE__ */ jsx(Tool, {
212
+ part: selected.part,
213
+ result: selected.result,
214
+ interrupt: selected.interrupt
215
+ });
216
+ }
217
+ const PartComponent = parts[selected.key] ?? parts.fallback;
218
+ if (!PartComponent) {
219
+ warn(`part:${selected.key}`, `[tanstack-ai-ui] Missing parts.${selected.key} component`);
220
+ return null;
221
+ }
222
+ return /* @__PURE__ */ jsx(PartComponent, { part: selected.part });
223
+ }, selectedPartPropsEqual);
224
+ const SelectedPartView = memo(function SelectedPartView({ selected }) {
225
+ return /* @__PURE__ */ jsx(PartContext.Provider, {
226
+ value: selected,
227
+ children: /* @__PURE__ */ jsx(SelectedPartInner, { selected })
228
+ });
229
+ });
230
+ function BoundRender() {
231
+ const selected = usePartContext();
232
+ return /* @__PURE__ */ jsx(SelectedPartInner, { selected });
233
+ }
234
+ function Part({ part, children }) {
235
+ const chat = useChatContext();
236
+ const selected = isSelectedPart(part) ? part : selectMessageUI({
237
+ id: "part",
238
+ role: "assistant",
239
+ parts: [part]
240
+ }, {
241
+ interrupts: readInterrupts(chat),
242
+ inlineToolNames: []
243
+ }).parts[0];
244
+ if (!selected) return null;
245
+ return /* @__PURE__ */ jsx(PartContext.Provider, {
246
+ value: selected,
247
+ children: children ? children(mixPart(selected)) : /* @__PURE__ */ jsx(SelectedPartInner, { selected })
248
+ });
249
+ }
250
+ function Interrupts({ children } = {}) {
251
+ const chat = useChatContext();
252
+ const selected = selectChatUI({
253
+ messages: readMessages(chat),
254
+ interrupts: readInterrupts(chat),
255
+ inlineToolNames
256
+ });
257
+ if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(selected.interrupts) });
258
+ return /* @__PURE__ */ jsx(Fragment, { children: selected.interrupts.map((interrupt) => /* @__PURE__ */ jsx(Interrupt, { interrupt }, interrupt.id)) });
259
+ }
260
+ const InterruptInner = memo(function InterruptInner({ interrupt }) {
261
+ const Component = resolveInterruptComponent(interrupt, interrupts);
262
+ if (!Component) {
263
+ warn(`interrupt:${interrupt.id}`, `[tanstack-ai-ui] Missing interrupt component for ${interrupt.kind}`);
264
+ return null;
265
+ }
266
+ return /* @__PURE__ */ jsx(Component, { interrupt });
267
+ });
268
+ function BoundInterruptRender() {
269
+ const interrupt = useInterruptContext();
270
+ return /* @__PURE__ */ jsx(InterruptInner, { interrupt });
271
+ }
272
+ const Interrupt = memo(function Interrupt({ interrupt, children }) {
273
+ return /* @__PURE__ */ jsx(InterruptContext.Provider, {
274
+ value: interrupt,
275
+ children: children ? children(mixInterrupt(interrupt)) : /* @__PURE__ */ jsx(InterruptInner, { interrupt })
276
+ });
277
+ });
278
+ return {
279
+ Chat,
280
+ Provider,
281
+ Messages,
282
+ Message,
283
+ Part,
284
+ Interrupts,
285
+ Interrupt,
286
+ Queue,
287
+ useChatContext,
288
+ Input: InputComponent
289
+ };
290
+ }
291
+ //#endregion
292
+ export { createChatUI };
293
+
294
+ //# sourceMappingURL=create-ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-ui.js","names":[],"sources":["../../../src/chat-ui/create-ui.tsx"],"sourcesContent":["import { createContext, memo, useContext } from 'react'\nimport type { ComponentProps, ComponentType, Context, ReactNode } from 'react'\nimport {\n automaticPartsForMessage,\n collectInlineToolNames,\n resolveInterruptComponent,\n selectChatUI,\n selectMessageUI,\n} from '@tanstack/ai-client/ui'\nimport type {\n ChatUIData,\n ChatUIHasNamedInterrupts,\n ChatUIHasNamedTools,\n ChatUIInterrupt,\n ChatUIInterruptName,\n ChatUIInterruptOf,\n ChatUIInterruptsOf,\n ChatUIMessages,\n ChatUINamedInterruptId,\n ChatUIPartKey,\n ChatUIPartOf,\n ChatUISchemaOf,\n ChatUISelectedPart,\n ChatUISelectedPartOf,\n ChatUIToolApproval,\n ChatUIToolName,\n ChatUIToolsOf,\n} from '@tanstack/ai-client/ui'\nimport type {\n MessagePart,\n QueuedMessage,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n} from '@tanstack/ai-client'\nimport type { UseChatReturn } from '../types'\nimport { defaultChatUIContexts } from './create-ui-contexts'\nimport type { ChatUIContexts } from './create-ui-contexts'\n\nexport type ChatUIHost<TOptions = unknown> = UseChatReturn<\n ChatUIToolsOf<TOptions>,\n ChatUISchemaOf<TOptions>,\n ChatUIInterruptsOf<TOptions>\n>\n\nexport type ChatUIQueueItem = QueuedMessage & {\n cancelQueued: () => void\n}\n\nexport type LayoutProps<\n TOptions,\n TInput extends ComponentType<any> | undefined = ComponentType<\n InputProps<TOptions>\n >,\n> = {\n Messages: ComponentType\n Interrupts: ComponentType\n Queue: ComponentType\n readonly __ui?: TOptions\n} & (TInput extends ComponentType<any> ? { Input: ComponentType } : {})\n\nexport type MessageProps<TOptions> = {\n message: UIMessage<ChatUIToolsOf<TOptions>, ChatUIData<TOptions>>\n Parts: ComponentType\n}\n\nexport type InputProps<TOptions> = {\n readonly __ui?: TOptions\n}\n\nexport type QueueProps<TOptions> = {\n item: ChatUIQueueItem\n readonly __ui?: TOptions\n}\n\nexport type PartProps<TOptions, TKey extends ChatUIPartKey = ChatUIPartKey> = {\n part: ChatUIPartOf<TOptions, TKey>\n}\n\nexport type ToolProps<\n TOptions,\n TName extends ChatUIToolName<TOptions> = ChatUIToolName<TOptions>,\n> = {\n part: Extract<ToolCallPart<ChatUIToolsOf<TOptions>>, { name: TName }>\n result?: ToolResultPart\n interrupt?: ChatUIToolApproval<TOptions, TName>\n}\n\nexport type InterruptProps<\n TOptions,\n TName extends ChatUIInterruptName<TOptions> = never,\n> = {\n interrupt: ChatUIInterruptOf<TOptions, TName>\n readonly __ui?: TOptions\n}\n\ntype GenericInterruptComponents<TOptions> =\n ChatUIHasNamedInterrupts<TOptions> extends true\n ? {\n [K in ChatUINamedInterruptId<TOptions>]: ComponentType<\n InterruptProps<TOptions, K & ChatUIInterruptName<TOptions>>\n >\n } & {\n fallback?: ComponentType<InterruptProps<TOptions>>\n }\n : {\n fallback?: ComponentType<InterruptProps<TOptions>>\n }\n\ntype ToolApprovalMap<TOptions> = {\n [K in ChatUIToolName<TOptions>]?: ComponentType<\n InterruptProps<TOptions, K & ChatUIInterruptName<TOptions>>\n >\n}\n\n/** The chrome around the message list: `layout`, `message`, and `input`. */\nexport type ChatUIChromeComponents<\n TOptions,\n TInput extends ComponentType<InputProps<TOptions>> | undefined =\n ComponentType<InputProps<TOptions>>,\n> = {\n layout: ComponentType<LayoutProps<TOptions, TInput>>\n message: ComponentType<MessageProps<TOptions>>\n input?: TInput\n queue?: ComponentType<QueueProps<TOptions>>\n}\n\nexport type ChatUIPartsComponents<TOptions> = {\n [K in ChatUIPartKey]?: ComponentType<PartProps<TOptions, K>>\n} & {\n fallback?: ComponentType<PartProps<TOptions>>\n}\n\nexport type ChatUIInterruptsComponents<TOptions> = {\n tools?: ToolApprovalMap<TOptions>\n generic: GenericInterruptComponents<TOptions>\n}\n\nexport type ChatUIComponents<\n TOptions,\n TInput extends ComponentType<InputProps<TOptions>> | undefined =\n ComponentType<InputProps<TOptions>>,\n> = {\n components: ChatUIChromeComponents<TOptions, TInput>\n partsComponents: ChatUIPartsComponents<TOptions>\n} & (ChatUIHasNamedTools<TOptions> extends true\n ? {\n toolsComponents: {\n [K in ChatUIToolName<TOptions>]: ComponentType<ToolProps<TOptions, K>>\n }\n }\n : {\n toolsComponents?: {\n [K in ChatUIToolName<TOptions>]?: ComponentType<ToolProps<TOptions, K>>\n }\n }) &\n (ChatUIHasNamedInterrupts<TOptions> extends true\n ? { interruptsComponents: ChatUIInterruptsComponents<TOptions> }\n : {\n interruptsComponents?: {\n tools?: ToolApprovalMap<TOptions>\n generic?: GenericInterruptComponents<TOptions>\n }\n })\n\n/** Scoped contexts, for widgets in other files or nested chat trees. */\nexport type ChatUIContextConfig = {\n chatContext?: ChatUIContexts['chatContext']\n partContext?: ChatUIContexts['partContext']\n interruptContext?: ChatUIContexts['interruptContext']\n}\n\nexport type ChatUIFactoryConfig<\n TOptions,\n TInput extends ComponentType<InputProps<TOptions>> | undefined =\n ComponentType<InputProps<TOptions>>,\n> = ChatUIComponents<TOptions, TInput> & {\n context?: ChatUIContextConfig\n}\n\ntype BoundWidget = ComponentType<Record<string, never>>\n\ntype PartMixins<TOptions> = {\n [K in ChatUIPartKey]?: BoundWidget\n} & {\n [K in ChatUIToolName<TOptions>]: BoundWidget\n} & {\n Render: BoundWidget\n}\n\ntype InterruptMixins<TOptions> = {\n [K in ChatUINamedInterruptId<TOptions>]: BoundWidget\n} & {\n [K in ChatUIToolName<TOptions>]?: BoundWidget\n} & {\n fallback?: BoundWidget\n Render: BoundWidget\n}\n\nfunction createWarnOnce() {\n const seen = new Set<string>()\n return (key: string, message: string) => {\n if (process.env.NODE_ENV === 'production') return\n if (seen.has(key)) return\n seen.add(key)\n console.warn(message)\n }\n}\n\nfunction readMessages<TOptions>(chat: ChatUIHost<TOptions>) {\n return chat.messages as ChatUIMessages<TOptions>\n}\n\nfunction readInterrupts<TOptions>(chat: ChatUIHost<TOptions>) {\n return chat.interrupts ?? []\n}\n\nfunction queueItemEqual(\n prev: { item: QueuedMessage; cancelQueued: (id: string) => void },\n next: { item: QueuedMessage; cancelQueued: (id: string) => void },\n) {\n return (\n prev.item.id === next.item.id &&\n prev.item.createdAt === next.item.createdAt &&\n prev.item.content === next.item.content &&\n prev.cancelQueued === next.cancelQueued\n )\n}\n\nfunction selectedPartPropsEqual(\n prev: { selected: ChatUISelectedPart },\n next: { selected: ChatUISelectedPart },\n) {\n if (prev.selected.key !== next.selected.key) return false\n if (prev.selected.part !== next.selected.part) return false\n if (prev.selected.key === 'toolCall' && next.selected.key === 'toolCall') {\n return (\n prev.selected.result === next.selected.result &&\n prev.selected.interrupt === next.selected.interrupt\n )\n }\n if (\n prev.selected.key === 'toolResult' &&\n next.selected.key === 'toolResult'\n ) {\n return prev.selected.matched === next.selected.matched\n }\n return true\n}\n\nfunction isSelectedPart(\n value: MessagePart | ChatUISelectedPart,\n): value is ChatUISelectedPart {\n return 'key' in value && 'part' in value\n}\n\nfunction bindMap(\n map: Record<string, ComponentType<any> | undefined> | undefined,\n bind: (component: ComponentType<any>) => BoundWidget,\n) {\n const out: Record<string, BoundWidget> = {}\n for (const [key, component] of Object.entries(map ?? {})) {\n if (component) out[key] = bind(component)\n }\n return out\n}\n\n/**\n * Bind chat options and UI widgets once at module scope. This matches Form\n * `createFormHook` and Table `createTableHook`: widgets register here, mix\n * onto Part / Interrupt / the UI kit, and automatic dispatch still walks the\n * message list.\n *\n * `chatOptions` is type-only at runtime. Pass optional `chatContext`,\n * `partContext`, and `interruptContext` from {@link createChatHookContexts}\n * when widgets live in other files or when nested chats need isolation.\n */\nexport function createChatUI<\n const TOptions,\n TInput extends ComponentType<any> | undefined =\n | ComponentType<InputProps<NoInfer<TOptions>>>\n | undefined,\n>(options: TOptions, config: ChatUIFactoryConfig<NoInfer<TOptions>, TInput>) {\n void options\n const {\n context: contextOption,\n components,\n partsComponents: parts,\n toolsComponents: tools,\n interruptsComponents: interrupts,\n } = config as ChatUIFactoryConfig<TOptions, TInput> & {\n toolsComponents?: Record<string, ComponentType<any> | undefined>\n interruptsComponents?: {\n tools?: Record<string, ComponentType<any> | undefined>\n generic?: Record<string, ComponentType<any> | undefined>\n }\n }\n const {\n layout: Layout,\n message: MessageComponent,\n input: InputComponent,\n queue: QueueItemComponent,\n } = components\n const {\n chatContext: chatContextOption,\n partContext: partContextOption,\n interruptContext: interruptContextOption,\n } = contextOption ?? {}\n const warn = createWarnOnce()\n const ChatContext = (chatContextOption ??\n defaultChatUIContexts.chatContext) as Context<ChatUIHost<TOptions> | null>\n const PartContext = (partContextOption ??\n defaultChatUIContexts.partContext) as Context<ChatUISelectedPart | null>\n const InterruptContext = (interruptContextOption ??\n defaultChatUIContexts.interruptContext) as Context<ChatUIInterrupt | null>\n const inlineToolNames = collectInlineToolNames(\n interrupts?.tools as Record<string, unknown> | undefined,\n Object.keys(tools ?? {}),\n )\n\n function useChatContext() {\n const chat = useContext(ChatContext)\n if (!chat) {\n throw new Error(\n '`useChatContext` must be used within `UI.Provider` or `UI.Chat`.',\n )\n }\n return chat\n }\n\n function usePartContext<TKey extends ChatUIPartKey = ChatUIPartKey>() {\n const selected = useContext(PartContext)\n if (!selected) {\n throw new Error(\n '`usePartContext` must be used within `UI.Part` or an automatic part.',\n )\n }\n return selected as ChatUISelectedPartOf<TOptions, TKey>\n }\n\n function useInterruptContext<\n TName extends ChatUIInterruptName<TOptions> = ChatUIInterruptName<TOptions>,\n >() {\n const interrupt = useContext(InterruptContext)\n if (!interrupt) {\n throw new Error(\n '`useInterruptContext` must be used within `UI.Interrupt`.',\n )\n }\n return interrupt as ChatUIInterruptOf<TOptions, TName>\n }\n\n function bindPart(Component: ComponentType<PartProps<TOptions>>) {\n return function BoundPart() {\n const selected = usePartContext()\n return <Component part={selected.part as PartProps<TOptions>['part']} />\n }\n }\n\n function bindTool(Component: ComponentType<ToolProps<TOptions>>) {\n return function BoundTool() {\n const selected = usePartContext()\n if (selected.key !== 'toolCall') return null\n return (\n <Component\n part={selected.part as ToolProps<TOptions>['part']}\n result={selected.result}\n interrupt={selected.interrupt as ToolProps<TOptions>['interrupt']}\n />\n )\n }\n }\n\n function bindInterrupt(Component: ComponentType<InterruptProps<TOptions>>) {\n return function BoundInterrupt() {\n const interrupt = useInterruptContext()\n return (\n <Component\n interrupt={interrupt as InterruptProps<TOptions>['interrupt']}\n />\n )\n }\n }\n\n const partMixins = bindMap(\n parts as Record<string, ComponentType<any> | undefined>,\n bindPart,\n )\n const toolMixins = bindMap(\n tools as Record<string, ComponentType<any> | undefined>,\n bindTool,\n )\n const interruptMixins = {\n ...bindMap(\n interrupts?.generic as\n | Record<string, ComponentType<any> | undefined>\n | undefined,\n bindInterrupt,\n ),\n ...bindMap(\n interrupts?.tools as\n | Record<string, ComponentType<any> | undefined>\n | undefined,\n bindInterrupt,\n ),\n }\n\n function mixPart(selected: ChatUISelectedPart) {\n return Object.assign({}, selected, partMixins, toolMixins, {\n Render: BoundRender,\n }) as ChatUISelectedPart & PartMixins<TOptions>\n }\n\n function mixInterrupt(interrupt: ChatUIInterrupt) {\n return Object.assign({}, interrupt, interruptMixins, {\n Render: BoundInterruptRender,\n }) as ChatUIInterrupt & InterruptMixins<TOptions>\n }\n\n function Provider({\n chat,\n children,\n }: {\n chat: ChatUIHost<TOptions>\n children?: ReactNode\n }) {\n return <ChatContext.Provider value={chat}>{children}</ChatContext.Provider>\n }\n\n // Backstop for when the conditional `Input` type cannot be inferred (see the\n // `input` note in docs/ui/react.md). The type hides `Input` when no `input`\n // is registered, but inference degrades on some config shapes, so always\n // supply a component: warn once rather than crash on an undefined element.\n function MissingInput() {\n warn(\n 'input',\n '[tanstack-ai-ui] Rendered <Input /> but no `input` component is registered.',\n )\n return null\n }\n\n // `Messages`, `Interrupts`, `Queue` and `InputComponent` are declared once\n // per factory, so these props are stable for the lifetime of the kit.\n const LayoutSlots = {\n Messages: Messages as ComponentType,\n Interrupts: Interrupts as ComponentType,\n Queue: Queue as ComponentType,\n Input: (InputComponent ?? MissingInput) as ComponentType,\n }\n\n function Chat({ chat }: { chat: ChatUIHost<TOptions> }) {\n return (\n <Provider chat={chat}>\n <Layout {...(LayoutSlots as ComponentProps<ComponentType<any>>)} />\n </Provider>\n )\n }\n\n function Queue() {\n const chat = useChatContext()\n if (!QueueItemComponent) return null\n const items = chat.queue\n return (\n <>\n {items.map((item) => (\n <QueueItemView\n key={item.id}\n cancelQueued={chat.cancelQueued}\n item={item}\n />\n ))}\n </>\n )\n }\n\n const QueueItemView = memo(function QueueItemView({\n item,\n cancelQueued,\n }: {\n item: QueuedMessage\n cancelQueued: (id: string) => void\n }) {\n if (!QueueItemComponent) return null\n return (\n <QueueItemComponent\n item={{\n ...item,\n cancelQueued: () => {\n cancelQueued(item.id)\n },\n }}\n />\n )\n }, queueItemEqual)\n\n function Messages({\n children,\n }: {\n children?: (messages: ChatUIMessages<TOptions>) => ReactNode\n } = {}) {\n const chat = useChatContext()\n const messages = readMessages(chat)\n const interrupts = readInterrupts(chat)\n if (children) return <>{children(messages)}</>\n return (\n <>\n {messages.map((message) => (\n <MessageView\n key={message.id}\n inlineToolNames={inlineToolNames}\n interrupts={interrupts}\n message={message}\n />\n ))}\n </>\n )\n }\n\n // Scoped to one message render. `Parts` reads it instead of closing over the\n // message, which is what keeps its identity constant across re-renders — a\n // fresh component per render would remount every part on each stream chunk.\n type MessageRenderValue = {\n message: ChatUIMessages<TOptions>[number]\n interrupts: ReadonlyArray<ChatUIInterrupt>\n inlineToolNames: ReadonlyArray<string>\n }\n const MessageRenderContext = createContext<MessageRenderValue | null>(null)\n\n function Parts() {\n const scope = useContext(MessageRenderContext)\n if (!scope) {\n throw new Error('`Parts` must be rendered by a `message` component.')\n }\n return (\n <AutomaticParts\n inlineToolNames={scope.inlineToolNames}\n interrupts={scope.interrupts}\n message={scope.message}\n />\n )\n }\n\n const MessageView = memo(function MessageView({\n message,\n interrupts,\n inlineToolNames: names,\n children,\n }: {\n message: ChatUIMessages<TOptions>[number]\n interrupts: ReadonlyArray<ChatUIInterrupt>\n inlineToolNames: ReadonlyArray<string>\n children?: (parts: Array<ChatUISelectedPart>) => ReactNode\n }) {\n const selected = selectMessageUI(message, {\n interrupts,\n inlineToolNames: names,\n })\n if (children) return <>{children(selected.parts)}</>\n return (\n <MessageRenderContext.Provider\n value={{ message, interrupts, inlineToolNames: names }}\n >\n <MessageComponent message={message} Parts={Parts} />\n </MessageRenderContext.Provider>\n )\n })\n\n function Message({\n message,\n children,\n }: {\n message: ChatUIMessages<TOptions>[number]\n children?: (parts: Array<ChatUISelectedPart>) => ReactNode\n }) {\n const chat = useChatContext()\n return (\n <MessageView\n children={children}\n inlineToolNames={inlineToolNames}\n interrupts={readInterrupts(chat)}\n message={message}\n />\n )\n }\n\n const AutomaticParts = memo(function AutomaticParts({\n message,\n interrupts,\n inlineToolNames: names,\n }: {\n message: ChatUIMessages<TOptions>[number]\n interrupts: ReadonlyArray<ChatUIInterrupt>\n inlineToolNames: ReadonlyArray<string>\n }) {\n const selected = selectMessageUI(message, {\n interrupts,\n inlineToolNames: names,\n })\n return (\n <>\n {automaticPartsForMessage(selected).map((part, index) => (\n <SelectedPartView key={`${message.id}-${index}`} selected={part} />\n ))}\n </>\n )\n })\n\n const SelectedPartInner = memo(function SelectedPartInner({\n selected,\n }: {\n selected: ChatUISelectedPart\n }) {\n if (selected.key === 'toolCall') {\n const name = selected.part.name\n const Tool = tools?.[name as ChatUIToolName<TOptions>] as\n | ComponentType<ToolProps<TOptions>>\n | undefined\n if (!Tool) {\n warn(`tool:${name}`, `[tanstack-ai-ui] Missing tools.${name} component`)\n return null\n }\n return (\n <Tool\n part={selected.part as ToolProps<TOptions>['part']}\n result={selected.result}\n interrupt={selected.interrupt as ToolProps<TOptions>['interrupt']}\n />\n )\n }\n\n const PartComponent = (parts[selected.key] ?? parts.fallback) as\n | ComponentType<PartProps<TOptions>>\n | undefined\n if (!PartComponent) {\n warn(\n `part:${selected.key}`,\n `[tanstack-ai-ui] Missing parts.${selected.key} component`,\n )\n return null\n }\n return <PartComponent part={selected.part as PartProps<TOptions>['part']} />\n }, selectedPartPropsEqual)\n\n const SelectedPartView = memo(function SelectedPartView({\n selected,\n }: {\n selected: ChatUISelectedPart\n }) {\n return (\n <PartContext.Provider value={selected}>\n <SelectedPartInner selected={selected} />\n </PartContext.Provider>\n )\n })\n\n function BoundRender() {\n const selected = usePartContext()\n return <SelectedPartInner selected={selected} />\n }\n\n function Part({\n part,\n children,\n }: {\n part: MessagePart | ChatUISelectedPart\n children?: (mixed: ChatUISelectedPart & PartMixins<TOptions>) => ReactNode\n }) {\n const chat = useChatContext()\n const selected = isSelectedPart(part)\n ? part\n : selectMessageUI(\n { id: 'part', role: 'assistant', parts: [part] },\n { interrupts: readInterrupts(chat), inlineToolNames: [] },\n ).parts[0]\n if (!selected) return null\n return (\n <PartContext.Provider value={selected}>\n {children ? (\n children(mixPart(selected))\n ) : (\n <SelectedPartInner selected={selected} />\n )}\n </PartContext.Provider>\n )\n }\n\n function Interrupts({\n children,\n }: {\n children?: (interrupts: ReadonlyArray<ChatUIInterrupt>) => ReactNode\n } = {}) {\n const chat = useChatContext()\n const selected = selectChatUI({\n messages: readMessages(chat),\n interrupts: readInterrupts(chat),\n inlineToolNames,\n })\n if (children) return <>{children(selected.interrupts)}</>\n return (\n <>\n {selected.interrupts.map((interrupt) => (\n <Interrupt key={interrupt.id} interrupt={interrupt} />\n ))}\n </>\n )\n }\n\n const InterruptInner = memo(function InterruptInner({\n interrupt,\n }: {\n interrupt: ChatUIInterrupt\n }) {\n const Component = resolveInterruptComponent(interrupt, interrupts) as\n | ComponentType<InterruptProps<TOptions>>\n | undefined\n if (!Component) {\n warn(\n `interrupt:${interrupt.id}`,\n `[tanstack-ai-ui] Missing interrupt component for ${interrupt.kind}`,\n )\n return null\n }\n return <Component interrupt={interrupt} />\n })\n\n function BoundInterruptRender() {\n const interrupt = useInterruptContext()\n return <InterruptInner interrupt={interrupt} />\n }\n\n const Interrupt = memo(function Interrupt({\n interrupt,\n children,\n }: {\n interrupt: ChatUIInterrupt\n children?: (mixed: ChatUIInterrupt & InterruptMixins<TOptions>) => ReactNode\n }) {\n return (\n <InterruptContext.Provider value={interrupt}>\n {children ? (\n children(mixInterrupt(interrupt))\n ) : (\n <InterruptInner interrupt={interrupt} />\n )}\n </InterruptContext.Provider>\n )\n })\n\n return {\n Chat,\n Provider,\n Messages,\n Message,\n Part,\n Interrupts,\n Interrupt,\n Queue,\n useChatContext,\n Input: InputComponent,\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { Options as ReactMarkdownOptions } from 'react-markdown';
2
+ export type PluggableList = NonNullable<ReactMarkdownOptions['remarkPlugins']>;
3
+ export declare const DEFAULT_REMARK_PLUGINS: PluggableList;
4
+ export declare const DEFAULT_REHYPE_PLUGINS_BEFORE_USER: PluggableList;
5
+ export declare const DEFAULT_REHYPE_PLUGINS_AFTER_USER: PluggableList;
6
+ export interface ResolveMarkdownPluginsOptions {
7
+ remarkPlugins?: PluggableList;
8
+ rehypePlugins?: PluggableList;
9
+ disableDefaultPlugins?: boolean;
10
+ }
11
+ export interface ResolvedMarkdownPlugins {
12
+ remarkPlugins: PluggableList;
13
+ rehypePlugins: PluggableList;
14
+ }
15
+ export declare function resolveMarkdownPlugins(options: ResolveMarkdownPluginsOptions): ResolvedMarkdownPlugins;
@@ -0,0 +1,28 @@
1
+ import remarkGfm from "remark-gfm";
2
+ import rehypeRaw from "rehype-raw";
3
+ import rehypeSanitize from "rehype-sanitize";
4
+ import rehypeHighlight from "rehype-highlight";
5
+ //#region src/chat-ui/markdown-plugins.ts
6
+ var DEFAULT_REMARK_PLUGINS = [remarkGfm];
7
+ var DEFAULT_REHYPE_PLUGINS_BEFORE_USER = [rehypeRaw, rehypeHighlight];
8
+ var DEFAULT_REHYPE_PLUGINS_AFTER_USER = [rehypeSanitize];
9
+ function resolveMarkdownPlugins(options) {
10
+ const userRemark = options.remarkPlugins ?? [];
11
+ const userRehype = options.rehypePlugins ?? [];
12
+ if (options.disableDefaultPlugins) return {
13
+ remarkPlugins: [...userRemark],
14
+ rehypePlugins: [...userRehype]
15
+ };
16
+ return {
17
+ remarkPlugins: [...DEFAULT_REMARK_PLUGINS, ...userRemark],
18
+ rehypePlugins: [
19
+ ...DEFAULT_REHYPE_PLUGINS_BEFORE_USER,
20
+ ...userRehype,
21
+ ...DEFAULT_REHYPE_PLUGINS_AFTER_USER
22
+ ]
23
+ };
24
+ }
25
+ //#endregion
26
+ export { DEFAULT_REHYPE_PLUGINS_AFTER_USER, DEFAULT_REHYPE_PLUGINS_BEFORE_USER, DEFAULT_REMARK_PLUGINS, resolveMarkdownPlugins };
27
+
28
+ //# sourceMappingURL=markdown-plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-plugins.js","names":[],"sources":["../../../src/chat-ui/markdown-plugins.ts"],"sourcesContent":["import remarkGfm from 'remark-gfm'\nimport rehypeRaw from 'rehype-raw'\nimport rehypeSanitize from 'rehype-sanitize'\nimport rehypeHighlight from 'rehype-highlight'\nimport type { Options as ReactMarkdownOptions } from 'react-markdown'\n\nexport type PluggableList = NonNullable<ReactMarkdownOptions['remarkPlugins']>\n\nexport const DEFAULT_REMARK_PLUGINS: PluggableList = [remarkGfm]\nexport const DEFAULT_REHYPE_PLUGINS_BEFORE_USER: PluggableList = [\n rehypeRaw,\n rehypeHighlight,\n]\nexport const DEFAULT_REHYPE_PLUGINS_AFTER_USER: PluggableList = [rehypeSanitize]\n\nexport interface ResolveMarkdownPluginsOptions {\n remarkPlugins?: PluggableList\n rehypePlugins?: PluggableList\n disableDefaultPlugins?: boolean\n}\n\nexport interface ResolvedMarkdownPlugins {\n remarkPlugins: PluggableList\n rehypePlugins: PluggableList\n}\n\nexport function resolveMarkdownPlugins(\n options: ResolveMarkdownPluginsOptions,\n): ResolvedMarkdownPlugins {\n const userRemark = options.remarkPlugins ?? []\n const userRehype = options.rehypePlugins ?? []\n\n if (options.disableDefaultPlugins) {\n return {\n remarkPlugins: [...userRemark],\n rehypePlugins: [...userRehype],\n }\n }\n\n return {\n remarkPlugins: [...DEFAULT_REMARK_PLUGINS, ...userRemark],\n rehypePlugins: [\n ...DEFAULT_REHYPE_PLUGINS_BEFORE_USER,\n ...userRehype,\n ...DEFAULT_REHYPE_PLUGINS_AFTER_USER,\n ],\n }\n}\n"],"mappings":";;;;;AAQA,IAAa,yBAAwC,CAAC,SAAS;AAC/D,IAAa,qCAAoD,CAC/D,WACA,eACF;AACA,IAAa,oCAAmD,CAAC,cAAc;AAa/E,SAAgB,uBACd,SACyB;CACzB,MAAM,aAAa,QAAQ,iBAAiB,CAAC;CAC7C,MAAM,aAAa,QAAQ,iBAAiB,CAAC;CAE7C,IAAI,QAAQ,uBACV,OAAO;EACL,eAAe,CAAC,GAAG,UAAU;EAC7B,eAAe,CAAC,GAAG,UAAU;CAC/B;CAGF,OAAO;EACL,eAAe,CAAC,GAAG,wBAAwB,GAAG,UAAU;EACxD,eAAe;GACb,GAAG;GACH,GAAG;GACH,GAAG;EACL;CACF;AACF"}
@@ -0,0 +1,57 @@
1
+ import { Components } from 'react-markdown';
2
+ import { PluggableList } from './markdown-plugins.js';
3
+ export interface TextPartProps {
4
+ /** The text content to render */
5
+ content: string;
6
+ /** The role of the message (user, assistant, or system) - optional for standalone use */
7
+ role?: 'user' | 'assistant' | 'system';
8
+ /** Base className applied to all text parts */
9
+ className?: string;
10
+ /** Additional className for user messages */
11
+ userClassName?: string;
12
+ /** Additional className for assistant messages (also used for system messages) */
13
+ assistantClassName?: string;
14
+ /**
15
+ * Additional remark plugins, appended after the defaults
16
+ * (or replacing them when `disableDefaultPlugins` is true).
17
+ */
18
+ remarkPlugins?: PluggableList;
19
+ /**
20
+ * Additional rehype plugins. Inserted between the built-in
21
+ * `rehypeRaw`/`rehypeHighlight` and the trailing `rehypeSanitize`
22
+ * so sanitization always runs last. When `disableDefaultPlugins`
23
+ * is true, replaces the entire chain.
24
+ */
25
+ rehypePlugins?: PluggableList;
26
+ /** react-markdown `components` overrides (e.g. custom `a`, `code`). */
27
+ components?: Components;
28
+ /**
29
+ * Drop the built-in plugin defaults entirely. The consumer becomes
30
+ * responsible for syntax highlighting, GFM, raw HTML handling, and
31
+ * sanitization. Use with care — disabling defaults removes the
32
+ * built-in XSS sanitizer.
33
+ */
34
+ disableDefaultPlugins?: boolean;
35
+ }
36
+ /**
37
+ * TextPart component - renders markdown text with syntax highlighting.
38
+ *
39
+ * @example Standalone usage
40
+ * ```tsx
41
+ * <TextPart
42
+ * content="Hello **world**!"
43
+ * role="user"
44
+ * className="p-4 rounded"
45
+ * userClassName="bg-blue-500"
46
+ * assistantClassName="bg-gray-500"
47
+ * />
48
+ * ```
49
+ *
50
+ * @example Add a markdown plugin (e.g. CJK bold/emphasis support)
51
+ * ```tsx
52
+ * import remarkCjkFriendly from 'remark-cjk-friendly'
53
+ *
54
+ * <TextPart content={content} remarkPlugins={[remarkCjkFriendly]} />
55
+ * ```
56
+ */
57
+ export declare function TextPart({ content, role, className, userClassName, assistantClassName, remarkPlugins, rehypePlugins, components, disableDefaultPlugins, }: TextPartProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,46 @@
1
+ import { resolveMarkdownPlugins } from "./markdown-plugins.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import ReactMarkdown from "react-markdown";
4
+ //#region src/chat-ui/text-part.tsx
5
+ /**
6
+ * TextPart component - renders markdown text with syntax highlighting.
7
+ *
8
+ * @example Standalone usage
9
+ * ```tsx
10
+ * <TextPart
11
+ * content="Hello **world**!"
12
+ * role="user"
13
+ * className="p-4 rounded"
14
+ * userClassName="bg-blue-500"
15
+ * assistantClassName="bg-gray-500"
16
+ * />
17
+ * ```
18
+ *
19
+ * @example Add a markdown plugin (e.g. CJK bold/emphasis support)
20
+ * ```tsx
21
+ * import remarkCjkFriendly from 'remark-cjk-friendly'
22
+ *
23
+ * <TextPart content={content} remarkPlugins={[remarkCjkFriendly]} />
24
+ * ```
25
+ */
26
+ function TextPart({ content, role, className = "", userClassName = "", assistantClassName = "", remarkPlugins, rehypePlugins, components, disableDefaultPlugins }) {
27
+ const combinedClassName = [className, role === "user" ? userClassName : role === "assistant" ? assistantClassName : ""].filter(Boolean).join(" ");
28
+ const resolved = resolveMarkdownPlugins({
29
+ remarkPlugins,
30
+ rehypePlugins,
31
+ disableDefaultPlugins
32
+ });
33
+ return /* @__PURE__ */ jsx("div", {
34
+ className: combinedClassName || void 0,
35
+ children: /* @__PURE__ */ jsx(ReactMarkdown, {
36
+ remarkPlugins: resolved.remarkPlugins,
37
+ rehypePlugins: resolved.rehypePlugins,
38
+ components,
39
+ children: content
40
+ })
41
+ });
42
+ }
43
+ //#endregion
44
+ export { TextPart };
45
+
46
+ //# sourceMappingURL=text-part.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-part.js","names":[],"sources":["../../../src/chat-ui/text-part.tsx"],"sourcesContent":["import ReactMarkdown from 'react-markdown'\nimport { resolveMarkdownPlugins } from './markdown-plugins'\nimport type { Components } from 'react-markdown'\nimport type { PluggableList } from './markdown-plugins'\n\nexport interface TextPartProps {\n /** The text content to render */\n content: string\n /** The role of the message (user, assistant, or system) - optional for standalone use */\n role?: 'user' | 'assistant' | 'system'\n /** Base className applied to all text parts */\n className?: string\n /** Additional className for user messages */\n userClassName?: string\n /** Additional className for assistant messages (also used for system messages) */\n assistantClassName?: string\n /**\n * Additional remark plugins, appended after the defaults\n * (or replacing them when `disableDefaultPlugins` is true).\n */\n remarkPlugins?: PluggableList\n /**\n * Additional rehype plugins. Inserted between the built-in\n * `rehypeRaw`/`rehypeHighlight` and the trailing `rehypeSanitize`\n * so sanitization always runs last. When `disableDefaultPlugins`\n * is true, replaces the entire chain.\n */\n rehypePlugins?: PluggableList\n /** react-markdown `components` overrides (e.g. custom `a`, `code`). */\n components?: Components\n /**\n * Drop the built-in plugin defaults entirely. The consumer becomes\n * responsible for syntax highlighting, GFM, raw HTML handling, and\n * sanitization. Use with care — disabling defaults removes the\n * built-in XSS sanitizer.\n */\n disableDefaultPlugins?: boolean\n}\n\n/**\n * TextPart component - renders markdown text with syntax highlighting.\n *\n * @example Standalone usage\n * ```tsx\n * <TextPart\n * content=\"Hello **world**!\"\n * role=\"user\"\n * className=\"p-4 rounded\"\n * userClassName=\"bg-blue-500\"\n * assistantClassName=\"bg-gray-500\"\n * />\n * ```\n *\n * @example Add a markdown plugin (e.g. CJK bold/emphasis support)\n * ```tsx\n * import remarkCjkFriendly from 'remark-cjk-friendly'\n *\n * <TextPart content={content} remarkPlugins={[remarkCjkFriendly]} />\n * ```\n */\nexport function TextPart({\n content,\n role,\n className = '',\n userClassName = '',\n assistantClassName = '',\n remarkPlugins,\n rehypePlugins,\n components,\n disableDefaultPlugins,\n}: TextPartProps) {\n const roleClassName =\n role === 'user'\n ? userClassName\n : role === 'assistant'\n ? assistantClassName\n : ''\n const combinedClassName = [className, roleClassName].filter(Boolean).join(' ')\n\n const resolved = resolveMarkdownPlugins({\n remarkPlugins,\n rehypePlugins,\n disableDefaultPlugins,\n })\n\n return (\n <div className={combinedClassName || undefined}>\n <ReactMarkdown\n remarkPlugins={resolved.remarkPlugins}\n rehypePlugins={resolved.rehypePlugins}\n components={components}\n >\n {content}\n </ReactMarkdown>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAgB,SAAS,EACvB,SACA,MACA,YAAY,IACZ,gBAAgB,IAChB,qBAAqB,IACrB,eACA,eACA,YACA,yBACgB;CAOhB,MAAM,oBAAoB,CAAC,WALzB,SAAS,SACL,gBACA,SAAS,cACP,qBACA,EAC2C,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;CAE7E,MAAM,WAAW,uBAAuB;EACtC;EACA;EACA;CACF,CAAC;CAED,OACE,oBAAC,OAAD;EAAK,WAAW,qBAAqB,KAAA;EACnC,UAAA,oBAAC,eAAD;GACE,eAAe,SAAS;GACxB,eAAe,SAAS;GACZ;GAEX,UAAA;EACY,CAAA;CACZ,CAAA;AAET"}
@@ -0,0 +1,41 @@
1
+ export interface ThinkingPartProps {
2
+ /** The thinking content to render */
3
+ content: string;
4
+ /** Base className applied to thinking parts */
5
+ className?: string;
6
+ /** Whether thinking is complete (has text content after) */
7
+ isComplete?: boolean;
8
+ }
9
+ /**
10
+ * ThinkingPart component - renders thinking/reasoning content
11
+ *
12
+ * This component displays the model's internal reasoning process,
13
+ * typically shown in a collapsed or expandable format to distinguish
14
+ * it from the final response. It automatically collapses when thinking
15
+ * is complete.
16
+ *
17
+ * @example Standalone usage
18
+ * ```tsx
19
+ * <ThinkingPart
20
+ * content="Let me think about this step by step..."
21
+ * className="p-4 rounded bg-gray-100"
22
+ * />
23
+ * ```
24
+ *
25
+ * @example Usage in partRenderers
26
+ * ```tsx
27
+ * <ChatMessage
28
+ * message={message}
29
+ * partRenderers={{
30
+ * thinking: ({ content, isComplete }) => (
31
+ * <ThinkingPart
32
+ * content={content}
33
+ * isComplete={isComplete}
34
+ * className="px-5 py-3 rounded-2xl bg-gray-800/50"
35
+ * />
36
+ * )
37
+ * }}
38
+ * />
39
+ * ```
40
+ */
41
+ export declare function ThinkingPart({ content, className, isComplete, }: ThinkingPartProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,73 @@
1
+ import { useEffect, useState } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/chat-ui/thinking-part.tsx
4
+ /**
5
+ * ThinkingPart component - renders thinking/reasoning content
6
+ *
7
+ * This component displays the model's internal reasoning process,
8
+ * typically shown in a collapsed or expandable format to distinguish
9
+ * it from the final response. It automatically collapses when thinking
10
+ * is complete.
11
+ *
12
+ * @example Standalone usage
13
+ * ```tsx
14
+ * <ThinkingPart
15
+ * content="Let me think about this step by step..."
16
+ * className="p-4 rounded bg-gray-100"
17
+ * />
18
+ * ```
19
+ *
20
+ * @example Usage in partRenderers
21
+ * ```tsx
22
+ * <ChatMessage
23
+ * message={message}
24
+ * partRenderers={{
25
+ * thinking: ({ content, isComplete }) => (
26
+ * <ThinkingPart
27
+ * content={content}
28
+ * isComplete={isComplete}
29
+ * className="px-5 py-3 rounded-2xl bg-gray-800/50"
30
+ * />
31
+ * )
32
+ * }}
33
+ * />
34
+ * ```
35
+ */
36
+ function ThinkingPart({ content, className = "", isComplete = false }) {
37
+ const [isCollapsed, setIsCollapsed] = useState(false);
38
+ useEffect(() => {
39
+ if (isComplete) setIsCollapsed(true);
40
+ }, [isComplete]);
41
+ return /* @__PURE__ */ jsxs("div", {
42
+ className: className || void 0,
43
+ "data-part-type": "thinking",
44
+ "data-part-content": true,
45
+ children: [/* @__PURE__ */ jsxs("button", {
46
+ onClick: () => setIsCollapsed(!isCollapsed),
47
+ className: "flex items-center gap-2 text-sm text-gray-400 hover:text-gray-300 transition-colors mb-2",
48
+ "aria-expanded": !isCollapsed,
49
+ "aria-label": isCollapsed ? "Expand thinking" : "Collapse thinking",
50
+ children: [
51
+ /* @__PURE__ */ jsx("span", {
52
+ className: "text-xs",
53
+ children: isCollapsed ? "▶" : "▼"
54
+ }),
55
+ /* @__PURE__ */ jsx("span", {
56
+ className: "italic",
57
+ children: "💭 Thinking..."
58
+ }),
59
+ isComplete && /* @__PURE__ */ jsx("span", {
60
+ className: "text-xs text-gray-500",
61
+ children: "(complete)"
62
+ })
63
+ ]
64
+ }), !isCollapsed && /* @__PURE__ */ jsx("div", {
65
+ className: "text-gray-300 whitespace-pre-wrap font-mono text-sm",
66
+ children: content
67
+ })]
68
+ });
69
+ }
70
+ //#endregion
71
+ export { ThinkingPart };
72
+
73
+ //# sourceMappingURL=thinking-part.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-part.js","names":[],"sources":["../../../src/chat-ui/thinking-part.tsx"],"sourcesContent":["import { useEffect, useState } from 'react'\n\nexport interface ThinkingPartProps {\n /** The thinking content to render */\n content: string\n /** Base className applied to thinking parts */\n className?: string\n /** Whether thinking is complete (has text content after) */\n isComplete?: boolean\n}\n\n/**\n * ThinkingPart component - renders thinking/reasoning content\n *\n * This component displays the model's internal reasoning process,\n * typically shown in a collapsed or expandable format to distinguish\n * it from the final response. It automatically collapses when thinking\n * is complete.\n *\n * @example Standalone usage\n * ```tsx\n * <ThinkingPart\n * content=\"Let me think about this step by step...\"\n * className=\"p-4 rounded bg-gray-100\"\n * />\n * ```\n *\n * @example Usage in partRenderers\n * ```tsx\n * <ChatMessage\n * message={message}\n * partRenderers={{\n * thinking: ({ content, isComplete }) => (\n * <ThinkingPart\n * content={content}\n * isComplete={isComplete}\n * className=\"px-5 py-3 rounded-2xl bg-gray-800/50\"\n * />\n * )\n * }}\n * />\n * ```\n */\nexport function ThinkingPart({\n content,\n className = '',\n isComplete = false,\n}: ThinkingPartProps) {\n const [isCollapsed, setIsCollapsed] = useState(false)\n\n // Auto-collapse when thinking completes\n useEffect(() => {\n if (isComplete) {\n setIsCollapsed(true)\n }\n }, [isComplete])\n\n return (\n <div\n className={className || undefined}\n data-part-type=\"thinking\"\n data-part-content\n >\n <button\n onClick={() => setIsCollapsed(!isCollapsed)}\n className=\"flex items-center gap-2 text-sm text-gray-400 hover:text-gray-300 transition-colors mb-2\"\n aria-expanded={!isCollapsed}\n aria-label={isCollapsed ? 'Expand thinking' : 'Collapse thinking'}\n >\n <span className=\"text-xs\">{isCollapsed ? '▶' : '▼'}</span>\n <span className=\"italic\">💭 Thinking...</span>\n {isComplete && (\n <span className=\"text-xs text-gray-500\">(complete)</span>\n )}\n </button>\n {!isCollapsed && (\n <div className=\"text-gray-300 whitespace-pre-wrap font-mono text-sm\">\n {content}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,aAAa,EAC3B,SACA,YAAY,IACZ,aAAa,SACO;CACpB,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CAGpD,gBAAgB;EACd,IAAI,YACF,eAAe,IAAI;CAEvB,GAAG,CAAC,UAAU,CAAC;CAEf,OACE,qBAAC,OAAD;EACE,WAAW,aAAa,KAAA;EACxB,kBAAe;EACf,qBAAA;EAHF,UAAA,CAKE,qBAAC,UAAD;GACE,eAAe,eAAe,CAAC,WAAW;GAC1C,WAAU;GACV,iBAAe,CAAC;GAChB,cAAY,cAAc,oBAAoB;GAJhD,UAAA;IAME,oBAAC,QAAD;KAAM,WAAU;KAAW,UAAA,cAAc,MAAM;IAAU,CAAA;IACzD,oBAAC,QAAD;KAAM,WAAU;KAAS,UAAA;IAAoB,CAAA;IAC5C,cACC,oBAAC,QAAD;KAAM,WAAU;KAAwB,UAAA;IAAgB,CAAA;GAEpD;EACP,CAAA,GAAA,CAAC,eACA,oBAAC,OAAD;GAAK,WAAU;GACZ,UAAA;EACE,CAAA,CAEJ;;AAET"}