वाचन
एक "यह पेज सुनें" प्लेयर, जो हर पेज को ज़ोर से पढ़ता है और पढ़े जा रहे वाक्य को हाइलाइट करता है। इसके लिए यह पाठक के अपने ब्राउज़र की आवाज़ों या बिल्ड के समय जनरेट की गई न्यूरल आवाज़ों का उपयोग करता है।
वाचन हर पेज के विवरण के नीचे एक यह पेज सुनें प्लेयर जोड़ता है। पाठक प्ले दबाते हैं और पेज शीर्षक से शुरू होकर नीचे तक ज़ोर से पढ़ा जाता है। इस दौरान पढ़ा जा रहा वाक्य हाइलाइट रहता है और स्क्रीन पर दिखता रहता है। यह सुविधा वैकल्पिक है, इसे आपको स्वयं चालू करना होता है:
export default defineConfig({
narration: true,
});
true सेट करने पर पेज पाठक की अपनी ब्राउज़र आवाज़ों में पढ़े जाते हैं। इसके लिए न कोई कुंजी चाहिए, न कोई बिल्ड चरण। यह हर होस्ट पर काम करता है, चाहे वह स्टैटिक हो या नहीं। इसके बजाय जनरेट की गई आवाज़ों का उपयोग करने के लिए एक provider जोड़ें।
यह क्या पढ़ता है
वाचन पेज का शीर्षक, विवरण, हेडिंग, पैराग्राफ़, सूची आइटम और कार्ड टेक्स्ट क्रम से पढ़ता है। कुछ कॉम्पोनेंट्स से पहले एक छोटा बोला गया संकेत आता है, ताकि श्रोता जान सकें कि आगे किस तरह की सामग्री आने वाली है:
| कॉम्पोनेंट | संकेत |
|---|---|
| कॉलआउट | “नोट।”, “टिप।”, “चेतावनी।”, इत्यादि |
| स्टेप्स | “चरण 1।”, “चरण 2।” |
| टैब | “macOS टैब।” केवल खुला हुआ टैब नहीं, बल्कि हर टैब पढ़ा जाता है |
| अकॉर्डियन और एक्सपैंडेबल | “विस्तार योग्य अनुभाग।” |
जब वाचन किसी बंद अनुभाग या छिपे हुए टैब तक पहुँचता है, तो वह उसे खोल देता है। इससे हाइलाइट हमेशा उसी टेक्स्ट पर रहता है जिसे पाठक देख सकें।
कोड ब्लॉक, टेबल, इमेज, वीडियो, डायग्राम, गणित, टाइप टेबल, फ़ाइल ट्री और लाइव कॉम्पोनेंट प्रीव्यू छोड़ दिए जाते हैं, क्योंकि इन्हें ज़ोर से पढ़ने का कोई अर्थ नहीं बनता।
प्लेयर केवल उन्हीं पेजों पर दिखाई देता है जिनमें सुनने लायक पर्याप्त गद्य हो, यानी लगभग 50 शब्द। बहुत छोटे पेजों पर प्लेयर नहीं दिखता। जिन पेजों में अधिकतर कोड या API फ़ील्ड हों, उन पर भी यह नहीं दिखता।
सुनना
पेज चलते समय प्लेयर हेडर के नीचे पिन रहता है। इसमें प्ले और पॉज़, पिछला और अगला वाक्य, एक प्रोग्रेस स्लाइडर, अब तक पढ़ा गया समय और पेज की कुल अवधि, और 0.8× से 2× तक का गति नियंत्रण होता है। चुनी गई गति अगले पेज के लिए याद रखी जाती है।
पढ़ा जा रहा वाक्य स्क्रीन पर बना रहे, इसके लिए पेज अपने-आप स्क्रॉल होता रहता है। यदि पाठक स्क्रॉल करके कहीं और चले जाते हैं, तो अपने-आप स्क्रॉल होना रुक जाता है, लेकिन ऑडियो चलता रहता है। उस वाक्य तक वापस स्क्रॉल करने पर या साथ-साथ फ़ॉलो करें दबाने पर यह फिर से शुरू हो जाता है। कोई दूसरा पेज खोलने पर वाचन रुक जाता है।
ब्राउज़र आवाज़ें
narration: true पेज को Web Speech API और पाठक के डिवाइस में मौजूद आवाज़ों से पढ़ता है। आवाज़ पेज की भाषा के अनुसार चुनी जाती है। यदि ब्राउज़र में उस भाषा की कोई आवाज़ नहीं है, तो प्लेयर छिपा रहता है, ताकि पेज गलत उच्चारण में न पढ़ा जाए।
इसके लिए आपको कुछ नहीं करना पड़ता और इसकी कोई लागत भी नहीं है। हालाँकि आवाज़ की गुणवत्ता डिवाइस पर निर्भर करती है। macOS, iOS और Windows की सिस्टम आवाज़ें अच्छी हैं, जबकि कुछ Linux ब्राउज़रों में बहुत कम आवाज़ें होती हैं या बिल्कुल नहीं होतीं।
जनरेट की गई आवाज़ें
बिल्ड के समय किसी न्यूरल स्पीच मॉडल से ऑडियो जनरेट करने के लिए एक provider पास करें। यह प्रोवाइडर असिस्टेंट के एडैप्टर्स में से ही कोई एक होता है, जिसमें एक स्पीच मॉडल सेट किया जाता है। Vercel AI Gateway के स्पीच मॉडलों के लिए gateway() का उपयोग करें। OpenAI के अपने स्पीच मॉडलों या आपके द्वारा स्वयं चलाए जा रहे स्पीच सर्वर के लिए openai() का उपयोग करें।
import { defineConfig } from "blume";
import { gateway } from "blume/ai";
export default defineConfig({
narration: {
provider: gateway({
model: "openai/tts-1-hd",
voice: "alloy",
}),
},
});
blume build हर बिल्ट पेज को ठीक उसी तरह पढ़ता है जैसे प्लेयर पढ़ेगा। फिर वह पेज को वाक्यों में बाँटता है और प्रोवाइडर के ज़रिए हर वाक्य के लिए एक क्लिप जनरेट करता है। इस उदाहरण में प्रोवाइडर Vercel AI Gateway है। ये क्लिप और हर पेज के लिए एक छोटा मैनिफ़ेस्ट आपके बिल्ड में /blume-narration/ के अंदर स्टैटिक फ़ाइलों के रूप में लिखे जाते हैं। इसलिए ऑडियो दोबारा चलाने पर कोई लागत नहीं आती और सर्वर पर कुछ भी नहीं चलता।
कुछ भी जनरेट करने से पहले बिल्ड लॉग में बताता है कि कितनी नई क्लिप चाहिए और उनमें कुल कितने कैरेक्टर हैं। इस तरह लागत पहले से पता चल जाती है:
Generating narration: 214 new clip(s), 15,880 characters, with openai/tts-1-hd
gateway() ये विकल्प लेता है:
| विकल्प | डिफ़ॉल्ट | विवरण |
|---|---|---|
model |
openai/tts-1-hd |
गेटवे का कोई स्पीच मॉडल, जैसे openai/tts-1, openai/tts-1-hd, fish-audio/s2.1-pro, spacexai/grok-tts, या गेटवे में सूचीबद्ध कोई अन्य मॉडल। |
voice |
alloy |
मॉडल की आवाज़। |
instructions |
आवाज़ कैसी सुनाई देनी चाहिए (“शांति से पढ़ें, जैसे कोई शिक्षक पढ़ाता है”)। यह केवल उन मॉडलों पर लागू होता है जो निर्देश स्वीकार करते हैं। | |
apiKeyEnv |
AI_GATEWAY_API_KEY |
वह env var जिसमें गेटवे कुंजी रखी है। Vercel पर बिल्ड का OIDC टोकन भी काम करता है। |
headers |
हर अनुरोध के साथ भेजे जाने वाले स्टैटिक हेडर। | |
providerOptions |
AI SDK के generateSpeech को बिना बदलाव के पास किया जाता है। यह उन मॉडल सेटिंग्स के लिए है जिनका विकल्प Blume में नहीं है। |
OpenAI और सेल्फ़-होस्टेड आवाज़ें
openai() इसके बजाय OpenAI के स्पीच API से क्लिप जनरेट करता है। यह आपकी OPENAI_API_KEY के साथ OpenAI के अपने मॉडलों का उपयोग करता है। baseUrl देने पर यह किसी भी ऐसे सर्वर का उपयोग कर सकता है जो वही POST /audio/speech एंडपॉइंट उपलब्ध कराता हो, जैसे Kokoro-FastAPI, Speaches, या स्पीच मॉडल वाला कोई LiteLLM प्रॉक्सी। जिस साइट का बिल्ड किसी होस्टेड सेवा तक नहीं पहुँच सकता, उसे इस तरीके से जनरेट की गई आवाज़ें मिलती हैं:
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",
}),
},
});
जो सर्वर कुंजी माँगता है, जैसे LiteLLM प्रॉक्सी, उसे वही कुंजी भेजी जाती है जिसका नाम आप apiKeyEnv में देते हैं (openai({ baseUrl, apiKeyEnv: "LITELLM_API_KEY", model }))। apiKeyEnv के बिना baseUrl वाले सर्वर को बिना किसी कुंजी के कॉल किया जाता है, इसलिए आपकी OPENAI_API_KEY कभी किसी दूसरे सर्वर तक नहीं जाती। जो रिस्पॉन्स MP3 नहीं होता, उसे कैश नहीं किया जाता, बल्कि विफल क्लिप माना जाता है। उदाहरण के लिए, सर्वर के आगे लगे किसी प्रॉक्सी का साइन-इन पेज, या ऐसे सर्वर से आया WAV जो माँगे गए फ़ॉर्मेट को अनदेखा करता है।
baseUrl हो या न हो, यह AI SDK के OpenAI प्रोवाइडर के ज़रिए काम करता है, इसलिए अपने प्रोजेक्ट में @ai-sdk/openai जोड़ें (npm install @ai-sdk/openai)। यदि यह पैकेज मौजूद नहीं है, तो blume build Vite चलने से पहले ही रुक जाता है और उस पैकेज का नाम बताता है।
| विकल्प | डिफ़ॉल्ट | विवरण |
|---|---|---|
model |
आवश्यक। कोई स्पीच मॉडल, जैसे OpenAI पर gpt-4o-mini-tts, tts-1 या tts-1-hd, या आपके सर्वर द्वारा उपलब्ध कराए गए मॉडल का ID। |
|
baseUrl |
सर्वर का OpenAI-संगत बेस URL, /v1 सहित। इसके बिना क्लिप OpenAI से आती हैं। यदि OPENAI_BASE_URL सेट हो, तो क्लिप उसमें दिए गए सर्वर से आती हैं, ठीक वैसे ही जैसे OpenAI के अपने SDK में होता है। |
|
voice |
alloy |
मॉडल की आवाज़। |
instructions |
आवाज़ कैसी सुनाई देनी चाहिए। यह केवल उन मॉडलों पर लागू होता है जो निर्देश स्वीकार करते हैं (gpt-4o-mini-tts)। |
|
apiKeyEnv |
baseUrl के बिना OPENAI_API_KEY |
वह env var जिसमें कुंजी रखी है। कुंजी बेयरर टोकन के रूप में भेजी जाती है। baseUrl होने पर केवल वही कुंजी भेजी जाती है जिसका नाम आप यहाँ देते हैं, ताकि आपकी OPENAI_API_KEY कभी किसी दूसरे सर्वर तक न पहुँचे। यदि आप कोई नाम नहीं देते, तो अनुरोध बिना बेयरर टोकन के भेजे जाते हैं (headers में सेट किया गया Authorization फिर भी भेजा जाता है)। |
headers |
हर अनुरोध के साथ भेजे जाने वाले स्टैटिक हेडर। | |
providerOptions |
AI SDK के generateSpeech को बिना बदलाव के पास किया जाता है। OpenAI का स्पीच API इसमें से speed पढ़ता है: { openai: { speed: 1.1 } }। |
OpenAI का स्पीच API भाषा स्वीकार नहीं करता, इसलिए openai() पेज की भाषा नहीं भेजता। इस कारण कई भाषाओं के पेजों में आने वाला एक ही वाक्य सिर्फ़ एक बार जनरेट होता है। OpenAI के मॉडल टेक्स्ट से ही भाषा पहचान लेते हैं, लेकिन कई सेल्फ़-होस्टेड सर्वर भाषा आवाज़ के आधार पर तय करते हैं। इसलिए ऐसी आवाज़ चुनें जो आपकी सामग्री की भाषा बोलती हो।
कैशिंग
क्लिप node_modules/.cache/blume/narration में कैश की जाती हैं। कैश उन चीज़ों पर आधारित होता है जो क्लिप की ध्वनि तय करती हैं, यानी वाक्य, उसकी भाषा (gateway() के लिए, क्योंकि वह भाषा भेजता है), मॉडल, आवाज़, निर्देश, providerOptions और openai() के लिए वह सर्वर जिसे वह कॉल करता है। baseUrl को किसी दूसरे सर्वर पर सेट करने से हर क्लिप दोबारा जनरेट होती है, भले ही मॉडल और आवाज़ वही हों। दोबारा बिल्ड करने पर केवल बदले हुए वाक्यों की लागत आती है। कई पेजों में आने वाला एक ही वाक्य सिर्फ़ एक बार जनरेट होता है। Vercel और Netlify अपने बिल्ड कैश से node_modules को वापस ले आते हैं, इसलिए वहाँ डिप्लॉय करने पर केवल बदली हुई सामग्री दोबारा जनरेट होती है। किसी अन्य CI पर हर रन के बीच इस डायरेक्टरी को कैश करें।
फ़ॉलबैक
जहाँ कोई क्लिप मौजूद नहीं होती, वहाँ जनरेट की गई आवाज़ों की जगह ब्राउज़र आवाज़ों का उपयोग होता है:
blume devमें, क्योंकि यह कभी ऑडियो जनरेट नहीं करता। जनरेट की गई आवाज़ को लोकल रूप से सुनने के लिएblume buildऔरblume previewचलाएँ।- जब बिल्ड के समय कुंजी सेट न हो। ऐसे में बिल्ड चेतावनी देता है और कुछ भी नया जनरेट नहीं करता, लेकिन जिन पेजों की सभी क्लिप कैश में हैं, उन्हें वे क्लिप फिर भी मिलती हैं।
baseUrlवाला और बिनाapiKeyEnvवालाopenai()कोई कुंजी नहीं भेजता, इसलिए उसमें जनरेशन जारी रहता है। - जब जनरेशन विफल हो जाए, जिसमें यह स्थिति भी शामिल है कि बिल्ड किसी सर्वर तक न पहुँच पाए। जनरेशन पहली विफल क्लिप पर रुक जाता है, क्योंकि AI SDK उसे पहले ही कई बार आज़मा चुका होता है। इसके बाद बिल्ड एक चेतावनी के साथ जारी रहता है और तब तक कैश हुई सभी क्लिप सुरक्षित रखता है।
भाषाएँ
वाचन हर पेज को उसकी सामग्री की भाषा में पढ़ता है। इसलिए किसी बहुभाषी साइट पर हर लोकेल अपनी भाषा की आवाज़ में पढ़ा जाता है। openai() के साथ यह मॉडल और आवाज़ पर निर्भर करता है। इसके लिए OpenAI और सेल्फ़-होस्टेड आवाज़ें देखें। बोले जाने वाले संकेत और प्लेयर के लेबल सभी बिल्ट-इन UI भाषाओं में अनुवादित हैं। इन्हें किसी लोकेल के लिए बदलने के लिए i18n.ui में narration के अंदर ओवरराइड करें।
किसी पेज के लिए इसे बंद करना
किसी पेज पर प्लेयर न दिखाने के लिए उस पेज के फ़्रंटमैटर में narration: false सेट करें:
---
title: Changelog
narration: false
---
सामग्री को बाहर रखना
किसी एलिमेंट और उसके अंदर की पूरी सामग्री को वाचन से बाहर रखने के लिए उसमें data-blume-narration="skip" जोड़ें। Blume अपनी टाइप टेबल और प्रीव्यू को इसी तरीके से छोड़ता है। यह आपके अपने कॉम्पोनेंट्स और आइलैंड्स पर भी इसी तरह काम करता है:
<div data-blume-narration="skip">
<PricingCalculator />
</div>
एनालिटिक्स
जब कोई पाठक सुनना शुरू करते हैं, तो प्लेयर एक narration_play इवेंट भेजता है। इसमें engine (audio या browser) और पेज का path शामिल होता है। जब कोई पेज अंत तक चल जाता है, तो प्लेयर narration_complete भेजता है। दूसरे कस्टम इवेंट्स की तरह ये दोनों इवेंट भी आपके एनालिटिक्स एडैप्टर्स से होकर जाते हैं।