mirror of
https://github.com/Tony0410/News-reader-pro.git
synced 2026-05-24 21:31:44 +08:00
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.
This commit is contained in:
38
App.tsx
38
App.tsx
@@ -91,6 +91,8 @@ export default function App() {
|
|||||||
const segmentsToBuffer = article.segments.slice(currentIndex, currentIndex + 5);
|
const segmentsToBuffer = article.segments.slice(currentIndex, currentIndex + 5);
|
||||||
|
|
||||||
for (const seg of segmentsToBuffer) {
|
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) {
|
if (!seg.audioUrl && !seg.isLoading && !seg.hasError) {
|
||||||
processSegmentAudio(article.id, seg.id, seg.text, playerState.selectedVoice);
|
processSegmentAudio(article.id, seg.id, seg.text, playerState.selectedVoice);
|
||||||
}
|
}
|
||||||
@@ -99,6 +101,38 @@ export default function App() {
|
|||||||
|
|
||||||
// -- Handlers --
|
// -- 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 () => {
|
const handleAddUrl = async () => {
|
||||||
if (!inputUrl.trim()) return;
|
if (!inputUrl.trim()) return;
|
||||||
|
|
||||||
@@ -373,8 +407,8 @@ export default function App() {
|
|||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<VoiceSelector
|
<VoiceSelector
|
||||||
selectedVoice={playerState.selectedVoice}
|
selectedVoice={playerState.selectedVoice}
|
||||||
onVoiceChange={(v) => setPlayerState(prev => ({ ...prev, selectedVoice: v }))}
|
onVoiceChange={handleVoiceChange}
|
||||||
disabled={playerState.isPlaying}
|
// Removed disabled prop to allow switching while playing
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowSettings(!showSettings)}
|
onClick={() => setShowSettings(!showSettings)}
|
||||||
|
|||||||
0
Dockerfile
Normal file
0
Dockerfile
Normal file
@@ -5,8 +5,9 @@ export const AVAILABLE_VOICES = [
|
|||||||
{ name: VoiceName.Puck, label: 'Puck (Standard American, Male)' },
|
{ name: VoiceName.Puck, label: 'Puck (Standard American, Male)' },
|
||||||
{ name: VoiceName.Charon, label: 'Charon (Deep, Authoritative Male)' },
|
{ name: VoiceName.Charon, label: 'Charon (Deep, Authoritative Male)' },
|
||||||
{ name: VoiceName.Kore, label: 'Kore (Soft, Calm Female)' },
|
{ name: VoiceName.Kore, label: 'Kore (Soft, Calm Female)' },
|
||||||
{ name: VoiceName.Fenrir, label: 'Fenrir (Energetic, Mid-Atlantic Male)' },
|
{ name: VoiceName.Fenrir, label: 'Fenrir (Mid-Atlantic/British Style, Male)' },
|
||||||
{ name: VoiceName.Zephyr, label: 'Zephyr (Clear, Professional Female)' },
|
{ name: VoiceName.Zephyr, label: 'Zephyr (Clear, Professional Female)' },
|
||||||
|
{ name: VoiceName.Aoede, label: 'Aoede (Confident, Professional Female)' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MIN_SPEED = 0.5;
|
export const MIN_SPEED = 0.5;
|
||||||
|
|||||||
@@ -20,15 +20,17 @@
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
|
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
|
||||||
"lucide-react": "https://aistudiocdn.com/lucide-react@^0.554.0",
|
"lucide-react": "https://aistudiocdn.com/lucide-react@^0.554.0",
|
||||||
"@google/genai": "https://aistudiocdn.com/@google/genai@^1.30.0",
|
"@google/genai": "https://aistudiocdn.com/@google/genai@^1.30.0",
|
||||||
"react": "https://aistudiocdn.com/react@^19.2.0",
|
"react": "https://aistudiocdn.com/react@^19.2.0",
|
||||||
"react/": "https://aistudiocdn.com/react@^19.2.0/",
|
"react/": "https://aistudiocdn.com/react@^19.2.0/",
|
||||||
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
|
"uuid": "https://aistudiocdn.com/uuid@^13.0.0",
|
||||||
"uuid": "https://aistudiocdn.com/uuid@^13.0.0"
|
"@vitejs/plugin-react": "https://aistudiocdn.com/@vitejs/plugin-react@^5.1.1",
|
||||||
|
"vite": "https://aistudiocdn.com/vite@^7.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
1
nginx.conf
Normal file
1
nginx.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<EFBFBD><EFBFBD><EFBFBD>z
|
||||||
15
package.json
15
package.json
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "newscaster-ai",
|
"name": "newscaster-ai",
|
||||||
"private": true,
|
"version": "1.0.0",
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -9,16 +8,18 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lucide-react": "^0.554.0",
|
|
||||||
"@google/genai": "^1.30.0",
|
"@google/genai": "^1.30.0",
|
||||||
|
"lucide-react": "^0.554.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.14.0",
|
"@types/react": "^19.0.0",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"typescript": "~5.8.2",
|
"@types/uuid": "^10.0.0",
|
||||||
"vite": "^6.2.0"
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "ES2020",
|
||||||
"experimentalDecorators": true,
|
"useDefineForClassFields": true,
|
||||||
"useDefineForClassFields": false,
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"lib": [
|
|
||||||
"ES2022",
|
|
||||||
"DOM",
|
|
||||||
"DOM.Iterable"
|
|
||||||
],
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": [
|
|
||||||
"node"
|
|
||||||
],
|
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"isolatedModules": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"allowJs": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"paths": {
|
|
||||||
"@/*": [
|
|
||||||
"./*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"noEmit": true
|
"resolveJsonModule": true,
|
||||||
}
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
1
types.ts
1
types.ts
@@ -5,6 +5,7 @@ export enum VoiceName {
|
|||||||
Kore = 'Kore',
|
Kore = 'Kore',
|
||||||
Fenrir = 'Fenrir',
|
Fenrir = 'Fenrir',
|
||||||
Zephyr = 'Zephyr',
|
Zephyr = 'Zephyr',
|
||||||
|
Aoede = 'Aoede',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PlaybackStatus {
|
export enum PlaybackStatus {
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
import path from 'path';
|
|
||||||
import { defineConfig, loadEnv } from 'vite';
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, '.', '');
|
// Load env file based on `mode` in the current working directory.
|
||||||
return {
|
const env = loadEnv(mode, (process as any).cwd(), '');
|
||||||
server: {
|
|
||||||
port: 3000,
|
return {
|
||||||
host: '0.0.0.0',
|
plugins: [react()],
|
||||||
},
|
define: {
|
||||||
plugins: [react()],
|
// This allows the app code to continue using process.env.API_KEY
|
||||||
define: {
|
// even though it is running in the browser.
|
||||||
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
'process.env.API_KEY': JSON.stringify(env.API_KEY)
|
||||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
}
|
||||||
},
|
};
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': path.resolve(__dirname, '.'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user