diff --git a/js/mzta-storage.js b/js/mzta-storage.js index 983a3451..5d12a54f 100644 --- a/js/mzta-storage.js +++ b/js/mzta-storage.js @@ -28,6 +28,9 @@ export class taStorage { taLog = null; + /** + * @param {boolean} [do_debug=false] - Enable debug logging. + */ constructor(do_debug = false) { this.taLog = new taLogger("mzta-storage", do_debug); } @@ -179,10 +182,11 @@ export class taStorage { /** * Write the summary field for a given Message-ID. * @param {string} messageId - The Message-ID header string. - * @param {string} text - The summary text. + * @param {object} summary_data - The summary data object with fields: + * summary, error, message, summary_date. * @param {boolean} [force=true] - If true, overwrite existing summary data. */ - async writeSummary(messageId, text, force = true) { + async writeSummary(messageId, summary_data, force = true) { this.taLog.log('[writeSummary] messageId: ' + messageId + ', force: ' + force); try { let key = this._buildKey(messageId); @@ -192,7 +196,15 @@ export class taStorage { return; } let now = Date.now(); - record[taStorage.FIELD_SUMMARY] = { text: text, ts: now }; + record[taStorage.FIELD_SUMMARY] = { + summary: summary_data.summary, + error: summary_data.error || false, + message: summary_data.message || '', + summary_date: summary_data.summary_date instanceof Date + ? summary_data.summary_date.toISOString() + : summary_data.summary_date, + ts: now, + }; record.ts = now; await messenger.storage.local.set({ [key]: record }); } catch (e) { @@ -200,6 +212,64 @@ export class taStorage { } } + /** + * Get all records that contain a summary field. + * @returns {Promise} Map of messageId -> summary data object. + */ + async getAllSummaryRecords() { + this.taLog.log('[getAllSummaryRecords] loading all summary records'); + try { + let all = await messenger.storage.local.get(null); + let result = {}; + for (let [key, record] of Object.entries(all)) { + if (!key.startsWith(taStorage.STORAGE_KEY_PREFIX)) continue; + if (!this.hasField(record, taStorage.FIELD_SUMMARY)) continue; + let messageId = key.slice(taStorage.STORAGE_KEY_PREFIX.length); + let summary = record[taStorage.FIELD_SUMMARY]; + result[messageId] = { + headerMessageId: messageId, + summary: summary.summary, + error: summary.error || false, + message: summary.message || '', + summary_date: new Date(summary.summary_date || summary.ts), + }; + } + this.taLog.log('[getAllSummaryRecords] found ' + Object.keys(result).length + ' summary records'); + return result; + } catch (e) { + this.taLog.error('getAllSummaryRecords error: ' + e); + return {}; + } + } + + /** + * Delete only the summary field from a record. + * Deletes the entire record if no other data fields remain. + * @param {string} messageId - The Message-ID header string. + */ + async deleteSummaryField(messageId) { + this.taLog.log('[deleteSummaryField] messageId: ' + messageId); + try { + let key = this._buildKey(messageId); + let record = await this.getRecord(messageId); + if (!record || !(taStorage.FIELD_SUMMARY in record)) { + this.taLog.log('[deleteSummaryField] no summary field found for messageId: ' + messageId); + return; + } + delete record[taStorage.FIELD_SUMMARY]; + const remainingFields = Object.keys(record).filter(k => k !== 'v' && k !== 'ts'); + if (remainingFields.length === 0) { + this.taLog.log('[deleteSummaryField] no remaining fields, deleting entire record'); + await messenger.storage.local.remove(key); + } else { + this.taLog.log('[deleteSummaryField] remaining fields: ' + remainingFields.join(', ')); + await messenger.storage.local.set({ [key]: record }); + } + } catch (e) { + this.taLog.error('deleteSummaryField error: ' + e); + } + } + /** * Write the translation field for a given Message-ID. * @param {string} messageId - The Message-ID header string. diff --git a/js/mzta-summarystore.js b/js/mzta-summarystore.js index c3f07ef8..52e3bc2a 100644 --- a/js/mzta-summarystore.js +++ b/js/mzta-summarystore.js @@ -16,30 +16,49 @@ * along with this program. If not, see . */ -export const taSummaryStore = { - logger: console, - _data_prefix: 'mzta-summary-', - _processing_prefix: 'mzta-summary-processing-', - _max_summaries: 100, +import { taStorage } from './mzta-storage.js'; +import { taLogger } from './mzta-logger.js'; + +export class taSummaryStore { + + _processing_prefix = 'mzta-summary-processing-'; + _max_summaries = 100; + _storage = null; + taLog = null; + + constructor(do_debug = false) { + this._storage = new taStorage(do_debug); + this.taLog = new taLogger('mzta-summarystore', do_debug); + } async setProcessing(data_id) { + this.taLog.log("[setProcessing] data_id: " + data_id); const key = this._processing_prefix + data_id; await browser.storage.session.set({ [key]: true }); - }, + } async isProcessing(data_id) { + this.taLog.log("[isProcessing] data_id: " + data_id); const key = this._processing_prefix + data_id; let output = await browser.storage.session.get(key); - return output[key] || false; - }, + let result = output[key] || false; + this.taLog.log("[isProcessing] result: " + result); + return result; + } async saveSummary(data, data_id) { - const key = this._data_prefix + data_id; - await browser.storage.session.set({ [key]: data }); - await browser.storage.session.remove(this._processing_prefix + data_id); - }, + this.taLog.log("[saveSummary] data_id: " + data_id); + try { + await this._storage.writeSummary(data_id, data, true); + await browser.storage.session.remove(this._processing_prefix + data_id); + } catch (e) { + this.taLog.error("[saveSummary] error: " + e); + throw e; + } + } async saveError(data_id, error_message) { + this.taLog.log("[saveError] data_id: " + data_id + ", error_message: " + error_message); let data = { error: true, message: error_message, @@ -48,53 +67,67 @@ export const taSummaryStore = { }; await this.saveSummary(data, data_id); return data; - }, + } async loadSummary(data_id) { - const key = this._data_prefix + data_id; - let output = await browser.storage.session.get(key); - return output[key] || null; - }, + this.taLog.log("[loadSummary] data_id: " + data_id); + let record = await this._storage.getRecord(data_id); + if (!record || !this._storage.hasField(record, taStorage.FIELD_SUMMARY)) { + this.taLog.log("[loadSummary] no record found for data_id: " + data_id); + return null; + } + let summary = record.summary; + return { + headerMessageId: data_id, + summary: summary.summary, + error: summary.error || false, + message: summary.message || '', + summary_date: new Date(summary.summary_date || summary.ts), + }; + } async removeSummary(data_id) { - const key = this._data_prefix + data_id; - await browser.storage.session.remove(key); + this.taLog.log("[removeSummary] data_id: " + data_id); + await this._storage.deleteSummaryField(data_id); await browser.storage.session.remove(this._processing_prefix + data_id); - }, + } async getAllSummaries() { - let allData = await browser.storage.session.get(null); - let summaryData = {}; - - for (const [key, value] of Object.entries(allData)) { - if (key.startsWith(this._data_prefix)) { - summaryData[key.replace(this._data_prefix, '')] = value; - } - } - - return summaryData; - }, + this.taLog.log("[getAllSummaries] loading all summaries"); + return await this._storage.getAllSummaryRecords(); + } async clearSummaries() { - let allData = await browser.storage.session.get(null); - let keysToDelete = Object.keys(allData).filter(key => key.startsWith(this._data_prefix) || key.startsWith(this._processing_prefix)); - + this.taLog.log("[clearSummaries] clearing all summary data"); + let allSummaries = await this._storage.getAllSummaryRecords(); + let summaryKeys = Object.keys(allSummaries); + this.taLog.log("[clearSummaries] deleting " + summaryKeys.length + " summary records"); + for (let messageId of summaryKeys) { + await this._storage.deleteSummaryField(messageId); + } + let allSession = await browser.storage.session.get(null); + let keysToDelete = Object.keys(allSession).filter(k => k.startsWith(this._processing_prefix)); + this.taLog.log("[clearSummaries] deleting " + keysToDelete.length + " session keys"); for (let key of keysToDelete) { await browser.storage.session.remove(key); } - }, + } async truncSummaries() { - let data = await this.getAllSummaries(); + this.taLog.log("[truncSummaries] checking summary count"); + let data = await this._storage.getAllSummaryRecords(); let sortedData = this.sortSummariesByDate(data); let keys = Object.keys(sortedData); + this.taLog.log("[truncSummaries] total summaries: " + keys.length + ", max: " + this._max_summaries); if (keys.length > this._max_summaries) { + let toDelete = keys.length - this._max_summaries; + this.taLog.log("[truncSummaries] truncating " + toDelete + " oldest summaries"); for (let i = this._max_summaries; i < keys.length; i++) { - await browser.storage.session.remove(this._data_prefix + keys[i]); + await this._storage.deleteSummaryField(keys[i]); } } - }, + } sortSummariesByDate(data) { if (!data) return {}; @@ -112,4 +145,4 @@ export const taSummaryStore = { return sortedSummaries; } -}; \ No newline at end of file +} diff --git a/mzta-background.js b/mzta-background.js index 9836e444..ec5adfea 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -96,6 +96,7 @@ await reload_pref_init(); let taLog = new taLogger("mzta-background",prefs_init.do_debug); taWorkingStatus.taLog = taLog; let spamReport = new taSpamReport(prefs_init.do_debug); +let summaryStore = new taSummaryStore(prefs_init.do_debug); let special_prompts_ids = getActiveSpecialPromptsIDs({ addtags: prefs_init.add_tags, @@ -227,13 +228,13 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => { let message = await browser.messageDisplay.getDisplayedMessage(tabId); if (!message) return; - let cachedSummary = await taSummaryStore.loadSummary(message.headerMessageId); + let cachedSummary = await summaryStore.loadSummary(message.headerMessageId); if (cachedSummary && !cachedSummary.error) { browser.tabs.sendMessage(tabId, { command: "showSummary", data: cachedSummary }); return; } - if (await taSummaryStore.isProcessing(message.headerMessageId)) { + if (await summaryStore.isProcessing(message.headerMessageId)) { browser.tabs.sendMessage(tabId, { command: "showSummaryGenerating" }); return; } @@ -265,7 +266,7 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => { case 'refreshSummary': async function _refreshSummary(message) { let tabId = sender.tab.id; - await taSummaryStore.removeSummary(message.headerMessageId); + await summaryStore.removeSummary(message.headerMessageId); await _generateSummaryForMessage(message.headerMessageId, tabId); } _refreshSummary(message); @@ -445,23 +446,23 @@ async function _generateSummaryForMessage(headerMessageId, tabId) { ...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type']) }); - let cachedSummary = await taSummaryStore.loadSummary(headerMessageId); + let cachedSummary = await summaryStore.loadSummary(headerMessageId); if (cachedSummary && !cachedSummary.error) { browser.tabs.sendMessage(tabId, { command: "showSummary", data: cachedSummary }); return; } - if (await taSummaryStore.isProcessing(headerMessageId)) { + if (await summaryStore.isProcessing(headerMessageId)) { browser.tabs.sendMessage(tabId, { command: "showSummaryGenerating" }); return; } - await taSummaryStore.setProcessing(headerMessageId); + await summaryStore.setProcessing(headerMessageId); browser.tabs.sendMessage(tabId, { command: "showSummaryGenerating" }); const messageResult = await browser.messages.query({ headerMessageId: headerMessageId }); if (!messageResult || messageResult.messages.length === 0) { - await taSummaryStore.saveError(headerMessageId, "Message not found"); + await summaryStore.saveError(headerMessageId, "Message not found"); browser.tabs.sendMessage(tabId, { command: "showSummary", data: { error: true, message: "Message not found" } }); return; } @@ -479,7 +480,7 @@ async function _generateSummaryForMessage(headerMessageId, tabId) { if (connectionType === 'chatgpt_web') { const errorMsg = browser.i18n.getMessage('summarize_chatgpt_web_not_supported'); - await taSummaryStore.saveError(headerMessageId, errorMsg); + await summaryStore.saveError(headerMessageId, errorMsg); browser.tabs.sendMessage(tabId, { command: "showSummary", data: { error: true, message: errorMsg } }); return; } @@ -503,12 +504,12 @@ async function _generateSummaryForMessage(headerMessageId, tabId) { summary_date: new Date(), headerMessageId: headerMessageId }; - await taSummaryStore.saveSummary(summaryData, headerMessageId); + await summaryStore.saveSummary(summaryData, headerMessageId); browser.tabs.sendMessage(tabId, { command: "showSummary", data: summaryData }); } catch (error) { console.error("[ThunderAI] Error generating summary:", error); - await taSummaryStore.saveError(headerMessageId, error.message || String(error)); + await summaryStore.saveError(headerMessageId, error.message || String(error)); browser.tabs.sendMessage(tabId, { command: "showSummary", data: { error: true, message: error.message || "Failed to generate summary" } }); } }