@tanstack/ai-react 0.6.3 → 0.7.1

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 (35) hide show
  1. package/dist/esm/index.d.ts +15 -1
  2. package/dist/esm/index.js +15 -1
  3. package/dist/esm/index.js.map +1 -1
  4. package/dist/esm/realtime-types.d.ts +109 -0
  5. package/dist/esm/use-generate-image.d.ts +87 -0
  6. package/dist/esm/use-generate-image.js +17 -0
  7. package/dist/esm/use-generate-image.js.map +1 -0
  8. package/dist/esm/use-generate-speech.d.ts +81 -0
  9. package/dist/esm/use-generate-speech.js +17 -0
  10. package/dist/esm/use-generate-speech.js.map +1 -0
  11. package/dist/esm/use-generate-video.d.ts +92 -0
  12. package/dist/esm/use-generate-video.js +83 -0
  13. package/dist/esm/use-generate-video.js.map +1 -0
  14. package/dist/esm/use-generation.d.ts +78 -0
  15. package/dist/esm/use-generation.js +75 -0
  16. package/dist/esm/use-generation.js.map +1 -0
  17. package/dist/esm/use-realtime-chat.d.ts +42 -0
  18. package/dist/esm/use-realtime-chat.js +174 -0
  19. package/dist/esm/use-realtime-chat.js.map +1 -0
  20. package/dist/esm/use-summarize.d.ts +84 -0
  21. package/dist/esm/use-summarize.js +19 -0
  22. package/dist/esm/use-summarize.js.map +1 -0
  23. package/dist/esm/use-transcription.d.ts +89 -0
  24. package/dist/esm/use-transcription.js +19 -0
  25. package/dist/esm/use-transcription.js.map +1 -0
  26. package/package.json +4 -4
  27. package/src/index.ts +47 -0
  28. package/src/realtime-types.ts +143 -0
  29. package/src/use-generate-image.ts +117 -0
  30. package/src/use-generate-speech.ts +108 -0
  31. package/src/use-generate-video.ts +206 -0
  32. package/src/use-generation.ts +180 -0
  33. package/src/use-realtime-chat.ts +277 -0
  34. package/src/use-summarize.ts +114 -0
  35. package/src/use-transcription.ts +121 -0
