### Build and Development Scripts (Bash) Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt Provides common npm scripts for managing the project, including installing dependencies, running in development mode with hot reloading for Chrome and Firefox, building for production, creating distribution zip files, and performing type checking. ```bash # Install dependencies npm install # Development mode with hot reload (Chrome) npm run dev # Development mode with hot reload (Firefox) npm run dev:firefox # Production build (Chrome) npm run build # Production build (Firefox) npm run build:firefox # Create distribution zip (Chrome) npm run zip # Create distribution zip (Firefox) npm run zip:firefox # Type checking without emit npm run compile ``` -------------------------------- ### Browser Storage API Usage (TypeScript) Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt Demonstrates using WXT's storage API for defining and managing persistent (local) and session-based storage items. It covers setting, getting, watching for changes, and removing values from storage. ```typescript import { storage } from '#imports'; // Local storage (persistent) const currentVoiceItem = storage.defineItem>('local:currentVoice'); const currentSettingsItem = storage.defineItem>('local:currentSettings'); const colorModeItem = storage.defineItem<'light' | 'dark'>('local:colorMode', { defaultValue: 'light' }); // Session storage (temporary) const textItem = storage.defineItem('session:text'); // Setting values await currentVoiceItem.setValue({ language: 'English', country: 'United States', voice: { name: 'Andrew', shortName: 'en-US-AndrewNeural' } }); await currentSettingsItem.setValue({ rate: 0, pitch: 0 }); await colorModeItem.setValue('dark'); await textItem.setValue('Hello world'); // Getting values const voice = await currentVoiceItem.getValue(); const settings = await currentSettingsItem.getValue(); const colorMode = await colorModeItem.getValue(); const text = await textItem.getValue(); // Watching for changes (reactive) const unwatch = textItem.watch((newValue) => { console.log('Text changed:', newValue); if (newValue) { // Process the new text generateAudio(newValue); } }); // Remove value await textItem.removeValue(); // Cleanup watcher unwatch(); ``` -------------------------------- ### Project Package Configuration (JSON) Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt Defines the project's metadata, version, and dependencies. It includes runtime dependencies like 'msedge-tts', 'react', and Material-UI components, as well as development dependencies such as 'typescript' and the 'wxt' framework. ```json { "name": "msedge-tts-extension", "version": "1.3.0", "type": "module", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.0.2", "@mui/material": "^7.0.2", "msedge-tts": "^2.0.2", "react": "^19.1.0", "react-dom": "^19.1.0", "xml-escape": "^1.1.0" }, "devDependencies": { "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@wxt-dev/module-react": "^1.1.3", "typescript": "^5.8.3", "vite-plugin-node-polyfills": "^0.23.0", "wxt": "^0.20.1" } } ``` -------------------------------- ### Settings Drawer for TTS Parameters (React/TypeScript) Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt A React drawer component, built with Material-UI, for adjusting advanced Text-to-Speech parameters like rate and pitch. It includes a back button, a title, and uses a CustomSlider component (assumed to be defined elsewhere) for input. It requires props for open state, toggle function, settings object, and a change handler. ```typescript // assets/components/TemporaryDrawer.tsx import Box from '@mui/material/Box'; import Drawer from '@mui/material/Drawer'; import { IconButton, Stack, Typography } from '@mui/material'; import { ArrowBackIosNew } from '@mui/icons-material'; import CustomSlider from './CustomSlider'; export default function TemporaryDrawer(props: any) { const { open, toggleDrawer, settings, handleSliderChange } = props; const DrawerContent = ( toggleDrawer(false)} > Additional Settings handleSliderChange(value, 'set_rate')} /> handleSliderChange(value, 'set_pitch')} /> ); return ( toggleDrawer(false)} slotProps={{ paper: { sx: { width: '100%' } } }} > {DrawerContent} ); } // Usage: // updateSettings(type, value)} // /> ``` -------------------------------- ### App Component: Manage TTS Workflow in TypeScript Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt The main App component in TypeScript manages the text-to-speech workflow. It utilizes React hooks for state management (useReducer, useEffect, useState), custom hooks for data fetching (useFetch) and TTS generation (useTTS), and integrates with local storage. It renders UI elements for voice selection, text input, and audio playback. Dependencies include React, Material-UI components, and custom hooks. ```typescript // assets/components/App.tsx (simplified structure) import { createContext, useReducer, useEffect, useRef, useState } from 'react'; import { Button, TextField } from '@mui/material'; import useFetch from '@/assets/custom hooks/useFetch'; import useTTS from '@/assets/custom hooks/useTTS'; import { storage } from '#imports'; const currentVoiceItem = storage.defineItem>('local:currentVoice'); const currentSettingsItem = storage.defineItem>('local:currentSettings'); const textItem = storage.defineItem('session:text'); const voiceReducer = (state: any, action: any) => { let currentVoice; switch (action.type) { case 'select_language': currentVoice = { language: action.value, country: '', voice: '' }; break; case 'select_country': currentVoice = { ...state, country: action.value, voice: '' }; break; case 'select_voice': currentVoice = { ...state, voice: action.value }; break; case 'set_voice': currentVoice = { ...action.value }; break; default: return state; } currentVoiceItem.setValue(currentVoice); return { ...currentVoice }; }; function App() { const [voiceState, voiceDispatch] = useReducer(voiceReducer, { language: '', country: '', voice: null, }); const [text, textDispatch] = useReducer(textReducer, ''); const [settings, settingsDispatch] = useReducer(settingsReducer, { rate: 0, pitch: 0, }); const [voicesLoading, voicesError, languages, countries, voices] = useFetch(voiceState); const { audioUrl, audioLoading, audioError, generateAudio } = useTTS(); const handleSubmit = () => { generateAudio(text, voiceState.voice.shortName, settings); }; // Watch for text changes from context menu useEffect(() => { const unwatch = textItem.watch((newValue) => { if (!newValue) return; textDispatch({ type: 'set_text', value: newValue }); textItem.removeValue(); if (voiceState && voiceState.voice) { generateAudio(newValue!, voiceState.voice.shortName, settings); } }); }, []); return ( <> voiceDispatch({ type: 'select_language', value })} /> voiceDispatch({ type: 'select_country', value })} isDisabled={!voiceState.language.length} /> voiceDispatch({ type: 'select_voice', value: voices[value] })} isDisabled={!voiceState.country.length} /> textDispatch({ type: 'set_text', value: e.target.value })} fullWidth label='Text' placeholder='Enter text to be spoken' multiline minRows={3} maxRows={20} /> ); } ``` -------------------------------- ### Manifest Configuration for Chrome and Firefox Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt Defines browser-specific manifest configurations using WXT framework, specifying permissions, commands, and version details for Chrome (Manifest V3) and Firefox (Manifest V2). It also configures Vite plugins and module settings. ```typescript // wxt.config.ts import { defineConfig, UserManifest } from 'wxt'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; const perBrowserManifest: Record> = { chrome: { 3: { version: "1.3.0", permissions: [ "storage", "contextMenus", "sidePanel", "scripting", "activeTab", ], commands: { "speak-selection": { suggested_key: { default: "Ctrl+Shift+S", mac: "Command+Shift+S" }, description: "Speak selected text" } }, minimum_chrome_version: "116", }, }, firefox: { 2: { version: "1.3.0", permissions: [ "storage", "contextMenus", "scripting", "activeTab", ], commands: { "speak-selection": { suggested_key: { default: "Ctrl+Shift+F", mac: "Command+Shift+F" }, description: "Speak selected text" } }, }, } }; export default defineConfig({ modules: ['@wxt-dev/module-react'], vite: () => ({ plugins: [nodePolyfills() as any], optimizeDeps: { include: ['@mui/icons-material', '@emotion/styled', '@emotion/react', '@mui/material'], } }), manifest: ({ browser, manifestVersion }) => ({ name: "MS Edge TTS (Text to Speech)", author: "https://github.com/yacine-bens", homepage_url: "https://github.com/yacine-bens/MsEdge-TTS-Extension.git", action: { "default_title": "MsEdge TTS" }, ...perBrowserManifest[browser][manifestVersion], }) }); ``` -------------------------------- ### TypeScript TTS Hook for MS Edge TTS Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt Implements a React hook (`useTTS`) to generate audio using the Microsoft Edge TTS service. It handles text-to-speech conversion, audio stream processing, and returns a Blob URL for the generated audio. Dependencies include 'msedge-tts' and 'xml-escape'. It takes text, voice, and settings (rate, pitch) as input and outputs an audio URL, loading state, and error state. ```typescript // assets/custom hooks/useTTS.tsx import { useState } from "react"; import { MsEdgeTTS, OUTPUT_FORMAT } from "msedge-tts"; import xmlescape from 'xml-escape'; const DEFAULT_VOICE = 'en-US-AndrewNeural'; export default function useTTS() { const [audioUrl, setAudioUrl] = useState(''); const [audioLoading, setAudioLoading] = useState(false); const [audioError, setAudioError] = useState(null); const generateAudio = async (text: string, voice: string, settings: Record) => { setAudioLoading(true); setAudioError(null); try { const url = await getAudioUrl(text, voice, settings); setAudioUrl(url); } catch (e) { setAudioError(true); } finally { setAudioLoading(false); } }; return { audioUrl, audioLoading, audioError, generateAudio }; } const getAudioUrl = async (text: string, voice: string, settings: Record) => { const tts = new MsEdgeTTS(); await tts.setMetadata( voice || DEFAULT_VOICE, OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3 ); return await new Promise(resolve => { const escapedText = xmlescape(text); const { audioStream: readable } = tts.toStream(escapedText, { pitch: settings.pitch + '%', rate: settings.rate + '%', volume: 100 }); let data64 = Buffer.from([]); readable.on('data', data => { data64 = Buffer.concat([data64, data]); }); readable.on('end', async () => { const blob = new Blob([data64], { type: 'audio/mpeg' }); if (blob.size) { resolve(URL.createObjectURL(blob)); } }); }); }; // Usage example: // const { audioUrl, audioLoading, audioError, generateAudio } = useTTS(); // await generateAudio("Hello world", "en-US-AndrewNeural", { rate: 0, pitch: 0 }); // // audioUrl will contain the blob URL for the audio element ``` -------------------------------- ### Voice Data Fetching Hook in TypeScript Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt A custom React hook (`useFetch`) designed to fetch and manage voice data hierarchy. It handles loading states, errors, and populates lists of languages, countries, and voices based on user selections. Dependencies include React hooks (`useReducer`, `useEffect`), a custom `storage` utility, and a local `voices` data object. It returns loading state, error object, languages, countries, and voices. ```typescript import { useReducer, useEffect } from "react"; import { storage } from "#imports"; import voices from "./voices"; type State = { data: Record; loading: boolean; error: any; languages: string[]; countries: string[]; voices: Record[]; }; type Action = | { type: "SET_LANGUAGES"; payload: Record } | { type: "SET_COUNTRIES"; payload: Record } | { type: "SET_VOICES"; payload: Record } | { type: "SET_LOADING"; payload: boolean } | { type: "SET_ERROR"; payload: any }; const initialState: State = { data: {}, loading: false, error: null, languages: [], countries: [], voices: [], }; const reducer = (state: State, action: Action): State => { switch (action.type) { case "SET_LANGUAGES": const data = action.payload.voices; const languages = Object.keys(data).sort(); if (action.payload.currentVoice && action.payload.currentVoice.language) { const countries = Object.keys(data[action.payload.currentVoice.language]).sort(); if (action.payload.currentVoice.country) { const voices = data[action.payload.currentVoice.language][action.payload.currentVoice.country]; return { ...state, data, languages, countries, voices }; } return { ...state, data, languages, countries } } return { ...state, data: action.payload.voices, languages }; case "SET_COUNTRIES": if (!state.data[action.payload.language]) return state; return { ...state, countries: Object.keys(state.data[action.payload.language]).sort() }; case "SET_VOICES": if (!state.data[action.payload.language] || !state.data[action.payload.language][action.payload.country]) return state; return { ...state, voices: state.data[action.payload.language][action.payload.country] }; case "SET_LOADING": return { ...state, loading: action.payload }; case "SET_ERROR": return { ...state, error: action.payload }; default: return state; } }; export default function useFetch(dependency: Record) { const [state, dispatch] = useReducer(reducer, initialState); useEffect(() => { const fetchData = async () => { dispatch({ type: "SET_LOADING", payload: true }); try { const voices = await getVoices(); const currentVoice = await storage.getItem('local:currentVoice'); dispatch({ type: "SET_LANGUAGES", payload: { voices, currentVoice } }); } catch (err) { dispatch({ type: "SET_ERROR", payload: err }); } finally { dispatch({ type: "SET_LOADING", payload: false }); } }; fetchData(); }, []); useEffect(() => { if (dependency.language) { if (dependency.country) { dispatch({ type: "SET_VOICES", payload: dependency }); } else { dispatch({ type: "SET_COUNTRIES", payload: dependency }); } } }, [dependency]); return [state.loading, state.error, state.languages, state.countries, state.voices]; } const getVoices = async () => { return voices; }; // Usage example: // const [voicesLoading, voicesError, languages, countries, voices] = useFetch(voiceState); // // languages: ["English", "Spanish", "French", ...] // // countries: ["United States", "United Kingdom", ...] (after language selected) // // voices: { "Andrew": { name: "Andrew", shortName: "en-US-AndrewNeural" }, ... } ``` -------------------------------- ### Background Service Worker for TTS Extension Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt Handles background tasks for the MS Edge TTS Extension, including context menu creation, keyboard shortcut listening, and triggering the text-to-speech UI. It manages session storage for text and interacts with browser APIs for UI display. ```typescript // entrypoints/background.ts import { storage } from "#imports"; export default defineBackground({ type: 'module', main: () => { if (import.meta.env.CHROME) { browser.sidePanel.setPanelBehavior({ openPanelOnActionClick: false }) .catch(e => console.log(e)); } // Create context menu on install/startup const onInstalled = async () => { browser.contextMenus.removeAll(() => { browser.contextMenus.create({ "id": "edgetts", "title": "Speak with MS-Edge TTS", "contexts": ["selection"] }); }); }; browser.runtime.onInstalled.addListener(onInstalled); browser.runtime.onStartup.addListener(onInstalled); // Handle text storage const handleTextToSpeech = async (text: string) => { if (!text || text.length === 0) return; const textStorage = storage.defineItem("session:text"); textStorage.setValue(text); }; // Open UI (side panel or popup) const openUI = async (tab: Browser.tabs.Tab, useSidePanel?: boolean) => { if (import.meta.env.CHROME) { if (useSidePanel) { browser.sidePanel.open({ tabId: tab.id! }); } else { browser.action.openPopup(); } } else if (import.meta.env.FIREFOX) { browser.browserAction.openPopup(); } } // Context menu handler browser.contextMenus.onClicked.addListener(async (clickData, tab) => { if (clickData.menuItemId != "edgetts" || !clickData.selectionText) return; openUI(tab!, true); await handleTextToSpeech(clickData.selectionText); }); // Keyboard shortcut handler browser.commands.onCommand.addListener(async (command, tab) => { if (command === "speak-selection") { openUI(tab, false); const [{ result: text }] = await browser.scripting.executeScript({ target: { tabId: tab.id! }, func: () => window.getSelection()?.toString() || "" }); if (text) { handleTextToSpeech(text); } } }); } }); ``` -------------------------------- ### Autocomplete Component for Selections (React/TypeScript) Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt A reusable React autocomplete component for selecting languages, countries, or voices. It integrates with Material-UI's Autocomplete and TextField, and includes a loading state indicator using CircularProgress. Dependencies include React, Material-UI components, and Fragment. ```typescript // assets/components/SelectAutocomplete.tsx import { Fragment, useState } from 'react'; import { CircularProgress, TextField, Autocomplete } from '@mui/material'; export default function SelectAutocomplete(props: any) { const { options, label, onChange, value, isDisabled, loading } = props; const [open, setOpen] = useState(false); return ( setOpen(true)} onClose={() => setOpen(false)} onChange={(e, value) => onChange(e, value)} value={value || null} disabled={isDisabled} disablePortal options={options} loading={loading} fullWidth renderInput={(params) => {loading && open ? : null} {params.InputProps.endAdornment} ) }} /> } /> ); } // Usage: // console.log(value)} // isDisabled={false} // /> ``` -------------------------------- ### Voice Data Structure Definition (TypeScript) Source: https://context7.com/yacine-bens/msedge-tts-extension/llms.txt Defines the structure for mapping languages, countries, and specific neural voices. It uses nested objects to organize voices and includes the 'shortName' property following a specific pattern. This structure is crucial for selecting and utilizing different text-to-speech voices within the extension. ```typescript export default { "English": { "United States": { "Ava": { "name": "Ava", "shortName": "en-US-AvaNeural" }, "Andrew": { "name": "Andrew", "shortName": "en-US-AndrewNeural" }, "Emma": { "name": "Emma", "shortName": "en-US-EmmaNeural" }, "Brian": { "name": "Brian", "shortName": "en-US-BrianNeural" } // ... more voices }, "United Kingdom": { "Libby": { "name": "Libby", "shortName": "en-GB-LibbyNeural" }, "Ryan": { "name": "Ryan", "shortName": "en-GB-RyanNeural" } // ... more voices } // ... more countries }, "Spanish": { "Spain": { "Alvaro": { "name": "Alvaro", "shortName": "es-ES-AlvaroNeural" } // ... more voices }, "Mexico": { "Dalia": { "name": "Dalia", "shortName": "es-MX-DaliaNeural" } // ... more voices } // ... more countries } // ... 100+ languages and dialects supported } // Structure pattern: voices[language][country][voiceName] = { name, shortName } // The shortName follows the pattern: {languageCode}-{countryCode}-{voiceName}Neural ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.