@tanstack/ai-svelte 0.19.3 → 0.20.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 (43) hide show
  1. package/dist/chat-ui/create-chat-hook.d.ts +27 -0
  2. package/dist/chat-ui/create-chat-hook.js +26 -0
  3. package/dist/chat-ui/create-ui.d.ts +131 -0
  4. package/dist/chat-ui/create-ui.js +100 -0
  5. package/dist/chat-ui/selected-part.svelte +45 -0
  6. package/dist/chat-ui/selected-part.svelte.d.ts +9 -0
  7. package/dist/chat-ui/ui-chat.svelte +42 -0
  8. package/dist/chat-ui/ui-chat.svelte.d.ts +9 -0
  9. package/dist/chat-ui/ui-interrupt.svelte +30 -0
  10. package/dist/chat-ui/ui-interrupt.svelte.d.ts +9 -0
  11. package/dist/chat-ui/ui-interrupts.svelte +30 -0
  12. package/dist/chat-ui/ui-interrupts.svelte.d.ts +9 -0
  13. package/dist/chat-ui/ui-message.svelte +45 -0
  14. package/dist/chat-ui/ui-message.svelte.d.ts +12 -0
  15. package/dist/chat-ui/ui-messages.svelte +31 -0
  16. package/dist/chat-ui/ui-messages.svelte.d.ts +10 -0
  17. package/dist/chat-ui/ui-part.svelte +30 -0
  18. package/dist/chat-ui/ui-part.svelte.d.ts +9 -0
  19. package/dist/chat-ui/ui-provider.svelte +29 -0
  20. package/dist/chat-ui/ui-provider.svelte.d.ts +10 -0
  21. package/dist/chat-ui/ui-queue.svelte +21 -0
  22. package/dist/chat-ui/ui-queue.svelte.d.ts +7 -0
  23. package/dist/create-chat-hook.d.ts +35 -0
  24. package/dist/create-chat-hook.js +31 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/ui.d.ts +11 -0
  28. package/dist/ui.js +11 -0
  29. package/package.json +9 -4
  30. package/src/chat-ui/create-chat-hook.ts +62 -0
  31. package/src/chat-ui/create-ui.ts +310 -0
  32. package/src/chat-ui/selected-part.svelte +45 -0
  33. package/src/chat-ui/ui-chat.svelte +42 -0
  34. package/src/chat-ui/ui-interrupt.svelte +30 -0
  35. package/src/chat-ui/ui-interrupts.svelte +30 -0
  36. package/src/chat-ui/ui-message.svelte +45 -0
  37. package/src/chat-ui/ui-messages.svelte +31 -0
  38. package/src/chat-ui/ui-part.svelte +30 -0
  39. package/src/chat-ui/ui-provider.svelte +29 -0
  40. package/src/chat-ui/ui-queue.svelte +21 -0
  41. package/src/create-chat-hook.ts +67 -0
  42. package/src/index.ts +1 -0
  43. package/src/ui.ts +25 -0
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import {
3
+ getChatContext,
4
+ getComponentsContext,
5
+ readQueue,
6
+ type UIDescriptor,
7
+ } from './create-ui'
8
+
9
+ let { ui }: { ui: UIDescriptor } = $props()
10
+
11
+ const chat = $derived(getChatContext(ui))
12
+ const comps = $derived(getComponentsContext(ui))
13
+ const items = $derived(readQueue(chat))
14
+ const QueueItem = $derived(comps.components.queue as any)
15
+ </script>
16
+
17
+ {#if QueueItem}
18
+ {#each items as item (item.id)}
19
+ <QueueItem {item} />
20
+ {/each}
21
+ {/if}
@@ -0,0 +1,7 @@
1
+ import { type UIDescriptor } from './create-ui';
2
+ type $$ComponentProps = {
3
+ ui: UIDescriptor;
4
+ };
5
+ declare const UiQueue: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type UiQueue = ReturnType<typeof UiQueue>;
7
+ export default UiQueue;
@@ -0,0 +1,35 @@
1
+ import type { AnyClientTool, InterruptDefinition, SchemaInput } from '@tanstack/ai';
2
+ import type { InferredClientContext } from '@tanstack/ai-client';
3
+ import type { CreateChatOptions } from './types';
4
+ type ChatHookOverrides<TTools extends ReadonlyArray<AnyClientTool>, TSchema extends SchemaInput | undefined, 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?: CreateChatOptions<TTools, TSchema, TContext, TInterrupts>['initialMessages'];
10
+ };
11
+ /**
12
+ * Bind chat options once at module scope. The returned `createChat`
13
+ * function 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
+ * Rename the function at the call site if you already import `createChat` from
20
+ * `@tanstack/ai-svelte`: `const { createChat: createAppChat } = createChatHook(chatOptions)`.
21
+ *
22
+ * @example
23
+ * ```svelte
24
+ * <script>
25
+ * import { createChatHook } from '@tanstack/ai-svelte'
26
+ *
27
+ * const { createChat } = createChatHook(chatOptions)
28
+ * const chat = createChat({ threadId: 'support-1' })
29
+ * </script>
30
+ * ```
31
+ */
32
+ export declare function createChatHook<const TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>, const TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = readonly []>(options: CreateChatOptions<TTools, TSchema, TContext, TInterrupts>): {
33
+ createChat: (overrides?: ChatHookOverrides<TTools, TSchema, TContext, TInterrupts>) => import("./types").CreateChatReturn<TTools, TSchema, TContext, TInterrupts>;
34
+ };
35
+ export {};
@@ -0,0 +1,31 @@
1
+ import { createChat as createUnboundChat } from './create-chat.svelte';
2
+ /**
3
+ * Bind chat options once at module scope. The returned `createChat`
4
+ * function creates a chat instance from those options.
5
+ *
6
+ * Pass per-call overrides for instance keys such as `threadId`,
7
+ * `initialMessages`, `live`, and `forwardedProps`. Do not change `tools`,
8
+ * `interrupts`, or `outputSchema` here. Those stay on the factory options.
9
+ *
10
+ * Rename the function at the call site if you already import `createChat` from
11
+ * `@tanstack/ai-svelte`: `const { createChat: createAppChat } = createChatHook(chatOptions)`.
12
+ *
13
+ * @example
14
+ * ```svelte
15
+ * <script>
16
+ * import { createChatHook } from '@tanstack/ai-svelte'
17
+ *
18
+ * const { createChat } = createChatHook(chatOptions)
19
+ * const chat = createChat({ threadId: 'support-1' })
20
+ * </script>
21
+ * ```
22
+ */
23
+ export function createChatHook(options) {
24
+ function createChat(overrides) {
25
+ if (!overrides) {
26
+ return createUnboundChat(options);
27
+ }
28
+ return createUnboundChat({ ...options, ...overrides });
29
+ }
30
+ return { createChat };
31
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { createChat } from './create-chat.svelte';
2
+ export { createChatHook } from './create-chat-hook';
2
3
  export { createByok } from './create-byok.svelte';
3
4
  export type { CreateChatOptions, CreateChatReturn, DeepPartial, UIMessage, ChatRequestBody, QueuedMessage, SendMessageOptions, WhenBusy, QueueConfig, QueueStrategy, QueueOption, } from './types';
4
5
  export { createGeneration } from './create-generation.svelte';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { createChat } from './create-chat.svelte';
2
+ export { createChatHook } from './create-chat-hook';
2
3
  export { createByok } from './create-byok.svelte';
3
4
  // Generation hooks
4
5
  export { createGeneration } from './create-generation.svelte';
package/dist/ui.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { createChatUI, getUIContext } from './chat-ui/create-ui';
2
+ export { createChatHook } from './chat-ui/create-chat-hook';
3
+ export type { ChatUIComponents, ChatUIFactoryConfig, ChatUIHost, ChatUIQueueItem, InputProps, InterruptProps, LayoutProps, MessageProps, PartProps, QueueProps, ToolProps, UIDescriptor, } from './chat-ui/create-ui';
4
+ export { default as UIChat } from './chat-ui/ui-chat.svelte';
5
+ export { default as UIProvider } from './chat-ui/ui-provider.svelte';
6
+ export { default as UIMessages } from './chat-ui/ui-messages.svelte';
7
+ export { default as UIMessage } from './chat-ui/ui-message.svelte';
8
+ export { default as UIPart } from './chat-ui/ui-part.svelte';
9
+ export { default as UIInterrupts } from './chat-ui/ui-interrupts.svelte';
10
+ export { default as UIInterrupt } from './chat-ui/ui-interrupt.svelte';
11
+ export { default as UIQueue } from './chat-ui/ui-queue.svelte';
package/dist/ui.js ADDED
@@ -0,0 +1,11 @@
1
+ // Barrel entry for the `@tanstack/ai-svelte/ui` subpath.
2
+ export { createChatUI, getUIContext } from './chat-ui/create-ui';
3
+ export { createChatHook } from './chat-ui/create-chat-hook';
4
+ export { default as UIChat } from './chat-ui/ui-chat.svelte';
5
+ export { default as UIProvider } from './chat-ui/ui-provider.svelte';
6
+ export { default as UIMessages } from './chat-ui/ui-messages.svelte';
7
+ export { default as UIMessage } from './chat-ui/ui-message.svelte';
8
+ export { default as UIPart } from './chat-ui/ui-part.svelte';
9
+ export { default as UIInterrupts } from './chat-ui/ui-interrupts.svelte';
10
+ export { default as UIInterrupt } from './chat-ui/ui-interrupt.svelte';
11
+ export { default as UIQueue } from './chat-ui/ui-queue.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-svelte",
3
- "version": "0.19.3",
3
+ "version": "0.20.0",
4
4
  "description": "Svelte 5 bindings for TanStack AI streaming chat, structured outputs, and media generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -26,6 +26,11 @@
26
26
  "types": "./dist/index.d.ts",
27
27
  "svelte": "./dist/index.js",
28
28
  "import": "./dist/index.js"
29
+ },
30
+ "./ui": {
31
+ "types": "./dist/ui.d.ts",
32
+ "svelte": "./dist/ui.js",
33
+ "import": "./dist/ui.js"
29
34
  }
30
35
  },
