feat(tts): add TTS buttons to PlanView and FilesView markdown preview with configurable input mode (#1443)
Add TTS controls to markdown preview surfaces so users can listen to plans and markdown files directly from the preview toolbar. Changes: - Add read-aloud / stop buttons to PlanView markdown preview. - Add read-aloud / stop buttons to FilesView markdown preview for markdown files. - Respect the existing showMessageTTSButtons preference in both preview views. - Add a persisted ttsInputMode setting with sanitized/raw modes. - Keep sanitized mode as the default for backward compatibility. - Allow raw markdown only for server TTS providers that can handle markdown. - Always use sanitized text for browser and macOS say fallback paths. - Add Voice Settings controls for TTS input mode. - Add i18n keys for the new preview buttons and setting labels. - Merge latest main and preserve newer FilesView toolbar/editor changes. Validation: - bun test packages/ui/src/stores/useConfigStore.test.ts packages/ui/src/components/chat/message/parts/ToolPart.test.ts packages/web/server/lib/tts/routes.test.js - bun run type-check - bun run lint - git diff --check --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
a6bf73b245
commit
ba95e13b36
@@ -35,6 +35,7 @@ export function useMessageTTS(): UseMessageTTSReturn {
|
||||
const openaiCompatibleUrl = useConfigStore((state) => state.openaiCompatibleUrl);
|
||||
const openaiCompatibleTtsModel = useConfigStore((state) => state.openaiCompatibleTtsModel);
|
||||
const showMessageTTSButtons = useConfigStore((state) => state.showMessageTTSButtons);
|
||||
const ttsInputMode = useConfigStore((state) => state.ttsInputMode);
|
||||
|
||||
const isServerProvider = voiceProvider === 'openai' || voiceProvider === 'openai-compatible';
|
||||
const shouldCheckOpenAIAvailability = showMessageTTSButtons && isServerProvider;
|
||||
@@ -64,7 +65,9 @@ export function useMessageTTS(): UseMessageTTSReturn {
|
||||
setIsPlaying(true);
|
||||
|
||||
try {
|
||||
const textToSpeak = sanitizeForTTS(text);
|
||||
const shouldUseRaw = ttsInputMode === 'raw' && isServerProvider;
|
||||
const sanitizedText = sanitizeForTTS(text);
|
||||
const textToSpeak = shouldUseRaw ? text : sanitizedText;
|
||||
|
||||
if (isServerProvider && isServerTTSAvailable) {
|
||||
const voice = voiceProvider === 'openai-compatible' ? openaiCompatibleVoice : openaiVoice;
|
||||
@@ -83,7 +86,7 @@ export function useMessageTTS(): UseMessageTTSReturn {
|
||||
});
|
||||
} else if (voiceProvider === 'say' && isSayTTSAvailable) {
|
||||
const wordsPerMinute = Math.round(100 + (speechRate - 0.5) * 200);
|
||||
await speakSayTTS(textToSpeak, {
|
||||
await speakSayTTS(sanitizedText, {
|
||||
voice: sayVoice,
|
||||
rate: wordsPerMinute,
|
||||
onEnd: () => setIsPlaying(false),
|
||||
@@ -94,7 +97,7 @@ export function useMessageTTS(): UseMessageTTSReturn {
|
||||
await browserVoiceService.waitForVoices();
|
||||
await browserVoiceService.resumeAudioContext();
|
||||
await browserVoiceService.speakText(
|
||||
textToSpeak,
|
||||
sanitizedText,
|
||||
navigator.language || 'en-US',
|
||||
() => setIsPlaying(false),
|
||||
{
|
||||
@@ -123,6 +126,7 @@ export function useMessageTTS(): UseMessageTTSReturn {
|
||||
openaiCompatibleTtsModel,
|
||||
isServerTTSAvailable,
|
||||
isSayTTSAvailable,
|
||||
ttsInputMode,
|
||||
speakServerTTS,
|
||||
speakSayTTS,
|
||||
stop,
|
||||
|
||||
Reference in New Issue
Block a user