@tanstack/ai-react 0.6.4 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/realtime-types.d.ts +109 -0
- package/dist/esm/types.d.ts +23 -2
- package/dist/esm/use-chat.js +28 -2
- package/dist/esm/use-chat.js.map +1 -1
- package/dist/esm/use-generate-image.d.ts +6 -6
- package/dist/esm/use-generate-image.js.map +1 -1
- package/dist/esm/use-generate-speech.d.ts +5 -5
- package/dist/esm/use-generate-speech.js.map +1 -1
- package/dist/esm/use-generate-video.d.ts +5 -5
- package/dist/esm/use-generate-video.js.map +1 -1
- package/dist/esm/use-generation.d.ts +5 -5
- package/dist/esm/use-generation.js.map +1 -1
- package/dist/esm/use-realtime-chat.d.ts +42 -0
- package/dist/esm/use-realtime-chat.js +174 -0
- package/dist/esm/use-realtime-chat.js.map +1 -0
- package/dist/esm/use-summarize.d.ts +5 -5
- package/dist/esm/use-summarize.js.map +1 -1
- package/dist/esm/use-transcription.d.ts +5 -5
- package/dist/esm/use-transcription.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +5 -0
- package/src/realtime-types.ts +143 -0
- package/src/types.ts +33 -2
- package/src/use-chat.ts +33 -4
- package/src/use-generate-image.ts +6 -6
- package/src/use-generate-speech.ts +5 -5
- package/src/use-generate-video.ts +5 -5
- package/src/use-generation.ts +5 -5
- package/src/use-realtime-chat.ts +277 -0
- package/src/use-summarize.ts +5 -5
- package/src/use-transcription.ts +5 -5
package/src/use-chat.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChatClient } from '@tanstack/ai-client'
|
|
2
2
|
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
|
|
4
|
-
import type { ChatClientState } from '@tanstack/ai-client'
|
|
4
|
+
import type { ChatClientState, ConnectionStatus } from '@tanstack/ai-client'
|
|
5
5
|
|
|
6
6
|
import type {
|
|
7
7
|
MultimodalContent,
|
|
@@ -22,6 +22,10 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
|
|
|
22
22
|
const [isLoading, setIsLoading] = useState(false)
|
|
23
23
|
const [error, setError] = useState<Error | undefined>(undefined)
|
|
24
24
|
const [status, setStatus] = useState<ChatClientState>('ready')
|
|
25
|
+
const [isSubscribed, setIsSubscribed] = useState(false)
|
|
26
|
+
const [connectionStatus, setConnectionStatus] =
|
|
27
|
+
useState<ConnectionStatus>('disconnected')
|
|
28
|
+
const [sessionGenerating, setSessionGenerating] = useState(false)
|
|
25
29
|
|
|
26
30
|
// Track current messages in a ref to preserve them when client is recreated
|
|
27
31
|
const messagesRef = useRef<Array<UIMessage<TTools>>>(
|
|
@@ -78,6 +82,15 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
|
|
|
78
82
|
onStatusChange: (status: ChatClientState) => {
|
|
79
83
|
setStatus(status)
|
|
80
84
|
},
|
|
85
|
+
onSubscriptionChange: (nextIsSubscribed: boolean) => {
|
|
86
|
+
setIsSubscribed(nextIsSubscribed)
|
|
87
|
+
},
|
|
88
|
+
onConnectionStatusChange: (nextStatus: ConnectionStatus) => {
|
|
89
|
+
setConnectionStatus(nextStatus)
|
|
90
|
+
},
|
|
91
|
+
onSessionGeneratingChange: (isGenerating: boolean) => {
|
|
92
|
+
setSessionGenerating(isGenerating)
|
|
93
|
+
},
|
|
81
94
|
})
|
|
82
95
|
}, [clientId])
|
|
83
96
|
|
|
@@ -99,16 +112,29 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
|
|
|
99
112
|
}
|
|
100
113
|
}, []) // Only run on mount - initialMessages are handled by ChatClient constructor
|
|
101
114
|
|
|
115
|
+
// Keep connection lifecycle opt-in and explicit.
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (options.live) {
|
|
118
|
+
client.subscribe()
|
|
119
|
+
} else {
|
|
120
|
+
client.unsubscribe()
|
|
121
|
+
}
|
|
122
|
+
}, [client, options.live])
|
|
123
|
+
|
|
102
124
|
// Cleanup on unmount: stop any in-flight requests
|
|
103
125
|
// Note: We only cleanup when client changes or component unmounts.
|
|
104
126
|
// DO NOT include isLoading in dependencies - that would cause the cleanup
|
|
105
127
|
// to run when isLoading changes, aborting continuation requests.
|
|
106
128
|
useEffect(() => {
|
|
107
129
|
return () => {
|
|
108
|
-
//
|
|
109
|
-
|
|
130
|
+
// live mode owns the connection lifecycle; non-live keeps request-only stop.
|
|
131
|
+
if (options.live) {
|
|
132
|
+
client.unsubscribe()
|
|
133
|
+
} else {
|
|
134
|
+
client.stop()
|
|
135
|
+
}
|
|
110
136
|
}
|
|
111
|
-
}, [client])
|
|
137
|
+
}, [client, options.live])
|
|
112
138
|
|
|
113
139
|
// Note: Callback options (onResponse, onChunk, onFinish, onError, onToolCall)
|
|
114
140
|
// are captured at client creation time. Changes to these callbacks require
|
|
@@ -176,6 +202,9 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
|
|
|
176
202
|
isLoading,
|
|
177
203
|
error,
|
|
178
204
|
status,
|
|
205
|
+
isSubscribed,
|
|
206
|
+
connectionStatus,
|
|
207
|
+
sessionGenerating,
|
|
179
208
|
setMessages: setMessagesManually,
|
|
180
209
|
clear,
|
|
181
210
|
addToolResult,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useGeneration } from './use-generation'
|
|
2
2
|
import type { ImageGenerationResult, StreamChunk } from '@tanstack/ai'
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
ConnectConnectionAdapter,
|
|
5
5
|
GenerationClientState,
|
|
6
6
|
GenerationFetcher,
|
|
7
7
|
ImageGenerateInput,
|
|
@@ -14,13 +14,13 @@ import type {
|
|
|
14
14
|
* @template TOutput - The output type after optional transform (defaults to ImageGenerationResult)
|
|
15
15
|
*/
|
|
16
16
|
export interface UseGenerateImageOptions<TOutput = ImageGenerationResult> {
|
|
17
|
-
/**
|
|
18
|
-
connection?:
|
|
17
|
+
/** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
|
|
18
|
+
connection?: ConnectConnectionAdapter
|
|
19
19
|
/** Direct async function for image generation */
|
|
20
20
|
fetcher?: GenerationFetcher<ImageGenerateInput, ImageGenerationResult>
|
|
21
21
|
/** Unique identifier for this generation instance */
|
|
22
22
|
id?: string
|
|
23
|
-
/** Additional body parameters to send with
|
|
23
|
+
/** Additional body parameters to send with connect-based adapter requests */
|
|
24
24
|
body?: Record<string, any>
|
|
25
25
|
/**
|
|
26
26
|
* Callback when images are generated. Can optionally return a transformed value.
|
|
@@ -34,7 +34,7 @@ export interface UseGenerateImageOptions<TOutput = ImageGenerationResult> {
|
|
|
34
34
|
onError?: (error: Error) => void
|
|
35
35
|
/** Callback when progress is reported (0-100) */
|
|
36
36
|
onProgress?: (progress: number, message?: string) => void
|
|
37
|
-
/** Callback for each stream chunk (
|
|
37
|
+
/** Callback for each stream chunk (connect-based adapter mode only) */
|
|
38
38
|
onChunk?: (chunk: StreamChunk) => void
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -64,7 +64,7 @@ export interface UseGenerateImageReturn<TOutput = ImageGenerationResult> {
|
|
|
64
64
|
* React hook for generating images using AI models.
|
|
65
65
|
*
|
|
66
66
|
* Supports two transport modes:
|
|
67
|
-
* - **
|
|
67
|
+
* - **ConnectConnectionAdapter** — Streaming transport (SSE, HTTP stream, custom)
|
|
68
68
|
* - **Fetcher** — Direct async function call
|
|
69
69
|
*
|
|
70
70
|
* @example
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useGeneration } from './use-generation'
|
|
2
2
|
import type { StreamChunk, TTSResult } from '@tanstack/ai'
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
ConnectConnectionAdapter,
|
|
5
5
|
GenerationClientState,
|
|
6
6
|
GenerationFetcher,
|
|
7
7
|
InferGenerationOutput,
|
|
@@ -14,13 +14,13 @@ import type {
|
|
|
14
14
|
* @template TOutput - The output type after optional transform (defaults to TTSResult)
|
|
15
15
|
*/
|
|
16
16
|
export interface UseGenerateSpeechOptions<TOutput = TTSResult> {
|
|
17
|
-
/**
|
|
18
|
-
connection?:
|
|
17
|
+
/** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
|
|
18
|
+
connection?: ConnectConnectionAdapter
|
|
19
19
|
/** Direct async function for speech generation */
|
|
20
20
|
fetcher?: GenerationFetcher<SpeechGenerateInput, TTSResult>
|
|
21
21
|
/** Unique identifier for this generation instance */
|
|
22
22
|
id?: string
|
|
23
|
-
/** Additional body parameters to send with
|
|
23
|
+
/** Additional body parameters to send with connect-based adapter requests */
|
|
24
24
|
body?: Record<string, any>
|
|
25
25
|
/**
|
|
26
26
|
* Callback when speech is generated. Can optionally return a transformed value.
|
|
@@ -34,7 +34,7 @@ export interface UseGenerateSpeechOptions<TOutput = TTSResult> {
|
|
|
34
34
|
onError?: (error: Error) => void
|
|
35
35
|
/** Callback when progress is reported (0-100) */
|
|
36
36
|
onProgress?: (progress: number, message?: string) => void
|
|
37
|
-
/** Callback for each stream chunk (
|
|
37
|
+
/** Callback for each stream chunk (connect-based adapter mode only) */
|
|
38
38
|
onChunk?: (chunk: StreamChunk) => void
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -2,7 +2,7 @@ import { VideoGenerationClient } from '@tanstack/ai-client'
|
|
|
2
2
|
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import type { StreamChunk } from '@tanstack/ai'
|
|
4
4
|
import type {
|
|
5
|
-
|
|
5
|
+
ConnectConnectionAdapter,
|
|
6
6
|
GenerationClientState,
|
|
7
7
|
GenerationFetcher,
|
|
8
8
|
InferGenerationOutput,
|
|
@@ -15,13 +15,13 @@ import type {
|
|
|
15
15
|
* Options for the useGenerateVideo hook.
|
|
16
16
|
*/
|
|
17
17
|
export interface UseGenerateVideoOptions<TOutput = VideoGenerateResult> {
|
|
18
|
-
/**
|
|
19
|
-
connection?:
|
|
18
|
+
/** Connect-based adapter for streaming transport (server handles polling) */
|
|
19
|
+
connection?: ConnectConnectionAdapter
|
|
20
20
|
/** Direct async function that returns a completed video result */
|
|
21
21
|
fetcher?: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>
|
|
22
22
|
/** Unique identifier for this generation instance */
|
|
23
23
|
id?: string
|
|
24
|
-
/** Additional body parameters to send with
|
|
24
|
+
/** Additional body parameters to send with connect-based adapter requests */
|
|
25
25
|
body?: Record<string, any>
|
|
26
26
|
/**
|
|
27
27
|
* Callback when video generation completes. Can optionally return a transformed value.
|
|
@@ -39,7 +39,7 @@ export interface UseGenerateVideoOptions<TOutput = VideoGenerateResult> {
|
|
|
39
39
|
onJobCreated?: (jobId: string) => void
|
|
40
40
|
/** Callback on each status update */
|
|
41
41
|
onStatusUpdate?: (status: VideoStatusInfo) => void
|
|
42
|
-
/** Callback for each stream chunk (
|
|
42
|
+
/** Callback for each stream chunk (connect-based adapter mode only) */
|
|
43
43
|
onChunk?: (chunk: StreamChunk) => void
|
|
44
44
|
}
|
|
45
45
|
|
package/src/use-generation.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { GenerationClient } from '@tanstack/ai-client'
|
|
|
2
2
|
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import type { StreamChunk } from '@tanstack/ai'
|
|
4
4
|
import type {
|
|
5
|
-
|
|
5
|
+
ConnectConnectionAdapter,
|
|
6
6
|
GenerationClientOptions,
|
|
7
7
|
GenerationClientState,
|
|
8
8
|
GenerationFetcher,
|
|
@@ -19,13 +19,13 @@ import type {
|
|
|
19
19
|
* @template TOutput - The output type after optional transform (defaults to TResult)
|
|
20
20
|
*/
|
|
21
21
|
export interface UseGenerationOptions<TInput, TResult, TOutput = TResult> {
|
|
22
|
-
/**
|
|
23
|
-
connection?:
|
|
22
|
+
/** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
|
|
23
|
+
connection?: ConnectConnectionAdapter
|
|
24
24
|
/** Direct async function for one-shot generation (no streaming protocol needed) */
|
|
25
25
|
fetcher?: GenerationFetcher<TInput, TResult>
|
|
26
26
|
/** Unique identifier for this generation instance */
|
|
27
27
|
id?: string
|
|
28
|
-
/** Additional body parameters to send with
|
|
28
|
+
/** Additional body parameters to send with connect-based adapter requests */
|
|
29
29
|
body?: Record<string, any>
|
|
30
30
|
/**
|
|
31
31
|
* Callback when a result is received. Can optionally return a transformed value.
|
|
@@ -39,7 +39,7 @@ export interface UseGenerationOptions<TInput, TResult, TOutput = TResult> {
|
|
|
39
39
|
onError?: (error: Error) => void
|
|
40
40
|
/** Callback when progress is reported (0-100) */
|
|
41
41
|
onProgress?: (progress: number, message?: string) => void
|
|
42
|
-
/** Callback for each stream chunk (
|
|
42
|
+
/** Callback for each stream chunk (connect-based adapter mode only) */
|
|
43
43
|
onChunk?: (chunk: StreamChunk) => void
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import { RealtimeClient } from '@tanstack/ai-client'
|
|
3
|
+
import type {
|
|
4
|
+
RealtimeMessage,
|
|
5
|
+
RealtimeMode,
|
|
6
|
+
RealtimeStatus,
|
|
7
|
+
} from '@tanstack/ai'
|
|
8
|
+
import type {
|
|
9
|
+
UseRealtimeChatOptions,
|
|
10
|
+
UseRealtimeChatReturn,
|
|
11
|
+
} from './realtime-types'
|
|
12
|
+
|
|
13
|
+
// Empty frequency data for when client is not connected
|
|
14
|
+
const emptyFrequencyData = new Uint8Array(128)
|
|
15
|
+
const emptyTimeDomainData = new Uint8Array(128).fill(128)
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* React hook for realtime voice conversations.
|
|
19
|
+
*
|
|
20
|
+
* Provides a simple interface for voice-to-voice AI interactions
|
|
21
|
+
* with support for multiple providers (OpenAI, ElevenLabs, etc.).
|
|
22
|
+
*
|
|
23
|
+
* @param options - Configuration options including adapter and callbacks
|
|
24
|
+
* @returns Hook return value with state and control methods
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { useRealtimeChat } from '@tanstack/ai-react'
|
|
29
|
+
* import { openaiRealtime } from '@tanstack/ai-openai'
|
|
30
|
+
*
|
|
31
|
+
* function VoiceChat() {
|
|
32
|
+
* const {
|
|
33
|
+
* status,
|
|
34
|
+
* mode,
|
|
35
|
+
* messages,
|
|
36
|
+
* connect,
|
|
37
|
+
* disconnect,
|
|
38
|
+
* inputLevel,
|
|
39
|
+
* outputLevel,
|
|
40
|
+
* } = useRealtimeChat({
|
|
41
|
+
* getToken: () => fetch('/api/realtime-token').then(r => r.json()),
|
|
42
|
+
* adapter: openaiRealtime(),
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* return (
|
|
46
|
+
* <div>
|
|
47
|
+
* <p>Status: {status}</p>
|
|
48
|
+
* <p>Mode: {mode}</p>
|
|
49
|
+
* <button onClick={status === 'idle' ? connect : disconnect}>
|
|
50
|
+
* {status === 'idle' ? 'Start' : 'Stop'}
|
|
51
|
+
* </button>
|
|
52
|
+
* </div>
|
|
53
|
+
* )
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function useRealtimeChat(
|
|
58
|
+
options: UseRealtimeChatOptions,
|
|
59
|
+
): UseRealtimeChatReturn {
|
|
60
|
+
// State
|
|
61
|
+
const [status, setStatus] = useState<RealtimeStatus>('idle')
|
|
62
|
+
const [mode, setMode] = useState<RealtimeMode>('idle')
|
|
63
|
+
const [messages, setMessages] = useState<Array<RealtimeMessage>>([])
|
|
64
|
+
const [pendingUserTranscript, setPendingUserTranscript] = useState<
|
|
65
|
+
string | null
|
|
66
|
+
>(null)
|
|
67
|
+
const [pendingAssistantTranscript, setPendingAssistantTranscript] = useState<
|
|
68
|
+
string | null
|
|
69
|
+
>(null)
|
|
70
|
+
const [error, setError] = useState<Error | null>(null)
|
|
71
|
+
const [inputLevel, setInputLevel] = useState(0)
|
|
72
|
+
const [outputLevel, setOutputLevel] = useState(0)
|
|
73
|
+
const [vadMode, setVADModeState] = useState<'server' | 'semantic' | 'manual'>(
|
|
74
|
+
options.vadMode ?? 'server',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
// Refs
|
|
78
|
+
const clientRef = useRef<RealtimeClient | null>(null)
|
|
79
|
+
const optionsRef = useRef(options)
|
|
80
|
+
optionsRef.current = options
|
|
81
|
+
const animationFrameRef = useRef<number | null>(null)
|
|
82
|
+
|
|
83
|
+
// Create client instance - use ref to ensure we reuse the same instance
|
|
84
|
+
// This handles React StrictMode double-rendering
|
|
85
|
+
if (!clientRef.current) {
|
|
86
|
+
clientRef.current = new RealtimeClient({
|
|
87
|
+
getToken: optionsRef.current.getToken,
|
|
88
|
+
adapter: optionsRef.current.adapter,
|
|
89
|
+
tools: optionsRef.current.tools,
|
|
90
|
+
instructions: optionsRef.current.instructions,
|
|
91
|
+
voice: optionsRef.current.voice,
|
|
92
|
+
autoPlayback: optionsRef.current.autoPlayback,
|
|
93
|
+
autoCapture: optionsRef.current.autoCapture,
|
|
94
|
+
vadMode: optionsRef.current.vadMode,
|
|
95
|
+
outputModalities: optionsRef.current.outputModalities,
|
|
96
|
+
temperature: optionsRef.current.temperature,
|
|
97
|
+
maxOutputTokens: optionsRef.current.maxOutputTokens,
|
|
98
|
+
semanticEagerness: optionsRef.current.semanticEagerness,
|
|
99
|
+
onStatusChange: (newStatus) => {
|
|
100
|
+
setStatus(newStatus)
|
|
101
|
+
},
|
|
102
|
+
onModeChange: (newMode) => {
|
|
103
|
+
setMode(newMode)
|
|
104
|
+
optionsRef.current.onModeChange?.(newMode)
|
|
105
|
+
},
|
|
106
|
+
onMessage: (message) => {
|
|
107
|
+
setMessages((prev) => [...prev, message])
|
|
108
|
+
optionsRef.current.onMessage?.(message)
|
|
109
|
+
},
|
|
110
|
+
onError: (err) => {
|
|
111
|
+
setError(err)
|
|
112
|
+
optionsRef.current.onError?.(err)
|
|
113
|
+
},
|
|
114
|
+
onConnect: () => {
|
|
115
|
+
setError(null)
|
|
116
|
+
optionsRef.current.onConnect?.()
|
|
117
|
+
},
|
|
118
|
+
onDisconnect: () => {
|
|
119
|
+
optionsRef.current.onDisconnect?.()
|
|
120
|
+
},
|
|
121
|
+
onInterrupted: () => {
|
|
122
|
+
setPendingAssistantTranscript(null)
|
|
123
|
+
optionsRef.current.onInterrupted?.()
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Subscribe to state changes for transcripts
|
|
128
|
+
clientRef.current.onStateChange((state) => {
|
|
129
|
+
setPendingUserTranscript(state.pendingUserTranscript)
|
|
130
|
+
setPendingAssistantTranscript(state.pendingAssistantTranscript)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const client = clientRef.current
|
|
135
|
+
|
|
136
|
+
// Audio level animation loop
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
function updateLevels() {
|
|
139
|
+
if (clientRef.current?.audio) {
|
|
140
|
+
setInputLevel(clientRef.current.audio.inputLevel)
|
|
141
|
+
setOutputLevel(clientRef.current.audio.outputLevel)
|
|
142
|
+
}
|
|
143
|
+
animationFrameRef.current = requestAnimationFrame(updateLevels)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (status === 'connected') {
|
|
147
|
+
updateLevels()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return () => {
|
|
151
|
+
if (animationFrameRef.current) {
|
|
152
|
+
cancelAnimationFrame(animationFrameRef.current)
|
|
153
|
+
animationFrameRef.current = null
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}, [status])
|
|
157
|
+
|
|
158
|
+
// Cleanup on unmount
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
return () => {
|
|
161
|
+
clientRef.current?.destroy()
|
|
162
|
+
}
|
|
163
|
+
}, [])
|
|
164
|
+
|
|
165
|
+
// Connection methods
|
|
166
|
+
const connect = useCallback(async () => {
|
|
167
|
+
setError(null)
|
|
168
|
+
setMessages([])
|
|
169
|
+
setPendingUserTranscript(null)
|
|
170
|
+
setPendingAssistantTranscript(null)
|
|
171
|
+
await client.connect()
|
|
172
|
+
}, [client])
|
|
173
|
+
|
|
174
|
+
const disconnect = useCallback(async () => {
|
|
175
|
+
await client.disconnect()
|
|
176
|
+
}, [client])
|
|
177
|
+
|
|
178
|
+
// Voice control methods
|
|
179
|
+
const startListening = useCallback(() => {
|
|
180
|
+
client.startListening()
|
|
181
|
+
}, [client])
|
|
182
|
+
|
|
183
|
+
const stopListening = useCallback(() => {
|
|
184
|
+
client.stopListening()
|
|
185
|
+
}, [client])
|
|
186
|
+
|
|
187
|
+
const interrupt = useCallback(() => {
|
|
188
|
+
client.interrupt()
|
|
189
|
+
}, [client])
|
|
190
|
+
|
|
191
|
+
// Text input
|
|
192
|
+
const sendText = useCallback(
|
|
193
|
+
(text: string) => {
|
|
194
|
+
client.sendText(text)
|
|
195
|
+
},
|
|
196
|
+
[client],
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
// Image input
|
|
200
|
+
const sendImage = useCallback(
|
|
201
|
+
(imageData: string, mimeType: string) => {
|
|
202
|
+
client.sendImage(imageData, mimeType)
|
|
203
|
+
},
|
|
204
|
+
[client],
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
// Audio visualization
|
|
208
|
+
const getInputFrequencyData = useCallback(() => {
|
|
209
|
+
return (
|
|
210
|
+
clientRef.current?.audio?.getInputFrequencyData() ?? emptyFrequencyData
|
|
211
|
+
)
|
|
212
|
+
}, [])
|
|
213
|
+
|
|
214
|
+
const getOutputFrequencyData = useCallback(() => {
|
|
215
|
+
return (
|
|
216
|
+
clientRef.current?.audio?.getOutputFrequencyData() ?? emptyFrequencyData
|
|
217
|
+
)
|
|
218
|
+
}, [])
|
|
219
|
+
|
|
220
|
+
const getInputTimeDomainData = useCallback(() => {
|
|
221
|
+
return (
|
|
222
|
+
clientRef.current?.audio?.getInputTimeDomainData() ?? emptyTimeDomainData
|
|
223
|
+
)
|
|
224
|
+
}, [])
|
|
225
|
+
|
|
226
|
+
const getOutputTimeDomainData = useCallback(() => {
|
|
227
|
+
return (
|
|
228
|
+
clientRef.current?.audio?.getOutputTimeDomainData() ?? emptyTimeDomainData
|
|
229
|
+
)
|
|
230
|
+
}, [])
|
|
231
|
+
|
|
232
|
+
// VAD mode control
|
|
233
|
+
const setVADMode = useCallback(
|
|
234
|
+
(newMode: 'server' | 'semantic' | 'manual') => {
|
|
235
|
+
setVADModeState(newMode)
|
|
236
|
+
// TODO: Update session config if connected
|
|
237
|
+
},
|
|
238
|
+
[],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
// Connection state
|
|
243
|
+
status,
|
|
244
|
+
error,
|
|
245
|
+
connect,
|
|
246
|
+
disconnect,
|
|
247
|
+
|
|
248
|
+
// Conversation state
|
|
249
|
+
mode,
|
|
250
|
+
messages,
|
|
251
|
+
pendingUserTranscript,
|
|
252
|
+
pendingAssistantTranscript,
|
|
253
|
+
|
|
254
|
+
// Voice control
|
|
255
|
+
startListening,
|
|
256
|
+
stopListening,
|
|
257
|
+
interrupt,
|
|
258
|
+
|
|
259
|
+
// Text input
|
|
260
|
+
sendText,
|
|
261
|
+
|
|
262
|
+
// Image input
|
|
263
|
+
sendImage,
|
|
264
|
+
|
|
265
|
+
// Audio visualization
|
|
266
|
+
inputLevel,
|
|
267
|
+
outputLevel,
|
|
268
|
+
getInputFrequencyData,
|
|
269
|
+
getOutputFrequencyData,
|
|
270
|
+
getInputTimeDomainData,
|
|
271
|
+
getOutputTimeDomainData,
|
|
272
|
+
|
|
273
|
+
// VAD control
|
|
274
|
+
vadMode,
|
|
275
|
+
setVADMode,
|
|
276
|
+
}
|
|
277
|
+
}
|
package/src/use-summarize.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useGeneration } from './use-generation'
|
|
2
2
|
import type { StreamChunk, SummarizationResult } from '@tanstack/ai'
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
ConnectConnectionAdapter,
|
|
5
5
|
GenerationClientState,
|
|
6
6
|
GenerationFetcher,
|
|
7
7
|
InferGenerationOutput,
|
|
@@ -14,13 +14,13 @@ import type {
|
|
|
14
14
|
* @template TOutput - The output type after optional transform (defaults to SummarizationResult)
|
|
15
15
|
*/
|
|
16
16
|
export interface UseSummarizeOptions<TOutput = SummarizationResult> {
|
|
17
|
-
/**
|
|
18
|
-
connection?:
|
|
17
|
+
/** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
|
|
18
|
+
connection?: ConnectConnectionAdapter
|
|
19
19
|
/** Direct async function for summarization */
|
|
20
20
|
fetcher?: GenerationFetcher<SummarizeGenerateInput, SummarizationResult>
|
|
21
21
|
/** Unique identifier for this generation instance */
|
|
22
22
|
id?: string
|
|
23
|
-
/** Additional body parameters to send with
|
|
23
|
+
/** Additional body parameters to send with connect-based adapter requests */
|
|
24
24
|
body?: Record<string, any>
|
|
25
25
|
/**
|
|
26
26
|
* Callback when summarization is complete. Can optionally return a transformed value.
|
|
@@ -34,7 +34,7 @@ export interface UseSummarizeOptions<TOutput = SummarizationResult> {
|
|
|
34
34
|
onError?: (error: Error) => void
|
|
35
35
|
/** Callback when progress is reported (0-100) */
|
|
36
36
|
onProgress?: (progress: number, message?: string) => void
|
|
37
|
-
/** Callback for each stream chunk (
|
|
37
|
+
/** Callback for each stream chunk (connect-based adapter mode only) */
|
|
38
38
|
onChunk?: (chunk: StreamChunk) => void
|
|
39
39
|
}
|
|
40
40
|
|
package/src/use-transcription.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useGeneration } from './use-generation'
|
|
2
2
|
import type { StreamChunk, TranscriptionResult } from '@tanstack/ai'
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
ConnectConnectionAdapter,
|
|
5
5
|
GenerationClientState,
|
|
6
6
|
GenerationFetcher,
|
|
7
7
|
InferGenerationOutput,
|
|
@@ -14,13 +14,13 @@ import type {
|
|
|
14
14
|
* @template TOutput - The output type after optional transform (defaults to TranscriptionResult)
|
|
15
15
|
*/
|
|
16
16
|
export interface UseTranscriptionOptions<TOutput = TranscriptionResult> {
|
|
17
|
-
/**
|
|
18
|
-
connection?:
|
|
17
|
+
/** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
|
|
18
|
+
connection?: ConnectConnectionAdapter
|
|
19
19
|
/** Direct async function for transcription */
|
|
20
20
|
fetcher?: GenerationFetcher<TranscriptionGenerateInput, TranscriptionResult>
|
|
21
21
|
/** Unique identifier for this generation instance */
|
|
22
22
|
id?: string
|
|
23
|
-
/** Additional body parameters to send with
|
|
23
|
+
/** Additional body parameters to send with connect-based adapter requests */
|
|
24
24
|
body?: Record<string, any>
|
|
25
25
|
/**
|
|
26
26
|
* Callback when transcription is complete. Can optionally return a transformed value.
|
|
@@ -34,7 +34,7 @@ export interface UseTranscriptionOptions<TOutput = TranscriptionResult> {
|
|
|
34
34
|
onError?: (error: Error) => void
|
|
35
35
|
/** Callback when progress is reported (0-100) */
|
|
36
36
|
onProgress?: (progress: number, message?: string) => void
|
|
37
|
-
/** Callback for each stream chunk (
|
|
37
|
+
/** Callback for each stream chunk (connect-based adapter mode only) */
|
|
38
38
|
onChunk?: (chunk: StreamChunk) => void
|
|
39
39
|
}
|
|
40
40
|
|