From 43435166f8c7493fd8fcad9b161233f93ff7675e Mon Sep 17 00:00:00 2001 From: Anthony <47945770+Tony0410@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:46:02 +0800 Subject: [PATCH] feat: Update dependencies and add new voice - Updates project dependencies to latest versions for improved performance and security. - Adds a new voice option, 'Aoede', to the available voices. - Modifies the voice change handler to force re-generation of future audio segments for the selected article. - Updates Vite configuration to align with newer Vite versions and load environment variables correctly. - Adjusts TypeScript configuration for a more modern setup. - Removes unnecessary configuration from Nginx file. --- App.tsx | 38 ++++++++++++++++++++++++++++++++++++-- Dockerfile | 0 constants.ts | 3 ++- index.html | 8 +++++--- nginx.conf | 1 + package.json | 17 +++++++++-------- tsconfig.json | 36 ++++++++++++++---------------------- types.ts | 1 + vite.config.ts | 31 ++++++++++++------------------- 9 files changed, 80 insertions(+), 55 deletions(-) create mode 100644 Dockerfile create mode 100644 nginx.conf diff --git a/App.tsx b/App.tsx index 7b117cf..23e77d4 100644 --- a/App.tsx +++ b/App.tsx @@ -91,6 +91,8 @@ export default function App() { const segmentsToBuffer = article.segments.slice(currentIndex, currentIndex + 5); for (const seg of segmentsToBuffer) { + // Only process if we don't have audio URL, and it's not currently loading. + // This handles cases where we cleared the URL to force regeneration. if (!seg.audioUrl && !seg.isLoading && !seg.hasError) { processSegmentAudio(article.id, seg.id, seg.text, playerState.selectedVoice); } @@ -99,6 +101,38 @@ export default function App() { // -- Handlers -- + const handleVoiceChange = useCallback((newVoice: VoiceName) => { + setPlayerState(prev => ({ ...prev, selectedVoice: newVoice })); + + // Force flush future buffer so new voice is applied immediately + setQueue(prevQueue => prevQueue.map(article => { + // If this is the currently active article + if (article.id === playerState.currentArticleId) { + return { + ...article, + segments: article.segments.map((seg, idx) => { + // Keep the current segment (and past ones) to avoid cutting off mid-speech abruptly + if (idx <= article.currentSegmentIndex) { + return seg; + } + // Invalidate all future segments + return { ...seg, audioUrl: undefined, isLoading: false, hasError: false }; + }) + }; + } + // For inactive articles, invalidate everything + return { + ...article, + segments: article.segments.map(seg => ({ + ...seg, + audioUrl: undefined, + isLoading: false, + hasError: false + })) + }; + })); + }, [playerState.currentArticleId]); + const handleAddUrl = async () => { if (!inputUrl.trim()) return; @@ -373,8 +407,8 @@ export default function App() {
setPlayerState(prev => ({ ...prev, selectedVoice: v }))} - disabled={playerState.isPlaying} + onVoiceChange={handleVoiceChange} + // Removed disabled prop to allow switching while playing />