생성 훅
TanStack AI는 이미지, 오디오, 음성, 전사, 요약, 동영상 등 모든 생성 유형을 위한 프레임워크 훅을 제공합니다. 각 훅은 서버 엔드포인트에 연결하고 로딩, 오류, 결과 상태를 관리합니다.
새로고침 및 연결 끊김 이후에도 유지: 모든 생성 훅은
useChat과 동일한persistence옵션을 사용하므로 장시간 실행의 상태와 결과가 페이지 새로고침이나 연결 끊김 이후에도 돌아옵니다. 생성에서threadId는 대화가 아니라 연속 실행이 채우는 슬롯(product-7-hero)의 이름으로 사용되며, 이를 기준으로 복원됩니다. 자세한 내용은 Id map을 참조합니다. 설정 방법은 Generation Persistence를 참조합니다.
개요
생성 훅은 모든 미디어 유형에서 일관된 API를 공유합니다.
| 훅 | 입력 | 결과 타입 |
|---|---|---|
useGenerateImage | ImageGenerateInput | ImageGenerationResult |
useGenerateAudio | AudioGenerateInput | AudioGenerationResult |
useGenerateSpeech | SpeechGenerateInput | TTSResult |
useTranscription | TranscriptionGenerateInput | TranscriptionResult |
useSummarize | SummarizeGenerateInput | SummarizationResult |
useGenerateVideo | VideoGenerateInput | VideoGenerateResult |
useGeneration | 일반 TInput | 일반 TResult |
모든 훅은 generate, result, isLoading, error, status, stop, reset, runId(진행 중인 작업의 ID 또는 null)로 구성된 동일한 핵심 형태를 반환합니다. connection(스트리밍 전송) 또는 fetcher(직접 비동기 호출) 중 하나를 제공합니다.
서버 설정
클라이언트에서 훅을 사용하기 전에 생성을 실행하고 결과를 SSE로 반환하는 서버 엔드포인트가 필요합니다. 다음은 최소한의 이미지 생성 엔드포인트입니다.
// routes/api/generate/image.ts
import { generateImage, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiImage } from '@tanstack/ai-openai'
export async function POST(req: Request) {
const { prompt, size, numberOfImages } = await req.json()
const stream = generateImage({
adapter: openaiImage('dall-e-3'),
prompt,
size,
numberOfImages,
stream: true,
})
return toServerSentEventsResponse(stream)
}
모든 생성 유형에 동일한 패턴을 적용할 수 있습니다. generateImage를 generateSpeech, generateTranscription, summarize 또는 generateVideo로 바꾸면 됩니다. 서버 측 세부 사항은 개별 미디어 가이드를 참조합니다.
useGenerateImage
이미지 생성을 실행하고 결과를 렌더링합니다.
import { useGenerateImage, fetchServerSentEvents } from '@tanstack/ai-react'
import { useState } from 'react'
function ImageGenerator() {
const [prompt, setPrompt] = useState('')
const { generate, result, isLoading, error, reset } = useGenerateImage({
connection: fetchServerSentEvents('/api/generate/image'),
})
return (
<div>
<input
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Describe an image..."
/>
<button
onClick={() => generate({ prompt })}
disabled={isLoading || !prompt.trim()}
>
{isLoading ? 'Generating...' : 'Generate'}
</button>
{error && <p>Error: {error.message}</p>}
{result?.images.map((img, i) => (
<img
key={i}
src={img.url || `data:image/png;base64,${img.b64Json}`}
alt={img.revisedPrompt || 'Generated image'}
/>
))}
{result && <button onClick={reset}>Clear</button>}
</div>
)
}
generate 함수는 ImageGenerateInput을 받습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
prompt | string | 원하는 이미지의 텍스트 설명(필수) |
numberOfImages | number | 생성할 이미지 수 |
size | string | WIDTHxHEIGHT 형식의 이미지 크기(예: "1024x1024") |
modelOptions | Record<string, any> | 모델별 옵션 |
useGenerateAudio
텍스트 프롬프트로 음악이나 음향 효과를 생성합니다.
fetcher를 사용할 때는 options.headers를 POST 요청에 전개합니다. 해당 헤더에 x-byok-* 키가 포함되도록 byok를 전달합니다.
import { useGenerateAudio } from '@tanstack/ai-react'
import { defineByok, defaultByokStorage } from '@tanstack/ai-client/byok'
const byok = defineByok({ storage: defaultByokStorage() })
function AudioGenerator() {
const { generate, result, isLoading, error } = useGenerateAudio({
byok,
byokProvider: () => 'fal',
fetcher: async (input, options) => {
return fetch('/api/generate/audio', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
body: JSON.stringify(input),
signal: options?.signal,
})
},
})
return (
<div>
<button
onClick={() => generate({ prompt: 'An upbeat electronic track' })}
disabled={isLoading}
>
{isLoading ? 'Generating...' : 'Generate'}
</button>
{error && <p>Error: {error.message}</p>}
{result?.audio.url && <audio src={result.audio.url} controls />}
</div>
)
}
서버에서는 헤더(또는 환경 변수)를 읽은 다음 generateAudio를 실행합니다.
import { generateAudio, toServerSentEventsResponse } from '@tanstack/ai'
import { falAudio } from '@tanstack/ai-fal'
import { falByok } from '@tanstack/ai-fal/byok'
import { byokMissing, getByokKey } from '@tanstack/ai/byok/server'
export async function POST(request: Request) {
const apiKey = getByokKey(request, falByok)
if (!apiKey) return byokMissing(falByok)
const body: unknown = await request.json()
if (typeof body !== 'object' || body === null || !('prompt' in body)) {
return new Response('Bad request', { status: 400 })
}
const prompt = body.prompt
if (typeof prompt !== 'string') {
return new Response('Bad request', { status: 400 })
}
const stream = generateAudio({
adapter: falAudio('fal-ai/minimax-music/v2.6', { apiKey }),
prompt,
stream: true,
})
return toServerSentEventsResponse(stream)
}
generate 함수는 AudioGenerateInput을 받습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
prompt | string | 원하는 오디오의 텍스트 설명(필수) |
duration | number | 원하는 재생 시간(초) |
modelOptions | Record<string, any> | 모델별 옵션 |
defineByok와 저장 UI에 대한 자세한 내용은 Bring Your Own Key를 참조합니다.
useGenerateSpeech
텍스트를 음성으로 변환하고 재생합니다.
import { useGenerateSpeech, fetchServerSentEvents } from '@tanstack/ai-react'
import { useRef } from 'react'
function SpeechGenerator() {
const audioRef = useRef<HTMLAudioElement>(null)
const { generate, result, isLoading, error } = useGenerateSpeech({
connection: fetchServerSentEvents('/api/generate/speech'),
})
return (
<div>
<button
onClick={() => generate({ text: 'Hello, welcome to TanStack AI!', voice: 'alloy' })}
disabled={isLoading}
>
{isLoading ? 'Generating...' : 'Generate Speech'}
</button>
{error && <p>Error: {error.message}</p>}
{result && (
<audio
ref={audioRef}
src={`data:audio/${result.format};base64,${result.audio}`}
controls
autoPlay
/>
)}
</div>
)
}
generate 함수는 SpeechGenerateInput을 받습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
text | string | 음성으로 변환할 텍스트(필수) |
voice | string | 사용할 음성(예: "alloy", "echo") |
format | 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm' | 출력 오디오 형식 |
speed | number | 오디오 속도(0.25~4.0) |
modelOptions | Record<string, any> | 모델별 옵션 |
TTSResult에는 audio(base64 인코딩), format, 그리고 선택적으로 duration 및 contentType이 포함됩니다.
useTranscription
오디오 파일을 텍스트로 전사합니다.
import { useTranscription, fetchServerSentEvents } from '@tanstack/ai-react'
function Transcriber() {
const { generate, result, isLoading, error } = useTranscription({
connection: fetchServerSentEvents('/api/transcribe'),
})
const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
const reader = new FileReader()
reader.onload = () => {
generate({ audio: reader.result as string, language: 'en' })
}
reader.readAsDataURL(file)
}
}
return (
<div>
<input type="file" accept="audio/*" onChange={handleFile} />
{isLoading && <p>Transcribing...</p>}
{error && <p>Error: {error.message}</p>}
{result && (
<div>
<h3>Transcription</h3>
<p>{result.text}</p>
{result.language && <p>Language: {result.language}</p>}
{result.duration && <p>Duration: {result.duration}s</p>}
</div>
)}
</div>
)
}
generate 함수는 TranscriptionGenerateInput을 받습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
audio | string | File | Blob | ArrayBuffer | 오디오 데이터 -- base64 문자열, File, Blob 또는 ArrayBuffer(필수) |
language | string | ISO-639-1 형식의 언어(예: "en") |
prompt | string | 전사를 안내하는 선택적 프롬프트 |
responseFormat | 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt' | 일반 출력 형식 |
modelOptions | Record<string, any> | 모델별 옵션 |
useSummarize
구성 가능한 출력 스타일로 긴 텍스트를 요약합니다.
import { useSummarize, fetchServerSentEvents } from '@tanstack/ai-react'
import { useState } from 'react'
function Summarizer() {
const [text, setText] = useState('')
const { generate, result, isLoading, error } = useSummarize({
connection: fetchServerSentEvents('/api/summarize'),
})
return (
<div>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Paste text to summarize..."
rows={8}
/>
<button
onClick={() => generate({ text, style: 'bullet-points', maxLength: 200 })}
disabled={isLoading || !text.trim()}
>
{isLoading ? 'Summarizing...' : 'Summarize'}
</button>
{error && <p>Error: {error.message}</p>}
{result && (
<div>
<h3>Summary</h3>
<p>{result.summary}</p>
</div>
)}
</div>
)
}
generate 함수는 SummarizeGenerateInput을 받습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
text | string | 요약할 텍스트(필수) |
maxLength | number | 요약의 최대 길이 |
style | 'bullet-points' | 'paragraph' | 'concise' | 요약 스타일 |
focus | Array<string> | 집중할 주제 |
modelOptions | Record<string, any> | 모델별 옵션 |
useGenerateVideo
동영상 생성은 비동기 방식입니다. 서버에서 작업을 생성한 다음 완료될 때까지 상태를 폴링합니다. 훅은 전체 수명 주기를 관리하고 jobId와 videoStatus를 노출하므로 진행률을 표시할 수 있습니다.
import { useGenerateVideo, fetchServerSentEvents } from '@tanstack/ai-react'
function VideoGenerator() {
const { generate, result, jobId, videoStatus, isLoading, error } =
useGenerateVideo({
connection: fetchServerSentEvents('/api/generate/video'),
onStatusUpdate: (status) => {
console.log(`Video ${status.jobId}: ${status.status} (${status.progress}%)`)
},
})
return (
<div>
<button
onClick={() => generate({ prompt: 'A flying car over a city', duration: 5 })}
disabled={isLoading}
>
{isLoading ? 'Generating...' : 'Generate Video'}
</button>
{isLoading && videoStatus && (
<div>
<p>Job: {jobId}</p>
<p>Status: {videoStatus.status}</p>
{videoStatus.progress != null && (
<progress value={videoStatus.progress} max={100} />
)}
</div>
)}
{error && <p>Error: {error.message}</p>}
{result && (
<video src={result.url} controls autoPlay style={{ maxWidth: '100%' }} />
)}
</div>
)
}
generate 함수는 VideoGenerateInput을 받습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
prompt | string | 원하는 동영상의 텍스트 설명(필수) |
size | string | 동영상 크기 -- 형식은 공급자에 따라 다름(예: "16:9", "1280x720") |
duration | number | 동영상 재생 시간(초) |
modelOptions | Record<string, any> | 모델별 옵션 |
useGenerateVideo는 표준 속성 외에 두 가지 속성을 추가로 반환합니다.
| 속성 | 타입 | 설명 |
|---|---|---|
jobId | string | null | 현재 작업 ID. 서버가 동영상 작업을 생성할 때 설정됩니다. |
videoStatus | VideoStatusInfo | null | status, progress, jobId가 포함된 실시간 상태 업데이트 |
VideoStatusInfo 타입은 다음과 같습니다.
interface VideoStatusInfo {
jobId: string
status: 'pending' | 'processing' | 'completed' | 'failed'
progress?: number // 0-100
url?: string // Set when completed
error?: string // Set when failed
}
세밀한 추적을 위해 훅에 onJobCreated 및 onStatusUpdate 콜백도 전달할 수 있습니다.
기본 훅: useGeneration
모든 특화 훅은 useGeneration을 기반으로 합니다. 기본 제공 훅에 맞지 않는 사용자 지정 생성 유형이 있을 때 직접 사용합니다.
import { useGeneration, fetchServerSentEvents } from '@tanstack/ai-react'
interface EmbeddingInput {
text: string
model?: string
}
interface EmbeddingResult {
embedding: Array<number>
model: string
usage: { totalTokens: number }
}
function EmbeddingGenerator() {
const { generate, result, isLoading, error } = useGeneration<
EmbeddingInput,
EmbeddingResult
>({
connection: fetchServerSentEvents('/api/generate/embedding'),
})
return (
<div>
<button onClick={() => generate({ text: 'Hello world' })} disabled={isLoading}>
Generate Embedding
</button>
{result && <p>Dimensions: {result.embedding.length}</p>}
</div>
)
}
옵션
UseGenerationOptions<TInput, TResult>는 다음을 받습니다.
| 옵션 | 타입 | 설명 |
|---|---|---|
connection | ConnectConnectionAdapter | 스트리밍 전송(SSE, HTTP 스트림, 사용자 지정) |
fetcher | GenerationFetcher<TInput, TResult> | 직접 비동기 함수(스트리밍 프로토콜 불필요) |
byok | ByokClient | 선택적 키링. 키는 x-byok-* 헤더에 들어가며 본문에는 절대 들어가지 않습니다. |
byokProvider | () => ProviderId | undefined | 선택적 공급자 슬러그. 슬러그를 반환하면 해당 키만 전송됩니다. 그렇지 않으면 body.provider를 사용합니다. 슬러그가 확인되지 않으면 generate에서 오류가 발생합니다. |
threadId | string | 이 생성의 안정적인 범위. persistence가 켜져 있으면 필수이며, 임시 실행에서는 선택 사항입니다. |
body | Record<string, any> | 연결 요청과 함께 전송할 추가 본문 매개변수 |
onResult | (result: TResult) => TOutput | null | void | 결과를 변환하거나 결과에 반응 |
onError | (error: Error) => void | 오류 콜백 |
onProgress | (progress: number, message?: string) => void | 진행률 업데이트(0~100) |
onChunk | (chunk: StreamChunk) => void | 청크별 콜백(연결 모드에서만) |
반환 값
UseGenerationReturn<TOutput>은 다음을 제공합니다.
| 속성 | 타입 | 설명 |
|---|---|---|
generate | (input: TInput) => Promise<void> | 생성 요청 실행 |
result | TOutput | null | 생성 결과 또는 null |
isLoading | boolean | 생성이 진행 중인지 여부 |
error | Error | undefined | 현재 오류(있는 경우) |
status | GenerationClientState | 'idle' | 'generating' | 'success' | 'error' |
stop | () => void | 현재 생성 중단 |
reset | () => void | 결과와 오류를 지우고 유휴 상태로 돌아감 |
결과 변환
onResult 콜백은 result에 저장되는 값을 변환할 수 있습니다.
import { useGenerateImage, fetchServerSentEvents } from '@tanstack/ai-react'
import type { ImageGenerationResult } from '@tanstack/ai'
const { result } = useGenerateImage({
connection: fetchServerSentEvents('/api/generate/image'),
onResult: (raw: ImageGenerationResult) => raw.images.map((img) => img.url || img.b64Json),
})
// result is now string[] instead of ImageGenerationResult
프레임워크 변형
모든 생성 훅은 React, Vue, Svelte에서 동일한 기능으로 제공됩니다. API 형태는 동일하며 명명 규칙과 반응형 프리미티브만 다릅니다.
| 생성 유형 | React (@tanstack/ai-react) | Vue (@tanstack/ai-vue) | Svelte (@tanstack/ai-svelte) |
|---|---|---|---|
| Image | useGenerateImage | useGenerateImage | createGenerateImage |
| Audio | useGenerateAudio | useGenerateAudio | createGenerateAudio |
| Speech | useGenerateSpeech | useGenerateSpeech | createGenerateSpeech |
| Transcription | useTranscription | useTranscription | createTranscription |
| Summarization | useSummarize | useSummarize | createSummarize |
| Video | useGenerateVideo | useGenerateVideo | createGenerateVideo |
| 기본(범용) | useGeneration | useGeneration | createGeneration |
세 패키지 모두 편의를 위해 @tanstack/ai-client의 fetchServerSentEvents, fetchHttpStream, stream을 다시 내보냅니다.
Vue 참고: 반환 값은 DeepReadonly<ShallowRef<>>로 래핑됩니다. <script>와 <template> 모두에서 .value로 액세스합니다.
Svelte 참고: 함수는 create* 명명 규칙을 사용하며 $state를 통해 Svelte 5 반응형 상태를 반환합니다.
다음 단계
- Image Generation -- 공급자별 옵션, 크기 및 모델 사용 가능 여부
- Text-to-Speech -- 음성 옵션, 오디오 형식 및 스트리밍 오디오
- Transcription -- 파일 형식, 언어 감지 및 단어 수준 타임스탬프
- Video Generation -- 작업 수명 주기, 폴링 및 공급자 설정
- Generations Overview -- 아키텍처 및 서버 측 스트리밍 패턴