@tanstack/ai-svelte 0.19.4 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-ui/create-chat-hook.d.ts +27 -0
- package/dist/chat-ui/create-chat-hook.js +26 -0
- package/dist/chat-ui/create-ui.d.ts +131 -0
- package/dist/chat-ui/create-ui.js +100 -0
- package/dist/chat-ui/selected-part.svelte +45 -0
- package/dist/chat-ui/selected-part.svelte.d.ts +9 -0
- package/dist/chat-ui/ui-chat.svelte +42 -0
- package/dist/chat-ui/ui-chat.svelte.d.ts +9 -0
- package/dist/chat-ui/ui-interrupt.svelte +30 -0
- package/dist/chat-ui/ui-interrupt.svelte.d.ts +9 -0
- package/dist/chat-ui/ui-interrupts.svelte +30 -0
- package/dist/chat-ui/ui-interrupts.svelte.d.ts +9 -0
- package/dist/chat-ui/ui-message.svelte +45 -0
- package/dist/chat-ui/ui-message.svelte.d.ts +12 -0
- package/dist/chat-ui/ui-messages.svelte +31 -0
- package/dist/chat-ui/ui-messages.svelte.d.ts +10 -0
- package/dist/chat-ui/ui-part.svelte +30 -0
- package/dist/chat-ui/ui-part.svelte.d.ts +9 -0
- package/dist/chat-ui/ui-provider.svelte +29 -0
- package/dist/chat-ui/ui-provider.svelte.d.ts +10 -0
- package/dist/chat-ui/ui-queue.svelte +21 -0
- package/dist/chat-ui/ui-queue.svelte.d.ts +7 -0
- package/dist/create-chat-hook.d.ts +35 -0
- package/dist/create-chat-hook.js +31 -0
- package/dist/create-web-mcp-tools.svelte.d.ts +24 -0
- package/dist/create-web-mcp-tools.svelte.js +29 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/ui.d.ts +11 -0
- package/dist/ui.js +11 -0
- package/package.json +9 -4
- package/src/chat-ui/create-chat-hook.ts +62 -0
- package/src/chat-ui/create-ui.ts +310 -0
- package/src/chat-ui/selected-part.svelte +45 -0
- package/src/chat-ui/ui-chat.svelte +42 -0
- package/src/chat-ui/ui-interrupt.svelte +30 -0
- package/src/chat-ui/ui-interrupts.svelte +30 -0
- package/src/chat-ui/ui-message.svelte +45 -0
- package/src/chat-ui/ui-messages.svelte +31 -0
- package/src/chat-ui/ui-part.svelte +30 -0
- package/src/chat-ui/ui-provider.svelte +29 -0
- package/src/chat-ui/ui-queue.svelte +21 -0
- package/src/create-chat-hook.ts +67 -0
- package/src/create-web-mcp-tools.svelte.ts +57 -0
- package/src/index.ts +3 -0
- 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,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
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AnyClientTool, InferredClientContext, RegisterWebMCPToolsOptions } from '@tanstack/ai-client';
|
|
2
|
+
/** Options for {@link createWebMCPTools}. */
|
|
3
|
+
export type CreateWebMCPToolsOptions<TTools extends ReadonlyArray<AnyClientTool>, TContext = InferredClientContext<TTools>> = Omit<RegisterWebMCPToolsOptions<TTools, TContext>, 'signal'> & {
|
|
4
|
+
/** Receives an asynchronous registration error. */
|
|
5
|
+
onError?: (error: unknown) => void;
|
|
6
|
+
};
|
|
7
|
+
type CreateWebMCPToolsArguments<TTools extends ReadonlyArray<AnyClientTool>, TContext> = {} extends CreateWebMCPToolsOptions<TTools, TContext> ? [options?: CreateWebMCPToolsOptions<TTools, TContext>] : [options: CreateWebMCPToolsOptions<TTools, TContext>];
|
|
8
|
+
/**
|
|
9
|
+
* Registers executable client tools with WebMCP for the current Svelte component.
|
|
10
|
+
*
|
|
11
|
+
* Component destruction removes every tool registered by this call.
|
|
12
|
+
*
|
|
13
|
+
* @param tools - The executable client tools to expose through WebMCP.
|
|
14
|
+
* @param options - Runtime context, per-tool options, and error handling.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```svelte
|
|
18
|
+
* <script lang="ts">
|
|
19
|
+
* createWebMCPTools([searchProducts])
|
|
20
|
+
* </script>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function createWebMCPTools<const TTools extends ReadonlyArray<AnyClientTool>, TContext = InferredClientContext<TTools>>(tools: TTools, ...[options]: CreateWebMCPToolsArguments<TTools, TContext>): void;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { onDestroy } from 'svelte';
|
|
2
|
+
import { registerWebMCPTools } from '@tanstack/ai-client';
|
|
3
|
+
/**
|
|
4
|
+
* Registers executable client tools with WebMCP for the current Svelte component.
|
|
5
|
+
*
|
|
6
|
+
* Component destruction removes every tool registered by this call.
|
|
7
|
+
*
|
|
8
|
+
* @param tools - The executable client tools to expose through WebMCP.
|
|
9
|
+
* @param options - Runtime context, per-tool options, and error handling.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <script lang="ts">
|
|
14
|
+
* createWebMCPTools([searchProducts])
|
|
15
|
+
* </script>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function createWebMCPTools(tools, ...[options]) {
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
void registerWebMCPTools(tools, {
|
|
21
|
+
...options,
|
|
22
|
+
signal: controller.signal,
|
|
23
|
+
}).catch((error) => {
|
|
24
|
+
if (!controller.signal.aborted) {
|
|
25
|
+
options?.onError?.(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
onDestroy(() => controller.abort());
|
|
29
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { createChat } from './create-chat.svelte';
|
|
2
|
+
export { createChatHook } from './create-chat-hook';
|
|
2
3
|
export { createByok } from './create-byok.svelte';
|
|
4
|
+
export { createWebMCPTools } from './create-web-mcp-tools.svelte';
|
|
5
|
+
export type { CreateWebMCPToolsOptions } from './create-web-mcp-tools.svelte';
|
|
3
6
|
export type { CreateChatOptions, CreateChatReturn, DeepPartial, UIMessage, ChatRequestBody, QueuedMessage, SendMessageOptions, WhenBusy, QueueConfig, QueueStrategy, QueueOption, } from './types';
|
|
4
7
|
export { createGeneration } from './create-generation.svelte';
|
|
5
8
|
export type { CreateGenerationOptions, CreateGenerationReturn, } from './create-generation.svelte';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { createChat } from './create-chat.svelte';
|
|
2
|
+
export { createChatHook } from './create-chat-hook';
|
|
2
3
|
export { createByok } from './create-byok.svelte';
|
|
4
|
+
export { createWebMCPTools } from './create-web-mcp-tools.svelte';
|
|
3
5
|
// Generation hooks
|
|
4
6
|
export { createGeneration } from './create-generation.svelte';
|
|
5
7
|
export { createGenerateImage } from './create-generate-image.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.
|
|
3
|
+
"version": "0.21.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.
|
|
54
|
+
"@tanstack/ai-client": "^0.31.0"
|
|
50
55
|
},
|
|
51
56
|
"peerDependencies": {
|
|
52
57
|
"svelte": "^5.0.0",
|
|
53
|
-
"@tanstack/ai": "^0.
|
|
58
|
+
"@tanstack/ai": "^0.53.0"
|
|
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.
|
|
71
|
+
"@tanstack/ai": "0.53.0"
|
|
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}
|