Skip to content

Commit b87b833

Browse files
committed
Rely on TTS API for splitting
1 parent 1db906f commit b87b833

File tree

5 files changed

+153
-221
lines changed

5 files changed

+153
-221
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ services:
103103

104104
3. Configure the environment:
105105
```bash
106-
cp .env.template .env
106+
cp template.env .env
107107
# Edit .env with your configuration settings
108108
```
109109
> Note: The base URL for the TTS API should be accessible and relative to the Next.js server

src/app/api/tts/route.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export async function POST(req: NextRequest) {
2020
// Initialize OpenAI client with abort signal
2121
const openai = new OpenAI({
2222
apiKey: openApiKey,
23-
baseURL: openApiBaseUrl || 'https://api.openai.com/v1',
23+
baseURL: openApiBaseUrl,
2424
});
2525

2626
// Request audio from OpenAI and pass along the abort signal
@@ -33,15 +33,15 @@ export async function POST(req: NextRequest) {
3333

3434
// Get the audio data as array buffer
3535
// This will also be aborted if the client cancels
36-
const arrayBuffer = await response.arrayBuffer();
36+
const stream = response.body;
3737

3838
// Return audio data with appropriate headers
39-
return new NextResponse(arrayBuffer);
39+
return new NextResponse(stream);
4040
} catch (error) {
4141
// Check if this was an abort error
4242
if (error instanceof Error && error.name === 'AbortError') {
4343
console.log('TTS request aborted by client');
44-
return new Response(null, { status: 499 }); // Use 499 status for client closed request
44+
return new NextResponse(null, { status: 499 }); // Use 499 status for client closed request
4545
}
4646

4747
console.error('Error generating TTS:', error);

src/components/DocumentSettings.tsx

Lines changed: 110 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
'use client';
22

33
import { Fragment, useState, useRef } from 'react';
4-
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild, Listbox, ListboxButton, ListboxOptions, ListboxOption, Button } from '@headlessui/react';
4+
import { Dialog, DialogPanel, Transition, TransitionChild, Listbox, ListboxButton, ListboxOptions, ListboxOption, Button } from '@headlessui/react';
55
import { useConfig, ViewType } from '@/contexts/ConfigContext';
66
import { ChevronUpDownIcon, CheckIcon } from '@/components/icons/Icons';
77
import { useEPUB } from '@/contexts/EPUBContext';
88

9+
const isDev = process.env.NEXT_PUBLIC_NODE_ENV !== 'production' || process.env.NODE_ENV == null;
10+
911
interface DocViewSettingsProps {
1012
isOpen: boolean;
1113
setIsOpen: (isOpen: boolean) => void;
@@ -93,136 +95,126 @@ export function DocumentSettings({ isOpen, setIsOpen, epub }: DocViewSettingsPro
9395
leaveTo="opacity-0 scale-95"
9496
>
9597
<DialogPanel className="w-full max-w-md transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition-all">
96-
<DialogTitle
97-
as="h3"
98-
className="text-lg font-semibold leading-6 text-foreground"
99-
>
100-
View Settings
101-
</DialogTitle>
102-
<div className="mt-4">
103-
<div className="space-y-4">
104-
{!epub && <div className="space-y-2">
105-
<label className="block text-sm font-medium text-foreground">Mode</label>
106-
<Listbox
107-
value={selectedView}
108-
onChange={(newView) => updateConfigKey('viewType', newView.id as ViewType)}
98+
<div className="space-y-4">
99+
{isDev && <div className="space-y-2 pb-2">
100+
{!isGenerating ? (
101+
<Button
102+
type="button"
103+
className="w-full inline-flex justify-center rounded-lg bg-accent px-4 py-2 text-sm
104+
font-medium text-background hover:opacity-95 focus:outline-none
105+
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
106+
transform transition-transform duration-200 ease-in-out hover:scale-[1.04]"
107+
onClick={handleStartGeneration}
109108
>
110-
<div className="relative z-10">
111-
<ListboxButton className="relative w-full cursor-pointer rounded-lg bg-background py-2 pl-3 pr-10 text-left text-foreground shadow-sm focus:outline-none focus:ring-2 focus:ring-accent transform transition-transform duration-200 ease-in-out hover:scale-[1.01] hover:text-accent">
112-
<span className="block truncate">{selectedView.name}</span>
113-
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
114-
<ChevronUpDownIcon className="h-5 w-5 text-muted" />
115-
</span>
116-
</ListboxButton>
117-
<Transition
118-
as={Fragment}
119-
leave="transition ease-in duration-100"
120-
leaveFrom="opacity-100"
121-
leaveTo="opacity-0"
109+
Export to Audiobook (experimental)
110+
</Button>
111+
) : (
112+
<div className="space-y-2">
113+
<div className="w-full bg-background rounded-lg overflow-hidden">
114+
<div
115+
className="h-2 bg-accent transition-all duration-300 ease-in-out"
116+
style={{ width: `${progress}%` }}
117+
/>
118+
</div>
119+
<div className="flex justify-between items-center text-sm text-muted">
120+
<span>{Math.round(progress)}% complete</span>
121+
<Button
122+
type="button"
123+
className="inline-flex justify-center rounded-lg px-2.5 py-1 text-sm
124+
font-medium text-foreground hover:text-accent focus:outline-none
125+
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
126+
transform transition-transform duration-200 ease-in-out hover:scale-[1.02]"
127+
onClick={handleCancel}
122128
>
123-
<ListboxOptions className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-background py-1 shadow-lg ring-1 ring-black/5 focus:outline-none">
124-
{viewTypes.map((view) => (
125-
<ListboxOption
126-
key={view.id}
127-
className={({ active }) =>
128-
`relative cursor-pointer select-none py-2 pl-10 pr-4 ${active ? 'bg-accent/10 text-accent' : 'text-foreground'
129-
}`
130-
}
131-
value={view}
132-
>
133-
{({ selected }) => (
134-
<>
135-
<span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>
136-
{view.name}
137-
</span>
138-
{selected ? (
139-
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-accent">
140-
<CheckIcon className="h-5 w-5" />
141-
</span>
142-
) : null}
143-
</>
144-
)}
145-
</ListboxOption>
146-
))}
147-
</ListboxOptions>
148-
</Transition>
129+
Cancel and download
130+
</Button>
149131
</div>
150-
</Listbox>
151-
{selectedView.id === 'scroll' && (
152-
<p className="text-sm text-warning pt-2">
153-
Note: Continuous scroll may perform poorly for larger documents.
154-
</p>
155-
)}
156-
</div>}
132+
</div>
133+
)}
134+
</div>}
135+
{!epub && <div className="space-y-2">
136+
<label className="block text-sm font-medium text-foreground">Mode</label>
137+
<Listbox
138+
value={selectedView}
139+
onChange={(newView) => updateConfigKey('viewType', newView.id as ViewType)}
140+
>
141+
<div className="relative z-10">
142+
<ListboxButton className="relative w-full cursor-pointer rounded-lg bg-background py-2 pl-3 pr-10 text-left text-foreground shadow-sm focus:outline-none focus:ring-2 focus:ring-accent transform transition-transform duration-200 ease-in-out hover:scale-[1.01] hover:text-accent">
143+
<span className="block truncate">{selectedView.name}</span>
144+
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
145+
<ChevronUpDownIcon className="h-5 w-5 text-muted" />
146+
</span>
147+
</ListboxButton>
148+
<Transition
149+
as={Fragment}
150+
leave="transition ease-in duration-100"
151+
leaveFrom="opacity-100"
152+
leaveTo="opacity-0"
153+
>
154+
<ListboxOptions className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-background py-1 shadow-lg ring-1 ring-black/5 focus:outline-none">
155+
{viewTypes.map((view) => (
156+
<ListboxOption
157+
key={view.id}
158+
className={({ active }) =>
159+
`relative cursor-pointer select-none py-2 pl-10 pr-4 ${active ? 'bg-accent/10 text-accent' : 'text-foreground'
160+
}`
161+
}
162+
value={view}
163+
>
164+
{({ selected }) => (
165+
<>
166+
<span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>
167+
{view.name}
168+
</span>
169+
{selected ? (
170+
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-accent">
171+
<CheckIcon className="h-5 w-5" />
172+
</span>
173+
) : null}
174+
</>
175+
)}
176+
</ListboxOption>
177+
))}
178+
</ListboxOptions>
179+
</Transition>
180+
</div>
181+
</Listbox>
182+
{selectedView.id === 'scroll' && (
183+
<p className="text-sm text-warning pt-2">
184+
Note: Continuous scroll may perform poorly for larger documents.
185+
</p>
186+
)}
187+
</div>}
188+
<div className="space-y-2">
189+
<label className="flex items-center space-x-2">
190+
<input
191+
type="checkbox"
192+
checked={skipBlank}
193+
onChange={(e) => updateConfigKey('skipBlank', e.target.checked)}
194+
className="form-checkbox h-4 w-4 text-accent rounded border-muted"
195+
/>
196+
<span className="text-sm font-medium text-foreground">Skip blank pages</span>
197+
</label>
198+
<p className="text-sm text-muted pl-6">
199+
Automatically skip pages with no text content
200+
</p>
201+
</div>
202+
{epub && (
157203
<div className="space-y-2">
158204
<label className="flex items-center space-x-2">
159205
<input
160206
type="checkbox"
161-
checked={skipBlank}
162-
onChange={(e) => updateConfigKey('skipBlank', e.target.checked)}
207+
checked={epubTheme}
208+
onChange={(e) => updateConfigKey('epubTheme', e.target.checked)}
163209
className="form-checkbox h-4 w-4 text-accent rounded border-muted"
164210
/>
165-
<span className="text-sm font-medium text-foreground">Skip blank pages</span>
211+
<span className="text-sm font-medium text-foreground">Use theme (experimental)</span>
166212
</label>
167213
<p className="text-sm text-muted pl-6">
168-
Automatically skip pages with no text content
214+
Apply the current app theme to the EPUB viewer
169215
</p>
170216
</div>
171-
{epub && (
172-
<>
173-
<div className="space-y-2">
174-
<label className="flex items-center space-x-2">
175-
<input
176-
type="checkbox"
177-
checked={epubTheme}
178-
onChange={(e) => updateConfigKey('epubTheme', e.target.checked)}
179-
className="form-checkbox h-4 w-4 text-accent rounded border-muted"
180-
/>
181-
<span className="text-sm font-medium text-foreground">Use theme (experimental)</span>
182-
</label>
183-
<p className="text-sm text-muted pl-6">
184-
Apply the current app theme to the EPUB viewer
185-
</p>
186-
</div>
187-
<div className="mt-4 space-y-2">
188-
{!isGenerating ? (
189-
<Button
190-
type="button"
191-
className="w-full inline-flex justify-center rounded-lg bg-accent px-4 py-2 text-sm
192-
font-medium text-background hover:opacity-95 focus:outline-none
193-
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
194-
transform transition-transform duration-200 ease-in-out hover:scale-[1.04]"
195-
onClick={handleStartGeneration}
196-
>
197-
Export to Audiobook (.mp3)
198-
</Button>
199-
) : (
200-
<div className="space-y-2">
201-
<div className="w-full bg-background rounded-lg overflow-hidden">
202-
<div
203-
className="h-2 bg-accent transition-all duration-300 ease-in-out"
204-
style={{ width: `${progress}%` }}
205-
/>
206-
</div>
207-
<div className="flex justify-between items-center text-sm text-muted">
208-
<span>{Math.round(progress)}% complete</span>
209-
<Button
210-
type="button"
211-
className="inline-flex justify-center rounded-lg px-2.5 py-1 text-sm
212-
font-medium text-foreground hover:text-accent focus:outline-none
213-
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
214-
transform transition-transform duration-200 ease-in-out hover:scale-[1.02]"
215-
onClick={handleCancel}
216-
>
217-
Cancel and download
218-
</Button>
219-
</div>
220-
</div>
221-
)}
222-
</div>
223-
</>
224-
)}
225-
</div>
217+
)}
226218
</div>
227219

228220
<div className="mt-3 flex justify-end">

0 commit comments

Comments
 (0)