feat: 默认使用IndexDB

This commit is contained in:
肖应宇 2026-03-13 10:08:50 +08:00
parent 29e11f8877
commit 823d6c15a7
1 changed files with 109 additions and 53 deletions

View File

@ -71,6 +71,16 @@ class AuthManager {
// ---------------- 后端存储实现 ---------------- // ---------------- 后端存储实现 ----------------
class BackendStorage { class BackendStorage {
// 回退到本地存储的方法
_fallbackTo(method, ...args) {
const localMethod = window[method];
if (typeof localMethod === 'function') {
return localMethod.apply(null, args);
}
console.warn(`[BackendStorage] No local fallback for ${method}`);
return Promise.resolve();
}
async fetchAPI(endpoint, options = {}) { async fetchAPI(endpoint, options = {}) {
const response = await fetch(`${API_BASE_URL}${endpoint}`, { const response = await fetch(`${API_BASE_URL}${endpoint}`, {
...options, ...options,
@ -94,112 +104,148 @@ class BackendStorage {
// 用户设置 // 用户设置
async loadSettings() { async loadSettings() {
try { try {
if (!AuthManager.isAuthenticated()) return this._getDefaultSettings(); if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('loadSettings');
}
const data = await this.fetchAPI('/user/settings'); const data = await this.fetchAPI('/user/settings');
return data; return data;
} catch (error) { } catch (error) {
console.error('Failed to load settings from backend:', error); console.error('Failed to load settings from backend:', error);
return this._getDefaultSettings(); return this._fallbackTo('loadSettings');
} }
} }
async saveSettings(settings) { async saveSettings(settings) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('saveSettings', settings);
}
await this.fetchAPI('/user/settings', { await this.fetchAPI('/user/settings', {
method: 'PUT', method: 'PUT',
body: JSON.stringify(settings) body: JSON.stringify(settings)
}); });
} catch (error) { } catch (error) {
console.error('Failed to save settings to backend:', error); console.error('Failed to save settings to backend:', error);
throw error; return this._fallbackTo('saveSettings', settings);
} }
} }
// API Keys // API Keys
async loadModelKeys(provider) { async loadModelKeys(provider) {
try { try {
if (!AuthManager.isAuthenticated()) return []; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('loadModelKeys', provider);
}
const keys = await this.fetchAPI(`/user/api-keys?provider=${provider}`); const keys = await this.fetchAPI(`/user/api-keys?provider=${provider}`);
return keys; return keys;
} catch (error) { } catch (error) {
console.error('Failed to load API keys:', error); console.error('Failed to load API keys:', error);
return []; return this._fallbackTo('loadModelKeys', provider);
} }
} }
async saveModelKeys(provider, keys) { async saveModelKeys(provider, keys) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('saveModelKeys', provider, keys);
}
await this.fetchAPI('/user/api-keys', { await this.fetchAPI('/user/api-keys', {
method: 'POST', method: 'POST',
body: JSON.stringify({ provider, keys }) body: JSON.stringify({ provider, keys })
}); });
} catch (error) { } catch (error) {
console.error('Failed to save API keys:', error); console.error('Failed to save API keys:', error);
throw error; return this._fallbackTo('saveModelKeys', provider, keys);
} }
} }
// 文档历史 // 文档历史
async saveResultToDB(document) { async saveResultToDB(document) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('saveResultToDB', document);
}
await this.fetchAPI('/documents', { method: 'POST', body: JSON.stringify(document) }); await this.fetchAPI('/documents', { method: 'POST', body: JSON.stringify(document) });
} catch (error) { } catch (error) {
console.error('Failed to save document:', error); console.error('Failed to save document to backend, falling back to local:', error);
throw error; return this._fallbackTo('saveResultToDB', document);
} }
} }
async getAllResultsFromDB() { async getAllResultsFromDB() {
try { try {
if (!AuthManager.isAuthenticated()) return []; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('getAllResultsFromDB');
}
const data = await this.fetchAPI('/documents'); const data = await this.fetchAPI('/documents');
return data.documents || []; return data.documents || [];
} catch (error) { } catch (error) {
console.error('Failed to load documents:', error); console.error('Failed to load documents from backend, falling back to local:', error);
return []; return this._fallbackTo('getAllResultsFromDB');
} }
} }
async getResultFromDB(id) { async getResultFromDB(id) {
try { try {
if (!AuthManager.isAuthenticated()) return null; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('getResultFromDB', id);
}
return await this.fetchAPI(`/documents/${id}`); return await this.fetchAPI(`/documents/${id}`);
} catch (error) { } catch (error) {
console.error('Failed to load document:', error); console.error('Failed to load document from backend, falling back to local:', error);
return null; return this._fallbackTo('getResultFromDB', id);
} }
} }
async deleteResultFromDB(id) { async deleteResultFromDB(id) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('deleteResultFromDB', id);
}
await this.fetchAPI(`/documents/${id}`, { method: 'DELETE' }); await this.fetchAPI(`/documents/${id}`, { method: 'DELETE' });
} catch (error) { } catch (error) {
console.error('Failed to delete document:', error); console.error('Failed to delete document from backend, falling back to local:', error);
throw error; return this._fallbackTo('deleteResultFromDB', id);
}
}
async clearAllResultsFromDB() {
try {
if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('clearAllResultsFromDB');
}
// 后端没有批量删除接口,逐个删除
const docs = await this.getAllResultsFromDB();
for (const doc of docs) {
await this.fetchAPI(`/documents/${doc.id}`, { method: 'DELETE' });
}
} catch (error) {
console.error('Failed to clear all documents from backend, falling back to local:', error);
return this._fallbackTo('clearAllResultsFromDB');
} }
} }
// 术语库 // 术语库
async loadGlossarySets() { async loadGlossarySets() {
try { try {
if (!AuthManager.isAuthenticated()) return {}; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('loadGlossarySets');
}
const glossaries = await this.fetchAPI('/user/glossaries'); const glossaries = await this.fetchAPI('/user/glossaries');
const sets = {}; const sets = {};
glossaries.forEach(g => { sets[g.id] = g; }); glossaries.forEach(g => { sets[g.id] = g; });
return sets; return sets;
} catch (error) { } catch (error) {
console.error('Failed to load glossaries:', error); console.error('Failed to load glossaries:', error);
return {}; return this._fallbackTo('loadGlossarySets');
} }
} }
async saveGlossarySets(sets) { async saveGlossarySets(sets) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('saveGlossarySets', sets);
}
// 批量保存(简化实现) // 批量保存(简化实现)
for (const [id, set] of Object.entries(sets)) { for (const [id, set] of Object.entries(sets)) {
if (set._isNew) { if (set._isNew) {
@ -210,35 +256,42 @@ class BackendStorage {
} }
} catch (error) { } catch (error) {
console.error('Failed to save glossaries:', error); console.error('Failed to save glossaries:', error);
throw error; return this._fallbackTo('saveGlossarySets', sets);
} }
} }
// 标注 // 标注
async saveAnnotationToDB(annotation) { async saveAnnotationToDB(annotation) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('saveAnnotationToDB', annotation);
}
await this.fetchAPI(`/documents/${annotation.documentId}/annotations`, { method: 'POST', body: JSON.stringify(annotation) }); await this.fetchAPI(`/documents/${annotation.documentId}/annotations`, { method: 'POST', body: JSON.stringify(annotation) });
} catch (error) { } catch (error) {
console.error('Failed to save annotation:', error); console.error('Failed to save annotation:', error);
throw error; return this._fallbackTo('saveAnnotationToDB', annotation);
} }
} }
async getAnnotationsForDocFromDB(docId) { async getAnnotationsForDocFromDB(docId) {
try { try {
if (!AuthManager.isAuthenticated()) return []; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('getAnnotationsForDocFromDB', docId);
}
return await this.fetchAPI(`/documents/${docId}/annotations`); return await this.fetchAPI(`/documents/${docId}/annotations`);
} catch (error) { } catch (error) {
console.error('Failed to load annotations:', error); console.error('Failed to load annotations:', error);
return []; return this._fallbackTo('getAnnotationsForDocFromDB', docId);
} }
} }
// 聊天历史 // 聊天历史
async loadChatHistory(docId) { async loadChatHistory(docId) {
try { try {
if (!AuthManager.isAuthenticated()) return []; if (!AuthManager.isAuthenticated()) {
// 聊天历史在本地存储中没有对应方法,返回空数组
return [];
}
const data = await this.fetchAPI(`/chat/${docId}/history`); const data = await this.fetchAPI(`/chat/${docId}/history`);
return data.messages || []; return data.messages || [];
} catch (error) { } catch (error) {
@ -249,82 +302,95 @@ class BackendStorage {
async saveChatMessage(docId, message) { async saveChatMessage(docId, message) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
// 聊天历史在本地存储中没有对应方法
return;
}
await this.fetchAPI(`/chat/${docId}/history`, { await this.fetchAPI(`/chat/${docId}/history`, {
method: 'POST', method: 'POST',
body: JSON.stringify(message) body: JSON.stringify(message)
}); });
} catch (error) { } catch (error) {
console.error('Failed to save chat message:', error); console.error('Failed to save chat message:', error);
throw error;
} }
} }
async clearChatHistory(docId) { async clearChatHistory(docId) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return;
}
await this.fetchAPI(`/chat/${docId}/history`, { method: 'DELETE' }); await this.fetchAPI(`/chat/${docId}/history`, { method: 'DELETE' });
} catch (error) { } catch (error) {
console.error('Failed to clear chat history:', error); console.error('Failed to clear chat history:', error);
throw error;
} }
} }
// 文献引用 // 文献引用
async loadReferences(docId) { async loadReferences(docId) {
try { try {
if (!AuthManager.isAuthenticated()) return []; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('loadReferences', docId) || [];
}
return await this.fetchAPI(`/references/${docId}/references`); return await this.fetchAPI(`/references/${docId}/references`);
} catch (error) { } catch (error) {
console.error('Failed to load references:', error); console.error('Failed to load references:', error);
return []; return this._fallbackTo('loadReferences', docId) || [];
} }
} }
async saveReference(docId, reference) { async saveReference(docId, reference) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('saveReference', docId, reference);
}
await this.fetchAPI(`/references/${docId}/references`, { await this.fetchAPI(`/references/${docId}/references`, {
method: 'POST', method: 'POST',
body: JSON.stringify(reference) body: JSON.stringify(reference)
}); });
} catch (error) { } catch (error) {
console.error('Failed to save reference:', error); console.error('Failed to save reference:', error);
throw error; return this._fallbackTo('saveReference', docId, reference);
} }
} }
async deleteReference(docId, refId) { async deleteReference(docId, refId) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('deleteReference', docId, refId);
}
await this.fetchAPI(`/references/${docId}/references/${refId}`, { method: 'DELETE' }); await this.fetchAPI(`/references/${docId}/references/${refId}`, { method: 'DELETE' });
} catch (error) { } catch (error) {
console.error('Failed to delete reference:', error); console.error('Failed to delete reference:', error);
throw error; return this._fallbackTo('deleteReference', docId, refId);
} }
} }
// Prompt Pool // Prompt Pool
async loadPromptPool() { async loadPromptPool() {
try { try {
if (!AuthManager.isAuthenticated()) return { prompts: [], healthConfig: null }; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('loadPromptPool');
}
return await this.fetchAPI('/prompt-pool'); return await this.fetchAPI('/prompt-pool');
} catch (error) { } catch (error) {
console.error('Failed to load prompt pool:', error); console.error('Failed to load prompt pool:', error);
return { prompts: [], healthConfig: null }; return this._fallbackTo('loadPromptPool');
} }
} }
async savePromptPool(data) { async savePromptPool(data) {
try { try {
if (!AuthManager.isAuthenticated()) return; if (!AuthManager.isAuthenticated()) {
return this._fallbackTo('savePromptPool', data);
}
await this.fetchAPI('/prompt-pool', { await this.fetchAPI('/prompt-pool', {
method: 'PUT', method: 'PUT',
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
} catch (error) { } catch (error) {
console.error('Failed to save prompt pool:', error); console.error('Failed to save prompt pool:', error);
throw error; return this._fallbackTo('savePromptPool', data);
} }
} }
@ -407,26 +473,16 @@ class StorageAdapterFactory {
// ---------------- 初始化与自动切换 ---------------- // ---------------- 初始化与自动切换 ----------------
function printBanner() { function printBanner() {
const logoStyle = 'font-size: 16px; font-weight: bold; color: #3b82f6;';
const infoStyle = 'font-size: 14px; color: #10b981;'; const infoStyle = 'font-size: 14px; color: #10b981;';
const modeStyle = 'font-size: 14px; font-weight: bold; color: #f59e0b;'; const modeStyle = 'font-size: 14px; font-weight: bold; color: #f59e0b;';
const borderStyle = 'color: #6366f1;'; const borderStyle = 'color: #6366f1;';
const linkStyle = 'font-size: 13px; color: #06b6d4; text-decoration: underline;'; const linkStyle = 'font-size: 13px; color: #06b6d4; text-decoration: underline;';
const logo = `
____ ____ __ __
| _ \\ __ _ _ __ ___ _ __ | __ ) _ _ _ __ _ __ ___ _ __ \\ \\/ /
| |_) / _\` | '_ \\ / _ \\ '__| | _ \\| | | | '__| '_ \\ / _ \\ '__| \\ /
| __/ (_| | |_) | __/ | | |_) | |_| | | | | | | __/ | / \\
|_| \\__,_| .__/ \\___|_| |____/ \\__,_|_| |_| |_|\\___|_| /_/\\_\\
|_|
`;
const mode = DEPLOYMENT_MODE === 'backend' ? '后端模式 (Backend Mode)' : '前端模式 (Frontend Mode)'; const mode = DEPLOYMENT_MODE === 'backend' ? '后端模式 (Backend Mode)' : '前端模式 (Frontend Mode)';
const storage = DEPLOYMENT_MODE === 'backend' ? 'Backend API + PostgreSQL' : 'localStorage + IndexedDB'; const storage = DEPLOYMENT_MODE === 'backend' ? 'Backend API + PostgreSQL' : 'localStorage + IndexedDB';
const auth = DEPLOYMENT_MODE === 'backend' ? 'JWT Authentication' : 'No Authentication'; const auth = DEPLOYMENT_MODE === 'backend' ? 'JWT Authentication' : 'No Authentication';
console.log('%c' + logo, logoStyle);
console.log('%c╔════════════════════════════════════════════════════════════╗', borderStyle); console.log('%c╔════════════════════════════════════════════════════════════╗', borderStyle);
console.log('%c║ 系统信息 / System Info ║', borderStyle); console.log('%c║ 系统信息 / System Info ║', borderStyle);
console.log('%c╠════════════════════════════════════════════════════════════╣', borderStyle); console.log('%c╠════════════════════════════════════════════════════════════╣', borderStyle);