@tanstack/ai-preact 0.14.3 → 0.15.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/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 +154 -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/create-chat-hook.d.ts +22 -0
- package/dist/esm/create-chat-hook.js +24 -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 +3 -0
- package/dist/esm/ui.js +4 -0
- package/package.json +8 -4
- package/src/chat-ui/create-chat-hook.tsx +98 -0
- package/src/chat-ui/create-ui-contexts.tsx +57 -0
- package/src/chat-ui/create-ui.tsx +764 -0
- package/src/create-chat-hook.ts +47 -0
- package/src/index.ts +1 -0
- package/src/ui.ts +20 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { defaultChatUIContexts } from "./create-ui-contexts.js";
|
|
2
|
+
import { createContext, memo, useContext } from "preact/compat";
|
|
3
|
+
import { Fragment, jsx } from "preact/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 'preact/compat'\nimport type { ComponentChildren } from 'preact'\nimport type { ComponentProps, ComponentType, Context } from 'preact/compat'\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 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 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?: ComponentChildren\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: QueuedMessage) => (\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>) => ComponentChildren\n } = {}) {\n const chat = useChatContext()\n const messages = readMessages(chat) as ChatUIMessages<TOptions>\n const interrupts = readInterrupts(chat)\n if (children) return <>{children(messages)}</>\n return (\n <>\n {messages.map((message: ChatUIMessages<TOptions>[number]) => (\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>) => ComponentChildren\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>) => ComponentChildren\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?: (\n mixed: ChatUISelectedPart & PartMixins<TOptions>,\n ) => ComponentChildren\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>) => ComponentChildren\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?: (\n mixed: ChatUIInterrupt & InterruptMixins<TOptions>,\n ) => ComponentChildren\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,22 @@
|
|
|
1
|
+
import { AnyClientTool, InterruptDefinition } from '@tanstack/ai/client';
|
|
2
|
+
import { InferredClientContext } from '@tanstack/ai-client';
|
|
3
|
+
import { UseChatOptions } from './types.js';
|
|
4
|
+
type ChatHookOverrides<TTools extends ReadonlyArray<AnyClientTool>, TContext, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>> = {
|
|
5
|
+
threadId?: string;
|
|
6
|
+
live?: boolean;
|
|
7
|
+
forwardedProps?: Record<string, any>;
|
|
8
|
+
body?: Record<string, any>;
|
|
9
|
+
initialMessages?: UseChatOptions<TTools, TContext, TInterrupts>['initialMessages'];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Bind chat options once at module scope. The returned `useChat` hook
|
|
13
|
+
* creates a chat instance from those options.
|
|
14
|
+
*
|
|
15
|
+
* Pass per-call overrides for instance keys such as `threadId`,
|
|
16
|
+
* `initialMessages`, `live`, and `forwardedProps`. Do not change `tools`,
|
|
17
|
+
* `interrupts`, or `outputSchema` here. Those stay on the factory options.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createChatHook<const TTools extends ReadonlyArray<AnyClientTool> = any, TContext = InferredClientContext<TTools>, const TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = readonly []>(options: UseChatOptions<TTools, TContext, TInterrupts>): {
|
|
20
|
+
useChat: (overrides?: ChatHookOverrides<TTools, TContext, TInterrupts>) => import('./types.js').UseChatReturn<TTools, TInterrupts>;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useChat } from "./use-chat.js";
|
|
2
|
+
//#region src/create-chat-hook.ts
|
|
3
|
+
/**
|
|
4
|
+
* Bind chat options once at module scope. The returned `useChat` hook
|
|
5
|
+
* creates a chat instance from those options.
|
|
6
|
+
*
|
|
7
|
+
* Pass per-call overrides for instance keys such as `threadId`,
|
|
8
|
+
* `initialMessages`, `live`, and `forwardedProps`. Do not change `tools`,
|
|
9
|
+
* `interrupts`, or `outputSchema` here. Those stay on the factory options.
|
|
10
|
+
*/
|
|
11
|
+
function createChatHook(options) {
|
|
12
|
+
function useChat$1(overrides) {
|
|
13
|
+
if (!overrides) return useChat(options);
|
|
14
|
+
return useChat({
|
|
15
|
+
...options,
|
|
16
|
+
...overrides
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return { useChat: useChat$1 };
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { createChatHook };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=create-chat-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-chat-hook.js","names":[],"sources":["../../src/create-chat-hook.ts"],"sourcesContent":["import { useChat as useUnboundChat } from './use-chat'\nimport type { AnyClientTool, InterruptDefinition } from '@tanstack/ai/client'\nimport type { InferredClientContext } from '@tanstack/ai-client'\nimport type { UseChatOptions } from './types'\n\ntype ChatHookOverrides<\n TTools extends ReadonlyArray<AnyClientTool>,\n TContext,\n TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,\n> = {\n threadId?: string\n live?: boolean\n forwardedProps?: Record<string, any>\n body?: Record<string, any>\n initialMessages?: UseChatOptions<\n TTools,\n TContext,\n TInterrupts\n >['initialMessages']\n}\n\n/**\n * Bind chat options once at module scope. The returned `useChat` hook\n * creates a chat instance from those options.\n *\n * Pass per-call overrides for instance keys such as `threadId`,\n * `initialMessages`, `live`, and `forwardedProps`. Do not change `tools`,\n * `interrupts`, or `outputSchema` here. Those stay on the factory options.\n */\nexport function createChatHook<\n const TTools extends ReadonlyArray<AnyClientTool> = any,\n TContext = InferredClientContext<TTools>,\n const TInterrupts extends ReadonlyArray<\n InterruptDefinition<any, any, any, any>\n > = readonly [],\n>(options: UseChatOptions<TTools, TContext, TInterrupts>) {\n function useChat(\n overrides?: ChatHookOverrides<TTools, TContext, TInterrupts>,\n ) {\n if (!overrides) {\n return useUnboundChat(options)\n }\n return useUnboundChat({ ...options, ...overrides })\n }\n\n return { useChat }\n}\n"],"mappings":";;;;;;;;;;AA6BA,SAAgB,eAMd,SAAwD;CACxD,SAAS,UACP,WACA;EACA,IAAI,CAAC,WACH,OAAO,QAAe,OAAO;EAE/B,OAAO,QAAe;GAAE,GAAG;GAAS,GAAG;EAAU,CAAC;CACpD;CAEA,OAAO,EAAE,SAAA,UAAQ;AACnB"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { useChat } from './use-chat.js';
|
|
2
|
+
export { createChatHook } from './create-chat-hook.js';
|
|
2
3
|
export { useByok } from './use-byok.js';
|
|
3
4
|
export { useMcpAppBridge } from './use-mcp-app-bridge.js';
|
|
4
5
|
export type { UseMcpAppBridgeOptions } from './use-mcp-app-bridge.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useChat } from "./use-chat.js";
|
|
2
|
+
import { createChatHook } from "./create-chat-hook.js";
|
|
2
3
|
import { useByok } from "./use-byok.js";
|
|
3
4
|
import { useMcpAppBridge } from "./use-mcp-app-bridge.js";
|
|
4
5
|
import { StorageUnavailableError, createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, xhrHttpStream, xhrServerSentEvents } from "@tanstack/ai-client";
|
|
5
|
-
export { StorageUnavailableError, createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, useByok, useChat, useMcpAppBridge, xhrHttpStream, xhrServerSentEvents };
|
|
6
|
+
export { StorageUnavailableError, createChatClientOptions, createChatHook, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, useByok, useChat, useMcpAppBridge, xhrHttpStream, xhrServerSentEvents };
|
package/dist/esm/ui.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createChatUI, type ChatUIComponents, type ChatUIFactoryConfig, type ChatUIHost, type ChatUIQueueItem, type InputProps, type InterruptProps, type LayoutProps, type MessageProps, type PartProps, type QueueProps, type ToolProps, } from './chat-ui/create-ui.js';
|
|
2
|
+
export { createChatHook } from './chat-ui/create-chat-hook.js';
|
|
3
|
+
export { createChatHookContexts, type ChatUIContexts, } from './chat-ui/create-ui-contexts.js';
|
package/dist/esm/ui.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-preact",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Preact hooks for TanStack AI streaming chat and typed messages.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"./mcp-apps": {
|
|
29
29
|
"types": "./dist/esm/mcp-apps.d.ts",
|
|
30
30
|
"import": "./dist/esm/mcp-apps.js"
|
|
31
|
+
},
|
|
32
|
+
"./ui": {
|
|
33
|
+
"types": "./dist/esm/ui.d.ts",
|
|
34
|
+
"import": "./dist/esm/ui.js"
|
|
31
35
|
}
|
|
32
36
|
},
|
|
33
37
|
"files": [
|
|
@@ -46,7 +50,7 @@
|
|
|
46
50
|
"tool-calling"
|
|
47
51
|
],
|
|
48
52
|
"dependencies": {
|
|
49
|
-
"@tanstack/ai-client": "^0.
|
|
53
|
+
"@tanstack/ai-client": "^0.30.0"
|
|
50
54
|
},
|
|
51
55
|
"devDependencies": {
|
|
52
56
|
"@mcp-ui/client": "^7.1.1",
|
|
@@ -55,12 +59,12 @@
|
|
|
55
59
|
"jsdom": "^27.4.0",
|
|
56
60
|
"preact": "^10.26.9",
|
|
57
61
|
"vite": "^8.2.1",
|
|
58
|
-
"@tanstack/ai": "0.
|
|
62
|
+
"@tanstack/ai": "0.52.1"
|
|
59
63
|
},
|
|
60
64
|
"peerDependencies": {
|
|
61
65
|
"@mcp-ui/client": "^7",
|
|
62
66
|
"preact": ">=10.11.0",
|
|
63
|
-
"@tanstack/ai": "^0.
|
|
67
|
+
"@tanstack/ai": "^0.52.1"
|
|
64
68
|
},
|
|
65
69
|
"peerDependenciesMeta": {
|
|
66
70
|
"@mcp-ui/client": {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useMemo, useRef } from 'preact/compat'
|
|
2
|
+
import type { ComponentType } from 'preact/compat'
|
|
3
|
+
import type { InferredClientContext } from '@tanstack/ai-client'
|
|
4
|
+
import type { ChatUIInterruptsOf, ChatUIToolsOf } from '@tanstack/ai-client/ui'
|
|
5
|
+
import { useChat as useUnboundChat } from '../use-chat'
|
|
6
|
+
import type { UseChatOptions } from '../types'
|
|
7
|
+
import { createChatUI } from './create-ui'
|
|
8
|
+
import type { ChatUIFactoryConfig, ChatUIHost, InputProps } from './create-ui'
|
|
9
|
+
|
|
10
|
+
type HeadlessOptions<TOptions> = UseChatOptions<
|
|
11
|
+
ChatUIToolsOf<TOptions>,
|
|
12
|
+
InferredClientContext<ChatUIToolsOf<TOptions>>,
|
|
13
|
+
ChatUIInterruptsOf<TOptions>
|
|
14
|
+
>
|
|
15
|
+
|
|
16
|
+
type ChatInstanceOverrides<TOptions> = {
|
|
17
|
+
threadId?: string
|
|
18
|
+
live?: boolean
|
|
19
|
+
forwardedProps?: Record<string, any>
|
|
20
|
+
body?: Record<string, any>
|
|
21
|
+
initialMessages?: HeadlessOptions<TOptions>['initialMessages']
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bind chat options and UI widgets once at module scope.
|
|
26
|
+
*
|
|
27
|
+
* This matches Form `createFormHook` and Table `createTableHook`. The
|
|
28
|
+
* returned `useAppChat` creates a chat instance. `chat.AppChat` renders it.
|
|
29
|
+
* `useChatContext` reads that instance. Part and interrupt widgets take
|
|
30
|
+
* `part` / `interrupt` as props. Do not read those from context.
|
|
31
|
+
*
|
|
32
|
+
* Pass instance overrides such as `threadId` into `useAppChat()`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* const { useAppChat, useChatContext } = createChatHook({
|
|
37
|
+
* options: chatOptions,
|
|
38
|
+
* components: { layout: ChatLayout, message: ChatMessage },
|
|
39
|
+
* partsComponents: { fallback: FallbackPart },
|
|
40
|
+
* toolsComponents: { getWeather: WeatherTool, purchaseItem: PurchaseTool },
|
|
41
|
+
* interruptsComponents: { generic: { choosePlan: ChoosePlan } },
|
|
42
|
+
* })
|
|
43
|
+
*
|
|
44
|
+
* function Support() {
|
|
45
|
+
* const chat = useAppChat({ threadId: 'support-1' })
|
|
46
|
+
* return <chat.AppChat />
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function createChatHook<
|
|
51
|
+
const TOptions,
|
|
52
|
+
TInput extends ComponentType<any> | undefined =
|
|
53
|
+
| ComponentType<InputProps<NoInfer<TOptions>>>
|
|
54
|
+
| undefined,
|
|
55
|
+
>({
|
|
56
|
+
options,
|
|
57
|
+
...chatComponents
|
|
58
|
+
}: {
|
|
59
|
+
options: TOptions
|
|
60
|
+
} & ChatUIFactoryConfig<NoInfer<TOptions>, TInput>) {
|
|
61
|
+
const ui = createChatUI(
|
|
62
|
+
options,
|
|
63
|
+
chatComponents as ChatUIFactoryConfig<NoInfer<TOptions>, TInput>,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
function useAppChat(overrides?: ChatInstanceOverrides<TOptions>) {
|
|
67
|
+
const chat = (
|
|
68
|
+
overrides
|
|
69
|
+
? useUnboundChat({
|
|
70
|
+
...(options as HeadlessOptions<TOptions>),
|
|
71
|
+
...overrides,
|
|
72
|
+
})
|
|
73
|
+
: useUnboundChat(options as HeadlessOptions<TOptions>)
|
|
74
|
+
) as ChatUIHost<TOptions>
|
|
75
|
+
|
|
76
|
+
// `useChat` returns a fresh object on every render. The mixed Chat
|
|
77
|
+
// component must stay stable, or React remounts the tree on each
|
|
78
|
+
// update. Read the current instance from a ref.
|
|
79
|
+
const chatRef = useRef(chat)
|
|
80
|
+
chatRef.current = chat
|
|
81
|
+
|
|
82
|
+
const AppChat = useMemo(() => {
|
|
83
|
+
function AppChatImpl() {
|
|
84
|
+
return <ui.Chat chat={chatRef.current} />
|
|
85
|
+
}
|
|
86
|
+
return AppChatImpl
|
|
87
|
+
}, [])
|
|
88
|
+
|
|
89
|
+
return Object.assign(chat, { AppChat }) as ChatUIHost<TOptions> & {
|
|
90
|
+
AppChat: ComponentType
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
useAppChat,
|
|
96
|
+
useChatContext: ui.useChatContext,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createContext, useContext } from 'preact/compat'
|
|
2
|
+
import type { Context } from 'preact/compat'
|
|
3
|
+
import type {
|
|
4
|
+
ChatUIInterrupt,
|
|
5
|
+
ChatUISelectedPart,
|
|
6
|
+
} from '@tanstack/ai-client/ui'
|
|
7
|
+
import type { UseChatReturn } from '../types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Scoped chat, part, and interrupt contexts plus matching context hooks.
|
|
11
|
+
* Pass these into {@link createChatHook} when widgets live in other files, or
|
|
12
|
+
* when two chat trees nest and must not share the default contexts.
|
|
13
|
+
*/
|
|
14
|
+
export interface ChatUIContexts {
|
|
15
|
+
chatContext: Context<UseChatReturn<any, any> | null>
|
|
16
|
+
partContext: Context<ChatUISelectedPart | null>
|
|
17
|
+
interruptContext: Context<ChatUIInterrupt | null>
|
|
18
|
+
useChatContext: () => UseChatReturn<any, any>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a fresh set of chat UI contexts. This matches Form
|
|
23
|
+
* `createFormHookContexts` and Table `createTableHookContexts`.
|
|
24
|
+
*
|
|
25
|
+
* Most apps can skip this. `createChatHook` uses shared module contexts by
|
|
26
|
+
* default. The hooks on this object are the escape hatch for a widget file
|
|
27
|
+
* that cannot import the `createChatHook` result (circular import), or for
|
|
28
|
+
* nested chats that need isolated providers.
|
|
29
|
+
*
|
|
30
|
+
* Prefer the `useChatContext` hook returned from `createChatHook` when you
|
|
31
|
+
* can import that result. That hook knows the `chatComponents` types. Part
|
|
32
|
+
* and interrupt widgets take `part` / `interrupt` as props.
|
|
33
|
+
*/
|
|
34
|
+
export function createChatHookContexts(): ChatUIContexts {
|
|
35
|
+
const chatContext = createContext<UseChatReturn<any, any> | null>(null)
|
|
36
|
+
const partContext = createContext<ChatUISelectedPart | null>(null)
|
|
37
|
+
const interruptContext = createContext<ChatUIInterrupt | null>(null)
|
|
38
|
+
|
|
39
|
+
function useChatContext() {
|
|
40
|
+
const chat = useContext(chatContext)
|
|
41
|
+
if (!chat) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
'`useChatContext` must be used within `UI.Provider` or `UI.Chat`.',
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return chat
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
chatContext,
|
|
51
|
+
partContext,
|
|
52
|
+
interruptContext,
|
|
53
|
+
useChatContext,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const defaultChatUIContexts = createChatHookContexts()
|