paper-burner/js/storage/reference-storage.js

449 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// js/storage/reference-storage.js
// 参考文献存储管理器 - 支持前端 localStorage 和后端 API 双模式
(function(global) {
'use strict';
const STORAGE_KEY_PREFIX = 'pbx_references_';
const STORAGE_KEY_INDEX = 'pbx_reference_index';
/**
* 判断是否为后端模式
*/
function isBackendMode() {
return window.storageAdapter && window.storageAdapter.isFrontendMode === false;
}
/**
* 参考文献存储类
*/
class ReferenceStorage {
constructor() {
this.cache = new Map();
this.loadIndex();
}
/**
* 加载索引(仅前端模式)
*/
loadIndex() {
if (isBackendMode()) {
this.documentIds = [];
this.metadata = {};
return;
}
try {
const indexData = localStorage.getItem(STORAGE_KEY_INDEX);
if (indexData) {
const index = JSON.parse(indexData);
this.documentIds = index.documentIds || [];
this.metadata = index.metadata || {};
} else {
this.documentIds = [];
this.metadata = {};
}
} catch (error) {
console.error('[ReferenceStorage] Failed to load index:', error);
this.documentIds = [];
this.metadata = {};
}
}
/**
* 保存索引(仅前端模式)
*/
saveIndex() {
if (isBackendMode()) return;
try {
const index = {
documentIds: this.documentIds,
metadata: this.metadata,
lastUpdated: new Date().toISOString()
};
localStorage.setItem(STORAGE_KEY_INDEX, JSON.stringify(index));
} catch (error) {
console.error('[ReferenceStorage] Failed to save index:', error);
}
}
/**
* 保存文档的参考文献
*/
async saveReferences(documentId, references, metadata = {}) {
try {
const data = {
documentId: documentId,
references: references,
metadata: {
...metadata,
totalCount: references.length,
savedAt: new Date().toISOString()
}
};
if (isBackendMode()) {
// 后端模式:批量保存引用
// 先清空旧的,再批量添加
const existing = await window.storageAdapter.loadReferences(documentId);
// TODO: 可优化为差异同步
for (const ref of references) {
await window.storageAdapter.saveReference(documentId, {
citationKey: ref.citationKey || `[${ref.index + 1}]`,
doi: ref.doi,
title: ref.title,
authors: ref.authors,
year: ref.year,
journal: ref.journal,
volume: ref.volume,
pages: ref.pages,
url: ref.url,
metadata: ref
});
}
} else {
// 前端模式localStorage
const key = STORAGE_KEY_PREFIX + documentId;
localStorage.setItem(key, JSON.stringify(data));
// 更新索引
if (!this.documentIds.includes(documentId)) {
this.documentIds.push(documentId);
}
this.metadata[documentId] = data.metadata;
this.saveIndex();
}
// 更新缓存
this.cache.set(documentId, data);
console.log(`[ReferenceStorage] Saved ${references.length} references for document ${documentId}`);
return true;
} catch (error) {
console.error('[ReferenceStorage] Failed to save references:', error);
return false;
}
}
/**
* 加载文档的参考文献
*/
async loadReferences(documentId) {
// 先检查缓存
if (this.cache.has(documentId)) {
return this.cache.get(documentId);
}
try {
if (isBackendMode()) {
// 后端模式:从 API 加载
const backendRefs = await window.storageAdapter.loadReferences(documentId);
const data = {
documentId: documentId,
references: backendRefs.map((ref, idx) => ({
index: idx,
citationKey: ref.citationKey,
doi: ref.doi,
title: ref.title,
authors: ref.authors,
year: ref.year,
journal: ref.journal,
volume: ref.volume,
pages: ref.pages,
url: ref.url,
...(ref.metadata || {})
})),
metadata: {
totalCount: backendRefs.length
}
};
this.cache.set(documentId, data);
return data;
} else {
// 前端模式localStorage
const key = STORAGE_KEY_PREFIX + documentId;
const storedData = localStorage.getItem(key);
if (storedData) {
const parsed = JSON.parse(storedData);
this.cache.set(documentId, parsed);
return parsed;
}
}
return null;
} catch (error) {
console.error('[ReferenceStorage] Failed to load references:', error);
return null;
}
}
/**
* 删除文档的参考文献
*/
async deleteReferences(documentId) {
try {
if (isBackendMode()) {
// 后端模式:调用 API 删除
const refs = await window.storageAdapter.loadReferences(documentId);
for (const ref of refs) {
await window.storageAdapter.deleteReference(documentId, ref.id);
}
} else {
// 前端模式localStorage
const key = STORAGE_KEY_PREFIX + documentId;
localStorage.removeItem(key);
// 更新索引
this.documentIds = this.documentIds.filter(id => id !== documentId);
delete this.metadata[documentId];
this.saveIndex();
}
// 清除缓存
this.cache.delete(documentId);
console.log(`[ReferenceStorage] Deleted references for document ${documentId}`);
return true;
} catch (error) {
console.error('[ReferenceStorage] Failed to delete references:', error);
return false;
}
}
/**
* 更新单个文献
*/
async updateReference(documentId, referenceIndex, updates) {
const data = await this.loadReferences(documentId);
if (!data || !data.references[referenceIndex]) {
console.error('[ReferenceStorage] Reference not found');
return false;
}
data.references[referenceIndex] = {
...data.references[referenceIndex],
...updates,
updatedAt: new Date().toISOString()
};
return await this.saveReferences(documentId, data.references, data.metadata);
}
/**
* 添加新文献
*/
async addReference(documentId, reference) {
let data = await this.loadReferences(documentId);
if (!data) {
data = {
documentId: documentId,
references: [],
metadata: {}
};
}
reference.index = data.references.length;
reference.addedAt = new Date().toISOString();
data.references.push(reference);
return await this.saveReferences(documentId, data.references, data.metadata);
}
/**
* 删除单个文献
*/
async removeReference(documentId, referenceIndex) {
const data = await this.loadReferences(documentId);
if (!data) {
return false;
}
data.references.splice(referenceIndex, 1);
// 重新索引
data.references.forEach((ref, idx) => {
ref.index = idx;
});
return await this.saveReferences(documentId, data.references, data.metadata);
}
/**
* 搜索文献
*/
async searchReferences(documentId, query) {
const data = await this.loadReferences(documentId);
if (!data) {
return [];
}
const lowerQuery = query.toLowerCase();
return data.references.filter(ref => {
const searchText = [
ref.title,
...(ref.authors || []),
ref.journal,
ref.doi,
ref.rawText
].filter(Boolean).join(' ').toLowerCase();
return searchText.includes(lowerQuery);
});
}
/**
* 按标签筛选文献
*/
async filterByTag(documentId, tag) {
const data = await this.loadReferences(documentId);
if (!data) {
return [];
}
return data.references.filter(ref =>
ref.tags && ref.tags.includes(tag)
);
}
/**
* 获取所有文档列表
*/
getAllDocuments() {
return this.documentIds.map(id => ({
documentId: id,
...this.metadata[id]
}));
}
/**
* 导出文献数据BibTeX格式
*/
async exportToBibTeX(documentId) {
const data = await this.loadReferences(documentId);
if (!data) {
return '';
}
const bibtex = data.references.map((ref, idx) => {
const key = `ref${idx + 1}`;
const type = ref.type || 'article';
const fields = [];
if (ref.authors && ref.authors.length > 0) {
fields.push(` author = {${ref.authors.join(' and ')}}`);
}
if (ref.title) {
fields.push(` title = {${ref.title}}`);
}
if (ref.journal) {
fields.push(` journal = {${ref.journal}}`);
}
if (ref.year) {
fields.push(` year = {${ref.year}}`);
}
if (ref.volume) {
fields.push(` volume = {${ref.volume}}`);
}
if (ref.issue) {
fields.push(` number = {${ref.issue}}`);
}
if (ref.pages) {
fields.push(` pages = {${ref.pages}}`);
}
if (ref.doi) {
fields.push(` doi = {${ref.doi}}`);
}
if (ref.url) {
fields.push(` url = {${ref.url}}`);
}
return `@${type}{${key},\n${fields.join(',\n')}\n}`;
});
return bibtex.join('\n\n');
}
/**
* 导出文献数据JSON格式
*/
async exportToJSON(documentId) {
const data = await this.loadReferences(documentId);
if (!data) {
return '[]';
}
return JSON.stringify(data.references, null, 2);
}
/**
* 清空缓存
*/
clearCache() {
this.cache.clear();
}
/**
* 获取统计信息
*/
async getStatistics(documentId) {
const data = await this.loadReferences(documentId);
if (!data) {
return null;
}
const stats = {
total: data.references.length,
withDOI: 0,
byType: {},
byYear: {},
byTag: {},
avgConfidence: 0
};
let totalConfidence = 0;
data.references.forEach(ref => {
// DOI统计
if (ref.doi) stats.withDOI++;
// 类型统计
const type = ref.type || 'unknown';
stats.byType[type] = (stats.byType[type] || 0) + 1;
// 年份统计
if (ref.year) {
stats.byYear[ref.year] = (stats.byYear[ref.year] || 0) + 1;
}
// 标签统计
if (ref.tags) {
ref.tags.forEach(tag => {
stats.byTag[tag] = (stats.byTag[tag] || 0) + 1;
});
}
// 置信度统计
if (ref.confidence !== undefined) {
totalConfidence += ref.confidence;
}
});
stats.avgConfidence = stats.total > 0 ? totalConfidence / stats.total : 0;
return stats;
}
}
// 创建全局实例
const storage = new ReferenceStorage();
// 导出API
global.ReferenceStorage = storage;
console.log('[ReferenceStorage] Reference storage loaded (supports frontend & backend modes).');
})(window);