@@ -0,0 +1,143 @@
1
+ import type {
2
+ AnyClientTool,
3
+ RealtimeMessage,
4
+ RealtimeMode,
5
+ RealtimeStatus,
6
+ RealtimeToken,
7
+ } from '@tanstack/ai'
8
+ import type { RealtimeAdapter } from '@tanstack/ai-client'
9
+
10
+ /**
11
+ * Options for the useRealtimeChat hook.
12
+ */
13
+ export interface UseRealtimeChatOptions {
14
+ /**
15
+ * Function to fetch a realtime token from the server.
16
+ * Called on connect and when token needs refresh.
17
+ */
18
+ getToken: () => Promise<RealtimeToken>
19
+
20
+ /**
21
+ * The realtime adapter to use (e.g., openaiRealtime())
22
+ */
23
+ adapter: RealtimeAdapter
24
+
25
+ /**
26
+ * Client-side tools with execution logic
27
+ */
28
+ tools?: ReadonlyArray<AnyClientTool>
29
+
30
+ /**
31
+ * Auto-play assistant audio (default: true)
32
+ */
33
+ autoPlayback?: boolean
34
+
35
+ /**
36
+ * Request microphone access on connect (default: true)
37
+ */
38
+ autoCapture?: boolean
39
+
40
+ /**
41
+ * System instructions for the assistant
42
+ */
43
+ instructions?: string
44
+
45
+ /**
46
+ * Voice to use for audio output
47
+ */
48
+ voice?: string
49
+
50
+ /**
51
+ * Voice activity detection mode (default: 'server')
52
+ */
53
+ vadMode?: 'server' | 'semantic' | 'manual'
54
+
55
+ /**
56
+ * Output modalities for responses (e.g., ['audio', 'text'])
57
+ */
58
+ outputModalities?: Array<'audio' | 'text'>
59
+
60
+ /**
61
+ * Temperature for generation (provider-specific range)
62
+ */
63
+ temperature?: number
64
+
65
+ /**
66
+ * Maximum number of tokens in a response
67
+ */
68
+ maxOutputTokens?: number | 'inf'
69
+
70
+ /**
71
+ * Eagerness level for semantic VAD ('low', 'medium', 'high')
72
+ */
73
+ semanticEagerness?: 'low' | 'medium' | 'high'
74
+
75
+ // Callbacks
76
+ onConnect?: () => void
77
+ onDisconnect?: () => void
78
+ onError?: (error: Error) => void
79
+ onMessage?: (message: RealtimeMessage) => void
80
+ onModeChange?: (mode: RealtimeMode) => void
81
+ onInterrupted?: () => void
82
+ }
83
+
84
+ /**
85
+ * Return type for the useRealtimeChat hook.
86
+ */
87
+ export interface UseRealtimeChatReturn {
88
+ // Connection state
89
+ /** Current connection status */
90
+ status: RealtimeStatus
91
+ /** Current error, if any */
92
+ error: Error | null
93
+ /** Connect to the realtime session */
94
+ connect: () => Promise<void>
95
+ /** Disconnect from the realtime session */
96
+ disconnect: () => Promise<void>
97
+
98
+ // Conversation state
99
+ /** Current mode (idle, listening, thinking, speaking) */
100
+ mode: RealtimeMode
101
+ /** Conversation messages */
102
+ messages: Array<RealtimeMessage>
103
+ /** User transcript while speaking (before finalized) */
104
+ pendingUserTranscript: string | null
105
+ /** Assistant transcript while speaking (before finalized) */
106
+ pendingAssistantTranscript: string | null
107
+
108
+ // Voice control
109
+ /** Start listening for voice input (manual VAD mode) */
110
+ startListening: () => void
111
+ /** Stop listening for voice input (manual VAD mode) */
112
+ stopListening: () => void
113
+ /** Interrupt the current assistant response */
114
+ interrupt: () => void
115
+
116
+ // Text input
117
+ /** Send a text message instead of voice */
118
+ sendText: (text: string) => void
119
+
120
+ // Image input
121
+ /** Send an image to the conversation */
122
+ sendImage: (imageData: string, mimeType: string) => void
123
+
124
+ // Audio visualization (0-1 normalized)
125
+ /** Current input (microphone) volume level */
126
+ inputLevel: number
127
+ /** Current output (speaker) volume level */
128
+ outputLevel: number
129
+ /** Get frequency data for input audio visualization */
130
+ getInputFrequencyData: () => Uint8Array
131
+ /** Get frequency data for output audio visualization */
132
+ getOutputFrequencyData: () => Uint8Array
133
+ /** Get time domain data for input waveform */
134
+ getInputTimeDomainData: () => Uint8Array
135
+ /** Get time domain data for output waveform */
136
+ getOutputTimeDomainData: () => Uint8Array
137
+
138
+ // VAD control
139
+ /** Current VAD mode */
140
+ vadMode: 'server' | 'semantic' | 'manual'
141
+ /** Change VAD mode at runtime */
142
+ setVADMode: (mode: 'server' | 'semantic' | 'manual') => void
143
+ }
@@ -0,0 +1,117 @@
1
+ import { useGeneration } from './use-generation'
2
+ import type { ImageGenerationResult, StreamChunk } from '@tanstack/ai'
3
+ import type {
4
+ ConnectionAdapter,
5
+ GenerationClientState,
6
+ GenerationFetcher,
7
+ ImageGenerateInput,
8
+ InferGenerationOutput,
9
+ } from '@tanstack/ai-client'
10
+
11
+ /**
12
+ * Options for the useGenerateImage hook.
13
+ *
14
+ * @template TOutput - The output type after optional transform (defaults to ImageGenerationResult)
15
+ */
16
+ export interface UseGenerateImageOptions<TOutput = ImageGenerationResult> {
17
+ /** Connection adapter for streaming transport (SSE, HTTP stream, custom) */
18
+ connection?: ConnectionAdapter
19
+ /** Direct async function for image generation */
20
+ fetcher?: GenerationFetcher<ImageGenerateInput, ImageGenerationResult>
21
+ /** Unique identifier for this generation instance */
22
+ id?: string
23
+ /** Additional body parameters to send with ConnectionAdapter requests */
24
+ body?: Record<string, any>
25
+ /**
26
+ * Callback when images are generated. Can optionally return a transformed value.
27
+ *
28
+ * - Return a non-null value to transform and store it as the result
29
+ * - Return `null` to keep the previous result unchanged
30
+ * - Return nothing (`void`) to store the raw result as-is
31
+ */
32
+ onResult?: (result: ImageGenerationResult) => TOutput | null | void
33
+ /** Callback when an error occurs */
34
+ onError?: (error: Error) => void
35
+ /** Callback when progress is reported (0-100) */
36
+ onProgress?: (progress: number, message?: string) => void
37
+ /** Callback for each stream chunk (ConnectionAdapter mode only) */
38
+ onChunk?: (chunk: StreamChunk) => void
39
+ }
40
+
41
+ /**
42
+ * Return type for the useGenerateImage hook.
43
+ *
44
+ * @template TOutput - The output type (after optional transform)
45
+ */
46
+ export interface UseGenerateImageReturn<TOutput = ImageGenerationResult> {
47
+ /** Trigger image generation */
48
+ generate: (input: ImageGenerateInput) => Promise<void>
49
+ /** The generation result containing images, or null */
50
+ result: TOutput | null
51
+ /** Whether generation is in progress */
52
+ isLoading: boolean
53
+ /** Current error, if any */
54
+ error: Error | undefined
55
+ /** Current state of the generation */
56
+ status: GenerationClientState
57
+ /** Abort the current generation */
58
+ stop: () => void
59
+ /** Clear result, error, and return to idle */
60
+ reset: () => void
61
+ }
62
+
63
+ /**
64
+ * React hook for generating images using AI models.
65
+ *
66
+ * Supports two transport modes:
67
+ * - **ConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom)
68
+ * - **Fetcher** — Direct async function call
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * import { useGenerateImage } from '@tanstack/ai-react'
73
+ * import { fetchServerSentEvents } from '@tanstack/ai-client'
74
+ *
75
+ * function ImageGenerator() {
76
+ * const { generate, result, isLoading, error, reset } = useGenerateImage({
77
+ * connection: fetchServerSentEvents('/api/generate/image'),
78
+ * })
79
+ *
80
+ * return (
81
+ * <div>
82
+ * <button onClick={() => generate({ prompt: 'A sunset over mountains' })}>
83
+ * Generate
84
+ * </button>
85
+ * {isLoading && <p>Generating...</p>}
86
+ * {error && <p>Error: {error.message}</p>}
87
+ * {result?.images.map((img, i) => (
88
+ * <img key={i} src={img.url || `data:image/png;base64,${img.b64Json}`} />
89
+ * ))}
90
+ * </div>
91
+ * )
92
+ * }
93
+ * ```
94
+ */
95
+ export function useGenerateImage<
96
+ TOnResult extends ((result: ImageGenerationResult) => any) | undefined =
97
+ undefined,
98
+ >(
99
+ options: Omit<UseGenerateImageOptions, 'onResult'> & {
100
+ onResult?: TOnResult
101
+ },
102
+ ): UseGenerateImageReturn<
103
+ InferGenerationOutput<ImageGenerationResult, TOnResult>
104
+ > {
105
+ const { generate, result, isLoading, error, status, stop, reset } =
106
+ useGeneration<ImageGenerateInput, ImageGenerationResult, TOnResult>(options)
107
+
108
+ return {
109
+ generate: generate as (input: ImageGenerateInput) => Promise<void>,
110
+ result,
111
+ isLoading,
112
+ error,
113
+ status,
114
+ stop,
115
+ reset,
116
+ }
117
+ }
@@ -0,0 +1,108 @@
1
+ import { useGeneration } from './use-generation'
2
+ import type { StreamChunk, TTSResult } from '@tanstack/ai'
3
+ import type {
4
+ ConnectionAdapter,
5
+ GenerationClientState,
6
+ GenerationFetcher,
7
+ InferGenerationOutput,
8
+ SpeechGenerateInput,
9
+ } from '@tanstack/ai-client'
10
+
11
+ /**
12
+ * Options for the useGenerateSpeech hook.
13
+ *
14
+ * @template TOutput - The output type after optional transform (defaults to TTSResult)
15
+ */
16
+ export interface UseGenerateSpeechOptions<TOutput = TTSResult> {
17
+ /** Connection adapter for streaming transport (SSE, HTTP stream, custom) */
18
+ connection?: ConnectionAdapter
19
+ /** Direct async function for speech generation */
20
+ fetcher?: GenerationFetcher<SpeechGenerateInput, TTSResult>
21
+ /** Unique identifier for this generation instance */
22
+ id?: string
23
+ /** Additional body parameters to send with ConnectionAdapter requests */
24
+ body?: Record<string, any>
25
+ /**
26
+ * Callback when speech is generated. Can optionally return a transformed value.
27
+ *
28
+ * - Return a non-null value to transform and store it as the result
29
+ * - Return `null` to keep the previous result unchanged
30
+ * - Return nothing (`void`) to store the raw result as-is
31
+ */
32
+ onResult?: (result: TTSResult) => TOutput | null | void
33
+ /** Callback when an error occurs */
34
+ onError?: (error: Error) => void
35
+ /** Callback when progress is reported (0-100) */
36
+ onProgress?: (progress: number, message?: string) => void
37
+ /** Callback for each stream chunk (ConnectionAdapter mode only) */
38
+ onChunk?: (chunk: StreamChunk) => void
39
+ }
40
+
41
+ /**
42
+ * Return type for the useGenerateSpeech hook.
43
+ *
44
+ * @template TOutput - The output type (after optional transform)
45
+ */
46
+ export interface UseGenerateSpeechReturn<TOutput = TTSResult> {
47
+ /** Trigger speech generation */
48
+ generate: (input: SpeechGenerateInput) => Promise<void>
49
+ /** The TTS result containing audio data, or null */
50
+ result: TOutput | null
51
+ /** Whether generation is in progress */
52
+ isLoading: boolean
53
+ /** Current error, if any */
54
+ error: Error | undefined
55
+ /** Current state of the generation */
56
+ status: GenerationClientState
57
+ /** Abort the current generation */
58
+ stop: () => void
59
+ /** Clear result, error, and return to idle */
60
+ reset: () => void
61
+ }
62
+
63
+ /**
64
+ * React hook for generating speech (text-to-speech) using AI models.
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * import { useGenerateSpeech } from '@tanstack/ai-react'
69
+ * import { fetchServerSentEvents } from '@tanstack/ai-client'
70
+ *
71
+ * function SpeechGenerator() {
72
+ * const { generate, result, isLoading } = useGenerateSpeech({
73
+ * connection: fetchServerSentEvents('/api/generate/speech'),
74
+ * })
75
+ *
76
+ * return (
77
+ * <div>
78
+ * <button onClick={() => generate({ text: 'Hello world', voice: 'alloy' })}>
79
+ * Generate Speech
80
+ * </button>
81
+ * {result && (
82
+ * <audio src={`data:audio/${result.format};base64,${result.audio}`} controls />
83
+ * )}
84
+ * </div>
85
+ * )
86
+ * }
87
+ * ```
88
+ */
89
+ export function useGenerateSpeech<
90
+ TOnResult extends ((result: TTSResult) => any) | undefined = undefined,
91
+ >(
92
+ options: Omit<UseGenerateSpeechOptions, 'onResult'> & {
93
+ onResult?: TOnResult
94
+ },
95
+ ): UseGenerateSpeechReturn<InferGenerationOutput<TTSResult, TOnResult>> {
96
+ const { generate, result, isLoading, error, status, stop, reset } =
97
+ useGeneration<SpeechGenerateInput, TTSResult, TOnResult>(options)
98
+
99
+ return {
100
+ generate: generate as (input: SpeechGenerateInput) => Promise<void>,
101
+ result,
102
+ isLoading,
103
+ error,
104
+ status,
105
+ stop,
106
+ reset,
107
+ }
108
+ }
@@ -0,0 +1,206 @@
1
+ import { VideoGenerationClient } from '@tanstack/ai-client'
2
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
3
+ import type { StreamChunk } from '@tanstack/ai'
4
+ import type {
5
+ ConnectionAdapter,
6
+ GenerationClientState,
7
+ GenerationFetcher,
8
+ InferGenerationOutput,
9
+ VideoGenerateInput,
10
+ VideoGenerateResult,
11
+ VideoStatusInfo,
12
+ } from '@tanstack/ai-client'
13
+
14
+ /**
15
+ * Options for the useGenerateVideo hook.
16
+ */
17
+ export interface UseGenerateVideoOptions<TOutput = VideoGenerateResult> {
18
+ /** Connection adapter for streaming transport (server handles polling) */
19
+ connection?: ConnectionAdapter
20
+ /** Direct async function that returns a completed video result */
21
+ fetcher?: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>
22
+ /** Unique identifier for this generation instance */
23
+ id?: string
24
+ /** Additional body parameters to send with ConnectionAdapter requests */
25
+ body?: Record<string, any>
26
+ /**
27
+ * Callback when video generation completes. Can optionally return a transformed value.
28
+ *
29
+ * - Return a non-null value to transform and store it as the result
30
+ * - Return `null` to keep the previous result unchanged
31
+ * - Return nothing (`void`) to store the raw result as-is
32
+ */
33
+ onResult?: (result: VideoGenerateResult) => TOutput | null | void
34
+ /** Callback when an error occurs */
35
+ onError?: (error: Error) => void
36
+ /** Callback when progress is reported (0-100) */
37
+ onProgress?: (progress: number, message?: string) => void
38
+ /** Callback when a video job is created */
39
+ onJobCreated?: (jobId: string) => void
40
+ /** Callback on each status update */
41
+ onStatusUpdate?: (status: VideoStatusInfo) => void
42
+ /** Callback for each stream chunk (ConnectionAdapter mode only) */
43
+ onChunk?: (chunk: StreamChunk) => void
44
+ }
45
+
46
+ /**
47
+ * Return type for the useGenerateVideo hook.
48
+ *
49
+ * @template TOutput - The output type (after optional transform)
50
+ */
51
+ export interface UseGenerateVideoReturn<TOutput = VideoGenerateResult> {
52
+ /** Trigger video generation */
53
+ generate: (input: VideoGenerateInput) => Promise<void>
54
+ /** The final video result (with URL), or null */
55
+ result: TOutput | null
56
+ /** The current job ID, or null */
57
+ jobId: string | null
58
+ /** Current video generation status info, or null */
59
+ videoStatus: VideoStatusInfo | null
60
+ /** Whether generation/polling is in progress */
61
+ isLoading: boolean
62
+ /** Current error, if any */
63
+ error: Error | undefined
64
+ /** Current state of the generation */
65
+ status: GenerationClientState
66
+ /** Abort the current generation/polling */
67
+ stop: () => void
68
+ /** Clear all state and return to idle */
69
+ reset: () => void
70
+ }
71
+
72
+ /**
73
+ * React hook for generating videos using AI models.
74
+ *
75
+ * Video generation is asynchronous: a job is created, then polled for status
76
+ * until completion. This hook handles the full lifecycle.
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * import { useGenerateVideo } from '@tanstack/ai-react'
81
+ * import { fetchServerSentEvents } from '@tanstack/ai-client'
82
+ *
83
+ * function VideoGenerator() {
84
+ * const { generate, result, videoStatus, isLoading } = useGenerateVideo({
85
+ * connection: fetchServerSentEvents('/api/generate/video'),
86
+ * onStatusUpdate: (status) => console.log(`Progress: ${status.progress}%`),
87
+ * })
88
+ *
89
+ * return (
90
+ * <div>
91
+ * <button onClick={() => generate({ prompt: 'A flying car over a city' })}>
92
+ * Generate Video
93
+ * </button>
94
+ * {isLoading && videoStatus && (
95
+ * <p>Status: {videoStatus.status} ({videoStatus.progress}%)</p>
96
+ * )}
97
+ * {result && <video src={result.url} controls />}
98
+ * </div>
99
+ * )
100
+ * }
101
+ * ```
102
+ */
103
+ export function useGenerateVideo<
104
+ TOnResult extends ((result: VideoGenerateResult) => any) | undefined =
105
+ undefined,
106
+ >(
107
+ options: Omit<UseGenerateVideoOptions, 'onResult'> & {
108
+ onResult?: TOnResult
109
+ },
110
+ ): UseGenerateVideoReturn<
111
+ InferGenerationOutput<VideoGenerateResult, TOnResult>
112
+ > {
113
+ type TOutput = InferGenerationOutput<VideoGenerateResult, TOnResult>
114
+ const hookId = useId()
115
+ const clientId = options.id || hookId
116
+
117
+ const [result, setResult] = useState<TOutput | null>(null)
118
+ const [jobId, setJobId] = useState<string | null>(null)
119
+ const [videoStatus, setVideoStatus] = useState<VideoStatusInfo | null>(null)
120
+ const [isLoading, setIsLoading] = useState(false)
121
+ const [error, setError] = useState<Error | undefined>(undefined)
122
+ const [status, setStatus] = useState<GenerationClientState>('idle')
123
+
124
+ const optionsRef = useRef(options)
125
+ optionsRef.current = options
126
+
127
+ const client = useMemo(() => {
128
+ const opts = optionsRef.current
129
+
130
+ const baseOptions = {
131
+ id: clientId,
132
+ body: opts.body,
133
+ onResult: (r: VideoGenerateResult) => optionsRef.current.onResult?.(r),
134
+ onError: (e: Error) => optionsRef.current.onError?.(e),
135
+ onProgress: (p: number, m?: string) =>
136
+ optionsRef.current.onProgress?.(p, m),
137
+ onChunk: (c: StreamChunk) => optionsRef.current.onChunk?.(c),
138
+ onJobCreated: (id: string) => optionsRef.current.onJobCreated?.(id),
139
+ onStatusUpdate: (s: VideoStatusInfo) =>
140
+ optionsRef.current.onStatusUpdate?.(s),
141
+ onResultChange: setResult,
142
+ onLoadingChange: setIsLoading,
143
+ onErrorChange: setError,
144
+ onStatusChange: setStatus,
145
+ onJobIdChange: setJobId,
146
+ onVideoStatusChange: setVideoStatus,
147
+ }
148
+
149
+ if (opts.connection) {
150
+ return new VideoGenerationClient<TOutput>({
151
+ ...baseOptions,
152
+ connection: opts.connection,
153
+ })
154
+ }
155
+
156
+ if (opts.fetcher) {
157
+ return new VideoGenerationClient<TOutput>({
158
+ ...baseOptions,
159
+ fetcher: opts.fetcher,
160
+ })
161
+ }
162
+
163
+ throw new Error(
164
+ 'useGenerateVideo requires either a connection or fetcher option',
165
+ )
166
+ }, [clientId])
167
+
168
+ // Sync body changes without recreating client
169
+ useEffect(() => {
170
+ client.updateOptions({ body: options.body })
171
+ }, [client, options.body])
172
+
173
+ // Cleanup on unmount
174
+ useEffect(() => {
175
+ return () => {
176
+ client.stop()
177
+ }
178
+ }, [client])
179
+
180
+ const generate = useCallback(
181
+ async (input: VideoGenerateInput) => {
182
+ await client.generate(input)
183
+ },
184
+ [client],
185
+ )
186
+
187
+ const stop = useCallback(() => {
188
+ client.stop()
189
+ }, [client])
190
+
191
+ const reset = useCallback(() => {
192
+ client.reset()
193
+ }, [client])
194
+
195
+ return {
196
+ generate,
197
+ result,
198
+ jobId,
199
+ videoStatus,
200
+ isLoading,
201
+ error,
202
+ status,
203
+ stop,
204
+ reset,
205
+ }
206
+ }