@tanstack/ai-react 0.18.1 → 0.19.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/README.md +15 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -23
- package/dist/esm/mcp-app-resource.js +45 -45
- package/dist/esm/mcp-app-resource.js.map +1 -1
- package/dist/esm/mcp-apps.js +1 -4
- package/dist/esm/types.d.ts +29 -3
- package/dist/esm/use-audio-recorder.js +41 -46
- package/dist/esm/use-audio-recorder.js.map +1 -1
- package/dist/esm/use-chat.js +330 -275
- package/dist/esm/use-chat.js.map +1 -1
- package/dist/esm/use-generate-audio.d.ts +50 -4
- package/dist/esm/use-generate-audio.js +47 -23
- package/dist/esm/use-generate-audio.js.map +1 -1
- package/dist/esm/use-generate-image.d.ts +50 -4
- package/dist/esm/use-generate-image.js +49 -23
- package/dist/esm/use-generate-image.js.map +1 -1
- package/dist/esm/use-generate-speech.d.ts +50 -4
- package/dist/esm/use-generate-speech.js +43 -23
- package/dist/esm/use-generate-speech.js.map +1 -1
- package/dist/esm/use-generate-video.d.ts +50 -4
- package/dist/esm/use-generate-video.js +141 -103
- package/dist/esm/use-generate-video.js.map +1 -1
- package/dist/esm/use-generation.d.ts +59 -6
- package/dist/esm/use-generation.js +113 -90
- package/dist/esm/use-generation.js.map +1 -1
- package/dist/esm/use-mcp-app-bridge.js +46 -23
- package/dist/esm/use-mcp-app-bridge.js.map +1 -1
- package/dist/esm/use-realtime-chat.js +185 -185
- package/dist/esm/use-realtime-chat.js.map +1 -1
- package/dist/esm/use-summarize.d.ts +50 -4
- package/dist/esm/use-summarize.js +46 -23
- package/dist/esm/use-summarize.js.map +1 -1
- package/dist/esm/use-transcription.d.ts +50 -4
- package/dist/esm/use-transcription.js +51 -20
- package/dist/esm/use-transcription.js.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +10 -0
- package/src/types.ts +44 -0
- package/src/use-chat.ts +221 -35
- package/src/use-generate-audio.ts +64 -17
- package/src/use-generate-image.ts +64 -17
- package/src/use-generate-speech.ts +64 -17
- package/src/use-generate-video.ts +99 -18
- package/src/use-generation.ts +112 -18
- package/src/use-summarize.ts +64 -17
- package/src/use-transcription.ts +60 -18
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/mcp-apps.js.map +0 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useGeneration } from './use-generation'
|
|
2
|
+
import { reconstructImageResult } from '@tanstack/ai-client'
|
|
2
3
|
import type { ImageGenerationResult, StreamChunk } from '@tanstack/ai'
|
|
3
4
|
import type {
|
|
4
5
|
AIDevtoolsDisplayOptions,
|
|
5
6
|
ConnectConnectionAdapter,
|
|
6
7
|
GenerationClientState,
|
|
7
8
|
GenerationFetcher,
|
|
9
|
+
GenerationPersistenceOptions,
|
|
8
10
|
ImageGenerateInput,
|
|
9
11
|
InferGenerationOutputFromReturn,
|
|
10
12
|
} from '@tanstack/ai-client'
|
|
@@ -19,12 +21,51 @@ export interface UseGenerateImageOptions<TOutput = ImageGenerationResult> {
|
|
|
19
21
|
connection?: ConnectConnectionAdapter
|
|
20
22
|
/** Direct async function for image generation */
|
|
21
23
|
fetcher?: GenerationFetcher<ImageGenerateInput, ImageGenerationResult>
|
|
22
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
|
|
26
|
+
*/
|
|
23
27
|
id?: string
|
|
24
28
|
/** Additional body parameters to send with connect-based adapter requests */
|
|
25
29
|
body?: Record<string, any>
|
|
26
30
|
/** Display options for TanStack AI Devtools. */
|
|
27
31
|
devtools?: AIDevtoolsDisplayOptions
|
|
32
|
+
/**
|
|
33
|
+
* How this generation persists across reloads.
|
|
34
|
+
* - Omit / `false`: ephemeral, in-memory only.
|
|
35
|
+
* - `true`: server-driven — on mount the client hydrates the last generation
|
|
36
|
+
* for its `threadId` from the server (needs a connection with a
|
|
37
|
+
* `hydrateGeneration` handler) and repaints it; it never auto-starts a run.
|
|
38
|
+
*/
|
|
39
|
+
persistence?: boolean
|
|
40
|
+
/**
|
|
41
|
+
* The **scope** this generation belongs to: a stable, app-chosen name for the
|
|
42
|
+
* slot successive runs fill — not a link to a chat conversation.
|
|
43
|
+
*
|
|
44
|
+
* The hook starts empty and produces many runs over its life; each gets its
|
|
45
|
+
* own `runId`, but all belong to one scope. Persistence keys on this, so
|
|
46
|
+
* derive it from your own domain and keep it identical across reloads (e.g.
|
|
47
|
+
* `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread
|
|
48
|
+
* id on the wire, which the protocol requires.
|
|
49
|
+
*
|
|
50
|
+
* **Required whenever `persistence` is set** — an app that cannot name the
|
|
51
|
+
* scope has nothing to restore to. Optional for ephemeral generations, where
|
|
52
|
+
* it falls back to `id` purely to satisfy the wire.
|
|
53
|
+
*/
|
|
54
|
+
threadId?: string
|
|
55
|
+
/**
|
|
56
|
+
* Server-driven hydration handler for `persistence: true` when the
|
|
57
|
+
* connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /
|
|
58
|
+
* `rpcStream()` adapter built without handlers) — typically a one-line
|
|
59
|
+
* server-function call. The connection's own handler takes precedence.
|
|
60
|
+
*/
|
|
61
|
+
hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']
|
|
62
|
+
/**
|
|
63
|
+
* Re-attach handler that replays a run still generating to completion on
|
|
64
|
+
* mount, when the connection doesn't carry one. Without it, a restored
|
|
65
|
+
* `running` snapshot surfaces as an (interrupted) error. The connection's
|
|
66
|
+
* own handler takes precedence.
|
|
67
|
+
*/
|
|
68
|
+
joinRun?: ConnectConnectionAdapter['joinRun']
|
|
28
69
|
/**
|
|
29
70
|
* Callback when images are generated. Can optionally return a transformed value.
|
|
30
71
|
*
|
|
@@ -61,6 +102,13 @@ export interface UseGenerateImageReturn<TOutput = ImageGenerationResult> {
|
|
|
61
102
|
stop: () => void
|
|
62
103
|
/** Clear result, error, and return to idle */
|
|
63
104
|
reset: () => void
|
|
105
|
+
/**
|
|
106
|
+
* The id of the generation job currently running, or `null` when nothing is in
|
|
107
|
+
* flight. Each call to `generate` is one job with its own id. Pass it to your
|
|
108
|
+
* own endpoint to cancel or poll the provider job — `stop()` only aborts the
|
|
109
|
+
* local stream, it does not stop work already running on the provider.
|
|
110
|
+
*/
|
|
111
|
+
runId: string | null
|
|
64
112
|
}
|
|
65
113
|
|
|
66
114
|
/**
|
|
@@ -96,9 +144,12 @@ export interface UseGenerateImageReturn<TOutput = ImageGenerationResult> {
|
|
|
96
144
|
* ```
|
|
97
145
|
*/
|
|
98
146
|
export function useGenerateImage<TTransformed = void>(
|
|
99
|
-
options: Omit<
|
|
147
|
+
options: Omit<
|
|
148
|
+
UseGenerateImageOptions,
|
|
149
|
+
'onResult' | 'persistence' | 'threadId' | 'id'
|
|
150
|
+
> & {
|
|
100
151
|
onResult?: (result: ImageGenerationResult) => TTransformed
|
|
101
|
-
},
|
|
152
|
+
} & GenerationPersistenceOptions,
|
|
102
153
|
): UseGenerateImageReturn<
|
|
103
154
|
InferGenerationOutputFromReturn<ImageGenerationResult, TTransformed>
|
|
104
155
|
> {
|
|
@@ -108,19 +159,15 @@ export function useGenerateImage<TTransformed = void>(
|
|
|
108
159
|
hookName: 'useGenerateImage',
|
|
109
160
|
outputKind: 'image' as const,
|
|
110
161
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
162
|
+
const generation = useGeneration<
|
|
163
|
+
ImageGenerateInput,
|
|
164
|
+
ImageGenerationResult,
|
|
165
|
+
TTransformed
|
|
166
|
+
>({
|
|
167
|
+
...options,
|
|
168
|
+
devtools,
|
|
169
|
+
reconstructResult: reconstructImageResult,
|
|
170
|
+
})
|
|
116
171
|
|
|
117
|
-
return
|
|
118
|
-
generate: generate as (input: ImageGenerateInput) => Promise<void>,
|
|
119
|
-
result,
|
|
120
|
-
isLoading,
|
|
121
|
-
error,
|
|
122
|
-
status,
|
|
123
|
-
stop,
|
|
124
|
-
reset,
|
|
125
|
-
}
|
|
172
|
+
return generation
|
|
126
173
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useGeneration } from './use-generation'
|
|
2
|
+
import { reconstructSpeechResult } from '@tanstack/ai-client'
|
|
2
3
|
import type { StreamChunk, TTSResult } from '@tanstack/ai'
|
|
3
4
|
import type {
|
|
4
5
|
AIDevtoolsDisplayOptions,
|
|
5
6
|
ConnectConnectionAdapter,
|
|
6
7
|
GenerationClientState,
|
|
7
8
|
GenerationFetcher,
|
|
9
|
+
GenerationPersistenceOptions,
|
|
8
10
|
InferGenerationOutputFromReturn,
|
|
9
11
|
SpeechGenerateInput,
|
|
10
12
|
} from '@tanstack/ai-client'
|
|
@@ -19,12 +21,51 @@ export interface UseGenerateSpeechOptions<TOutput = TTSResult> {
|
|
|
19
21
|
connection?: ConnectConnectionAdapter
|
|
20
22
|
/** Direct async function for speech generation */
|
|
21
23
|
fetcher?: GenerationFetcher<SpeechGenerateInput, TTSResult>
|
|
22
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
|
|
26
|
+
*/
|
|
23
27
|
id?: string
|
|
24
28
|
/** Additional body parameters to send with connect-based adapter requests */
|
|
25
29
|
body?: Record<string, any>
|
|
26
30
|
/** Display options for TanStack AI Devtools. */
|
|
27
31
|
devtools?: AIDevtoolsDisplayOptions
|
|
32
|
+
/**
|
|
33
|
+
* How this generation persists across reloads.
|
|
34
|
+
* - Omit / `false`: ephemeral, in-memory only.
|
|
35
|
+
* - `true`: server-driven — on mount the client hydrates the last generation
|
|
36
|
+
* for its `threadId` from the server (needs a connection with a
|
|
37
|
+
* `hydrateGeneration` handler) and repaints it; it never auto-starts a run.
|
|
38
|
+
*/
|
|
39
|
+
persistence?: boolean
|
|
40
|
+
/**
|
|
41
|
+
* The **scope** this generation belongs to: a stable, app-chosen name for the
|
|
42
|
+
* slot successive runs fill — not a link to a chat conversation.
|
|
43
|
+
*
|
|
44
|
+
* The hook starts empty and produces many runs over its life; each gets its
|
|
45
|
+
* own `runId`, but all belong to one scope. Persistence keys on this, so
|
|
46
|
+
* derive it from your own domain and keep it identical across reloads (e.g.
|
|
47
|
+
* `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread
|
|
48
|
+
* id on the wire, which the protocol requires.
|
|
49
|
+
*
|
|
50
|
+
* **Required whenever `persistence` is set** — an app that cannot name the
|
|
51
|
+
* scope has nothing to restore to. Optional for ephemeral generations, where
|
|
52
|
+
* it falls back to `id` purely to satisfy the wire.
|
|
53
|
+
*/
|
|
54
|
+
threadId?: string
|
|
55
|
+
/**
|
|
56
|
+
* Server-driven hydration handler for `persistence: true` when the
|
|
57
|
+
* connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /
|
|
58
|
+
* `rpcStream()` adapter built without handlers) — typically a one-line
|
|
59
|
+
* server-function call. The connection's own handler takes precedence.
|
|
60
|
+
*/
|
|
61
|
+
hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']
|
|
62
|
+
/**
|
|
63
|
+
* Re-attach handler that replays a run still generating to completion on
|
|
64
|
+
* mount, when the connection doesn't carry one. Without it, a restored
|
|
65
|
+
* `running` snapshot surfaces as an (interrupted) error. The connection's
|
|
66
|
+
* own handler takes precedence.
|
|
67
|
+
*/
|
|
68
|
+
joinRun?: ConnectConnectionAdapter['joinRun']
|
|
28
69
|
/**
|
|
29
70
|
* Callback when speech is generated. Can optionally return a transformed value.
|
|
30
71
|
*
|
|
@@ -61,6 +102,13 @@ export interface UseGenerateSpeechReturn<TOutput = TTSResult> {
|
|
|
61
102
|
stop: () => void
|
|
62
103
|
/** Clear result, error, and return to idle */
|
|
63
104
|
reset: () => void
|
|
105
|
+
/**
|
|
106
|
+
* The id of the generation job currently running, or `null` when nothing is in
|
|
107
|
+
* flight. Each call to `generate` is one job with its own id. Pass it to your
|
|
108
|
+
* own endpoint to cancel or poll the provider job — `stop()` only aborts the
|
|
109
|
+
* local stream, it does not stop work already running on the provider.
|
|
110
|
+
*/
|
|
111
|
+
runId: string | null
|
|
64
112
|
}
|
|
65
113
|
|
|
66
114
|
/**
|
|
@@ -90,9 +138,12 @@ export interface UseGenerateSpeechReturn<TOutput = TTSResult> {
|
|
|
90
138
|
* ```
|
|
91
139
|
*/
|
|
92
140
|
export function useGenerateSpeech<TTransformed = void>(
|
|
93
|
-
options: Omit<
|
|
141
|
+
options: Omit<
|
|
142
|
+
UseGenerateSpeechOptions,
|
|
143
|
+
'onResult' | 'persistence' | 'threadId' | 'id'
|
|
144
|
+
> & {
|
|
94
145
|
onResult?: (result: TTSResult) => TTransformed
|
|
95
|
-
},
|
|
146
|
+
} & GenerationPersistenceOptions,
|
|
96
147
|
): UseGenerateSpeechReturn<
|
|
97
148
|
InferGenerationOutputFromReturn<TTSResult, TTransformed>
|
|
98
149
|
> {
|
|
@@ -102,19 +153,15 @@ export function useGenerateSpeech<TTransformed = void>(
|
|
|
102
153
|
hookName: 'useGenerateSpeech',
|
|
103
154
|
outputKind: 'audio' as const,
|
|
104
155
|
}
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
const generation = useGeneration<
|
|
157
|
+
SpeechGenerateInput,
|
|
158
|
+
TTSResult,
|
|
159
|
+
TTransformed
|
|
160
|
+
>({
|
|
161
|
+
...options,
|
|
162
|
+
devtools,
|
|
163
|
+
reconstructResult: reconstructSpeechResult,
|
|
164
|
+
})
|
|
110
165
|
|
|
111
|
-
return
|
|
112
|
-
generate: generate as (input: SpeechGenerateInput) => Promise<void>,
|
|
113
|
-
result,
|
|
114
|
-
isLoading,
|
|
115
|
-
error,
|
|
116
|
-
status,
|
|
117
|
-
stop,
|
|
118
|
-
reset,
|
|
119
|
-
}
|
|
166
|
+
return generation
|
|
120
167
|
}
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
ConnectConnectionAdapter,
|
|
8
8
|
GenerationClientState,
|
|
9
9
|
GenerationFetcher,
|
|
10
|
+
GenerationPersistenceOptions,
|
|
10
11
|
InferGenerationOutputFromReturn,
|
|
11
12
|
VideoGenerateInput,
|
|
12
13
|
VideoGenerateResult,
|
|
@@ -21,12 +22,51 @@ export interface UseGenerateVideoOptions<TOutput = VideoGenerateResult> {
|
|
|
21
22
|
connection?: ConnectConnectionAdapter
|
|
22
23
|
/** Direct async function that returns a completed video result */
|
|
23
24
|
fetcher?: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>
|
|
24
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
|
|
27
|
+
*/
|
|
25
28
|
id?: string
|
|
26
29
|
/** Additional body parameters to send with connect-based adapter requests */
|
|
27
30
|
body?: Record<string, any>
|
|
28
31
|
/** Display options for TanStack AI Devtools. */
|
|
29
32
|
devtools?: AIDevtoolsDisplayOptions
|
|
33
|
+
/**
|
|
34
|
+
* How this generation persists across reloads.
|
|
35
|
+
* - Omit / `false`: ephemeral, in-memory only.
|
|
36
|
+
* - `true`: server-driven — on mount the client hydrates the last generation
|
|
37
|
+
* for its `threadId` from the server (needs a connection with a
|
|
38
|
+
* `hydrateGeneration` handler) and repaints it; it never auto-starts a run.
|
|
39
|
+
*/
|
|
40
|
+
persistence?: boolean
|
|
41
|
+
/**
|
|
42
|
+
* The **scope** this generation belongs to: a stable, app-chosen name for the
|
|
43
|
+
* slot successive runs fill — not a link to a chat conversation.
|
|
44
|
+
*
|
|
45
|
+
* The hook starts empty and produces many runs over its life; each gets its
|
|
46
|
+
* own `runId`, but all belong to one scope. Persistence keys on this, so
|
|
47
|
+
* derive it from your own domain and keep it identical across reloads (e.g.
|
|
48
|
+
* `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread
|
|
49
|
+
* id on the wire, which the protocol requires.
|
|
50
|
+
*
|
|
51
|
+
* **Required whenever `persistence` is set** — an app that cannot name the
|
|
52
|
+
* scope has nothing to restore to. Optional for ephemeral generations, where
|
|
53
|
+
* it falls back to `id` purely to satisfy the wire.
|
|
54
|
+
*/
|
|
55
|
+
threadId?: string
|
|
56
|
+
/**
|
|
57
|
+
* Server-driven hydration handler for `persistence: true` when the
|
|
58
|
+
* connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /
|
|
59
|
+
* `rpcStream()` adapter built without handlers) — typically a one-line
|
|
60
|
+
* server-function call. The connection's own handler takes precedence.
|
|
61
|
+
*/
|
|
62
|
+
hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']
|
|
63
|
+
/**
|
|
64
|
+
* Re-attach handler that replays a run still generating to completion on
|
|
65
|
+
* mount, when the connection doesn't carry one. Without it, a restored
|
|
66
|
+
* `running` snapshot surfaces as an (interrupted) error. The connection's
|
|
67
|
+
* own handler takes precedence.
|
|
68
|
+
*/
|
|
69
|
+
joinRun?: ConnectConnectionAdapter['joinRun']
|
|
30
70
|
/**
|
|
31
71
|
* Callback when video generation completes. Can optionally return a transformed value.
|
|
32
72
|
*
|
|
@@ -71,6 +111,13 @@ export interface UseGenerateVideoReturn<TOutput = VideoGenerateResult> {
|
|
|
71
111
|
stop: () => void
|
|
72
112
|
/** Clear all state and return to idle */
|
|
73
113
|
reset: () => void
|
|
114
|
+
/**
|
|
115
|
+
* The id of the generation job currently running, or `null` when nothing is in
|
|
116
|
+
* flight. Each call to `generate` is one job with its own id. Pass it to your
|
|
117
|
+
* own endpoint to cancel or poll the provider job — `stop()` only aborts the
|
|
118
|
+
* local stream, it does not stop work already running on the provider.
|
|
119
|
+
*/
|
|
120
|
+
runId: string | null
|
|
74
121
|
}
|
|
75
122
|
|
|
76
123
|
/**
|
|
@@ -108,9 +155,12 @@ export interface UseGenerateVideoReturn<TOutput = VideoGenerateResult> {
|
|
|
108
155
|
// parameter is typed as `VideoGenerateResult` and `result` narrows to the
|
|
109
156
|
// transform's return. See issue #848.
|
|
110
157
|
export function useGenerateVideo<TTransformed = void>(
|
|
111
|
-
options: Omit<
|
|
158
|
+
options: Omit<
|
|
159
|
+
UseGenerateVideoOptions,
|
|
160
|
+
'onResult' | 'persistence' | 'threadId' | 'id'
|
|
161
|
+
> & {
|
|
112
162
|
onResult?: (result: VideoGenerateResult) => TTransformed
|
|
113
|
-
},
|
|
163
|
+
} & GenerationPersistenceOptions,
|
|
114
164
|
): UseGenerateVideoReturn<
|
|
115
165
|
InferGenerationOutputFromReturn<VideoGenerateResult, TTransformed>
|
|
116
166
|
> {
|
|
@@ -119,7 +169,8 @@ export function useGenerateVideo<TTransformed = void>(
|
|
|
119
169
|
TTransformed
|
|
120
170
|
>
|
|
121
171
|
const hookId = useId()
|
|
122
|
-
|
|
172
|
+
// Single identity: prefer `threadId`; deprecated `id` only when no threadId.
|
|
173
|
+
const clientIdentity = options.threadId ?? options.id ?? hookId
|
|
123
174
|
|
|
124
175
|
const [result, setResult] = useState<TOutput | null>(null)
|
|
125
176
|
const [jobId, setJobId] = useState<string | null>(null)
|
|
@@ -127,9 +178,11 @@ export function useGenerateVideo<TTransformed = void>(
|
|
|
127
178
|
const [isLoading, setIsLoading] = useState(false)
|
|
128
179
|
const [error, setError] = useState<Error | undefined>(undefined)
|
|
129
180
|
const [status, setStatus] = useState<GenerationClientState>('idle')
|
|
181
|
+
const [runId, setRunId] = useState<string | null>(null)
|
|
130
182
|
|
|
131
183
|
const optionsRef = useRef(options)
|
|
132
184
|
optionsRef.current = options
|
|
185
|
+
const disposedRef = useRef(false)
|
|
133
186
|
|
|
134
187
|
const client = useMemo(() => {
|
|
135
188
|
const opts = optionsRef.current
|
|
@@ -139,9 +192,17 @@ export function useGenerateVideo<TTransformed = void>(
|
|
|
139
192
|
// `?.()`'s implicit `undefined` doesn't widen the function
|
|
140
193
|
// return type (which `exactOptionalPropertyTypes` rejects
|
|
141
194
|
// against the strict-optional target).
|
|
195
|
+
// Identity: pass `threadId` alone when set (never also pass deprecated `id`).
|
|
142
196
|
const baseOptions = {
|
|
143
|
-
id: clientId,
|
|
144
197
|
body: opts.body,
|
|
198
|
+
...(opts.threadId !== undefined
|
|
199
|
+
? { threadId: opts.threadId }
|
|
200
|
+
: { id: opts.id ?? hookId }),
|
|
201
|
+
...(opts.persistence !== undefined && { persistence: opts.persistence }),
|
|
202
|
+
...(opts.hydrateGeneration !== undefined && {
|
|
203
|
+
hydrateGeneration: opts.hydrateGeneration,
|
|
204
|
+
}),
|
|
205
|
+
...(opts.joinRun !== undefined && { joinRun: opts.joinRun }),
|
|
145
206
|
devtoolsBridgeFactory: createVideoDevtoolsBridge,
|
|
146
207
|
devtools: {
|
|
147
208
|
...opts.devtools,
|
|
@@ -157,26 +218,41 @@ export function useGenerateVideo<TTransformed = void>(
|
|
|
157
218
|
result: VideoGenerateResult,
|
|
158
219
|
) => TOutput | null | void,
|
|
159
220
|
onError: (e: Error) => {
|
|
160
|
-
optionsRef.current.onError?.(e)
|
|
221
|
+
if (!disposedRef.current) optionsRef.current.onError?.(e)
|
|
161
222
|
},
|
|
162
223
|
onProgress: (p: number, m?: string) => {
|
|
163
|
-
optionsRef.current.onProgress?.(p, m)
|
|
224
|
+
if (!disposedRef.current) optionsRef.current.onProgress?.(p, m)
|
|
164
225
|
},
|
|
165
226
|
onChunk: (c: StreamChunk) => {
|
|
166
|
-
optionsRef.current.onChunk?.(c)
|
|
227
|
+
if (!disposedRef.current) optionsRef.current.onChunk?.(c)
|
|
167
228
|
},
|
|
168
229
|
onJobCreated: (id: string) => {
|
|
169
|
-
optionsRef.current.onJobCreated?.(id)
|
|
230
|
+
if (!disposedRef.current) optionsRef.current.onJobCreated?.(id)
|
|
170
231
|
},
|
|
171
232
|
onStatusUpdate: (s: VideoStatusInfo) => {
|
|
172
|
-
optionsRef.current.onStatusUpdate?.(s)
|
|
233
|
+
if (!disposedRef.current) optionsRef.current.onStatusUpdate?.(s)
|
|
234
|
+
},
|
|
235
|
+
onResultChange: (r: TOutput | null) => {
|
|
236
|
+
if (!disposedRef.current) setResult(r)
|
|
237
|
+
},
|
|
238
|
+
onLoadingChange: (l: boolean) => {
|
|
239
|
+
if (!disposedRef.current) setIsLoading(l)
|
|
240
|
+
},
|
|
241
|
+
onErrorChange: (e: Error | undefined) => {
|
|
242
|
+
if (!disposedRef.current) setError(e)
|
|
243
|
+
},
|
|
244
|
+
onStatusChange: (s: GenerationClientState) => {
|
|
245
|
+
if (!disposedRef.current) setStatus(s)
|
|
246
|
+
},
|
|
247
|
+
onJobIdChange: (id: string | null) => {
|
|
248
|
+
if (!disposedRef.current) setJobId(id)
|
|
249
|
+
},
|
|
250
|
+
onVideoStatusChange: (s: VideoStatusInfo | null) => {
|
|
251
|
+
if (!disposedRef.current) setVideoStatus(s)
|
|
252
|
+
},
|
|
253
|
+
onResumeStateChange: (rs: { runId: string } | null) => {
|
|
254
|
+
if (!disposedRef.current) setRunId(rs?.runId ?? null)
|
|
173
255
|
},
|
|
174
|
-
onResultChange: setResult,
|
|
175
|
-
onLoadingChange: setIsLoading,
|
|
176
|
-
onErrorChange: setError,
|
|
177
|
-
onStatusChange: setStatus,
|
|
178
|
-
onJobIdChange: setJobId,
|
|
179
|
-
onVideoStatusChange: setVideoStatus,
|
|
180
256
|
}
|
|
181
257
|
|
|
182
258
|
if (opts.connection) {
|
|
@@ -196,7 +272,7 @@ export function useGenerateVideo<TTransformed = void>(
|
|
|
196
272
|
throw new Error(
|
|
197
273
|
'useGenerateVideo requires either a connection or fetcher option',
|
|
198
274
|
)
|
|
199
|
-
}, [
|
|
275
|
+
}, [clientIdentity, hookId])
|
|
200
276
|
|
|
201
277
|
// Sync body changes without recreating client
|
|
202
278
|
useEffect(() => {
|
|
@@ -206,11 +282,15 @@ export function useGenerateVideo<TTransformed = void>(
|
|
|
206
282
|
})
|
|
207
283
|
}, [client, options.body])
|
|
208
284
|
|
|
209
|
-
//
|
|
285
|
+
// Mount devtools and clean up on unmount. Generation runs are never
|
|
286
|
+
// auto-started on mount — persisted state is only displayed. Mounting
|
|
287
|
+
// revives the client after a StrictMode dispose → remount replay.
|
|
210
288
|
useEffect(() => {
|
|
289
|
+
disposedRef.current = false
|
|
211
290
|
client.mountDevtools()
|
|
212
291
|
|
|
213
292
|
return () => {
|
|
293
|
+
disposedRef.current = true
|
|
214
294
|
client.dispose()
|
|
215
295
|
}
|
|
216
296
|
}, [client])
|
|
@@ -240,5 +320,6 @@ export function useGenerateVideo<TTransformed = void>(
|
|
|
240
320
|
status,
|
|
241
321
|
stop,
|
|
242
322
|
reset,
|
|
323
|
+
runId,
|
|
243
324
|
}
|
|
244
325
|
}
|