Narração
Um player "Ouvir esta página" que lê cada página em voz alta e destaca a frase que está sendo lida, usando as vozes do próprio navegador do leitor ou vozes neurais geradas em tempo de build.
A narração adiciona um player Ouvir esta página abaixo da descrição de cada página. O leitor aperta play e a página é lida em voz alta a partir do título, enquanto a frase que está sendo lida fica destacada e sempre visível na tela. O recurso é opcional:
export default defineConfig({
narration: true,
});
Com true, as páginas são lidas com as próprias vozes do navegador do leitor. Você não precisa de chave nem de etapa de build, e funciona em qualquer host, estático ou não. Adicione um provider para usar vozes geradas em vez disso.
O que é lido
A narração lê em ordem o título da página, a descrição, os títulos, os parágrafos, os itens de lista e o texto dos cards. Alguns componentes são anunciados com uma breve deixa falada, para que o ouvinte saiba que tipo de conteúdo vem a seguir:
| Componente | Deixa |
|---|---|
| Callouts | “Nota.”, “Dica.”, “Aviso.” e assim por diante |
| Steps | “Passo 1.”, “Passo 2.” |
| Tabs | “Aba macOS.” Todas as abas são lidas, não só a que está aberta |
| Accordions e Expandable | “Seção expansível.” |
Quando a narração chega a uma seção fechada ou a uma aba que não está visível, ela abre esse conteúdo. Assim, o destaque sempre cai em um texto que o leitor consegue ver.
Blocos de código, tabelas, imagens, vídeos, diagramas, fórmulas matemáticas, tabelas de tipos, árvores de arquivos e previews de componentes ao vivo são pulados, porque não fazem sentido lidos em voz alta.
O player só aparece em páginas com texto suficiente para valer a pena ouvir, cerca de 50 palavras. Páginas muito curtas e páginas compostas principalmente de código ou campos de API não ganham um player.
Ouvindo
Enquanto uma página está tocando, o player fica fixado abaixo do cabeçalho. Ele traz play e pause, botões para a frase anterior e a próxima, um controle deslizante de progresso, o tempo já lido em relação à duração da página e um controle de velocidade de 0,8× a 2×. A velocidade escolhida é mantida na próxima página.
A página rola para manter visível a frase que está sendo lida. Se o leitor rolar para outro ponto, a página para de acompanhar a leitura, mas o áudio continua. Para retomar o acompanhamento, basta voltar até a frase ou apertar Acompanhar. Abrir outra página interrompe a narração.
Vozes do navegador
narration: true lê a página com a Web Speech API e com as vozes do dispositivo do leitor, escolhendo uma para o idioma da página. Se o navegador não tiver nenhuma voz para esse idioma, o player fica oculto, em vez de ler a página com o sotaque errado.
Isso não exige nada de você e não custa nada, mas a qualidade do som depende do dispositivo. As vozes do sistema no macOS, no iOS e no Windows são boas, enquanto alguns navegadores no Linux vêm com poucas vozes ou nenhuma.
Vozes geradas
Passe um provider para gerar áudio com um modelo neural de fala em tempo de build. O provider é um dos adaptadores do assistente, apontado para um modelo de fala: gateway() para os modelos de fala do Vercel AI Gateway ou openai() para os próprios modelos de fala da OpenAI ou para um servidor de fala que você mesmo roda.
import { defineConfig } from "blume";
import { gateway } from "blume/ai";
export default defineConfig({
narration: {
provider: gateway({
model: "openai/tts-1-hd",
voice: "alloy",
}),
},
});
O blume build lê cada página gerada exatamente como o player vai ler, divide o texto em frases e gera um clipe por frase pelo provider, aqui o Vercel AI Gateway. Os clipes e um pequeno manifest por página são gravados no seu build como arquivos estáticos em /blume-narration/. Assim, ouvir de novo não custa nada e nada roda em um servidor.
Antes de gerar qualquer coisa, o build mostra no log quantos clipes novos precisa criar e quantos caracteres eles somam, para que você veja o custo logo de início:
Generating narration: 214 new clip(s), 15,880 characters, with openai/tts-1-hd
O gateway() aceita:
| Opção | Padrão | Descrição |
|---|---|---|
model |
openai/tts-1-hd |
Um modelo de fala do gateway: openai/tts-1, openai/tts-1-hd, fish-audio/s2.1-pro, spacexai/grok-tts e outros listados pelo gateway. |
voice |
alloy |
A voz do modelo. |
instructions |
Como a voz deve soar, para modelos que aceitam instruções (“Leia com calma, como um professor”). | |
apiKeyEnv |
AI_GATEWAY_API_KEY |
A variável de ambiente que guarda a chave do gateway. Na Vercel, o token OIDC do build também funciona. |
headers |
Headers estáticos enviados em todas as requisições. | |
providerOptions |
Repassado sem alterações para o generateSpeech do AI SDK, para configurações do modelo que o Blume não expõe por nome. |
Vozes da OpenAI e auto-hospedadas
Já o openai() gera os clipes com a API de fala da OpenAI: os próprios modelos da OpenAI com a sua OPENAI_API_KEY ou, com um baseUrl, qualquer servidor que ofereça o mesmo endpoint POST /audio/speech, como o Kokoro-FastAPI, o Speaches ou um proxy LiteLLM com um modelo de fala. Assim, um site cujo build não consegue acessar um serviço hospedado também pode ter vozes geradas:
import { defineConfig } from "blume";
import { openai } from "blume/ai";
export default defineConfig({
narration: {
provider: openai({
baseUrl: "http://localhost:8880/v1",
model: "kokoro",
voice: "af_heart",
}),
},
});
Um servidor que exige chave, como um proxy LiteLLM, recebe a chave que você indicar em apiKeyEnv (openai({ baseUrl, apiKeyEnv: "LITELLM_API_KEY", model })). Sem apiKeyEnv, o servidor do baseUrl é chamado sem chave, então a sua OPENAI_API_KEY nunca vai parar em outro servidor. Uma resposta que não seja MP3, como uma página de login de um proxy na frente do servidor ou um WAV de um servidor que ignora o formato pedido, conta como um clipe que falhou, em vez de ir para o cache.
Com ou sem baseUrl, ele fala pelo provider da OpenAI do AI SDK, então adicione o @ai-sdk/openai ao seu projeto (npm install @ai-sdk/openai). Se ele estiver faltando, o blume build para antes de o Vite rodar e informa o nome do pacote.
| Opção | Padrão | Descrição |
|---|---|---|
model |
Obrigatório. Um modelo de fala: gpt-4o-mini-tts, tts-1 ou tts-1-hd na OpenAI, ou o id que o seu servidor oferece. |
|
baseUrl |
A URL base do servidor compatível com a OpenAI, incluindo o /v1. Sem ela, os clipes vêm da OpenAI ou, quando OPENAI_BASE_URL está definida, do servidor que ela indica, como acontece nos SDKs da própria OpenAI. |
|
voice |
alloy |
A voz do modelo. |
instructions |
Como a voz deve soar, para modelos que aceitam instruções (gpt-4o-mini-tts). |
|
apiKeyEnv |
OPENAI_API_KEY sem um baseUrl |
A variável de ambiente que guarda a chave, enviada como bearer token. Com um baseUrl, só é enviada uma chave que você indicar aqui, então a sua OPENAI_API_KEY nunca chega a outro servidor; se você não indicar nenhuma, as requisições vão sem bearer token (um Authorization definido em headers continua sendo enviado). |
headers |
Headers estáticos enviados em todas as requisições. | |
providerOptions |
Repassado sem alterações para o generateSpeech do AI SDK. A API de fala da OpenAI lê o speed daqui: { openai: { speed: 1.1 } }. |
A API de fala da OpenAI não recebe idioma, então o openai() não envia o idioma da página, e uma frase que aparece em vários idiomas é gerada uma única vez. Os modelos da OpenAI identificam o idioma pelo próprio texto, mas muitos servidores auto-hospedados se guiam pela voz, então escolha uma que fale o idioma do seu conteúdo.
Cache
Os clipes ficam em cache em node_modules/.cache/blume/narration, identificados pelo que define o som deles: a frase, o idioma (no gateway(), que o envia), o modelo, a voz, as instruções, o providerOptions e, no openai(), o servidor chamado. Apontar o baseUrl para outro servidor gera de novo todos os clipes, mesmo com o mesmo modelo e a mesma voz. Um novo build só paga pelas frases que mudaram, e uma frase que aparece em várias páginas é gerada uma única vez. A Vercel e a Netlify restauram o node_modules dos caches de build, então um deploy nelas gera de novo só o que mudou. Em outros serviços de CI, configure o cache desse diretório entre as execuções.
Fallbacks
Quando não existem clipes, as vozes geradas são substituídas pelas vozes do navegador:
- No
blume dev, que nunca gera áudio. Rodeblume buildeblume previewpara ouvir a voz gerada localmente. - Quando a chave não está definida no momento do build. O build mostra um aviso e não gera nada novo, mas as páginas que têm todos os clipes em cache continuam usando esses clipes. O
openai()com umbaseUrle semapiKeyEnvnão envia chave, então a geração segue normalmente. - Quando a geração falha, inclusive quando o build não consegue acessar o servidor. A geração para no primeiro clipe que falhar, já que o AI SDK já fez novas tentativas, e o build continua com um aviso, mantendo todos os clipes que já estão em cache.
Idiomas
A narração lê cada página no idioma do conteúdo, então, em um site internacionalizado, cada locale é lido com a própria voz. Com o openai(), isso depende do modelo e da voz: veja Vozes da OpenAI e auto-hospedadas. As deixas faladas e os rótulos do player já vêm traduzidos em todos os idiomas de interface incluídos. Para personalizar esses textos em um locale, use a chave narration em i18n.ui.
Desativando em uma página
Defina narration: false no frontmatter de uma página para que ela fique sem player:
---
title: Changelog
narration: false
---
Deixando conteúdo de fora
Adicione data-blume-narration="skip" a um elemento para deixar esse elemento e todo o seu conteúdo fora da narração. É assim que o Blume pula as próprias tabelas de tipos e previews, e funciona do mesmo jeito nos seus próprios componentes e islands:
<div data-blume-narration="skip">
<PricingCalculator />
</div>
Analytics
Quando um leitor começa a ouvir, o player envia um evento narration_play com o engine (audio ou browser) e o path da página. Quando uma página toca até o fim, ele envia narration_complete. Os dois eventos passam pelos seus adaptadores de analytics, como os outros eventos personalizados.