Skip to content

Commit f464b05

Browse files
committed
Update API settings
1 parent 12b46cc commit f464b05

File tree

7 files changed

+257
-175
lines changed

7 files changed

+257
-175
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ OpenReader WebUI is a document reader with Text-to-Speech capabilities, offering
1717
- 💾 **Local-First Architecture**: Uses IndexedDB browser storage for documents
1818
- 🛜 **Optional Server-side documents**: Manually upload documents to the next backend for all users to download
1919
- 📖 **Read Along Experience**: Follow along with highlighted text as the TTS narrates
20-
- 📄 **Document formats**: EPUB, PDF, DOCX
20+
- 📄 **Document formats**: EPUB, PDF, DOCX (with libreoffice installed)
2121
- 🎧 **Audiobook Creation**: Create and export audiobooks from PDF and ePub files with m4b format
2222
- 📲 **Mobile Support**: Works on mobile devices, and can be added as a PWA web app
2323
- 🎨 **Customizable Experience**:
@@ -33,7 +33,7 @@ OpenReader WebUI is a document reader with Text-to-Speech capabilities, offering
3333
- [x] **Get PDFs on iOS 17 and below working 🤞**
3434
- [ ] **End-to-end Testing**: More playwright tests (in progress)
3535
- [ ] **More document formats**: .txt, .md
36-
- [ ] **Support more TTS APIs**: ElevenLabs, Ollama, etc.
36+
- [ ] **Support more TTS APIs**: ElevenLabs, etc.
3737
- [ ] **Accessibility Improvements**
3838

3939
## 🐳 Docker Quick Start

