feat: 默认使用IndexDB
This commit is contained in:
parent
29e11f8877
commit
823d6c15a7
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue