Uranus® Design System
AI

useAudioRecorder

Hook MediaRecorder com level meter — apresentacional por padrão, com transcribe opcional.

useAudioRecorder cuida do ciclo de vida da MediaRecorder API: pede permissão, grava chunks, expõe level (0–1) para a barra de volume e devolve o Blob final. Não transcreve por padrão — você passa um transcribe(blob) => Promise<string> quando quiser texto direto, ou usa o blob como anexo (attachOnStop).

Este exemplo precisa de mais altura para rolar e interagir como em um app real. Abra em tela cheia.

'use client';

import { CodeBlock, Composer } from '@uranus-workspace/ai';

const code = `'use client';

import { Composer } from '@uranus-workspace/ai';

export function VoiceComposer() {
  return (
    <Composer.Root status="idle" onSubmit={() => {}}>
      <Composer.Textarea />
      <Composer.Toolbar>
        <Composer.RecordButton
          transcribe={async (blob) => {
            const fd = new FormData();
            fd.append('audio', blob);
            const res = await fetch('/api/transcribe', { method: 'POST', body: fd });
            const { text } = await res.json();
            return text;
          }}
        />
      </Composer.Toolbar>
    </Composer.Root>
  );
}`;

export default function UseAudioRecorderDefault() {
  return (
    <div className="mx-auto flex w-full max-w-3xl flex-col gap-6">
      <div className="rounded-lg border bg-background p-4">
        <Composer.Root status="idle" onSubmit={() => {}}>
          <Composer.Textarea placeholder="Grave pelo botão ou digite aqui…" />
          <Composer.Toolbar>
            <Composer.RecordButton
              transcribe={async () =>
                '(simulado) Texto que viria da sua API de transcrição — em produção envie o Blob ao servidor.'
              }
            />
          </Composer.Toolbar>
        </Composer.Root>
      </div>
      <CodeBlock language="tsx" code={code} />
    </div>
  );
}

Uso direto

'use client';
import { useAudioRecorder } from '@uranus-workspace/ai';

const recorder = useAudioRecorder({
  transcribe: async (blob) => {
    const fd = new FormData();
    fd.append('audio', blob);
    const res = await fetch('/api/transcribe', { method: 'POST', body: fd });
    const { text } = await res.json();
    return text;
  },
});

<button onClick={recorder.start}>Gravar</button>
<button onClick={recorder.stop}>Parar</button>
<span>{recorder.level.toFixed(2)}</span>

Via Composer.RecordButton

Composer.RecordButton consome este hook internamente. Você só precisa decidir entre:

{/* Anexa o blob — você processa no submit */}
<Composer.RecordButton attachOnStop />

{/* Transcreve para o textarea */}
<Composer.RecordButton transcribe={transcribeAudio} />

Server route (opcional)

export async function POST(req: Request) {
  const form = await req.formData();
  const audio = form.get('audio') as File;
  const transcript = await fetch('https://api.openai.com/v1/audio/transcriptions', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
    body: form,
  }).then((r) => r.json());
  return Response.json({ text: transcript.text });
}

A11y

  • O botão de gravação altera aria-pressed enquanto está ativo.
  • Em prefers-reduced-motion: reduce, o waveform pulsa de forma reduzida e a barra usa só fade.