31
36
  "files": [
@@ -46,11 +51,11 @@
46
51
  "media-generation"
47
52
  ],
48
53
  "dependencies": {
49
- "@tanstack/ai-client": "^0.29.1"
54
+ "@tanstack/ai-client": "^0.30.0"
50
55
  },
51
56
  "peerDependencies": {
52
57
  "svelte": "^5.0.0",
53
- "@tanstack/ai": "^0.51.0"
58
+ "@tanstack/ai": "^0.52.1"
54
59
  },
55
60
  "devDependencies": {
56
61
  "@standard-schema/spec": "^1.1.0",
@@ -63,7 +68,7 @@
63
68
  "svelte-check": "^4.2.0",
64
69
  "typescript": "5.9.3",
65
70
  "vite": "^8.2.1",
66
- "@tanstack/ai": "0.51.0"
71
+ "@tanstack/ai": "0.52.1"
67
72
  },
68
73
  "scripts": {
69
74
  "clean": "rm -rf ./dist",
@@ -0,0 +1,62 @@
1
+ import type { InferredClientContext } from '@tanstack/ai-client'
2
+ import type {
3
+ ChatUIInterruptsOf,
4
+ ChatUISchemaOf,
5
+ ChatUIToolsOf,
6
+ } from '@tanstack/ai-client/ui'
7
+ import { createChat as createUnboundChat } from '../create-chat.svelte'
8
+ import type { CreateChatOptions } from '../types'
9
+ import { createChatUI } from './create-ui'
10
+ import type { ChatUIFactoryConfig, ChatUIHost } from './create-ui'
11
+
12
+ type HeadlessOptions<TOptions> = CreateChatOptions<
13
+ ChatUIToolsOf<TOptions>,
14
+ ChatUISchemaOf<TOptions>,
15
+ InferredClientContext<ChatUIToolsOf<TOptions>>,
16
+ ChatUIInterruptsOf<TOptions>
17
+ >
18
+
19
+ type ChatInstanceOverrides<TOptions> = {
20
+ threadId?: string
21
+ live?: boolean
22
+ forwardedProps?: Record<string, any>
23
+ body?: Record<string, any>
24
+ initialMessages?: HeadlessOptions<TOptions>['initialMessages']
25
+ }
26
+
27
+ /**
28
+ * Bind chat options and UI widgets once at module scope.
29
+ *
30
+ * Returns a bound `createAppChat`, the UI descriptor (`ui`), and
31
+ * `useChatContext`. Pass `{ui}` into `UIChat`. Pass instance overrides
32
+ * such as `threadId` into `createAppChat()`.
33
+ */
34
+ export function createChatHook<const TOptions>({
35
+ options,
36
+ ...chatComponents
37
+ }: {
38
+ options: TOptions
39
+ } & ChatUIFactoryConfig<NoInfer<TOptions>>) {
40
+ const ui = createChatUI(
41
+ options,
42
+ chatComponents as ChatUIFactoryConfig<NoInfer<TOptions>>,
43
+ )
44
+
45
+ function createAppChat(overrides?: ChatInstanceOverrides<TOptions>) {
46
+ if (!overrides) {
47
+ return createUnboundChat(
48
+ options as HeadlessOptions<TOptions>,
49
+ ) as ChatUIHost<TOptions>
50
+ }
51
+ return createUnboundChat({
52
+ ...(options as HeadlessOptions<TOptions>),
53
+ ...overrides,
54
+ }) as ChatUIHost<TOptions>
55
+ }
56
+
57
+ return {
58
+ createAppChat,
59
+ ui,
60
+ useChatContext: ui.useChatContext,
61
+ }
62
+ }
@@ -0,0 +1,310 @@
1
+ import { getContext, setContext } from 'svelte'
2
+ import {
3
+ automaticPartsForMessage,
4
+ collectInlineToolNames,
5
+ resolveInterruptComponent,
6
+ selectChatUI,
7
+ selectMessageUI,
8
+ } from '@tanstack/ai-client/ui'
9
+ import type {
10
+ ChatUIData,
11
+ ChatUIHasNamedInterrupts,
12
+ ChatUIHasNamedTools,
13
+ ChatUIInterrupt,
14
+ ChatUIInterruptName,
15
+ ChatUIInterruptOf,
16
+ ChatUIInterruptsOf,
17
+ ChatUINamedInterruptId,
18
+ ChatUIPartKey,
19
+ ChatUIPartOf,
20
+ ChatUISchemaOf,
21
+ ChatUISelectedPart,
22
+ ChatUIToolApproval,
23
+ ChatUIToolName,
24
+ ChatUIToolsOf,
25
+ } from '@tanstack/ai-client/ui'
26
+ import type {
27
+ QueuedMessage,
28
+ ToolCallPart,
29
+ ToolResultPart,
30
+ UIMessage,
31
+ } from '@tanstack/ai-client'
32
+ import type { CreateChatReturn } from '../types'
33
+
34
+ export type ChatUIHost<TOptions = unknown> = CreateChatReturn<
35
+ ChatUIToolsOf<TOptions>,
36
+ ChatUISchemaOf<TOptions>,
37
+ unknown,
38
+ ChatUIInterruptsOf<TOptions>
39
+ >
40
+
41
+ type GenericInterruptComponents<TOptions> =
42
+ ChatUIHasNamedInterrupts<TOptions> extends true
43
+ ? {
44
+ [K in ChatUINamedInterruptId<TOptions>]: unknown
45
+ } & {
46
+ fallback?: unknown
47
+ }
48
+ : {
49
+ fallback?: unknown
50
+ }
51
+
52
+ type ToolApprovalMap<TOptions> = {
53
+ [K in ChatUIToolName<TOptions>]?: unknown
54
+ }
55
+
56
+ /** The chrome around the message list: `layout`, `message`, and `input`. */
57
+ export type ChatUIChromeComponents = {
58
+ layout: unknown
59
+ message: unknown
60
+ input?: unknown
61
+ queue?: unknown
62
+ }
63
+
64
+ export type ChatUIPartsComponents = {
65
+ [K in ChatUIPartKey]?: unknown
66
+ } & {
67
+ fallback?: unknown
68
+ }
69
+
70
+ export type ChatUIInterruptsComponents<TOptions> = {
71
+ tools?: ToolApprovalMap<TOptions>
72
+ generic: GenericInterruptComponents<TOptions>
73
+ }
74
+
75
+ /** The flat shape the runtime reads, assembled from the factory config. */
76
+ export type ChatUIComponents<TOptions> = ChatUIChromeComponents & {
77
+ parts: ChatUIPartsComponents
78
+ tools?: { [K in ChatUIToolName<TOptions>]?: unknown }
79
+ interrupts?: {
80
+ tools?: ToolApprovalMap<TOptions>
81
+ generic?: GenericInterruptComponents<TOptions>
82
+ }
83
+ }
84
+
85
+ export type ChatUIFactoryConfig<TOptions> = {
86
+ components: ChatUIChromeComponents
87
+ partsComponents: ChatUIPartsComponents
88
+ } & (ChatUIHasNamedTools<TOptions> extends true
89
+ ? { toolsComponents: { [K in ChatUIToolName<TOptions>]: unknown } }
90
+ : { toolsComponents?: { [K in ChatUIToolName<TOptions>]?: unknown } }) &
91
+ (ChatUIHasNamedInterrupts<TOptions> extends true
92
+ ? { interruptsComponents: ChatUIInterruptsComponents<TOptions> }
93
+ : {
94
+ interruptsComponents?: {
95
+ tools?: ToolApprovalMap<TOptions>
96
+ generic?: GenericInterruptComponents<TOptions>
97
+ }
98
+ })
99
+
100
+ export type UIDescriptor<TOptions = unknown> = {
101
+ key: symbol
102
+ warn: (key: string, message: string) => void
103
+ components: ChatUIComponents<TOptions>
104
+ useChatContext: () => ChatUIHost<TOptions>
105
+ }
106
+
107
+ export type UIContextValue<TOptions = unknown> = {
108
+ chat: ChatUIHost<TOptions>
109
+ components: ChatUIComponents<TOptions>
110
+ ui: UIDescriptor<TOptions>
111
+ }
112
+
113
+ type ComponentsValue<TOptions = unknown> = {
114
+ components: ChatUIComponents<TOptions>
115
+ warn: (key: string, message: string) => void
116
+ inlineToolNames: ReadonlyArray<string>
117
+ }
118
+
119
+ type UIRuntime<TOptions = unknown> = UIDescriptor<TOptions> & {
120
+ componentsKey: symbol
121
+ }
122
+
123
+ function createWarnOnce() {
124
+ const seen = new Set<string>()
125
+ return (key: string, message: string) => {
126
+ if (process.env.NODE_ENV === 'production') return
127
+ if (seen.has(key)) return
128
+ seen.add(key)
129
+ console.warn(message)
130
+ }
131
+ }
132
+
133
+ export function createChatUI<const TOptions>(
134
+ options: TOptions,
135
+ config: ChatUIFactoryConfig<NoInfer<TOptions>>,
136
+ ): UIDescriptor<TOptions> {
137
+ void options
138
+ const {
139
+ components: chrome,
140
+ partsComponents,
141
+ toolsComponents,
142
+ interruptsComponents,
143
+ } = config as ChatUIFactoryConfig<TOptions> & {
144
+ toolsComponents?: Record<string, unknown>
145
+ interruptsComponents?: {
146
+ tools?: Record<string, unknown>
147
+ generic?: Record<string, unknown>
148
+ }
149
+ }
150
+ // The runtime keeps the flat shape the rest of this module reads.
151
+ const components = {
152
+ ...chrome,
153
+ parts: partsComponents,
154
+ tools: toolsComponents,
155
+ interrupts: interruptsComponents,
156
+ } as ChatUIComponents<TOptions>
157
+ const ui: UIRuntime<TOptions> = {
158
+ key: Symbol('tanstack-ai-ui-chat'),
159
+ componentsKey: Symbol('tanstack-ai-ui-components'),
160
+ warn: createWarnOnce(),
161
+ components,
162
+ useChatContext() {
163
+ return getChatContext(ui)
164
+ },
165
+ }
166
+ return ui
167
+ }
168
+
169
+ export function setUIContext<TOptions>(value: UIContextValue<TOptions>) {
170
+ setContext(value.ui.key, value.chat)
171
+ setContext((value.ui as UIRuntime).componentsKey, {
172
+ components: value.components,
173
+ warn: value.ui.warn,
174
+ inlineToolNames: inlineNames(value.components),
175
+ } satisfies ComponentsValue<TOptions>)
176
+ }
177
+
178
+ export function getChatContext<TOptions>(ui: UIDescriptor<TOptions>) {
179
+ const chat = getContext<ChatUIHost<TOptions> | undefined>(ui.key)
180
+ if (!chat) {
181
+ throw new Error(
182
+ 'Chat UI components must be wrapped in UIProvider or UIChat.',
183
+ )
184
+ }
185
+ return chat
186
+ }
187
+
188
+ export function getComponentsContext<TOptions>(ui: UIDescriptor<TOptions>) {
189
+ const value = getContext<ComponentsValue<TOptions> | undefined>(
190
+ (ui as UIRuntime).componentsKey,
191
+ )
192
+ if (!value) {
193
+ throw new Error(
194
+ 'Chat UI components must be wrapped in UIProvider or UIChat.',
195
+ )
196
+ }
197
+ return value
198
+ }
199
+
200
+ export function getUIContext<TOptions>(ui: UIDescriptor<TOptions>) {
201
+ return {
202
+ ui,
203
+ chat: getChatContext(ui),
204
+ components: getComponentsContext(ui).components,
205
+ } satisfies UIContextValue<TOptions>
206
+ }
207
+
208
+ export function readMessages<TOptions>(chat: ChatUIHost<TOptions>) {
209
+ return chat.messages ?? []
210
+ }
211
+
212
+ export function readInterrupts<TOptions>(chat: ChatUIHost<TOptions>) {
213
+ return chat.interrupts ?? []
214
+ }
215
+
216
+ export function readQueue<TOptions>(chat: ChatUIHost<TOptions>) {
217
+ const items = chat.queue ?? []
218
+ return items.map((item) => ({
219
+ ...item,
220
+ cancelQueued: () => {
221
+ chat.cancelQueued(item.id)
222
+ },
223
+ }))
224
+ }
225
+
226
+ export function inlineNames<TOptions>(components: ChatUIComponents<TOptions>) {
227
+ return collectInlineToolNames(
228
+ components.interrupts?.tools as Record<string, unknown> | undefined,
229
+ Object.keys(components.tools ?? {}),
230
+ )
231
+ }
232
+
233
+ export function messageParts(
234
+ message: UIMessage,
235
+ interrupts: ReadonlyArray<ChatUIInterrupt>,
236
+ inlineToolNames: ReadonlyArray<string>,
237
+ ): Array<ChatUISelectedPart> {
238
+ return automaticPartsForMessage(
239
+ selectMessageUI(message, { interrupts, inlineToolNames }),
240
+ )
241
+ }
242
+
243
+ export function listInterrupts<TOptions>(
244
+ chat: ChatUIHost<TOptions>,
245
+ inlineToolNames: ReadonlyArray<string>,
246
+ ) {
247
+ return selectChatUI({
248
+ messages: readMessages(chat),
249
+ interrupts: readInterrupts(chat),
250
+ inlineToolNames,
251
+ }).interrupts
252
+ }
253
+
254
+ export function toolComponent<TOptions>(
255
+ components: ChatUIComponents<TOptions>,
256
+ name: string,
257
+ ) {
258
+ return components.tools?.[name as never]
259
+ }
260
+
261
+ export function partComponent<TOptions>(
262
+ components: ChatUIComponents<TOptions>,
263
+ key: string,
264
+ ) {
265
+ return components.parts[key as ChatUIPartKey] ?? components.parts.fallback
266
+ }
267
+
268
+ export function interruptComponent<TOptions>(
269
+ components: ChatUIComponents<TOptions>,
270
+ interrupt: ChatUIInterrupt,
271
+ ) {
272
+ return resolveInterruptComponent(interrupt, components.interrupts)
273
+ }
274
+
275
+ export type ChatUIQueueItem = QueuedMessage & {
276
+ cancelQueued: () => void
277
+ }
278
+
279
+ export type LayoutProps<TOptions> = {
280
+ readonly __ui?: TOptions
281
+ }
282
+ export type MessageProps<TOptions> = {
283
+ message: UIMessage<ChatUIToolsOf<TOptions>, ChatUIData<TOptions>>
284
+ }
285
+ export type InputProps<TOptions> = {
286
+ readonly __ui?: TOptions
287
+ }
288
+ export type QueueProps<TOptions> = {
289
+ item: ChatUIQueueItem
290
+ readonly __ui?: TOptions
291
+ }
292
+ export type PartProps<TOptions, TKey extends ChatUIPartKey = ChatUIPartKey> = {
293
+ part: ChatUIPartOf<TOptions, TKey>
294
+ }
295
+ export type ToolProps<
296
+ TOptions,
297
+ TName extends ChatUIToolName<TOptions> = ChatUIToolName<TOptions>,
298
+ > = {
299
+ part: Extract<ToolCallPart<ChatUIToolsOf<TOptions>>, { name: TName }>
300
+ result?: ToolResultPart
301
+ interrupt?: ChatUIToolApproval<TOptions, TName>
302
+ readonly __ui?: TOptions
303
+ }
304
+ export type InterruptProps<
305
+ TOptions,
306
+ TName extends ChatUIInterruptName<TOptions> = never,
307
+ > = {
308
+ interrupt: ChatUIInterruptOf<TOptions, TName>
309
+ readonly __ui?: TOptions
310
+ }
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import {
3
+ getComponentsContext,
4
+ partComponent,
5
+ toolComponent,
6
+ type UIDescriptor,
7
+ } from './create-ui'
8
+ import type { ChatUISelectedPart } from '@tanstack/ai-client/ui'
9
+
10
+ let {
11
+ ui,
12
+ selected,
13
+ }: {
14
+ ui: UIDescriptor
15
+ selected: ChatUISelectedPart
16
+ } = $props()
17
+
18
+ const comps = $derived(getComponentsContext(ui))
19
+ </script>
20
+
21
+ {#if selected.key === 'toolCall'}
22
+ {@const Tool = toolComponent(comps.components, selected.part.name) as any}
23
+ {#if Tool}
24
+ <Tool
25
+ part={selected.part}
26
+ result={selected.result}
27
+ interrupt={selected.interrupt}
28
+ />
29
+ {:else}
30
+ {comps.warn(
31
+ `tool:${selected.part.name}`,
32
+ `[tanstack-ai-ui] Missing tools.${selected.part.name} component`,
33
+ )}
34
+ {/if}
35
+ {:else}
36
+ {@const Part = partComponent(comps.components, selected.key) as any}
37
+ {#if Part}
38
+ <Part part={selected.part} />
39
+ {:else}
40
+ {comps.warn(
41
+ `part:${selected.key}`,
42
+ `[tanstack-ai-ui] Missing parts.${selected.key} component`,
43
+ )}
44
+ {/if}
45
+ {/if}
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import UIProvider from './ui-provider.svelte'
3
+ import UIMessages from './ui-messages.svelte'
4
+ import UIInterrupts from './ui-interrupts.svelte'
5
+ import UIQueue from './ui-queue.svelte'
6
+ import type { ChatUIComponents, UIDescriptor } from './create-ui'
7
+
8
+ let {
9
+ ui,
10
+ chat,
11
+ components,
12
+ }: {
13
+ ui: UIDescriptor
14
+ // ponytail: Svelte components are not generic, so ChatUIHost<TOptions> cannot
15
+ // accept a typed createChat() host with tool-approval interrupts.
16
+ chat: any
17
+ components?: ChatUIComponents<unknown>
18
+ } = $props()
19
+
20
+ const bound = $derived(components ?? ui.components)
21
+ const Layout = $derived(bound.layout as any)
22
+ const Input = $derived(bound.input as any)
23
+ </script>
24
+
25
+ <UIProvider {ui} {chat} components={bound}>
26
+ <Layout>
27
+ {#snippet messages()}
28
+ <UIMessages {ui} />
29
+ {/snippet}
30
+ {#snippet interrupts()}
31
+ <UIInterrupts {ui} />
32
+ {/snippet}
33
+ {#snippet queue()}
34
+ <UIQueue {ui} />
35
+ {/snippet}
36
+ {#snippet input()}
37
+ {#if Input}
38
+ <Input />
39
+ {/if}
40
+ {/snippet}
41
+ </Layout>
42
+ </UIProvider>
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import {
3
+ getComponentsContext,
4
+ interruptComponent,
5
+ type UIDescriptor,
6
+ } from './create-ui'
7
+ import type { ChatUIInterrupt } from '@tanstack/ai-client/ui'
8
+
9
+ let {
10
+ ui,
11
+ interrupt,
12
+ }: {
13
+ ui: UIDescriptor
14
+ interrupt: ChatUIInterrupt
15
+ } = $props()
16
+
17
+ const comps = $derived(getComponentsContext(ui))
18
+ const Component = $derived(
19
+ interruptComponent(comps.components, interrupt) as any,
20
+ )
21
+ </script>
22
+
23
+ {#if Component}
24
+ <Component {interrupt} />
25
+ {:else}
26
+ {comps.warn(
27
+ `interrupt:${interrupt.id}`,
28
+ `[tanstack-ai-ui] Missing interrupt component for ${interrupt.kind}`,
29
+ )}
30
+ {/if}