React SDK

useScribe: React에서 실시간 음성 텍스트 변환

Scribe와 기능에 관한 개요는 음성-텍스트 변환 개요를 참고하세요. 단계별 사용 가이드는 클라이언트 측 스트리밍을 참고하세요.

설치

npm install @elevenlabs/react
# or
yarn add @elevenlabs/react
# or
pnpm install @elevenlabs/react

AI 코딩 어시스턴트로 오디오를 전사하려면 ElevenLabs 음성-텍스트 변환 스킬을 사용하세요.

npx skills add elevenlabs/skills --skill speech-to-text

@elevenlabs/react는 @elevenlabs/client의 모든 항목을 다시 내보내므로 두 패키지를 모두 설치할 필요가 없습니다.

사용 방법

Scribe에 연결하여 실시간 전사 결과를 표시하는 최소 실행 예시는 다음과 같습니다.

import { useScribe } from "@elevenlabs/react";
import { useEffect } from "react";
function MyComponent() {
const scribe = useScribe({
modelId: "scribe_v2_realtime",
onPartialTranscript: (data) => {
console.log("Partial:", data.text);
},
onCommittedTranscript: (data) => {
console.log("Committed:", data.text);
},
});
// Start recording
const handleStart = async () => {
try {
const token = await fetchTokenFromServer();
await scribe.connect({
token,
microphone: {
echoCancellation: true,
noiseSuppression: true,
},
});
} catch (err) {
console.error("Failed to start recording:", err);
}
};
// Stop recording
const handleDisconnect = () => {
scribe.disconnect();
};
// Disconnect on unmount
useEffect(() => {
return () => {
if (scribe.isConnected) {
scribe.disconnect();
}
};
}, [scribe]);
return (
<div>
<button onClick={handleStart} disabled={scribe.isConnected}>
Start Recording
</button>
<button onClick={handleDisconnect} disabled={!scribe.isConnected}>
Stop
</button>
{scribe.partialTranscript && <p>Live: {scribe.partialTranscript}</p>}
<div>
{scribe.committedTranscripts.map((t) => (
<p key={t.id}>{t.text}</p>
))}
</div>
</div>
);
}

토큰 가져오기

Scribe는 인증을 위해 일회용 토큰이 필요합니다. 서버에 API 엔드포인트를 만드세요.

// Node.js server
app.get("/scribe-token", yourAuthMiddleware, async (req, res) => {
const response = await fetch("https://api.elevenlabs.io/v1/single-use-token/realtime_scribe", {
method: "POST",
headers: {
"xi-api-key": process.env.ELEVENLABS_API_KEY,
},
});
const data = await response.json();
res.json({ token: data.token });
});

ElevenLabs API 키는 민감한 정보입니다. 절대 클라이언트에 노출하지 마세요. 항상 서버에서 토큰을 생성하세요.

// Client
const fetchToken = async () => {
const response = await fetch("/scribe-token");
const { token } = await response.json();
return token;
};

Hook 옵션

기본 옵션과 콜백으로 Hook을 구성하세요.