src/app/api/tts/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export async function POST(req: NextRequest) {
66
// Get API credentials from headers or fall back to environment variables
77
const openApiKey = req.headers.get('x-openai-key') || process.env.API_KEY || 'none';
88
const openApiBaseUrl = req.headers.get('x-openai-base-url') || process.env.API_BASE;
9-
const { text, voice, speed, format } = await req.json();
10-
console.log('Received TTS request:', text, voice, speed, format);
9+
const { text, voice, speed, format, model } = await req.json();
10+
console.log('Received TTS request:', text, voice, speed, format, model);
1111

1212
if (!openApiKey) {
1313
return NextResponse.json({ error: 'Missing OpenAI API key' }, { status: 401 });
@@ -25,7 +25,7 @@ export async function POST(req: NextRequest) {
2525

2626
// Request audio from OpenAI and pass along the abort signal
2727
const response = await openai.audio.speech.create({
28-
model: 'tts-1',
28+
model: model || 'tts-1',
2929
voice: voice as "alloy",
3030
input: text,
3131
speed: speed,

src/app/layout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export const metadata: Metadata = {
4040
},
4141
},
4242
verification: {
43-
// Add your verification codes if you have them
4443
google: "MJXyTudn1kgQF8EtGD-tsnAWev7Iawso9hEvqeGHB3U",
4544
},
4645
};

src/components/SettingsModal.tsx

Lines changed: 226 additions & 154 deletions
Large diffs are not rendered by default.

src/contexts/ConfigContext.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ type ConfigValues = {
1515
voice: string;
1616
skipBlank: boolean;
1717
epubTheme: boolean;
18-
textExtractionMargin: number;
1918
headerMargin: number;
2019
footerMargin: number;
2120
leftMargin: number;
2221
rightMargin: number;
22+
ttsModel: string;
2323
};
2424

2525
/** Interface defining the configuration context shape and functionality */
@@ -31,11 +31,11 @@ interface ConfigContextType {
3131
voice: string;
3232
skipBlank: boolean;
3333
epubTheme: boolean;
34-
textExtractionMargin: number;
3534
headerMargin: number;
3635
footerMargin: number;
3736
leftMargin: number;
3837
rightMargin: number;
38+
ttsModel: string;
3939
updateConfig: (newConfig: Partial<{ apiKey: string; baseUrl: string; viewType: ViewType }>) => Promise<void>;
4040
updateConfigKey: <K extends keyof ConfigValues>(key: K, value: ConfigValues[K]) => Promise<void>;
4141
isLoading: boolean;
@@ -59,11 +59,11 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
5959
const [voice, setVoice] = useState<string>('af_sarah');
6060
const [skipBlank, setSkipBlank] = useState<boolean>(true);
6161
const [epubTheme, setEpubTheme] = useState<boolean>(false);
62-
const [textExtractionMargin, setTextExtractionMargin] = useState<number>(0.07);
6362
const [headerMargin, setHeaderMargin] = useState<number>(0.07);
6463
const [footerMargin, setFooterMargin] = useState<number>(0.07);
6564
const [leftMargin, setLeftMargin] = useState<number>(0.07);
6665
const [rightMargin, setRightMargin] = useState<number>(0.07);
66+
const [ttsModel, setTTSModel] = useState<string>('tts-1');
6767

6868
const [isLoading, setIsLoading] = useState(true);
6969
const [isDBReady, setIsDBReady] = useState(false);
@@ -83,11 +83,11 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
8383
const cachedVoice = await getItem('voice');
8484
const cachedSkipBlank = await getItem('skipBlank');
8585
const cachedEpubTheme = await getItem('epubTheme');
86-
const cachedMargin = await getItem('textExtractionMargin');
8786
const cachedHeaderMargin = await getItem('headerMargin');
8887
const cachedFooterMargin = await getItem('footerMargin');
8988
const cachedLeftMargin = await getItem('leftMargin');
9089
const cachedRightMargin = await getItem('rightMargin');
90+
const cachedTTSModel = await getItem('ttsModel');
9191

9292
// Only set API key and base URL if they were explicitly saved by the user
9393
if (cachedApiKey) {
@@ -105,11 +105,11 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
105105
setVoice(cachedVoice || 'af_sarah');
106106
setSkipBlank(cachedSkipBlank === 'false' ? false : true);
107107
setEpubTheme(cachedEpubTheme === 'true');
108-
setTextExtractionMargin(parseFloat(cachedMargin || '0.07'));
109108
setHeaderMargin(parseFloat(cachedHeaderMargin || '0.07'));
110109
setFooterMargin(parseFloat(cachedFooterMargin || '0.07'));
111110
setLeftMargin(parseFloat(cachedLeftMargin || '0.07'));
112111
setRightMargin(parseFloat(cachedRightMargin || '0.07'));
112+
setTTSModel(cachedTTSModel || 'tts-1');
113113

114114
// Only save non-sensitive settings by default
115115
if (!cachedViewType) {
@@ -121,13 +121,13 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
121121
if (cachedEpubTheme === null) {
122122
await setItem('epubTheme', 'false');
123123
}
124-
if (cachedMargin === null) {
125-
await setItem('textExtractionMargin', '0.07');
126-
}
127124
if (cachedHeaderMargin === null) await setItem('headerMargin', '0.07');
128125
if (cachedFooterMargin === null) await setItem('footerMargin', '0.07');
129126
if (cachedLeftMargin === null) await setItem('leftMargin', '0.0');
130127
if (cachedRightMargin === null) await setItem('rightMargin', '0.0');
128+
if (cachedTTSModel === null) {
129+
await setItem('ttsModel', 'tts-1');
130+
}
131131

132132
} catch (error) {
133133
console.error('Error initializing:', error);
@@ -145,6 +145,7 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
145145
*/
146146
const updateConfig = async (newConfig: Partial<{ apiKey: string; baseUrl: string }>) => {
147147
try {
148+
setIsLoading(true);
148149
if (newConfig.apiKey !== undefined || newConfig.apiKey !== '') {
149150
// Only save API key to IndexedDB if it's different from env default
150151
await setItem('apiKey', newConfig.apiKey!);
@@ -169,6 +170,8 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
169170
} catch (error) {
170171
console.error('Error updating config:', error);
171172
throw error;
173+
} finally {
174+
setIsLoading(false);
172175
}
173176
};
174177

@@ -179,6 +182,7 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
179182
*/
180183
const updateConfigKey = async <K extends keyof ConfigValues>(key: K, value: ConfigValues[K]) => {
181184
try {
185+
setIsLoading(true);
182186
await setItem(key, value.toString());
183187
switch (key) {
184188
case 'apiKey':
@@ -202,9 +206,6 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
202206
case 'epubTheme':
203207
setEpubTheme(value as boolean);
204208
break;
205-
case 'textExtractionMargin':
206-
setTextExtractionMargin(value as number);
207-
break;
208209
case 'headerMargin':
209210
setHeaderMargin(value as number);
210211
break;
@@ -217,10 +218,15 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
217218
case 'rightMargin':
218219
setRightMargin(value as number);
219220
break;
221+
case 'ttsModel':
222+
setTTSModel(value as string);
223+
break;
220224
}
221225
} catch (error) {
222226
console.error(`Error updating config key ${key}:`, error);
223227
throw error;
228+
} finally {
229+
setIsLoading(false);
224230
}
225231
};
226232

@@ -233,11 +239,11 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
233239
voice,
234240
skipBlank,
235241
epubTheme,
236-
textExtractionMargin,
237242
headerMargin,
238243
footerMargin,
239244
leftMargin,
240245
rightMargin,
246+
ttsModel,
241247
updateConfig,
242248
updateConfigKey,
243249
isLoading,

src/contexts/TTSContext.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
9797
isLoading: configIsLoading,
9898
voiceSpeed,
9999
voice: configVoice,
100+
ttsModel: configTTSModel,
100101
updateConfigKey,
101102
skipBlank,
102103
} = useConfig();
@@ -138,6 +139,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
138139
const [activeHowl, setActiveHowl] = useState<Howl | null>(null);
139140
const [speed, setSpeed] = useState(voiceSpeed);
140141
const [voice, setVoice] = useState(configVoice);
142+
const [ttsModel, setTTSModel] = useState(configTTSModel);
141143

142144
// Track pending preload requests
143145
const preloadRequests = useRef<Map<string, Promise<string>>>(new Map());
@@ -386,8 +388,9 @@ export function TTSProvider({ children }: { children: ReactNode }) {
386388
if (!configIsLoading) {
387389
fetchVoices();
388390
updateVoiceAndSpeed();
391+
setTTSModel(configTTSModel);
389392
}
390-
}, [configIsLoading, openApiKey, openApiBaseUrl, updateVoiceAndSpeed, fetchVoices]);
393+
}, [configIsLoading, openApiKey, openApiBaseUrl, updateVoiceAndSpeed, fetchVoices, configTTSModel]);
391394

392395
/**
393396
* Generates and plays audio for the current sentence
@@ -423,6 +426,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
423426
text: sentence,
424427
voice: voice,
425428
speed: speed,
429+
model: ttsModel
426430
}),
427431
signal: controller.signal,
428432
});
@@ -466,7 +470,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
466470
});
467471
throw error;
468472
}
469-
}, [voice, speed, audioCache, openApiKey, openApiBaseUrl]);
473+
}, [voice, speed, ttsModel, audioCache, openApiKey, openApiBaseUrl]);
470474

471475
/**
472476
* Processes and plays the current sentence

tests/helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,6 @@ export async function setupTest(page: Page) {
7575
await page.waitForLoadState('networkidle');
7676

7777
// Click the "done" button to dismiss the welcome message
78-
await page.getByText('Done').click();
78+
await page.getByRole('tab', { name: '🔑 API' }).click();
79+
await page.getByRole('button', { name: 'Done' }).click();
7980
}

0 commit comments

Comments
 (0)