@tanstack/ai-solid 0.6.3 → 0.6.4

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,109 @@
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
+ import type { Accessor } from 'solid-js'
11
+
12
+ /**
13
+ * Options for the useGenerateSpeech hook.
14
+ *
15
+ * @template TOutput - The transformed output type (defaults to TTSResult)
16
+ */
17
+ export interface UseGenerateSpeechOptions<TOutput = TTSResult> {
18
+ /** Connection adapter for streaming transport (SSE, HTTP stream, custom) */
19
+ connection?: ConnectionAdapter
20
+ /** Direct async function for speech generation */
21
+ fetcher?: GenerationFetcher<SpeechGenerateInput, TTSResult>
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 speech is generated. 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: TTSResult) => 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 for each stream chunk (ConnectionAdapter mode only) */
39
+ onChunk?: (chunk: StreamChunk) => void
40
+ }
41
+
42
+ /**
43
+ * Return type for the useGenerateSpeech hook.
44
+ *
45
+ * @template TOutput - The transformed output type (defaults to TTSResult)
46
+ */
47
+ export interface UseGenerateSpeechReturn<TOutput = TTSResult> {
48
+ /** Trigger speech generation */
49
+ generate: (input: SpeechGenerateInput) => Promise<void>
50
+ /** The TTS result containing audio data, or null */
51
+ result: Accessor<TOutput | null>
52
+ /** Whether generation is in progress */
53
+ isLoading: Accessor<boolean>
54
+ /** Current error, if any */
55
+ error: Accessor<Error | undefined>
56
+ /** Current state of the generation */
57
+ status: Accessor<GenerationClientState>
58
+ /** Abort the current generation */
59
+ stop: () => void
60
+ /** Clear result, error, and return to idle */
61
+ reset: () => void
62
+ }
63
+
64
+ /**
65
+ * Solid hook for generating speech (text-to-speech) using AI models.
66
+ *
67
+ * @example
68
+ * ```tsx
69
+ * import { useGenerateSpeech } from '@tanstack/ai-solid'
70
+ * import { fetchServerSentEvents } from '@tanstack/ai-client'
71
+ *
72
+ * function SpeechGenerator() {
73
+ * const { generate, result, isLoading } = useGenerateSpeech({
74
+ * connection: fetchServerSentEvents('/api/generate/speech'),
75
+ * })
76
+ *
77
+ * return (
78
+ * <div>
79
+ * <button onClick={() => generate({ text: 'Hello world', voice: 'alloy' })}>
80
+ * Generate Speech
81
+ * </button>
82
+ * {result() && (
83
+ * <audio src={`data:audio/${result()!.format};base64,${result()!.audio}`} controls />
84
+ * )}
85
+ * </div>
86
+ * )
87
+ * }
88
+ * ```
89
+ */
90
+ export function useGenerateSpeech<
91
+ TOnResult extends ((result: TTSResult) => any) | undefined = undefined,
92
+ >(
93
+ options: Omit<UseGenerateSpeechOptions, 'onResult'> & {
94
+ onResult?: TOnResult
95
+ },
96
+ ): UseGenerateSpeechReturn<InferGenerationOutput<TTSResult, TOnResult>> {
97
+ const { generate, result, isLoading, error, status, stop, reset } =
98
+ useGeneration<SpeechGenerateInput, TTSResult, TOnResult>(options)
99
+
100
+ return {
101
+ generate: generate as (input: SpeechGenerateInput) => Promise<void>,
102
+ result,
103
+ isLoading,
104
+ error,
105
+ status,
106
+ stop,
107
+ reset,
108
+ }
109
+ }
@@ -0,0 +1,207 @@
1
+ import { VideoGenerationClient } from '@tanstack/ai-client'
2
+ import {
3
+ createEffect,
4
+ createMemo,
5
+ createSignal,
6
+ createUniqueId,
7
+ } from 'solid-js'
8
+ import type { StreamChunk } from '@tanstack/ai'
9
+ import type {
10
+ ConnectionAdapter,
11
+ GenerationClientState,
12
+ GenerationFetcher,
13
+ InferGenerationOutput,
14
+ VideoGenerateInput,
15
+ VideoGenerateResult,
16
+ VideoStatusInfo,
17
+ } from '@tanstack/ai-client'
18
+ import type { Accessor } from 'solid-js'
19
+
20
+ /**
21
+ * Options for the useGenerateVideo hook.
22
+ *
23
+ * @template TOutput - The transformed output type (defaults to VideoGenerateResult)
24
+ */
25
+ export interface UseGenerateVideoOptions<TOutput = VideoGenerateResult> {
26
+ /** Connection adapter for streaming transport (server handles polling) */
27
+ connection?: ConnectionAdapter
28
+ /** Direct async function that returns a completed video result */
29
+ fetcher?: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>
30
+ /** Unique identifier for this generation instance */
31
+ id?: string
32
+ /** Additional body parameters to send with ConnectionAdapter requests */
33
+ body?: Record<string, any>
34
+ /**
35
+ * Callback when video generation completes. Can optionally return a transformed value.
36
+ *
37
+ * - Return a non-null value to transform and store it as the result
38
+ * - Return `null` to keep the previous result unchanged
39
+ * - Return nothing (`void`) to store the raw result as-is
40
+ */
41
+ onResult?: (result: VideoGenerateResult) => TOutput | null | void
42
+ /** Callback when an error occurs */
43
+ onError?: (error: Error) => void
44
+ /** Callback when progress is reported (0-100) */
45
+ onProgress?: (progress: number, message?: string) => void
46
+ /** Callback when a video job is created */
47
+ onJobCreated?: (jobId: string) => void
48
+ /** Callback on each status update */
49
+ onStatusUpdate?: (status: VideoStatusInfo) => void
50
+ /** Callback for each stream chunk (ConnectionAdapter mode only) */
51
+ onChunk?: (chunk: StreamChunk) => void
52
+ }
53
+
54
+ /**
55
+ * Return type for the useGenerateVideo hook.
56
+ *
57
+ * @template TOutput - The transformed output type (defaults to VideoGenerateResult)
58
+ */
59
+ export interface UseGenerateVideoReturn<TOutput = VideoGenerateResult> {
60
+ /** Trigger video generation */
61
+ generate: (input: VideoGenerateInput) => Promise<void>
62
+ /** The final video result (with URL), or null */
63
+ result: Accessor<TOutput | null>
64
+ /** The current job ID, or null */
65
+ jobId: Accessor<string | null>
66
+ /** Current video generation status info, or null */
67
+ videoStatus: Accessor<VideoStatusInfo | null>
68
+ /** Whether generation/polling is in progress */
69
+ isLoading: Accessor<boolean>
70
+ /** Current error, if any */
71
+ error: Accessor<Error | undefined>
72
+ /** Current state of the generation */
73
+ status: Accessor<GenerationClientState>
74
+ /** Abort the current generation/polling */
75
+ stop: () => void
76
+ /** Clear all state and return to idle */
77
+ reset: () => void
78
+ }
79
+
80
+ /**
81
+ * Solid hook for generating videos using AI models.
82
+ *
83
+ * Video generation is asynchronous: a job is created, then polled for status
84
+ * until completion. This hook handles the full lifecycle.
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * import { useGenerateVideo } from '@tanstack/ai-solid'
89
+ * import { fetchServerSentEvents } from '@tanstack/ai-client'
90
+ *
91
+ * function VideoGenerator() {
92
+ * const { generate, result, videoStatus, isLoading } = useGenerateVideo({
93
+ * connection: fetchServerSentEvents('/api/generate/video'),
94
+ * onStatusUpdate: (status) => console.log(`Progress: ${status.progress}%`),
95
+ * })
96
+ *
97
+ * return (
98
+ * <div>
99
+ * <button onClick={() => generate({ prompt: 'A flying car over a city' })}>
100
+ * Generate Video
101
+ * </button>
102
+ * {isLoading() && videoStatus() && (
103
+ * <p>Status: {videoStatus()!.status} ({videoStatus()!.progress}%)</p>
104
+ * )}
105
+ * {result() && <video src={result()!.url} controls />}
106
+ * </div>
107
+ * )
108
+ * }
109
+ * ```
110
+ */
111
+ export function useGenerateVideo<
112
+ TOnResult extends ((result: VideoGenerateResult) => any) | undefined =
113
+ undefined,
114
+ >(
115
+ options: Omit<UseGenerateVideoOptions, 'onResult'> & {
116
+ onResult?: TOnResult
117
+ },
118
+ ): UseGenerateVideoReturn<
119
+ InferGenerationOutput<VideoGenerateResult, TOnResult>
120
+ > {
121
+ type TOutput = InferGenerationOutput<VideoGenerateResult, TOnResult>
122
+ const hookId = createUniqueId()
123
+ const clientId = options.id || hookId
124
+
125
+ const [result, setResult] = createSignal<TOutput | null>(null)
126
+ const [jobId, setJobId] = createSignal<string | null>(null)
127
+ const [videoStatus, setVideoStatus] = createSignal<VideoStatusInfo | null>(
128
+ null,
129
+ )
130
+ const [isLoading, setIsLoading] = createSignal(false)
131
+ const [error, setError] = createSignal<Error | undefined>(undefined)
132
+ const [status, setStatus] = createSignal<GenerationClientState>('idle')
133
+
134
+ const client = createMemo(() => {
135
+ const baseOptions = {
136
+ id: clientId,
137
+ body: options.body,
138
+ onResult: (r: VideoGenerateResult) => options.onResult?.(r),
139
+ onError: (e: Error) => options.onError?.(e),
140
+ onProgress: (p: number, m?: string) => options.onProgress?.(p, m),
141
+ onChunk: (c: StreamChunk) => options.onChunk?.(c),
142
+ onJobCreated: (id: string) => options.onJobCreated?.(id),
143
+ onStatusUpdate: (s: VideoStatusInfo) => options.onStatusUpdate?.(s),
144
+ onResultChange: setResult,
145
+ onLoadingChange: setIsLoading,
146
+ onErrorChange: setError,
147
+ onStatusChange: setStatus,
148
+ onJobIdChange: setJobId,
149
+ onVideoStatusChange: setVideoStatus,
150
+ }
151
+
152
+ if (options.connection) {
153
+ return new VideoGenerationClient<TOutput>({
154
+ ...baseOptions,
155
+ connection: options.connection,
156
+ })
157
+ }
158
+
159
+ if (options.fetcher) {
160
+ return new VideoGenerationClient<TOutput>({
161
+ ...baseOptions,
162
+ fetcher: options.fetcher,
163
+ })
164
+ }
165
+
166
+ throw new Error(
167
+ 'useGenerateVideo requires either a connection or fetcher option',
168
+ )
169
+ }, [clientId])
170
+
171
+ // Sync body changes without recreating client
172
+ createEffect(() => {
173
+ const currentBody = options.body
174
+ client().updateOptions({ body: currentBody })
175
+ })
176
+
177
+ // Cleanup on unmount: stop any in-flight requests
178
+ createEffect(() => {
179
+ return () => {
180
+ client().stop()
181
+ }
182
+ })
183
+
184
+ const generate = async (input: VideoGenerateInput) => {
185
+ await client().generate(input)
186
+ }
187
+
188
+ const stop = () => {
189
+ client().stop()
190
+ }
191
+
192
+ const reset = () => {
193
+ client().reset()
194
+ }
195
+
196
+ return {
197
+ generate,
198
+ result,
199
+ jobId,
200
+ videoStatus,
201
+ isLoading,
202
+ error,
203
+ status,
204
+ stop,
205
+ reset,
206
+ }
207
+ }
@@ -0,0 +1,179 @@
1
+ import { GenerationClient } from '@tanstack/ai-client'
2
+ import {
3
+ createEffect,
4
+ createMemo,
5
+ createSignal,
6
+ createUniqueId,
7
+ } from 'solid-js'
8
+ import type { StreamChunk } from '@tanstack/ai'
9
+ import type {
10
+ ConnectionAdapter,
11
+ GenerationClientOptions,
12
+ GenerationClientState,
13
+ GenerationFetcher,
14
+ InferGenerationOutput,
15
+ } from '@tanstack/ai-client'
16
+ import type { Accessor } from 'solid-js'
17
+
18
+ /**
19
+ * Options for the useGeneration hook.
20
+ *
21
+ * Accepts either a `connection` (streaming transport) or a `fetcher` (direct async call).
22
+ *
23
+ * @template TInput - The input type for the generation request
24
+ * @template TResult - The result type returned by the generation
25
+ * @template TOutput - The transformed output type (defaults to TResult)
26
+ */
27
+ export interface UseGenerationOptions<TInput, TResult, TOutput = TResult> {
28
+ /** Connection adapter for streaming transport (SSE, HTTP stream, custom) */
29
+ connection?: ConnectionAdapter
30
+ /** Direct async function for one-shot generation (no streaming protocol needed) */
31
+ fetcher?: GenerationFetcher<TInput, TResult>
32
+ /** Unique identifier for this generation instance */
33
+ id?: string
34
+ /** Additional body parameters to send with ConnectionAdapter requests */
35
+ body?: Record<string, any>
36
+ /**
37
+ * Callback when a result is received. Can optionally return a transformed value.
38
+ *
39
+ * - Return a non-null value to transform and store it as the result
40
+ * - Return `null` to keep the previous result unchanged
41
+ * - Return nothing (`void`) to store the raw result as-is
42
+ */
43
+ onResult?: (result: TResult) => TOutput | null | void
44
+ /** Callback when an error occurs */
45
+ onError?: (error: Error) => void
46
+ /** Callback when progress is reported (0-100) */
47
+ onProgress?: (progress: number, message?: string) => void
48
+ /** Callback for each stream chunk (ConnectionAdapter mode only) */
49
+ onChunk?: (chunk: StreamChunk) => void
50
+ }
51
+
52
+ /**
53
+ * Return type for the useGeneration hook.
54
+ *
55
+ * @template TOutput - The output type (possibly transformed from the raw result)
56
+ */
57
+ export interface UseGenerationReturn<TOutput> {
58
+ /** Trigger a generation request */
59
+ generate: (input: Record<string, any>) => Promise<void>
60
+ /** The generation result, or null if not yet generated */
61
+ result: Accessor<TOutput | null>
62
+ /** Whether a generation is currently in progress */
63
+ isLoading: Accessor<boolean>
64
+ /** Current error, if any */
65
+ error: Accessor<Error | undefined>
66
+ /** Current state of the generation client */
67
+ status: Accessor<GenerationClientState>
68
+ /** Abort the current generation */
69
+ stop: () => void
70
+ /** Clear result, error, and return to idle */
71
+ reset: () => void
72
+ }
73
+
74
+ /**
75
+ * Generic Solid hook for one-shot generation tasks.
76
+ *
77
+ * This is the base hook used by `useGenerateImage`, `useGenerateSpeech`,
78
+ * `useTranscription`, and `useSummarize`. You can also use it directly
79
+ * for custom generation types.
80
+ *
81
+ * @template TInput - The input type for the generation request
82
+ * @template TResult - The result type returned by the generation
83
+ * @template TOutput - The transformed output type (defaults to TResult)
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * const { generate, result, isLoading } = useGeneration<MyInput, MyResult>({
88
+ * connection: fetchServerSentEvents('/api/generate/custom'),
89
+ * })
90
+ *
91
+ * await generate({ prompt: 'Hello' })
92
+ * ```
93
+ */
94
+ export function useGeneration<
95
+ TInput extends Record<string, any>,
96
+ TResult,
97
+ TOnResult extends ((result: TResult) => any) | undefined = undefined,
98
+ >(
99
+ options: Omit<UseGenerationOptions<TInput, TResult>, 'onResult'> & {
100
+ onResult?: TOnResult
101
+ },
102
+ ): UseGenerationReturn<InferGenerationOutput<TResult, TOnResult>> {
103
+ type TOutput = InferGenerationOutput<TResult, TOnResult>
104
+ const hookId = createUniqueId()
105
+ const clientId = options.id || hookId
106
+
107
+ const [result, setResult] = createSignal<TOutput | null>(null)
108
+ const [isLoading, setIsLoading] = createSignal(false)
109
+ const [error, setError] = createSignal<Error | undefined>(undefined)
110
+ const [status, setStatus] = createSignal<GenerationClientState>('idle')
111
+
112
+ const client = createMemo(() => {
113
+ const clientOptions: GenerationClientOptions<TInput, TResult, TOutput> = {
114
+ id: clientId,
115
+ body: options.body,
116
+ onResult: (r: TResult) => options.onResult?.(r),
117
+ onError: (e: Error) => options.onError?.(e),
118
+ onProgress: (p: number, m?: string) => options.onProgress?.(p, m),
119
+ onChunk: (c: StreamChunk) => options.onChunk?.(c),
120
+ onResultChange: setResult,
121
+ onLoadingChange: setIsLoading,
122
+ onErrorChange: setError,
123
+ onStatusChange: setStatus,
124
+ }
125
+
126
+ if (options.connection) {
127
+ return new GenerationClient<TInput, TResult, TOutput>({
128
+ ...clientOptions,
129
+ connection: options.connection,
130
+ })
131
+ }
132
+
133
+ if (options.fetcher) {
134
+ return new GenerationClient<TInput, TResult, TOutput>({
135
+ ...clientOptions,
136
+ fetcher: options.fetcher,
137
+ })
138
+ }
139
+
140
+ throw new Error(
141
+ 'useGeneration requires either a connection or fetcher option',
142
+ )
143
+ }, [clientId])
144
+
145
+ // Sync body changes without recreating client
146
+ createEffect(() => {
147
+ const currentBody = options.body
148
+ client().updateOptions({ body: currentBody })
149
+ })
150
+
151
+ // Cleanup on unmount: stop any in-flight requests
152
+ createEffect(() => {
153
+ return () => {
154
+ client().stop()
155
+ }
156
+ })
157
+
158
+ const generate = async (input: TInput) => {
159
+ await client().generate(input)
160
+ }
161
+
162
+ const stop = () => {
163
+ client().stop()
164
+ }
165
+
166
+ const reset = () => {
167
+ client().reset()
168
+ }
169
+
170
+ return {
171
+ generate: generate as (input: Record<string, any>) => Promise<void>,
172
+ result,
173
+ isLoading,
174
+ error,
175
+ status,
176
+ stop,
177
+ reset,
178
+ }
179
+ }
@@ -0,0 +1,115 @@
1
+ import { useGeneration } from './use-generation'
2
+ import type { StreamChunk, SummarizationResult } from '@tanstack/ai'
3
+ import type {
4
+ ConnectionAdapter,
5
+ GenerationClientState,
6
+ GenerationFetcher,
7
+ InferGenerationOutput,
8
+ SummarizeGenerateInput,
9
+ } from '@tanstack/ai-client'
10
+ import type { Accessor } from 'solid-js'
11
+
12
+ /**
13
+ * Options for the useSummarize hook.
14
+ *
15
+ * @template TOutput - The transformed output type (defaults to SummarizationResult)
16
+ */
17
+ export interface UseSummarizeOptions<TOutput = SummarizationResult> {
18
+ /** Connection adapter for streaming transport (SSE, HTTP stream, custom) */
19
+ connection?: ConnectionAdapter
20
+ /** Direct async function for summarization */
21
+ fetcher?: GenerationFetcher<SummarizeGenerateInput, SummarizationResult>
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 summarization is complete. 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: SummarizationResult) => 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 for each stream chunk (ConnectionAdapter mode only) */
39
+ onChunk?: (chunk: StreamChunk) => void
40
+ }
41
+
42
+ /**
43
+ * Return type for the useSummarize hook.
44
+ *
45
+ * @template TOutput - The transformed output type (defaults to SummarizationResult)
46
+ */
47
+ export interface UseSummarizeReturn<TOutput = SummarizationResult> {
48
+ /** Trigger summarization */
49
+ generate: (input: SummarizeGenerateInput) => Promise<void>
50
+ /** The summarization result, or null */
51
+ result: Accessor<TOutput | null>
52
+ /** Whether summarization is in progress */
53
+ isLoading: Accessor<boolean>
54
+ /** Current error, if any */
55
+ error: Accessor<Error | undefined>
56
+ /** Current state of the generation */
57
+ status: Accessor<GenerationClientState>
58
+ /** Abort the current summarization */
59
+ stop: () => void
60
+ /** Clear result, error, and return to idle */
61
+ reset: () => void
62
+ }
63
+
64
+ /**
65
+ * Solid hook for summarizing text using AI models.
66
+ *
67
+ * @example
68
+ * ```tsx
69
+ * import { useSummarize } from '@tanstack/ai-solid'
70
+ * import { fetchServerSentEvents } from '@tanstack/ai-client'
71
+ *
72
+ * function Summarizer() {
73
+ * const { generate, result, isLoading } = useSummarize({
74
+ * connection: fetchServerSentEvents('/api/summarize'),
75
+ * })
76
+ *
77
+ * return (
78
+ * <div>
79
+ * <button onClick={() => generate({
80
+ * text: 'Long article text...',
81
+ * style: 'bullet-points',
82
+ * maxLength: 200,
83
+ * })}>
84
+ * Summarize
85
+ * </button>
86
+ * {isLoading() && <p>Summarizing...</p>}
87
+ * {result() && <p>{result()!.summary}</p>}
88
+ * </div>
89
+ * )
90
+ * }
91
+ * ```
92
+ */
93
+ export function useSummarize<
94
+ TOnResult extends ((result: SummarizationResult) => any) | undefined =
95
+ undefined,
96
+ >(
97
+ options: Omit<UseSummarizeOptions, 'onResult'> & {
98
+ onResult?: TOnResult
99
+ },
100
+ ): UseSummarizeReturn<InferGenerationOutput<SummarizationResult, TOnResult>> {
101
+ const { generate, result, isLoading, error, status, stop, reset } =
102
+ useGeneration<SummarizeGenerateInput, SummarizationResult, TOnResult>(
103
+ options,
104
+ )
105
+
106
+ return {
107
+ generate: generate as (input: SummarizeGenerateInput) => Promise<void>,
108
+ result,
109
+ isLoading,
110
+ error,
111
+ status,
112
+ stop,
113
+ reset,
114
+ }
115
+ }