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
/>