Skip to content

Commit

Permalink
Use server instead of client for open ai request + large refactors an…
Browse files Browse the repository at this point in the history
…d bug fixes
  • Loading branch information
richardr1126 committed Feb 21, 2025
1 parent 86be935 commit 9d6632d
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 303 deletions.
46 changes: 46 additions & 0 deletions src/app/api/tts/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NextRequest, NextResponse } from 'next/server';
import OpenAI from 'openai';

export async function POST(req: NextRequest) {
try {
// Get API credentials from headers
const openApiKey = req.headers.get('x-openai-key');
const openApiBaseUrl = req.headers.get('x-openai-base-url');
const { text, voice, speed } = await req.json();
console.log('Received TTS request:', text, voice, speed);

if (!openApiKey || !openApiBaseUrl) {
return NextResponse.json({ error: 'Missing API credentials' }, { status: 401 });
}

if (!text || !voice || !speed) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}

// Initialize OpenAI client
const openai = new OpenAI({
apiKey: openApiKey,
baseURL: openApiBaseUrl,
});

// Request audio from OpenAI
const response = await openai.audio.speech.create({
model: 'tts-1',
voice: voice as "alloy",
input: text,
speed: speed,
});

// Get the audio data as array buffer
const arrayBuffer = await response.arrayBuffer();

// Return audio data with appropriate headers
return new NextResponse(arrayBuffer);
} catch (error) {
console.error('Error generating TTS:', error);
return NextResponse.json(
{ error: 'Failed to generate audio' },
{ status: 500 }
);
}
}
34 changes: 34 additions & 0 deletions src/app/api/tts/voices/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from 'next/server';

const DEFAULT_VOICES = ['alloy', 'ash', 'coral', 'echo', 'fable', 'onyx', 'nova', 'sage', 'shimmer'];

export async function GET(req: NextRequest) {
try {
// Get API credentials from headers
const openApiKey = req.headers.get('x-openai-key');
const openApiBaseUrl = req.headers.get('x-openai-base-url');

if (!openApiKey || !openApiBaseUrl) {
return NextResponse.json({ error: 'Missing API credentials' }, { status: 401 });
}

// Request voices from OpenAI
const response = await fetch(`${openApiBaseUrl}/audio/voices`, {
headers: {
'Authorization': `Bearer ${openApiKey}`,
'Content-Type': 'application/json',
},
});

if (!response.ok) {
throw new Error('Failed to fetch voices');
}

const data = await response.json();
return NextResponse.json({ voices: data.voices || DEFAULT_VOICES });
} catch (error) {
console.error('Error fetching voices:', error);
// Return default voices on error
return NextResponse.json({ voices: DEFAULT_VOICES });
}
}
48 changes: 34 additions & 14 deletions src/components/EPUBViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface EPUBViewerProps {
export function EPUBViewer({ className = '' }: EPUBViewerProps) {
const { id } = useParams();
const { currDocData, currDocName, currDocPage, extractPageText } = useEPUB();
const { skipToLocation, registerLocationChangeHandler, setIsEPUB } = useTTS();
const { skipToLocation, registerLocationChangeHandler, setIsEPUB, pause } = useTTS();
const { epubTheme } = useConfig();
const bookRef = useRef<Book | null>(null);
const rendition = useRef<Rendition | undefined>(undefined);
Expand All @@ -35,9 +35,7 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
const containerRef = useRef<HTMLDivElement>(null);

const isEPUBSetOnce = useRef(false);
const isResizing = useRef(false);

useEPUBResize(containerRef, isResizing);
const { isResizing, setIsResizing, dimensions } = useEPUBResize(containerRef);

const handleLocationChanged = useCallback((location: string | number) => {
// Set the EPUB flag once the location changes
Expand Down Expand Up @@ -68,23 +66,45 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
setLastDocumentLocation(id as string, location.toString());
}

if (isResizing.current) {
skipToLocation(location, false);
isResizing.current = false;
} else {
skipToLocation(location, true);
}
skipToLocation(location);

locationRef.current = location;
extractPageText(bookRef.current, rendition.current);

}, [id, skipToLocation, extractPageText, setIsEPUB]);

// Load the initial location
const initialExtract = useCallback(() => {
if (!bookRef.current || !rendition.current?.location || isEPUBSetOnce.current) return;
extractPageText(bookRef.current, rendition.current, false);
}, [extractPageText]);

const checkResize = useCallback(() => {
if (isResizing && bookRef.current?.isOpen && rendition.current && isEPUBSetOnce.current) {
pause();
// Only extract text when we have dimensions, ensuring the resize is complete
if (dimensions) extractPageText(bookRef.current, rendition.current, true);
setIsResizing(false);

return true;
} else {
return false;
}
}, [isResizing, dimensions, pause, extractPageText]);

// Check for isResizing to pause TTS and re-extract text
useEffect(() => {
if (!bookRef.current || !rendition.current || isEPUBSetOnce.current) return;
if (checkResize()) return;

extractPageText(bookRef.current, rendition.current);
}, [extractPageText]);
// Load initial location when not resizing
initialExtract();
}, [checkResize, initialExtract]);

// Load the initial location
// useEffect(() => {
// if (!bookRef.current || !rendition.current || isEPUBSetOnce.current) return;

// extractPageText(bookRef.current, rendition.current, false);
// }, [extractPageText]);

// Register the location change handler
useEffect(() => {
Expand Down
7 changes: 4 additions & 3 deletions src/contexts/EPUBContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface EPUBContextType {
currDocText: string | undefined;
setCurrentDocument: (id: string) => Promise<void>;
clearCurrDoc: () => void;
extractPageText: (book: Book, rendition: Rendition) => Promise<string>;
extractPageText: (book: Book, rendition: Rendition, shouldPause?: boolean) => Promise<string>;
}

const EPUBContext = createContext<EPUBContextType | undefined>(undefined);
Expand Down Expand Up @@ -83,9 +83,10 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
* Extracts text content from the current EPUB page/location
* @param {Book} book - The EPUB.js Book instance
* @param {Rendition} rendition - The EPUB.js Rendition instance
* @param {boolean} shouldPause - Whether to pause TTS
* @returns {Promise<string>} The extracted text content
*/
const extractPageText = useCallback(async (book: Book, rendition: Rendition): Promise<string> => {
const extractPageText = useCallback(async (book: Book, rendition: Rendition, shouldPause = false): Promise<string> => {
try {
const { start, end } = rendition?.location;
if (!start?.cfi || !end?.cfi || !book || !book.isOpen || !rendition) return '';
Expand All @@ -95,7 +96,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
const range = await book.getRange(rangeCfi);
const textContent = range.toString().trim();

setTTSText(textContent);
setTTSText(textContent, shouldPause);
setCurrDocText(textContent);

return textContent;
Expand Down
11 changes: 7 additions & 4 deletions src/contexts/PDFContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,16 @@ export function PDFProvider({ children }: { children: ReactNode }) {
try {
if (!pdfDocument) return;
const text = await extractTextFromPDF(pdfDocument, currDocPage);
setCurrDocText(text);
setTTSText(text);

// Only update TTS text if the content has actually changed
// This prevents unnecessary resets of the sentence index
if (text !== currDocText || text === '') {
setCurrDocText(text);
setTTSText(text);
}
} catch (error) {
console.error('Error loading PDF text:', error);
}
}, [pdfDocument, currDocPage, setTTSText]);
}, [pdfDocument, currDocPage, setTTSText, currDocText]);

/**
* Effect hook to update document text when the page changes
Expand Down
Loading

0 comments on commit 9d6632d

Please # to comment.