{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "transcriber-01", "description": "Transcriber", "dependencies": [ "@elevenlabs/elevenlabs-js", "streamdown" ], "registryDependencies": [ "https://ui.elevenlabs.io/r/live-waveform.json", "button", "card", "scroll-area", "separator" ], "files": [ { "path": "blocks/transcriber-01/page.tsx", "content": "\"use client\"\n\nimport { Fragment, useCallback, useEffect, useRef, useState } from \"react\"\nimport { Copy } from \"lucide-react\"\nimport { Streamdown } from \"streamdown\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n transcribeAudio,\n type TranscriptionResult,\n} from \"@/app/transcriber-01/actions/transcribe\"\nimport { Button } from \"@/components/ui/button\"\nimport { Card } from \"@/components/ui/card\"\nimport { LiveWaveform } from \"@/components/ui/live-waveform\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport { Separator } from \"@/components/ui/separator\"\n\ninterface RecordingState {\n isRecording: boolean\n isProcessing: boolean\n transcript: string\n error: string\n transcriptionTime?: number\n}\n\nexport default function Transcriber01() {\n const [recording, setRecording] = useState({\n isRecording: false,\n isProcessing: false,\n transcript: \"\",\n error: \"\",\n })\n\n const mediaRecorderRef = useRef(null)\n const audioChunksRef = useRef([])\n const streamRef = useRef(null)\n\n const updateRecording = useCallback((updates: Partial) => {\n setRecording((prev) => ({ ...prev, ...updates }))\n }, [])\n\n const cleanupStream = useCallback(() => {\n if (streamRef.current) {\n streamRef.current.getTracks().forEach((track) => track.stop())\n streamRef.current = null\n }\n }, [])\n\n const stopRecording = useCallback(() => {\n if (mediaRecorderRef.current?.state !== \"inactive\") {\n mediaRecorderRef.current?.stop()\n }\n cleanupStream()\n updateRecording({ isRecording: false })\n }, [cleanupStream, updateRecording])\n\n const processAudio = useCallback(\n async (audioBlob: Blob) => {\n updateRecording({ isProcessing: true, error: \"\" })\n\n try {\n const result: TranscriptionResult = await transcribeAudio({\n audio: new File([audioBlob], \"recording.webm\", {\n type: \"audio/webm\",\n }),\n })\n\n if (result.error) {\n throw new Error(result.error)\n }\n\n updateRecording({\n transcript: result.text || \"\",\n transcriptionTime: result.transcriptionTime,\n isProcessing: false,\n })\n } catch (err) {\n console.error(\"Transcription error:\", err)\n updateRecording({\n error:\n err instanceof Error ? err.message : \"Failed to transcribe audio\",\n isProcessing: false,\n })\n }\n },\n [updateRecording]\n )\n\n const startRecording = useCallback(async () => {\n try {\n updateRecording({\n transcript: \"\",\n error: \"\",\n transcriptionTime: undefined,\n })\n audioChunksRef.current = []\n\n const stream =\n await navigator.mediaDevices.getUserMedia(AUDIO_CONSTRAINTS)\n streamRef.current = stream\n\n const mimeType = getMimeType()\n const mediaRecorder = new MediaRecorder(stream, { mimeType })\n mediaRecorderRef.current = mediaRecorder\n\n mediaRecorder.ondataavailable = (event: BlobEvent) => {\n if (event.data.size > 0) {\n audioChunksRef.current.push(event.data)\n }\n }\n\n mediaRecorder.onstop = () => {\n const audioBlob = new Blob(audioChunksRef.current, { type: mimeType })\n processAudio(audioBlob)\n }\n\n mediaRecorder.start()\n updateRecording({ isRecording: true })\n } catch (err) {\n updateRecording({\n error: \"Microphone permission denied\",\n isRecording: false,\n })\n console.error(\"Microphone error:\", err)\n }\n }, [processAudio, updateRecording])\n\n const handleRecordToggle = useCallback(() => {\n if (recording.isRecording) {\n stopRecording()\n } else {\n startRecording()\n }\n }, [recording.isRecording, startRecording, stopRecording])\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.altKey && e.code === \"Space\") {\n e.preventDefault()\n handleRecordToggle()\n }\n }\n\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => window.removeEventListener(\"keydown\", handleKeyDown)\n }, [handleRecordToggle])\n\n useEffect(() => {\n return cleanupStream\n }, [cleanupStream])\n\n return (\n
\n \n
\n
\n {recording.isProcessing && }\n {(Boolean(recording.transcript) || Boolean(recording.error)) && (\n \n )}\n\n {!recording.isProcessing &&\n !Boolean(recording.transcript) &&\n !Boolean(recording.error) && (\n \n )}\n
\n
\n\n \n\n
\n
\n
\n \n {recording.error\n ? \"Error\"\n : recording.transcriptionTime\n ? `${(recording.transcriptionTime / 1000).toFixed(2)}s`\n : \"0.00s\"}\n \n
\n\n
\n \n {recording.isRecording || recording.isProcessing\n ? \"Stop\"\n : \"Record\"}\n \n Space\n \n \n
\n
\n
\n
\n
\n )\n}\n\nconst TranscriberProcessing = () => {\n return (\n \n )\n}\n\nconst TranscriberTranscript = ({\n transcript,\n error,\n}: {\n transcript: string\n error: string\n}) => {\n const displayText = error || transcript\n return (\n \n
\n \n \n {displayText}\n
\n \n {transcript && !error && (\n {\n navigator.clipboard.writeText(transcript)\n }}\n aria-label=\"Copy transcript\"\n >\n \n \n )}\n \n
\n )\n}\n\nconst AUDIO_CONSTRAINTS: MediaStreamConstraints = {\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n}\n\nconst SUPPORTED_MIME_TYPES = [\"audio/webm;codecs=opus\", \"audio/webm\"] as const\n\nfunction getMimeType(): string {\n for (const type of SUPPORTED_MIME_TYPES) {\n if (MediaRecorder.isTypeSupported(type)) {\n return type\n }\n }\n return \"audio/webm\"\n}\n", "type": "registry:page", "target": "app/transcriber-01/page.tsx" }, { "path": "blocks/transcriber-01/actions/transcribe.ts", "content": "\"use server\"\n\nimport { ElevenLabsClient } from \"@elevenlabs/elevenlabs-js\"\nimport { SpeechToTextChunkResponseModel } from \"@elevenlabs/elevenlabs-js/api/types/SpeechToTextChunkResponseModel\"\n\nexport interface TranscriptionResult {\n text?: string\n error?: string\n transcriptionTime?: number\n}\nexport type TranscribeAudioInput = {\n audio: File\n}\n\nconst MODEL_ID = \"scribe_v1\"\n\nexport async function transcribeAudio({\n audio,\n}: TranscribeAudioInput): Promise {\n try {\n if (!audio) {\n return { error: \"No audio file provided\" }\n }\n\n const apiKey = process.env.ELEVENLABS_API_KEY\n if (!process.env.ELEVENLABS_API_KEY) {\n return { error: \"Service not configured\" }\n }\n\n const client = new ElevenLabsClient({ apiKey })\n const audioBuffer = await audio.arrayBuffer()\n\n const file = new File([audioBuffer], audio.name || \"audio.webm\", {\n type: audio.type || \"audio/webm\",\n })\n\n const startTime = Date.now()\n const transcriptionResult = await client.speechToText.convert({\n file,\n modelId: MODEL_ID,\n languageCode: \"en\",\n })\n const transcriptionTime = Date.now() - startTime\n\n const rawText = (transcriptionResult as SpeechToTextChunkResponseModel).text\n\n if (!rawText) {\n return { error: \"No transcription available\" }\n }\n\n return { text: rawText, transcriptionTime }\n } catch (error) {\n console.error(\"Transcription error:\", error)\n return {\n error:\n error instanceof Error ? error.message : \"Failed to transcribe audio\",\n }\n }\n}\n", "type": "registry:file", "target": "app/transcriber-01/actions/transcribe.ts" } ], "meta": { "iframeHeight": "600px", "container": "w-full bg-surface min-h-svh flex px-4 py-12 items-center md:py-20 justify-center min-w-0", "mobile": "component" }, "categories": [ "audio" ], "type": "registry:block" }