import React, { useState, useRef, useEffect } from 'react';
import { Box, IconButton, Tooltip } from '@mui/material';
import {
PlayArrow as PlayIcon,
Pause as PauseIcon,
Stop as StopIcon
} from '@mui/icons-material';
/**
* MessageContent Component
* Renders message text with inline word highlighting during TTS playback
*/
function MessageContent({ content, isPlaying, currentWordIndex, formatMarkdown }) {
if (!isPlaying || currentWordIndex < 0) {
// Normal rendering without highlighting
return (
);
}
// Parse content into words for highlighting
const words = content.split(/(\s+)/);
let wordCount = 0;
return (
{words.map((word, idx) => {
if (word.trim().length === 0) {
return {word};
}
const thisWordIndex = wordCount;
wordCount++;
const isCurrentWord = thisWordIndex === currentWordIndex;
const isPastWord = thisWordIndex < currentWordIndex;
return (
{word}
);
})}
);
}
/**
* InlineTTSControls Component
* Simple play/pause/stop controls for TTS - no progress bar, no loaders
*/
function InlineTTSControls({ isPlaying, onPlay, onPause, onStop }) {
return (
{!isPlaying ? (
) : (
<>
>
)}
);
}
/**
* useTTS Hook
* Handles Web Speech API with word boundary tracking
*/
export function useTTS(text) {
const [isPlaying, setIsPlaying] = useState(false);
const [isPaused, setIsPaused] = useState(false);
const [currentWordIndex, setCurrentWordIndex] = useState(-1);
const utteranceRef = useRef(null);
const wordsRef = useRef([]);
useEffect(() => {
if (text) {
wordsRef.current = text.split(/\s+/).filter(w => w.length > 0);
}
return () => {
if (window.speechSynthesis) {
window.speechSynthesis.cancel();
}
};
}, [text]);
const play = () => {
if (isPaused && window.speechSynthesis) {
window.speechSynthesis.resume();
setIsPlaying(true);
setIsPaused(false);
return;
}
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utteranceRef.current = utterance;
utterance.rate = 1.0;
utterance.pitch = 1.0;
// Track word boundaries
utterance.onboundary = (event) => {
if (event.name === 'word') {
const charIndex = event.charIndex;
let wordIdx = 0;
let charCount = 0;
for (let i = 0; i < wordsRef.current.length; i++) {
if (charCount >= charIndex) {
wordIdx = i;
break;
}
charCount += wordsRef.current[i].length + 1;
}
setCurrentWordIndex(wordIdx);
}
};
utterance.onstart = () => {
setIsPlaying(true);
setIsPaused(false);
setCurrentWordIndex(0);
};
utterance.onend = () => {
setIsPlaying(false);
setIsPaused(false);
setCurrentWordIndex(-1);
};
utterance.onerror = () => {
setIsPlaying(false);
setIsPaused(false);
setCurrentWordIndex(-1);
};
window.speechSynthesis.speak(utterance);
}
};
const pause = () => {
if (window.speechSynthesis) {
window.speechSynthesis.pause();
setIsPlaying(false);
setIsPaused(true);
}
};
const stop = () => {
if (window.speechSynthesis) {
window.speechSynthesis.cancel();
}
setIsPlaying(false);
setIsPaused(false);
setCurrentWordIndex(-1);
};
return {
isPlaying,
isPaused,
currentWordIndex,
play,
pause,
stop
};
}
export { MessageContent, InlineTTSControls };