Skip to content

Commit 23d7fae

Browse files
committed
Add api settings to settings modal + fix README.md
1 parent b978e8c commit 23d7fae

File tree

8 files changed

+384
-79
lines changed

8 files changed

+384
-79
lines changed

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,13 @@ https://github.com/user-attachments/assets/7a3457ba-feda-4939-928a-cb587b1c0884
3636

3737
1. Clone the repository:
3838
```bash
39-
git clone [repository-url]
40-
cd openreader-webui
39+
git clone https://github.com/richardr1126/OpenReader-WebUI.git
40+
cd OpenReader-WebUI
4141
```
4242

4343
2. Install dependencies:
4444
```bash
4545
npm install
46-
# or
47-
yarn install
4846
```
4947

5048
3. Set up environment variables:

src/app/pdf/[id]/page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import dynamic from 'next/dynamic';
44
import { usePDF } from '@/contexts/PDFContext';
5-
import { useParams, useRouter } from 'next/navigation';
5+
import { useParams } from 'next/navigation';
66
import Link from 'next/link';
77
import { useEffect, useState } from 'react';
88
import { PDFSkeleton } from '@/components/PDFSkeleton';
@@ -21,7 +21,6 @@ const PDFViewer = dynamic(
2121
export default function PDFViewerPage() {
2222
const { id } = useParams();
2323
const { getDocument } = usePDF();
24-
const router = useRouter();
2524
const { setText, stop } = useTTS();
2625
const [document, setDocument] = useState<{ name: string; data: Blob } | null>(null);
2726
const [error, setError] = useState<string | null>(null);
@@ -86,7 +85,7 @@ export default function PDFViewerPage() {
8685
</svg>
8786
Documents
8887
</Link>
89-
<h1 className="mr-2 text-xl font-semibold text-foreground">
88+
<h1 className="mr-2 text-md font-semibold text-foreground">
9089
{isLoading ? 'Loading...' : document?.name}
9190
</h1>
9291
</div>

src/app/providers.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
import { PDFProvider } from '@/contexts/PDFContext';
44
import { TTSProvider } from '@/contexts/TTSContext';
55
import { ThemeProvider } from '@/contexts/ThemeContext';
6+
import { ConfigProvider } from '@/contexts/ConfigContext';
67
import { ReactNode } from 'react';
78

89
export function Providers({ children }: { children: ReactNode }) {
910
return (
1011
<ThemeProvider>
11-
<TTSProvider>
12-
<PDFProvider>
13-
{children}
14-
</PDFProvider>
15-
</TTSProvider>
12+
<ConfigProvider>
13+
<TTSProvider>
14+
<PDFProvider>
15+
{children}
16+
</PDFProvider>
17+
</TTSProvider>
18+
</ConfigProvider>
1619
</ThemeProvider>
1720
);
1821
}

src/components/SettingsModal.tsx

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use client';
22

3-
import { Fragment } from 'react';
3+
import { Fragment, useState, useEffect } from 'react';
44
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild, Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/react';
55
import { useTheme } from '@/contexts/ThemeContext';
6+
import { useConfig } from '@/contexts/ConfigContext';
67

78
interface SettingsModalProps {
89
isOpen: boolean;
@@ -17,8 +18,16 @@ const themes = [
1718

1819
export function SettingsModal({ isOpen, setIsOpen }: SettingsModalProps) {
1920
const { theme, setTheme } = useTheme();
21+
const { apiKey, baseUrl, updateConfig } = useConfig();
22+
const [localApiKey, setLocalApiKey] = useState(apiKey);
23+
const [localBaseUrl, setLocalBaseUrl] = useState(baseUrl);
2024
const selectedTheme = themes.find(t => t.id === theme) || themes[0];
2125

26+
useEffect(() => {
27+
setLocalApiKey(apiKey);
28+
setLocalBaseUrl(baseUrl);
29+
}, [apiKey, baseUrl]);
30+
2231
return (
2332
<Transition appear show={isOpen} as={Fragment}>
2433
<Dialog as="div" className="relative z-50" onClose={() => setIsOpen(false)}>
@@ -122,19 +131,59 @@ export function SettingsModal({ isOpen, setIsOpen }: SettingsModalProps) {
122131
</div>
123132
</Listbox>
124133
</div>
134+
135+
<div className="space-y-2">
136+
<label className="block text-sm font-medium text-foreground">OpenAI API Key</label>
137+
<input
138+
type="password"
139+
value={localApiKey}
140+
onChange={(e) => setLocalApiKey(e.target.value)}
141+
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm focus:outline-none focus:ring-2 focus:ring-accent"
142+
/>
143+
</div>
144+
145+
<div className="space-y-2">
146+
<label className="block text-sm font-medium text-foreground">OpenAI API Base URL</label>
147+
<input
148+
type="text"
149+
value={localBaseUrl}
150+
onChange={(e) => setLocalBaseUrl(e.target.value)}
151+
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm focus:outline-none focus:ring-2 focus:ring-accent"
152+
/>
153+
</div>
125154
</div>
126155
</div>
127156

128-
<div className="mt-6 flex justify-end">
157+
<div className="mt-6 flex justify-end space-x-3">
129158
<button
130159
type="button"
131160
className="inline-flex justify-center rounded-lg bg-accent px-4 py-2 text-sm
132161
font-medium text-white hover:bg-accent/90 focus:outline-none
133162
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
134163
transition-colors"
135-
onClick={() => setIsOpen(false)}
164+
onClick={async () => {
165+
await updateConfig({
166+
apiKey: localApiKey,
167+
baseUrl: localBaseUrl
168+
});
169+
setIsOpen(false);
170+
}}
171+
>
172+
Save
173+
</button>
174+
<button
175+
type="button"
176+
className="inline-flex justify-center rounded-lg bg-background px-4 py-2 text-sm
177+
font-medium text-foreground hover:bg-background/90 focus:outline-none
178+
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
179+
transition-colors"
180+
onClick={() => {
181+
setLocalApiKey(apiKey);
182+
setLocalBaseUrl(baseUrl);
183+
setIsOpen(false);
184+
}}
136185
>
137-
Close
186+
Cancel
138187
</button>
139188
</div>
140189
</DialogPanel>

src/contexts/ConfigContext.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use client';
2+
3+
import { createContext, useContext, useEffect, useState } from 'react';
4+
import { getItem, indexedDBService, setItem } from '@/services/indexedDB';
5+
6+
interface ConfigContextType {
7+
apiKey: string;
8+
baseUrl: string;
9+
updateConfig: (newConfig: Partial<{ apiKey: string; baseUrl: string }>) => Promise<void>;
10+
isLoading: boolean;
11+
isDBReady: boolean;
12+
}
13+
14+
const ConfigContext = createContext<ConfigContextType | undefined>(undefined);
15+
16+
export function ConfigProvider({ children }: { children: React.ReactNode }) {
17+
const [apiKey, setApiKey] = useState<string>('');
18+
const [baseUrl, setBaseUrl] = useState<string>('');
19+
const [isLoading, setIsLoading] = useState(true);
20+
const [isDBReady, setIsDBReady] = useState(false);
21+
22+
useEffect(() => {
23+
const initializeDB = async () => {
24+
try {
25+
setIsLoading(true);
26+
await indexedDBService.init();
27+
setIsDBReady(true);
28+
29+
// Now load config
30+
const cachedApiKey = await getItem('apiKey');
31+
const cachedBaseUrl = await getItem('baseUrl');
32+
33+
if (cachedApiKey) {
34+
console.log('Cached API key found:', cachedApiKey);
35+
}
36+
if (cachedBaseUrl) {
37+
console.log('Cached base URL found:', cachedBaseUrl);
38+
}
39+
40+
// If not in cache, use env variables
41+
const defaultApiKey = process.env.NEXT_PUBLIC_OPENAI_API_KEY || '';
42+
const defaultBaseUrl = process.env.NEXT_PUBLIC_OPENAI_API_BASE || '';
43+
44+
// Set the values
45+
setApiKey(cachedApiKey || defaultApiKey);
46+
setBaseUrl(cachedBaseUrl || defaultBaseUrl);
47+
48+
// If not in cache, save to cache
49+
if (!cachedApiKey) {
50+
await setItem('apiKey', defaultApiKey);
51+
}
52+
if (!cachedBaseUrl) {
53+
await setItem('baseUrl', defaultBaseUrl);
54+
}
55+
56+
} catch (error) {
57+
console.error('Error initializing:', error);
58+
} finally {
59+
setIsLoading(false);
60+
}
61+
};
62+
63+
initializeDB();
64+
}, []);
65+
66+
const updateConfig = async (newConfig: Partial<{ apiKey: string; baseUrl: string }>) => {
67+
try {
68+
if (newConfig.apiKey !== undefined) {
69+
await setItem('apiKey', newConfig.apiKey);
70+
setApiKey(newConfig.apiKey);
71+
}
72+
if (newConfig.baseUrl !== undefined) {
73+
await setItem('baseUrl', newConfig.baseUrl);
74+
setBaseUrl(newConfig.baseUrl);
75+
}
76+
} catch (error) {
77+
console.error('Error updating config:', error);
78+
throw error;
79+
}
80+
};
81+
82+
return (
83+
<ConfigContext.Provider value={{ apiKey, baseUrl, updateConfig, isLoading, isDBReady }}>
84+
{children}
85+
</ConfigContext.Provider>
86+
);
87+
}
88+
89+
export function useConfig() {
90+
const context = useContext(ConfigContext);
91+
if (context === undefined) {
92+
throw new Error('useConfig must be used within a ConfigProvider');
93+
}
94+
return context;
95+
}

src/contexts/PDFContext.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import nlp from 'compromise';
1717

1818
// Add the correct type import
1919
import type { TextContent, TextItem } from 'pdfjs-dist/types/src/display/api';
20+
import { useConfig } from '@/contexts/ConfigContext';
2021

2122
// Set worker from public directory
2223
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.mjs';
@@ -43,28 +44,30 @@ interface PDFContextType {
4344
const PDFContext = createContext<PDFContextType | undefined>(undefined);
4445

4546
export function PDFProvider({ children }: { children: ReactNode }) {
47+
const { isDBReady } = useConfig();
4648
const [documents, setDocuments] = useState<PDFDocument[]>([]);
4749
const [isLoading, setIsLoading] = useState(true);
4850
const [error, setError] = useState<string | null>(null);
4951

50-
// Load documents from IndexedDB on mount
52+
// Load documents from IndexedDB once DB is ready
5153
useEffect(() => {
5254
const loadDocuments = async () => {
55+
if (!isDBReady) return;
56+
5357
try {
5458
setError(null);
55-
await indexedDBService.init();
5659
const docs = await indexedDBService.getAllDocuments();
5760
setDocuments(docs);
5861
} catch (error) {
5962
console.error('Failed to load documents:', error);
60-
setError('Failed to initialize document storage. Please check if your browser supports IndexedDB.');
63+
setError('Failed to load documents. Please try again.');
6164
} finally {
6265
setIsLoading(false);
6366
}
6467
};
6568

6669
loadDocuments();
67-
}, []);
70+
}, [isDBReady]);
6871

6972
// Add a new document to IndexedDB
7073
const addDocument = useCallback(async (file: File): Promise<string> => {

0 commit comments

Comments
 (0)