+
+
diff --git a/src/contexts/ConfigContext.tsx b/src/contexts/ConfigContext.tsx
new file mode 100644
index 0000000..44a4c18
--- /dev/null
+++ b/src/contexts/ConfigContext.tsx
@@ -0,0 +1,77 @@
+'use client';
+
+import { createContext, useContext, useEffect, useState } from 'react';
+import { getItem, setItem } from '@/services/indexedDB';
+
+interface ConfigContextType {
+ apiKey: string;
+ baseUrl: string;
+ updateConfig: (newConfig: Partial<{ apiKey: string; baseUrl: string }>) => Promise
;
+}
+
+const ConfigContext = createContext(undefined);
+
+export function ConfigProvider({ children }: { children: React.ReactNode }) {
+ const [apiKey, setApiKey] = useState('');
+ const [baseUrl, setBaseUrl] = useState('');
+
+ useEffect(() => {
+ const loadConfig = async () => {
+ try {
+ // Try to load from IndexedDB first
+ const cachedApiKey = await getItem('apiKey');
+ const cachedBaseUrl = await getItem('baseUrl');
+
+ // If not in cache, use env variables
+ const defaultApiKey = process.env.NEXT_PUBLIC_OPENAI_API_KEY || '';
+ const defaultBaseUrl = process.env.NEXT_PUBLIC_OPENAI_API_BASE || '';
+
+ // Set the values
+ setApiKey(cachedApiKey || defaultApiKey);
+ setBaseUrl(cachedBaseUrl || defaultBaseUrl);
+
+ // If we used default values and they're not empty, store them in IndexedDB
+ if (!cachedApiKey && defaultApiKey) {
+ await setItem('apiKey', defaultApiKey);
+ }
+ if (!cachedBaseUrl && defaultBaseUrl) {
+ await setItem('baseUrl', defaultBaseUrl);
+ }
+ } catch (error) {
+ console.error('Error loading config:', error);
+ }
+ };
+
+ loadConfig();
+ }, []);
+
+ const updateConfig = async (newConfig: Partial<{ apiKey: string; baseUrl: string }>) => {
+ try {
+ if (newConfig.apiKey !== undefined) {
+ await setItem('apiKey', newConfig.apiKey);
+ setApiKey(newConfig.apiKey);
+ }
+ if (newConfig.baseUrl !== undefined) {
+ await setItem('baseUrl', newConfig.baseUrl);
+ setBaseUrl(newConfig.baseUrl);
+ }
+ } catch (error) {
+ console.error('Error updating config:', error);
+ throw error;
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useConfig() {
+ const context = useContext(ConfigContext);
+ if (context === undefined) {
+ throw new Error('useConfig must be used within a ConfigProvider');
+ }
+ return context;
+}
\ No newline at end of file
diff --git a/src/services/indexedDB.ts b/src/services/indexedDB.ts
index 5315235..bcf06e7 100644
--- a/src/services/indexedDB.ts
+++ b/src/services/indexedDB.ts
@@ -1,6 +1,7 @@
const DB_NAME = 'openreader-db';
-const DB_VERSION = 1;
-const STORE_NAME = 'pdf-documents';
+const DB_VERSION = 2;
+const PDF_STORE_NAME = 'pdf-documents';
+const CONFIG_STORE_NAME = 'config';
export interface PDFDocument {
id: string;
@@ -10,6 +11,11 @@ export interface PDFDocument {
data: Blob;
}
+export interface Config {
+ key: string;
+ value: string;
+}
+
class IndexedDBService {
private db: IDBDatabase | null = null;
@@ -37,25 +43,31 @@ class IndexedDBService {
request.onupgradeneeded = (event) => {
console.log('Upgrading IndexedDB schema...');
const db = (event.target as IDBOpenDBRequest).result;
- if (!db.objectStoreNames.contains(STORE_NAME)) {
+
+ if (!db.objectStoreNames.contains(PDF_STORE_NAME)) {
console.log('Creating PDF documents store...');
- db.createObjectStore(STORE_NAME, { keyPath: 'id' });
+ db.createObjectStore(PDF_STORE_NAME, { keyPath: 'id' });
+ }
+
+ if (!db.objectStoreNames.contains(CONFIG_STORE_NAME)) {
+ console.log('Creating config store...');
+ db.createObjectStore(CONFIG_STORE_NAME, { keyPath: 'key' });
}
};
});
}
+ // PDF Document Methods
async addDocument(document: PDFDocument): Promise {
if (!this.db) {
- console.log('Database not initialized, initializing now...');
await this.init();
}
return new Promise((resolve, reject) => {
try {
console.log('Adding document to IndexedDB:', document.name);
- const transaction = this.db!.transaction([STORE_NAME], 'readwrite');
- const store = transaction.objectStore(STORE_NAME);
+ const transaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
+ const store = transaction.objectStore(PDF_STORE_NAME);
const request = store.put(document);
request.onerror = (event) => {
@@ -77,15 +89,14 @@ class IndexedDBService {
async getDocument(id: string): Promise {
if (!this.db) {
- console.log('Database not initialized, initializing now...');
await this.init();
}
return new Promise((resolve, reject) => {
try {
console.log('Fetching document:', id);
- const transaction = this.db!.transaction([STORE_NAME], 'readonly');
- const store = transaction.objectStore(STORE_NAME);
+ const transaction = this.db!.transaction([PDF_STORE_NAME], 'readonly');
+ const store = transaction.objectStore(PDF_STORE_NAME);
const request = store.get(id);
request.onerror = (event) => {
@@ -107,15 +118,14 @@ class IndexedDBService {
async getAllDocuments(): Promise {
if (!this.db) {
- console.log('Database not initialized, initializing now...');
await this.init();
}
return new Promise((resolve, reject) => {
try {
console.log('Fetching all documents');
- const transaction = this.db!.transaction([STORE_NAME], 'readonly');
- const store = transaction.objectStore(STORE_NAME);
+ const transaction = this.db!.transaction([PDF_STORE_NAME], 'readonly');
+ const store = transaction.objectStore(PDF_STORE_NAME);
const request = store.getAll();
request.onerror = (event) => {
@@ -137,15 +147,14 @@ class IndexedDBService {
async removeDocument(id: string): Promise {
if (!this.db) {
- console.log('Database not initialized, initializing now...');
await this.init();
}
return new Promise((resolve, reject) => {
try {
console.log('Removing document:', id);
- const transaction = this.db!.transaction([STORE_NAME], 'readwrite');
- const store = transaction.objectStore(STORE_NAME);
+ const transaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
+ const store = transaction.objectStore(PDF_STORE_NAME);
const request = store.delete(id);
request.onerror = (event) => {
@@ -164,6 +173,138 @@ class IndexedDBService {
}
});
}
+
+ // Config Methods
+ async setConfigItem(key: string, value: string): Promise {
+ if (!this.db) {
+ await this.init();
+ }
+
+ return new Promise((resolve, reject) => {
+ try {
+ console.log('Setting config item:', key);
+ const transaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite');
+ const store = transaction.objectStore(CONFIG_STORE_NAME);
+ const request = store.put({ key, value });
+
+ request.onerror = (event) => {
+ const error = (event.target as IDBRequest).error;
+ console.error('Error setting config item:', error);
+ reject(error);
+ };
+
+ transaction.oncomplete = () => {
+ console.log('Config item set successfully:', key);
+ resolve();
+ };
+ } catch (error) {
+ console.error('Error in setConfigItem transaction:', error);
+ reject(error);
+ }
+ });
+ }
+
+ async getConfigItem(key: string): Promise {
+ if (!this.db) {
+ await this.init();
+ }
+
+ return new Promise((resolve, reject) => {
+ try {
+ console.log('Fetching config item:', key);
+ const transaction = this.db!.transaction([CONFIG_STORE_NAME], 'readonly');
+ const store = transaction.objectStore(CONFIG_STORE_NAME);
+ const request = store.get(key);
+
+ request.onerror = (event) => {
+ const error = (event.target as IDBRequest).error;
+ console.error('Error fetching config item:', error);
+ reject(error);
+ };
+
+ request.onsuccess = () => {
+ const result = request.result as Config | undefined;
+ console.log('Config item fetch result:', result ? 'found' : 'not found');
+ resolve(result ? result.value : null);
+ };
+ } catch (error) {
+ console.error('Error in getConfigItem transaction:', error);
+ reject(error);
+ }
+ });
+ }
+
+ async getAllConfig(): Promise> {
+ if (!this.db) {
+ await this.init();
+ }
+
+ return new Promise((resolve, reject) => {
+ try {
+ console.log('Fetching all config items');
+ const transaction = this.db!.transaction([CONFIG_STORE_NAME], 'readonly');
+ const store = transaction.objectStore(CONFIG_STORE_NAME);
+ const request = store.getAll();
+
+ request.onerror = (event) => {
+ const error = (event.target as IDBRequest).error;
+ console.error('Error fetching all config items:', error);
+ reject(error);
+ };
+
+ request.onsuccess = () => {
+ const result = request.result as Config[];
+ const config: Record = {};
+ result.forEach((item) => {
+ config[item.key] = item.value;
+ });
+ console.log('Retrieved config items count:', result.length);
+ resolve(config);
+ };
+ } catch (error) {
+ console.error('Error in getAllConfig transaction:', error);
+ reject(error);
+ }
+ });
+ }
+
+ async removeConfigItem(key: string): Promise {
+ if (!this.db) {
+ await this.init();
+ }
+
+ return new Promise((resolve, reject) => {
+ try {
+ console.log('Removing config item:', key);
+ const transaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite');
+ const store = transaction.objectStore(CONFIG_STORE_NAME);
+ const request = store.delete(key);
+
+ request.onerror = (event) => {
+ const error = (event.target as IDBRequest).error;
+ console.error('Error removing config item:', error);
+ reject(error);
+ };
+
+ transaction.oncomplete = () => {
+ console.log('Config item removed successfully:', key);
+ resolve();
+ };
+ } catch (error) {
+ console.error('Error in removeConfigItem transaction:', error);
+ reject(error);
+ }
+ });
+ }
}
export const indexedDBService = new IndexedDBService();
+
+// Helper functions for the ConfigContext
+export async function getItem(key: string): Promise {
+ return indexedDBService.getConfigItem(key);
+}
+
+export async function setItem(key: string, value: string): Promise {
+ return indexedDBService.setConfigItem(key, value);
+}
\ No newline at end of file