const scribe = useScribe({
// Connection options (can be overridden in connect())
token: "optional-default-token",
modelId: "scribe_v2_realtime",
baseUri: "wss://api.elevenlabs.io",
// VAD options
commitStrategy: CommitStrategy.VAD,
vadSilenceThresholdSecs: 0.5,
vadThreshold: 0.5,
minSpeechDurationMs: 100,
minSilenceDurationMs: 500,
languageCode: "en",
// Microphone options (for automatic mode)
microphone: {
deviceId: "optional-device-id",
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
// Manual audio options (for file transcription)
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
// Auto-connect on mount
autoConnect: false,
// Event callbacks
onSessionStarted: () => console.log("Session started"),
onPartialTranscript: (data) => console.log("Partial:", data.text),
onCommittedTranscript: (data) => console.log("Committed:", data.text),
onCommittedTranscriptWithTimestamps: (data) => console.log("With timestamps:", data),
onError: (error) => console.error("Error:", error),
onAuthError: (data) => console.error("Auth error:", data.error),
onQuotaExceededError: (data) => console.error("Quota exceeded:", data.error),
onConnect: () => console.log("Connected"),
onDisconnect: () => console.log("Disconnected"),
});

연결 옵션

속성유형설명
tokenstringWebSocket 인증용 일회용 토큰입니다.
modelIdstring모델 ID(예: "scribe_v2_realtime")입니다.
baseUristring맞춤 WebSocket 기본 URI입니다. 기본값은 wss://api.elevenlabs.io입니다.

VAD 옵션

이 옵션은 VAD 커밋 전략을 사용할 때 전사 결과를 자동으로 커밋하는 시점을 제어합니다.

속성유형기본값설명
commitStrategyCommitStrategy"manual""manual" 또는 "vad"입니다.
vadSilenceThresholdSecsnumber1.5VAD가 커밋하기 전 무음 시간(초)입니다(0.3~3.0).
vadThresholdnumber0.4VAD 민감도입니다(0.1~0.9, 낮을수록 민감).
minSpeechDurationMsnumber100최소 음성 지속 시간(ms)입니다(50~2000).
minSilenceDurationMsnumber100최소 무음 지속 시간(ms)입니다(50~2000).

오디오 옵션

속성유형설명
languageCodestringISO-639-1 또는 ISO-639-3 언어 코드입니다. 자동 감지하려면 비워 두세요.
microphoneobject마이크 모드용 마이크 설정입니다. 아래를 참고하세요.
audioFormatAudioFormat수동 모드용 오디오 인코딩 형식입니다(예: AudioFormat.PCM_16000).
sampleRatenumber수동 모드의 샘플 레이트입니다. audioFormat과 일치해야 합니다.

microphone 객체에서 사용할 수 있는 속성은 다음과 같습니다.

속성유형설명
deviceIdstring특정 마이크 장치 ID입니다.
echoCancellationboolean에코 제거를 활성화합니다.
noiseSuppressionboolean노이즈 억제를 활성화합니다.
autoGainControlboolean자동 게인 제어를 활성화합니다.

동작 옵션

속성유형기본값설명
autoConnectbooleanfalse컴포넌트 마운트 시 자동으로 연결합니다.
includeTimestampsbooleanfalse단어 수준 타임스탬프를 받습니다. onCommittedTranscriptWithTimestamps가 제공되면 자동 활성화됩니다.

콜백

모든 이벤트 콜백은 선택 사항이며 Hook 옵션으로 제공할 수 있습니다.

  • onConnect - WebSocket 연결이 설정될 때 호출되는 핸들러입니다.
  • onDisconnect - WebSocket 연결이 닫힐 때 호출되는 핸들러입니다.
  • onSessionStarted - Scribe 세션이 시작될 때 호출되는 핸들러입니다.
  • onPartialTranscript - 중간 전사 결과와 함께 호출되는 핸들러입니다. { text: string }을 받습니다.
  • onCommittedTranscript - 최종 전사 결과와 함께 호출되는 핸들러입니다. { text: string }을 받습니다.
  • onCommittedTranscriptWithTimestamps - 단어 수준 타이밍을 포함한 최종 전사 결과와 함께 호출되는 핸들러입니다. { text: string; words?: { start: number; end: number }[] }을 받습니다.
  • onError - 모든 오류를 처리하는 일반 오류 핸들러입니다. Error | Event를 받습니다.
  • onAuthError - 인증 오류 발생 시 호출되는 핸들러입니다. { error: string }을 받습니다.

오류 콜백

일반 onError 콜백은 모든 오류에 대해 실행됩니다. 세부적인 처리를 위한 특정 오류 콜백도 제공됩니다. 모든 특정 오류 콜백은 { error: string }을 받습니다.

콜백설명
onError모든 오류를 처리하는 일반 오류 핸들러입니다.
onAuthError인증 오류입니다.
onQuotaExceededError사용량 할당량을 초과했습니다.
onCommitThrottledError커밋 요청이 제한되었습니다.
onTranscriberError전사 엔진 오류입니다.
onUnacceptedTermsError서비스 약관에 동의하지 않았습니다.
onRateLimitedError요청 한도가 적용되었습니다.
onInputError잘못된 입력 형식입니다.
onQueueOverflowError처리 큐가 가득 찼습니다.
onResourceExhaustedError서버 리소스가 한계에 도달했습니다.
onSessionTimeLimitExceededError최대 세션 시간에 도달했습니다.
onChunkSizeExceededError오디오 청크가 너무 큽니다.
onInsufficientAudioActivityError연결을 유지하기에 오디오 활동이 충분하지 않습니다.

마이크 모드

사용자의 마이크에서 직접 오디오를 스트리밍합니다.

function MicrophoneTranscription() {
const scribe = useScribe({
modelId: "scribe_v2_realtime",
});
const startRecording = async () => {
const token = await fetchToken();
await scribe.connect({
token,
microphone: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
};
return (
<div>
<button onClick={startRecording} disabled={scribe.isConnected}>
{scribe.status === "connecting" ? "Connecting..." : "Start"}
</button>
<button onClick={scribe.disconnect} disabled={!scribe.isConnected}>
Stop
</button>
{scribe.partialTranscript && (
<div>
<strong>Speaking:</strong> {scribe.partialTranscript}
</div>
)}
{scribe.committedTranscripts.map((transcript) => (
<div key={transcript.id}>{transcript.text}</div>
))}
</div>
);
}

수동 오디오 모드(파일 전사)

미리 녹음된 오디오 파일을 전사합니다.

import { useScribe, AudioFormat } from "@elevenlabs/react";
import { useState } from "react";
function FileTranscription() {
const [file, setFile] = useState<File | null>(null);
const scribe = useScribe({
modelId: "scribe_v2_realtime",
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
});
const transcribeFile = async () => {
if (!file) return;
const token = await fetchToken();
await scribe.connect({ token });
// Decode audio file
const arrayBuffer = await file.arrayBuffer();
const audioContext = new AudioContext({ sampleRate: 16000 });
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// Convert to PCM16
const channelData = audioBuffer.getChannelData(0);
const pcmData = new Int16Array(channelData.length);
for (let i = 0; i < channelData.length; i++) {
const sample = Math.max(-1, Math.min(1, channelData[i]));
pcmData[i] = sample < 0 ? sample * 32768 : sample * 32767;
}
// Send in chunks
const chunkSize = 4096;
for (let offset = 0; offset < pcmData.length; offset += chunkSize) {
const chunk = pcmData.slice(offset, offset + chunkSize);
const bytes = new Uint8Array(chunk.buffer);
const base64 = btoa(String.fromCharCode(...bytes));
scribe.sendAudio(base64);
await new Promise((resolve) => setTimeout(resolve, 50));
}
// Commit transcription
scribe.commit();
};
return (
<div>
<input type="file" accept="audio/*" onChange={(e) => setFile(e.target.files?.[0] || null)} />
<button onClick={transcribeFile} disabled={!file || scribe.isConnected}>
Transcribe
</button>
{scribe.committedTranscripts.map((transcript) => (
<div key={transcript.id}>{transcript.text}</div>
))}
</div>
);
}

반환 값

상태

  • status - 현재 연결 상태: "disconnected", "connecting", "connected", "transcribing" 또는 "error".
  • isConnected - 연결 여부를 나타내는 불리언 값입니다.
  • isTranscribing - 현재 전사 중인지 나타내는 불리언 값입니다.
  • partialTranscript - 현재 부분(중간) 전사 문자열입니다.
  • committedTranscripts - TranscriptSegment 객체 배열입니다(아래 참고).
  • error - 현재 오류 메시지 또는 null입니다.
const scribe = useScribe(/* options */);
console.log(scribe.status); // "connected"
console.log(scribe.isConnected); // true
console.log(scribe.partialTranscript); // "hello world"
console.log(scribe.committedTranscripts); // [{ id: "...", text: "...", words: ..., isFinal: true }]
console.log(scribe.error); // null or error string

커밋된 각 전사 세그먼트는 다음 구조를 가집니다.

interface TranscriptSegment {
id: string; // Unique identifier
text: string; // Transcript text
timestamp: number; // Unix timestamp
isFinal: boolean; // Always true for committed transcripts
}

메서드

connect(options?)

Scribe에 연결합니다. 여기서 제공한 옵션은 Hook 기본값을 재정의합니다.

await scribe.connect({
token: "your-token", // Required
microphone: {
/* ... */
}, // For microphone mode
// OR
audioFormat: AudioFormat.PCM_16000, // For manual mode
sampleRate: 16000,
});

disconnect()

연결을 종료하고 리소스를 정리합니다.

scribe.disconnect();

sendAudio(audioBase64, options?)

오디오 데이터를 전송합니다(수동 모드 전용).

scribe.sendAudio(base64AudioChunk, {
commit: false, // Optional: commit immediately
sampleRate: 16000, // Optional: override sample rate
previousText: "Previous transcription text", // Optional: context from a previous transcription. Can only be sent in the first audio chunk.
});

previousText 필드는 세션의 첫 번째 오디오 청크에서만 전송할 수 있습니다. 이후 청크에서 전송하면 오류가 발생합니다.

commit()

현재 전사 결과를 수동으로 커밋합니다.

scribe.commit();

clearTranscripts()

상태에서 모든 전사 결과를 지웁니다.

scribe.clearTranscripts();

getConnection()

기본 연결 인스턴스를 가져옵니다.

const connection = scribe.getConnection();
// Returns RealtimeConnection | null

커밋 전략

전사 결과를 커밋하는 시점을 제어합니다.

import { CommitStrategy } from '@elevenlabs/react';
// Manual (default) - you control when to commit
const scribe = useScribe({
commitStrategy: CommitStrategy.MANUAL,
});
// Later...
scribe.commit(); // Commit transcription
// Voice Activity Detection - model detects silences and automatically commits
const scribe = useScribe({
commitStrategy: CommitStrategy.VAD,
});

자세한 내용은 전사 결과 및 커밋 전략을 참고하세요.

전체 예시

다음은 VAD 기반 커밋 전략과 함께 useScribe Hook을 사용하는 React 컴포넌트의 전체 예시입니다.

import { useScribe, CommitStrategy } from "@elevenlabs/react";
import { useEffect } from "react";
function ScribeDemo() {
const scribe = useScribe({
modelId: "scribe_v2_realtime",
commitStrategy: CommitStrategy.VAD,
onSessionStarted: () => console.log("Started"),
onCommittedTranscript: (data) => console.log("Committed:", data.text),
onError: (error) => console.error("Error:", error),
});
const startMicrophone = async () => {
const token = await fetchToken();
await scribe.connect({
token,
microphone: {
echoCancellation: true,
noiseSuppression: true,
},
});
};
const handleDisconnect = () => scribe.disconnect();
const handleClearTranscripts = () => scribe.clearTranscripts();
useEffect(() => {
return () => {
handleDisconnect();
};
}, []);
return (
<div>
<h1>Scribe Demo</h1>
{/* Status */}
<div>
Status: {scribe.status}
{scribe.error && <span>Error: {scribe.error}</span>}
</div>
{/* Controls */}
<div>
{!scribe.isConnected ? (
<button onClick={startMicrophone}>Start Recording</button>
) : (
<button onClick={handleDisconnect}>Stop</button>
)}
<button onClick={handleClearTranscripts}>Clear</button>
</div>
{/* Live Transcript */}
{scribe.partialTranscript && (
<div>
<strong>Live:</strong> {scribe.partialTranscript}
</div>
)}
{/* Committed Transcripts */}
<div>
<h2>Transcripts ({scribe.committedTranscripts.length})</h2>
{scribe.committedTranscripts.map((t) => (
<div key={t.id}>
<span>{new Date(t.timestamp).toLocaleTimeString()}</span>
<p>{t.text}</p>
</div>
))}
</div>
</div>
);
}