@tanstack/ai-react 0.15.14 → 0.15.15

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.
@@ -0,0 +1,19 @@
1
+ export { useChat } from './use-chat.js';
2
+ export { useRealtimeChat } from './use-realtime-chat.js';
3
+ export type { DeepPartial, UseChatOptions, UseChatReturn, UIMessage, ChatRequestBody, } from './types.js';
4
+ export type { UseRealtimeChatOptions, UseRealtimeChatReturn, } from './realtime-types.js';
5
+ export { useGeneration } from './use-generation.js';
6
+ export type { UseGenerationOptions, UseGenerationReturn, } from './use-generation.js';
7
+ export { useGenerateImage } from './use-generate-image.js';
8
+ export type { UseGenerateImageOptions, UseGenerateImageReturn, } from './use-generate-image.js';
9
+ export { useGenerateAudio } from './use-generate-audio.js';
10
+ export type { UseGenerateAudioOptions, UseGenerateAudioReturn, } from './use-generate-audio.js';
11
+ export { useGenerateSpeech } from './use-generate-speech.js';
12
+ export type { UseGenerateSpeechOptions, UseGenerateSpeechReturn, } from './use-generate-speech.js';
13
+ export { useTranscription } from './use-transcription.js';
14
+ export type { UseTranscriptionOptions, UseTranscriptionReturn, } from './use-transcription.js';
15
+ export { useSummarize } from './use-summarize.js';
16
+ export type { UseSummarizeOptions, UseSummarizeReturn } from './use-summarize.js';
17
+ export { useGenerateVideo } from './use-generate-video.js';
18
+ export type { UseGenerateVideoOptions, UseGenerateVideoReturn, } from './use-generate-video.js';
19
+ export { fetchServerSentEvents, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, type ChatFetcher, type ChatFetcherInput, type ChatFetcherOptions, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type InferChatMessages, type GenerationClientState, type ImageGenerateInput, type AudioGenerateInput, type SpeechGenerateInput, type TranscriptionGenerateInput, type SummarizeGenerateInput, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, } from '@tanstack/ai-client';
@@ -0,0 +1,29 @@
1
+ import { useChat } from "./use-chat.js";
2
+ import { useRealtimeChat } from "./use-realtime-chat.js";
3
+ import { useGeneration } from "./use-generation.js";
4
+ import { useGenerateImage } from "./use-generate-image.js";
5
+ import { useGenerateAudio } from "./use-generate-audio.js";
6
+ import { useGenerateSpeech } from "./use-generate-speech.js";
7
+ import { useTranscription } from "./use-transcription.js";
8
+ import { useSummarize } from "./use-summarize.js";
9
+ import { useGenerateVideo } from "./use-generate-video.js";
10
+ import { createChatClientOptions, fetchHttpStream, fetchServerSentEvents, rpcStream, stream, xhrHttpStream, xhrServerSentEvents } from "@tanstack/ai-client";
11
+ export {
12
+ createChatClientOptions,
13
+ fetchHttpStream,
14
+ fetchServerSentEvents,
15
+ rpcStream,
16
+ stream,
17
+ useChat,
18
+ useGenerateAudio,
19
+ useGenerateImage,
20
+ useGenerateSpeech,
21
+ useGenerateVideo,
22
+ useGeneration,
23
+ useRealtimeChat,
24
+ useSummarize,
25
+ useTranscription,
26
+ xhrHttpStream,
27
+ xhrServerSentEvents
28
+ };
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -0,0 +1,109 @@
1
+ import { AnyClientTool, RealtimeMessage, RealtimeMode, RealtimeStatus, RealtimeToken } from '@tanstack/ai';
2
+ import { RealtimeAdapter } from '@tanstack/ai-client';
3
+ /**
4
+ * Options for the useRealtimeChat hook.
5
+ */
6
+ export interface UseRealtimeChatOptions {
7
+ /**
8
+ * Function to fetch a realtime token from the server.
9
+ * Called on connect and when token needs refresh.
10
+ */
11
+ getToken: () => Promise<RealtimeToken>;
12
+ /**
13
+ * The realtime adapter to use (e.g., openaiRealtime())
14
+ */
15
+ adapter: RealtimeAdapter;
16
+ /**
17
+ * Client-side tools with execution logic
18
+ */
19
+ tools?: ReadonlyArray<AnyClientTool>;
20
+ /**
21
+ * Auto-play assistant audio (default: true)
22
+ */
23
+ autoPlayback?: boolean;
24
+ /**
25
+ * Request microphone access on connect (default: true)
26
+ */
27
+ autoCapture?: boolean;
28
+ /**
29
+ * System instructions for the assistant
30
+ */
31
+ instructions?: string;
32
+ /**
33
+ * Voice to use for audio output
34
+ */
35
+ voice?: string;
36
+ /**
37
+ * Voice activity detection mode (default: 'server')
38
+ */
39
+ vadMode?: 'server' | 'semantic' | 'manual';
40
+ /**
41
+ * Output modalities for responses (e.g., ['audio', 'text'])
42
+ */
43
+ outputModalities?: Array<'audio' | 'text'>;
44
+ /**
45
+ * Temperature for generation (provider-specific range)
46
+ */
47
+ temperature?: number;
48
+ /**
49
+ * Maximum number of tokens in a response
50
+ */
51
+ maxOutputTokens?: number | 'inf';
52
+ /**
53
+ * Eagerness level for semantic VAD ('low', 'medium', 'high')
54
+ */
55
+ semanticEagerness?: 'low' | 'medium' | 'high';
56
+ onConnect?: () => void;
57
+ onDisconnect?: () => void;
58
+ onError?: (error: Error) => void;
59
+ onMessage?: (message: RealtimeMessage) => void;
60
+ onModeChange?: (mode: RealtimeMode) => void;
61
+ onInterrupted?: () => void;
62
+ }
63
+ /**
64
+ * Return type for the useRealtimeChat hook.
65
+ */
66
+ export interface UseRealtimeChatReturn {
67
+ /** Current connection status */
68
+ status: RealtimeStatus;
69
+ /** Current error, if any */
70
+ error: Error | null;
71
+ /** Connect to the realtime session */
72
+ connect: () => Promise<void>;
73
+ /** Disconnect from the realtime session */
74
+ disconnect: () => Promise<void>;
75
+ /** Current mode (idle, listening, thinking, speaking) */
76
+ mode: RealtimeMode;
77
+ /** Conversation messages */
78
+ messages: Array<RealtimeMessage>;
79
+ /** User transcript while speaking (before finalized) */
80
+ pendingUserTranscript: string | null;
81
+ /** Assistant transcript while speaking (before finalized) */
82
+ pendingAssistantTranscript: string | null;
83
+ /** Start listening for voice input (manual VAD mode) */
84
+ startListening: () => void;
85
+ /** Stop listening for voice input (manual VAD mode) */
86
+ stopListening: () => void;
87
+ /** Interrupt the current assistant response */
88
+ interrupt: () => void;
89
+ /** Send a text message instead of voice */
90
+ sendText: (text: string) => void;
91
+ /** Send an image to the conversation */
92
+ sendImage: (imageData: string, mimeType: string) => void;
93
+ /** Current input (microphone) volume level */
94
+ inputLevel: number;
95
+ /** Current output (speaker) volume level */
96
+ outputLevel: number;
97
+ /** Get frequency data for input audio visualization */
98
+ getInputFrequencyData: () => Uint8Array;
99
+ /** Get frequency data for output audio visualization */
100
+ getOutputFrequencyData: () => Uint8Array;
101
+ /** Get time domain data for input waveform */
102
+ getInputTimeDomainData: () => Uint8Array;
103
+ /** Get time domain data for output waveform */
104
+ getOutputTimeDomainData: () => Uint8Array;
105
+ /** Current VAD mode */
106
+ vadMode: 'server' | 'semantic' | 'manual';
107
+ /** Change VAD mode at runtime */
108
+ setVADMode: (mode: 'server' | 'semantic' | 'manual') => void;
109
+ }
@@ -0,0 +1,150 @@
1
+ import { AnyClientTool, InferSchemaType, ModelMessage, SchemaInput } from '@tanstack/ai/client';
2
+ import { AIDevtoolsDisplayOptions, ChatClientOptions, ChatClientState, ChatRequestBody, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, UIMessage } from '@tanstack/ai-client';
3
+ export type { ChatRequestBody, MultimodalContent, UIMessage };
4
+ /**
5
+ * Recursive partial — every property and every nested array element is optional.
6
+ * Used to type the in-flight `partial` value the hook exposes while a structured
7
+ * output stream is still arriving (the JSON has shape but is incomplete).
8
+ */
9
+ export type DeepPartial<T> = T extends ReadonlyArray<infer U> ? Array<DeepPartial<U>> : T extends object ? {
10
+ [K in keyof T]?: DeepPartial<T[K]>;
11
+ } : T;
12
+ /**
13
+ * Options for the useChat hook.
14
+ *
15
+ * Pass either `connection` or `fetcher` — the XOR is enforced at the type
16
+ * level via `ChatTransport`.
17
+ *
18
+ * This extends ChatClientOptions but omits the state change callbacks that are
19
+ * managed internally by React state:
20
+ * - `onMessagesChange` - Managed by React state (exposed as `messages`)
21
+ * - `onLoadingChange` - Managed by React state (exposed as `isLoading`)
22
+ * - `onErrorChange` - Managed by React state (exposed as `error`)
23
+ * - `onStatusChange` - Managed by React state (exposed as `status`)
24
+ *
25
+ * All other callbacks (onResponse, onChunk, onFinish, onError) are
26
+ * passed through to the underlying ChatClient and can be used for side effects.
27
+ *
28
+ * When `outputSchema` is supplied, the hook returns a typed `partial` (live
29
+ * progressive object, updated from `TEXT_MESSAGE_CONTENT` deltas via
30
+ * `parsePartialJSON`) and `final` (validated terminal payload from the
31
+ * `structured-output.complete` event). The schema is used purely for type
32
+ * inference on the client — server-side validation still runs against the
33
+ * schema you pass to `chat({ outputSchema })` on the server route.
34
+ *
35
+ * Note: Connection and body changes will recreate the ChatClient instance.
36
+ * To update these options, remount the component or use a key prop.
37
+ */
38
+ export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>> = DistributedOmit<ChatClientOptions<TTools, TContext>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange' | 'context' | 'devtools'> & {
39
+ /** Display options for TanStack AI Devtools. */
40
+ devtools?: AIDevtoolsDisplayOptions;
41
+ /**
42
+ * Opt into mount-time live subscription behavior.
43
+ * When enabled, the hook subscribes on mount and unsubscribes on unmount.
44
+ */
45
+ live?: boolean;
46
+ /**
47
+ * Standard-schema-compatible schema (Zod, Valibot, ArkType, or a plain JSON
48
+ * Schema). Used to infer the shape of `partial` and `final` in the return.
49
+ * The schema is **not** sent to the server — server-side validation runs
50
+ * against the schema passed to `chat({ outputSchema })` on the server route.
51
+ */
52
+ outputSchema?: TSchema;
53
+ } & ClientContextOptionFromTools<TTools, TContext>;
54
+ /**
55
+ * Discriminated return shape: when `outputSchema` is supplied, the hook adds
56
+ * typed `partial` / `final` fields; when it is omitted (default), the return
57
+ * is unchanged.
58
+ */
59
+ export type UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined> = BaseUseChatReturn<TTools, TSchema extends SchemaInput ? InferSchemaType<TSchema> : unknown> & (TSchema extends SchemaInput ? {
60
+ /**
61
+ * Live, progressively-parsed structured output. Updated from
62
+ * `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON` while the stream
63
+ * is still arriving, and snapped to the validated payload when
64
+ * `structured-output.complete` fires. Resets on every new run
65
+ * (`sendMessage` / `reload`).
66
+ */
67
+ partial: DeepPartial<InferSchemaType<TSchema>>;
68
+ /**
69
+ * Final, schema-validated structured output. `null` until the terminal
70
+ * `structured-output.complete` event arrives. Resets on every new run.
71
+ */
72
+ final: InferSchemaType<TSchema> | null;
73
+ } : Record<never, never>);
74
+ interface BaseUseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TData = unknown> {
75
+ /**
76
+ * Current messages in the conversation. When `outputSchema` is supplied,
77
+ * `messages[i].parts.find(p => p.type === 'structured-output')` is typed
78
+ * with the schema's inferred shape — `data: T`, `partial: DeepPartial<T>`.
79
+ */
80
+ messages: Array<UIMessage<TTools, TData>>;
81
+ /**
82
+ * Send a message and get a response.
83
+ * Can be a simple string or multimodal content with images, audio, etc.
84
+ */
85
+ sendMessage: (content: string | MultimodalContent) => Promise<void>;
86
+ /**
87
+ * Append a message to the conversation
88
+ */
89
+ append: (message: ModelMessage | UIMessage<TTools, TData>) => Promise<void>;
90
+ /**
91
+ * Add the result of a client-side tool execution
92
+ */
93
+ addToolResult: (result: {
94
+ toolCallId: string;
95
+ tool: string;
96
+ output: any;
97
+ state?: 'output-available' | 'output-error';
98
+ errorText?: string;
99
+ }) => Promise<void>;
100
+ /**
101
+ * Respond to a tool approval request
102
+ */
103
+ addToolApprovalResponse: (response: {
104
+ id: string;
105
+ approved: boolean;
106
+ }) => Promise<void>;
107
+ /**
108
+ * Reload the last assistant message
109
+ */
110
+ reload: () => Promise<void>;
111
+ /**
112
+ * Stop the current response generation
113
+ */
114
+ stop: () => void;
115
+ /**
116
+ * Whether a response is currently being generated
117
+ */
118
+ isLoading: boolean;
119
+ /**
120
+ * Current error, if any
121
+ */
122
+ error: Error | undefined;
123
+ /**
124
+ * Current status of the chat client
125
+ */
126
+ status: ChatClientState;
127
+ /**
128
+ * Whether the subscription loop is currently active
129
+ */
130
+ isSubscribed: boolean;
131
+ /**
132
+ * Current connection lifecycle status
133
+ */
134
+ connectionStatus: ConnectionStatus;
135
+ /**
136
+ * Whether the shared session is actively generating.
137
+ * Derived from stream run events (RUN_STARTED / RUN_FINISHED / RUN_ERROR).
138
+ * Unlike `isLoading` (request-local), this reflects shared generation
139
+ * activity visible to all subscribers (e.g. across tabs/devices).
140
+ */
141
+ sessionGenerating: boolean;
142
+ /**
143
+ * Set messages manually
144
+ */
145
+ setMessages: (messages: Array<UIMessage<TTools, TData>>) => void;
146
+ /**
147
+ * Clear all messages
148
+ */
149
+ clear: () => void;
150
+ }
@@ -0,0 +1,4 @@
1
+ import { AnyClientTool, SchemaInput } from '@tanstack/ai/client';
2
+ import { InferredClientContext } from '@tanstack/ai-client';
3
+ import { UseChatOptions, UseChatReturn } from './types.js';
4
+ export declare function useChat<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>>(options: UseChatOptions<TTools, TSchema, TContext>): UseChatReturn<TTools, TSchema>;
@@ -0,0 +1,258 @@
1
+ import { ChatClient } from "@tanstack/ai-client";
2
+ import { createChatDevtoolsBridge } from "@tanstack/ai-client/devtools";
3
+ import { useId, useState, useRef, useMemo, useEffect, useCallback } from "react";
4
+ function useChat(options) {
5
+ const hookId = useId();
6
+ const clientId = options.id || hookId;
7
+ const [messages, setMessages] = useState(
8
+ options.initialMessages || []
9
+ );
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [error, setError] = useState(void 0);
12
+ const [status, setStatus] = useState("ready");
13
+ const [isSubscribed, setIsSubscribed] = useState(false);
14
+ const [connectionStatus, setConnectionStatus] = useState("disconnected");
15
+ const [sessionGenerating, setSessionGenerating] = useState(false);
16
+ const messagesRef = useRef(
17
+ options.initialMessages || []
18
+ );
19
+ const isFirstMountRef = useRef(true);
20
+ const activeClientRef = useRef(null);
21
+ const cleanupInvalidationRef = useRef(
22
+ null
23
+ );
24
+ messagesRef.current = messages;
25
+ const optionsRef = useRef(options);
26
+ optionsRef.current = options;
27
+ const client = useMemo(() => {
28
+ const messagesToUse = options.initialMessages || [];
29
+ isFirstMountRef.current = false;
30
+ const initialOptions = optionsRef.current;
31
+ const transport = initialOptions.connection ? { connection: initialOptions.connection } : { fetcher: initialOptions.fetcher };
32
+ const instance = new ChatClient({
33
+ devtoolsBridgeFactory: createChatDevtoolsBridge,
34
+ ...transport,
35
+ id: clientId,
36
+ initialMessages: messagesToUse,
37
+ ...initialOptions.body !== void 0 && { body: initialOptions.body },
38
+ ...initialOptions.threadId !== void 0 && {
39
+ threadId: initialOptions.threadId
40
+ },
41
+ ...initialOptions.forwardedProps !== void 0 && {
42
+ forwardedProps: initialOptions.forwardedProps
43
+ },
44
+ ...initialOptions.persistence !== void 0 && {
45
+ persistence: initialOptions.persistence
46
+ },
47
+ ...initialOptions.context !== void 0 && {
48
+ context: initialOptions.context
49
+ },
50
+ devtools: {
51
+ ...initialOptions.devtools,
52
+ framework: "react",
53
+ hookName: "useChat",
54
+ outputKind: initialOptions.outputSchema ? "structured" : "chat"
55
+ },
56
+ onResponse: (response) => {
57
+ if (activeClientRef.current !== instance) return;
58
+ void optionsRef.current.onResponse?.(response);
59
+ },
60
+ onChunk: (chunk) => {
61
+ if (activeClientRef.current !== instance) return;
62
+ optionsRef.current.onChunk?.(chunk);
63
+ },
64
+ onFinish: (message) => {
65
+ if (activeClientRef.current !== instance) return;
66
+ optionsRef.current.onFinish?.(message);
67
+ },
68
+ onError: (error2) => {
69
+ if (activeClientRef.current !== instance) return;
70
+ optionsRef.current.onError?.(error2);
71
+ },
72
+ ...initialOptions.tools !== void 0 && {
73
+ tools: initialOptions.tools
74
+ },
75
+ onCustomEvent: (eventType, data, context) => {
76
+ if (activeClientRef.current !== instance) return;
77
+ optionsRef.current.onCustomEvent?.(eventType, data, context);
78
+ },
79
+ ...options.streamProcessor !== void 0 && {
80
+ streamProcessor: options.streamProcessor
81
+ },
82
+ onMessagesChange: (newMessages) => {
83
+ if (activeClientRef.current !== instance) return;
84
+ setMessages(newMessages);
85
+ },
86
+ onLoadingChange: (newIsLoading) => {
87
+ if (activeClientRef.current !== instance) return;
88
+ setIsLoading(newIsLoading);
89
+ },
90
+ onErrorChange: (newError) => {
91
+ if (activeClientRef.current !== instance) return;
92
+ setError(newError);
93
+ },
94
+ onStatusChange: (status2) => {
95
+ if (activeClientRef.current !== instance) return;
96
+ setStatus(status2);
97
+ },
98
+ onSubscriptionChange: (nextIsSubscribed) => {
99
+ if (activeClientRef.current !== instance) return;
100
+ setIsSubscribed(nextIsSubscribed);
101
+ },
102
+ onConnectionStatusChange: (nextStatus) => {
103
+ if (activeClientRef.current !== instance) return;
104
+ setConnectionStatus(nextStatus);
105
+ },
106
+ onSessionGeneratingChange: (isGenerating) => {
107
+ if (activeClientRef.current !== instance) return;
108
+ setSessionGenerating(isGenerating);
109
+ }
110
+ });
111
+ activeClientRef.current = instance;
112
+ return instance;
113
+ }, [clientId]);
114
+ useEffect(() => {
115
+ const clientMessages = client.getMessages();
116
+ if (clientMessages !== messagesRef.current) {
117
+ setMessages(clientMessages);
118
+ }
119
+ }, [client]);
120
+ useEffect(() => {
121
+ client.updateOptions({ body: options.body });
122
+ }, [client, options.body]);
123
+ useEffect(() => {
124
+ if (options.forwardedProps !== void 0) {
125
+ client.updateOptions({ forwardedProps: options.forwardedProps });
126
+ }
127
+ }, [client, options.forwardedProps]);
128
+ useEffect(() => {
129
+ if (options.tools !== void 0) {
130
+ client.updateOptions({ tools: options.tools });
131
+ }
132
+ }, [client, options.tools]);
133
+ useEffect(() => {
134
+ client.updateOptions({ context: options.context });
135
+ }, [client, options.context]);
136
+ useEffect(() => {
137
+ if (options.live) {
138
+ client.subscribe();
139
+ } else {
140
+ client.unsubscribe();
141
+ }
142
+ }, [client, options.live]);
143
+ useEffect(() => {
144
+ if (cleanupInvalidationRef.current) {
145
+ clearTimeout(cleanupInvalidationRef.current);
146
+ cleanupInvalidationRef.current = null;
147
+ }
148
+ activeClientRef.current = client;
149
+ client.mountDevtools();
150
+ return () => {
151
+ cleanupInvalidationRef.current = setTimeout(() => {
152
+ if (activeClientRef.current === client) {
153
+ activeClientRef.current = null;
154
+ }
155
+ cleanupInvalidationRef.current = null;
156
+ }, 0);
157
+ if (optionsRef.current.live) {
158
+ client.unsubscribe();
159
+ } else {
160
+ client.stop();
161
+ }
162
+ client.dispose();
163
+ };
164
+ }, [client]);
165
+ const sendMessage = useCallback(
166
+ async (content) => {
167
+ await client.sendMessage(content);
168
+ },
169
+ [client]
170
+ );
171
+ const append = useCallback(
172
+ async (message) => {
173
+ await client.append(message);
174
+ },
175
+ [client]
176
+ );
177
+ const reload = useCallback(async () => {
178
+ await client.reload();
179
+ }, [client]);
180
+ const stop = useCallback(() => {
181
+ client.stop();
182
+ }, [client]);
183
+ const clear = useCallback(() => {
184
+ client.clear();
185
+ }, [client]);
186
+ const setMessagesManually = useCallback(
187
+ (newMessages) => {
188
+ client.setMessagesManually(newMessages);
189
+ },
190
+ [client]
191
+ );
192
+ const addToolResult = useCallback(
193
+ async (result) => {
194
+ await client.addToolResult(result);
195
+ },
196
+ [client]
197
+ );
198
+ const addToolApprovalResponse = useCallback(
199
+ async (response) => {
200
+ await client.addToolApprovalResponse(response);
201
+ },
202
+ [client]
203
+ );
204
+ const renderedMessages = client.getMessages();
205
+ const activeStructuredPart = useMemo(() => {
206
+ let lastUserIndex = -1;
207
+ for (let i = renderedMessages.length - 1; i >= 0; i--) {
208
+ if (renderedMessages[i]?.role === "user") {
209
+ lastUserIndex = i;
210
+ break;
211
+ }
212
+ }
213
+ if (lastUserIndex === -1) return null;
214
+ for (let i = renderedMessages.length - 1; i > lastUserIndex; i--) {
215
+ const m = renderedMessages[i];
216
+ if (m?.role !== "assistant") continue;
217
+ const part = m.parts.find(
218
+ (p) => p.type === "structured-output"
219
+ );
220
+ if (part) return part;
221
+ }
222
+ return null;
223
+ }, [renderedMessages]);
224
+ const partial = useMemo(() => {
225
+ if (!activeStructuredPart) return {};
226
+ const v = activeStructuredPart.partial ?? activeStructuredPart.data;
227
+ return v ?? {};
228
+ }, [activeStructuredPart]);
229
+ const final = useMemo(() => {
230
+ if (!activeStructuredPart || activeStructuredPart.status !== "complete") {
231
+ return null;
232
+ }
233
+ return activeStructuredPart.data;
234
+ }, [activeStructuredPart]);
235
+ return {
236
+ messages: renderedMessages,
237
+ sendMessage,
238
+ append,
239
+ reload,
240
+ stop,
241
+ isLoading,
242
+ error,
243
+ status,
244
+ isSubscribed,
245
+ connectionStatus,
246
+ sessionGenerating,
247
+ setMessages: setMessagesManually,
248
+ clear,
249
+ addToolResult,
250
+ addToolApprovalResponse,
251
+ partial,
252
+ final
253
+ };
254
+ }
255
+ export {
256
+ useChat
257
+ };
258
+ //# sourceMappingURL=use-chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-chat.js","sources":["../../src/use-chat.ts"],"sourcesContent":["import { ChatClient } from '@tanstack/ai-client'\nimport { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport type {\n AnyClientTool,\n InferSchemaType,\n ModelMessage,\n SchemaInput,\n StreamChunk,\n} from '@tanstack/ai/client'\nimport type {\n ChatClientState,\n ConnectionStatus,\n InferredClientContext,\n StructuredOutputPart,\n} from '@tanstack/ai-client'\n\nimport type {\n DeepPartial,\n MultimodalContent,\n UIMessage,\n UseChatOptions,\n UseChatReturn,\n} from './types'\n\nexport function useChat<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TSchema extends SchemaInput | undefined = undefined,\n TContext = InferredClientContext<TTools>,\n>(\n options: UseChatOptions<TTools, TSchema, TContext>,\n): UseChatReturn<TTools, TSchema> {\n const hookId = useId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = useState<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<Error | undefined>(undefined)\n const [status, setStatus] = useState<ChatClientState>('ready')\n const [isSubscribed, setIsSubscribed] = useState(false)\n const [connectionStatus, setConnectionStatus] =\n useState<ConnectionStatus>('disconnected')\n const [sessionGenerating, setSessionGenerating] = useState(false)\n\n type Partial = DeepPartial<InferSchemaType<NonNullable<TSchema>>>\n type Final = InferSchemaType<NonNullable<TSchema>>\n\n // Track current messages in a ref to preserve them when client is recreated\n const messagesRef = useRef<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const isFirstMountRef = useRef(true)\n const activeClientRef = useRef<ChatClient | null>(null)\n const cleanupInvalidationRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n )\n\n // Update ref synchronously during render so it's always current when useMemo runs.\n messagesRef.current = messages\n\n // Track current options in a ref to avoid recreating client when options change\n const optionsRef = useRef<UseChatOptions<TTools, TSchema, TContext>>(options)\n optionsRef.current = options\n\n // Create ChatClient instance with callbacks to sync state\n const client = useMemo(() => {\n const messagesToUse = options.initialMessages || []\n isFirstMountRef.current = false\n\n // Build options with conditional spreads for fields whose source\n // type is `T | undefined` but the ChatClient target uses a strict\n // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects\n // assigning `undefined` to those, so we omit the key when absent.\n const initialOptions = optionsRef.current\n const transport = initialOptions.connection\n ? { connection: initialOptions.connection }\n : { fetcher: initialOptions.fetcher }\n\n const instance = new ChatClient<TTools, TContext>({\n devtoolsBridgeFactory: createChatDevtoolsBridge,\n ...transport,\n id: clientId,\n initialMessages: messagesToUse,\n ...(initialOptions.body !== undefined && { body: initialOptions.body }),\n ...(initialOptions.threadId !== undefined && {\n threadId: initialOptions.threadId,\n }),\n ...(initialOptions.forwardedProps !== undefined && {\n forwardedProps: initialOptions.forwardedProps,\n }),\n ...(initialOptions.persistence !== undefined && {\n persistence: initialOptions.persistence,\n }),\n ...(initialOptions.context !== undefined && {\n context: initialOptions.context,\n }),\n devtools: {\n ...initialOptions.devtools,\n framework: 'react',\n hookName: 'useChat',\n outputKind: initialOptions.outputSchema ? 'structured' : 'chat',\n },\n onResponse: (response) => {\n if (activeClientRef.current !== instance) return\n void optionsRef.current.onResponse?.(response)\n },\n onChunk: (chunk: StreamChunk) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onChunk?.(chunk)\n },\n onFinish: (message: UIMessage<TTools>) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onFinish?.(message)\n },\n onError: (error: Error) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onError?.(error)\n },\n ...(initialOptions.tools !== undefined && {\n tools: initialOptions.tools,\n }),\n onCustomEvent: (eventType, data, context) => {\n if (activeClientRef.current !== instance) return\n optionsRef.current.onCustomEvent?.(eventType, data, context)\n },\n ...(options.streamProcessor !== undefined && {\n streamProcessor: options.streamProcessor,\n }),\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n if (activeClientRef.current !== instance) return\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n if (activeClientRef.current !== instance) return\n setIsLoading(newIsLoading)\n },\n onErrorChange: (newError: Error | undefined) => {\n if (activeClientRef.current !== instance) return\n setError(newError)\n },\n onStatusChange: (status: ChatClientState) => {\n if (activeClientRef.current !== instance) return\n setStatus(status)\n },\n onSubscriptionChange: (nextIsSubscribed: boolean) => {\n if (activeClientRef.current !== instance) return\n setIsSubscribed(nextIsSubscribed)\n },\n onConnectionStatusChange: (nextStatus: ConnectionStatus) => {\n if (activeClientRef.current !== instance) return\n setConnectionStatus(nextStatus)\n },\n onSessionGeneratingChange: (isGenerating: boolean) => {\n if (activeClientRef.current !== instance) return\n setSessionGenerating(isGenerating)\n },\n })\n activeClientRef.current = instance\n return instance\n }, [clientId])\n\n useEffect(() => {\n const clientMessages = client.getMessages()\n if (clientMessages !== messagesRef.current) {\n setMessages(clientMessages)\n }\n }, [client])\n\n // Sync each wire-payload slot in its own effect so an unrelated option\n // changing doesn't re-run the others. `updateOptions` declares strict-optional\n // fields and rejects explicit `undefined` under EOPT, so guard the optional\n // slots before passing them.\n useEffect(() => {\n client.updateOptions({ body: options.body })\n }, [client, options.body])\n\n useEffect(() => {\n if (options.forwardedProps !== undefined) {\n client.updateOptions({ forwardedProps: options.forwardedProps })\n }\n }, [client, options.forwardedProps])\n\n useEffect(() => {\n if (options.tools !== undefined) {\n client.updateOptions({ tools: options.tools })\n }\n }, [client, options.tools])\n\n useEffect(() => {\n client.updateOptions({ context: options.context })\n }, [client, options.context])\n\n useEffect(() => {\n if (options.live) {\n client.subscribe()\n } else {\n client.unsubscribe()\n }\n }, [client, options.live])\n\n useEffect(() => {\n if (cleanupInvalidationRef.current) {\n clearTimeout(cleanupInvalidationRef.current)\n cleanupInvalidationRef.current = null\n }\n activeClientRef.current = client\n client.mountDevtools()\n\n return () => {\n cleanupInvalidationRef.current = setTimeout(() => {\n if (activeClientRef.current === client) {\n activeClientRef.current = null\n }\n cleanupInvalidationRef.current = null\n }, 0)\n // Subscribe/unsubscribe on `options.live` is owned by the dedicated\n // effect above. This cleanup only fires on unmount or client swap,\n // so read `live` through the ref to avoid disposing the client every\n // time `live` toggles.\n if (optionsRef.current.live) {\n client.unsubscribe()\n } else {\n client.stop()\n }\n client.dispose()\n }\n }, [client])\n\n const sendMessage = useCallback(\n async (content: string | MultimodalContent) => {\n await client.sendMessage(content)\n },\n [client],\n )\n\n const append = useCallback(\n async (message: ModelMessage | UIMessage) => {\n await client.append(message)\n },\n [client],\n )\n\n const reload = useCallback(async () => {\n await client.reload()\n }, [client])\n\n const stop = useCallback(() => {\n client.stop()\n }, [client])\n\n const clear = useCallback(() => {\n client.clear()\n }, [client])\n\n const setMessagesManually = useCallback(\n (newMessages: Array<UIMessage<TTools>>) => {\n client.setMessagesManually(newMessages)\n },\n [client],\n )\n\n const addToolResult = useCallback(\n async (result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client.addToolResult(result)\n },\n [client],\n )\n\n const addToolApprovalResponse = useCallback(\n async (response: { id: string; approved: boolean }) => {\n await client.addToolApprovalResponse(response)\n },\n [client],\n )\n\n // The \"active\" structured-output part is the one on the assistant message\n // that follows the latest user message. No such message exists between\n // sendMessage() and the first chunk, so partial/final naturally read as\n // cleared. Historical parts on earlier assistant messages remain available\n // via `messages` directly.\n //\n // When there is NO user message yet (e.g. `initialMessages` contains only\n // a stale assistant turn or a system prompt) we deliberately return null\n // rather than scanning historical assistants — otherwise a `final` from a\n // previous session would leak into the hook value on first render.\n const renderedMessages = client.getMessages()\n\n const activeStructuredPart = useMemo<StructuredOutputPart | null>(() => {\n let lastUserIndex = -1\n for (let i = renderedMessages.length - 1; i >= 0; i--) {\n if (renderedMessages[i]?.role === 'user') {\n lastUserIndex = i\n break\n }\n }\n if (lastUserIndex === -1) return null\n for (let i = renderedMessages.length - 1; i > lastUserIndex; i--) {\n const m = renderedMessages[i]\n if (m?.role !== 'assistant') continue\n const part = m.parts.find(\n (p): p is StructuredOutputPart => p.type === 'structured-output',\n )\n if (part) return part\n }\n return null\n }, [renderedMessages])\n\n const partial = useMemo<Partial>(() => {\n if (!activeStructuredPart) return {} as Partial\n const v = activeStructuredPart.partial ?? activeStructuredPart.data\n return (v ?? {}) as Partial\n }, [activeStructuredPart])\n\n const final = useMemo<Final | null>(() => {\n if (!activeStructuredPart || activeStructuredPart.status !== 'complete') {\n return null\n }\n return activeStructuredPart.data as Final\n }, [activeStructuredPart])\n\n // The runtime shape unconditionally exposes partial/final; the public\n // return type hides them when no outputSchema was supplied. TS can't\n // structurally narrow across that conditional, so the `as` is the seam.\n // eslint-disable-next-line no-restricted-syntax -- hook return shape diverges from generic UseChatReturn<TTools, TSchema> due to conditional type on TSchema; TS can't structurally narrow\n return {\n messages: renderedMessages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n status,\n isSubscribed,\n connectionStatus,\n sessionGenerating,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n partial,\n final,\n } as unknown as UseChatReturn<TTools, TSchema>\n}\n"],"names":["error","status"],"mappings":";;;AAyBO,SAAS,QAKd,SACgC;AAChC,QAAM,SAAS,MAAA;AACf,QAAM,WAAW,QAAQ,MAAM;AAE/B,QAAM,CAAC,UAAU,WAAW,IAAI;AAAA,IAC9B,QAAQ,mBAAmB,CAAA;AAAA,EAAC;AAE9B,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA4B,MAAS;AAC/D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA0B,OAAO;AAC7D,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AACtD,QAAM,CAAC,kBAAkB,mBAAmB,IAC1C,SAA2B,cAAc;AAC3C,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAS,KAAK;AAMhE,QAAM,cAAc;AAAA,IAClB,QAAQ,mBAAmB,CAAA;AAAA,EAAC;AAE9B,QAAM,kBAAkB,OAAO,IAAI;AACnC,QAAM,kBAAkB,OAA0B,IAAI;AACtD,QAAM,yBAAyB;AAAA,IAC7B;AAAA,EAAA;AAIF,cAAY,UAAU;AAGtB,QAAM,aAAa,OAAkD,OAAO;AAC5E,aAAW,UAAU;AAGrB,QAAM,SAAS,QAAQ,MAAM;AAC3B,UAAM,gBAAgB,QAAQ,mBAAmB,CAAA;AACjD,oBAAgB,UAAU;AAM1B,UAAM,iBAAiB,WAAW;AAClC,UAAM,YAAY,eAAe,aAC7B,EAAE,YAAY,eAAe,eAC7B,EAAE,SAAS,eAAe,QAAA;AAE9B,UAAM,WAAW,IAAI,WAA6B;AAAA,MAChD,uBAAuB;AAAA,MACvB,GAAG;AAAA,MACH,IAAI;AAAA,MACJ,iBAAiB;AAAA,MACjB,GAAI,eAAe,SAAS,UAAa,EAAE,MAAM,eAAe,KAAA;AAAA,MAChE,GAAI,eAAe,aAAa,UAAa;AAAA,QAC3C,UAAU,eAAe;AAAA,MAAA;AAAA,MAE3B,GAAI,eAAe,mBAAmB,UAAa;AAAA,QACjD,gBAAgB,eAAe;AAAA,MAAA;AAAA,MAEjC,GAAI,eAAe,gBAAgB,UAAa;AAAA,QAC9C,aAAa,eAAe;AAAA,MAAA;AAAA,MAE9B,GAAI,eAAe,YAAY,UAAa;AAAA,QAC1C,SAAS,eAAe;AAAA,MAAA;AAAA,MAE1B,UAAU;AAAA,QACR,GAAG,eAAe;AAAA,QAClB,WAAW;AAAA,QACX,UAAU;AAAA,QACV,YAAY,eAAe,eAAe,eAAe;AAAA,MAAA;AAAA,MAE3D,YAAY,CAAC,aAAa;AACxB,YAAI,gBAAgB,YAAY,SAAU;AAC1C,aAAK,WAAW,QAAQ,aAAa,QAAQ;AAAA,MAC/C;AAAA,MACA,SAAS,CAAC,UAAuB;AAC/B,YAAI,gBAAgB,YAAY,SAAU;AAC1C,mBAAW,QAAQ,UAAU,KAAK;AAAA,MACpC;AAAA,MACA,UAAU,CAAC,YAA+B;AACxC,YAAI,gBAAgB,YAAY,SAAU;AAC1C,mBAAW,QAAQ,WAAW,OAAO;AAAA,MACvC;AAAA,MACA,SAAS,CAACA,WAAiB;AACzB,YAAI,gBAAgB,YAAY,SAAU;AAC1C,mBAAW,QAAQ,UAAUA,MAAK;AAAA,MACpC;AAAA,MACA,GAAI,eAAe,UAAU,UAAa;AAAA,QACxC,OAAO,eAAe;AAAA,MAAA;AAAA,MAExB,eAAe,CAAC,WAAW,MAAM,YAAY;AAC3C,YAAI,gBAAgB,YAAY,SAAU;AAC1C,mBAAW,QAAQ,gBAAgB,WAAW,MAAM,OAAO;AAAA,MAC7D;AAAA,MACA,GAAI,QAAQ,oBAAoB,UAAa;AAAA,QAC3C,iBAAiB,QAAQ;AAAA,MAAA;AAAA,MAE3B,kBAAkB,CAAC,gBAA0C;AAC3D,YAAI,gBAAgB,YAAY,SAAU;AAC1C,oBAAY,WAAW;AAAA,MACzB;AAAA,MACA,iBAAiB,CAAC,iBAA0B;AAC1C,YAAI,gBAAgB,YAAY,SAAU;AAC1C,qBAAa,YAAY;AAAA,MAC3B;AAAA,MACA,eAAe,CAAC,aAAgC;AAC9C,YAAI,gBAAgB,YAAY,SAAU;AAC1C,iBAAS,QAAQ;AAAA,MACnB;AAAA,MACA,gBAAgB,CAACC,YAA4B;AAC3C,YAAI,gBAAgB,YAAY,SAAU;AAC1C,kBAAUA,OAAM;AAAA,MAClB;AAAA,MACA,sBAAsB,CAAC,qBAA8B;AACnD,YAAI,gBAAgB,YAAY,SAAU;AAC1C,wBAAgB,gBAAgB;AAAA,MAClC;AAAA,MACA,0BAA0B,CAAC,eAAiC;AAC1D,YAAI,gBAAgB,YAAY,SAAU;AAC1C,4BAAoB,UAAU;AAAA,MAChC;AAAA,MACA,2BAA2B,CAAC,iBAA0B;AACpD,YAAI,gBAAgB,YAAY,SAAU;AAC1C,6BAAqB,YAAY;AAAA,MACnC;AAAA,IAAA,CACD;AACD,oBAAgB,UAAU;AAC1B,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,CAAC;AAEb,YAAU,MAAM;AACd,UAAM,iBAAiB,OAAO,YAAA;AAC9B,QAAI,mBAAmB,YAAY,SAAS;AAC1C,kBAAY,cAAc;AAAA,IAC5B;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAMX,YAAU,MAAM;AACd,WAAO,cAAc,EAAE,MAAM,QAAQ,MAAM;AAAA,EAC7C,GAAG,CAAC,QAAQ,QAAQ,IAAI,CAAC;AAEzB,YAAU,MAAM;AACd,QAAI,QAAQ,mBAAmB,QAAW;AACxC,aAAO,cAAc,EAAE,gBAAgB,QAAQ,gBAAgB;AAAA,IACjE;AAAA,EACF,GAAG,CAAC,QAAQ,QAAQ,cAAc,CAAC;AAEnC,YAAU,MAAM;AACd,QAAI,QAAQ,UAAU,QAAW;AAC/B,aAAO,cAAc,EAAE,OAAO,QAAQ,OAAO;AAAA,IAC/C;AAAA,EACF,GAAG,CAAC,QAAQ,QAAQ,KAAK,CAAC;AAE1B,YAAU,MAAM;AACd,WAAO,cAAc,EAAE,SAAS,QAAQ,SAAS;AAAA,EACnD,GAAG,CAAC,QAAQ,QAAQ,OAAO,CAAC;AAE5B,YAAU,MAAM;AACd,QAAI,QAAQ,MAAM;AAChB,aAAO,UAAA;AAAA,IACT,OAAO;AACL,aAAO,YAAA;AAAA,IACT;AAAA,EACF,GAAG,CAAC,QAAQ,QAAQ,IAAI,CAAC;AAEzB,YAAU,MAAM;AACd,QAAI,uBAAuB,SAAS;AAClC,mBAAa,uBAAuB,OAAO;AAC3C,6BAAuB,UAAU;AAAA,IACnC;AACA,oBAAgB,UAAU;AAC1B,WAAO,cAAA;AAEP,WAAO,MAAM;AACX,6BAAuB,UAAU,WAAW,MAAM;AAChD,YAAI,gBAAgB,YAAY,QAAQ;AACtC,0BAAgB,UAAU;AAAA,QAC5B;AACA,+BAAuB,UAAU;AAAA,MACnC,GAAG,CAAC;AAKJ,UAAI,WAAW,QAAQ,MAAM;AAC3B,eAAO,YAAA;AAAA,MACT,OAAO;AACL,eAAO,KAAA;AAAA,MACT;AACA,aAAO,QAAA;AAAA,IACT;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,cAAc;AAAA,IAClB,OAAO,YAAwC;AAC7C,YAAM,OAAO,YAAY,OAAO;AAAA,IAClC;AAAA,IACA,CAAC,MAAM;AAAA,EAAA;AAGT,QAAM,SAAS;AAAA,IACb,OAAO,YAAsC;AAC3C,YAAM,OAAO,OAAO,OAAO;AAAA,IAC7B;AAAA,IACA,CAAC,MAAM;AAAA,EAAA;AAGT,QAAM,SAAS,YAAY,YAAY;AACrC,UAAM,OAAO,OAAA;AAAA,EACf,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,OAAO,YAAY,MAAM;AAC7B,WAAO,KAAA;AAAA,EACT,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,QAAQ,YAAY,MAAM;AAC9B,WAAO,MAAA;AAAA,EACT,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,sBAAsB;AAAA,IAC1B,CAAC,gBAA0C;AACzC,aAAO,oBAAoB,WAAW;AAAA,IACxC;AAAA,IACA,CAAC,MAAM;AAAA,EAAA;AAGT,QAAM,gBAAgB;AAAA,IACpB,OAAO,WAMD;AACJ,YAAM,OAAO,cAAc,MAAM;AAAA,IACnC;AAAA,IACA,CAAC,MAAM;AAAA,EAAA;AAGT,QAAM,0BAA0B;AAAA,IAC9B,OAAO,aAAgD;AACrD,YAAM,OAAO,wBAAwB,QAAQ;AAAA,IAC/C;AAAA,IACA,CAAC,MAAM;AAAA,EAAA;AAaT,QAAM,mBAAmB,OAAO,YAAA;AAEhC,QAAM,uBAAuB,QAAqC,MAAM;AACtE,QAAI,gBAAgB;AACpB,aAAS,IAAI,iBAAiB,SAAS,GAAG,KAAK,GAAG,KAAK;AACrD,UAAI,iBAAiB,CAAC,GAAG,SAAS,QAAQ;AACxC,wBAAgB;AAChB;AAAA,MACF;AAAA,IACF;AACA,QAAI,kBAAkB,GAAI,QAAO;AACjC,aAAS,IAAI,iBAAiB,SAAS,GAAG,IAAI,eAAe,KAAK;AAChE,YAAM,IAAI,iBAAiB,CAAC;AAC5B,UAAI,GAAG,SAAS,YAAa;AAC7B,YAAM,OAAO,EAAE,MAAM;AAAA,QACnB,CAAC,MAAiC,EAAE,SAAS;AAAA,MAAA;AAE/C,UAAI,KAAM,QAAO;AAAA,IACnB;AACA,WAAO;AAAA,EACT,GAAG,CAAC,gBAAgB,CAAC;AAErB,QAAM,UAAU,QAAiB,MAAM;AACrC,QAAI,CAAC,qBAAsB,QAAO,CAAA;AAClC,UAAM,IAAI,qBAAqB,WAAW,qBAAqB;AAC/D,WAAQ,KAAK,CAAA;AAAA,EACf,GAAG,CAAC,oBAAoB,CAAC;AAEzB,QAAM,QAAQ,QAAsB,MAAM;AACxC,QAAI,CAAC,wBAAwB,qBAAqB,WAAW,YAAY;AACvE,aAAO;AAAA,IACT;AACA,WAAO,qBAAqB;AAAA,EAC9B,GAAG,CAAC,oBAAoB,CAAC;AAMzB,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}