// KIMI DATA MANAGER (extracted from kimi-module.js) // This file contains only the KimiDataManager class and its global exposure. // Depends on: KimiBaseManager (defined in kimi-utils.js) and DOM APIs. class KimiDataManager extends KimiBaseManager { constructor(database) { super(); this.db = database; } async init() { this.setupDataControls(); await this.updateStorageInfo(); } setupDataControls() { const exportButton = document.getElementById("export-data"); if (exportButton) { exportButton.addEventListener("click", () => this.exportAllData()); } const importButton = document.getElementById("import-data"); const importFile = document.getElementById("import-file"); if (importButton && importFile) { importButton.addEventListener("click", () => importFile.click()); importFile.addEventListener("change", e => this.importData(e)); } const cleanButton = document.getElementById("clean-old-data"); if (cleanButton) { cleanButton.addEventListener("click", async () => { if (!this.db) return; const confirmClean = confirm( "Delete all conversation messages?\n\n" + "This will remove all chat history but keep your preferences and settings.\n\n" + "This action cannot be undone." ); if (!confirmClean) { return; } try { // Clear all conversations directly await this.db.db.conversations.clear(); // Clear chat UI const chatMessages = document.getElementById("chat-messages"); if (chatMessages) { chatMessages.textContent = ""; } // Reload chat history if (typeof window.loadChatHistory === "function") { window.loadChatHistory(); } await this.updateStorageInfo(); alert("All conversation messages have been deleted successfully!"); } catch (error) { console.error("Error cleaning conversations:", error); alert("Error while cleaning conversations. Please try again."); } }); } const resetButton = document.getElementById("reset-all-data"); if (resetButton) { resetButton.addEventListener("click", () => this.resetAllData()); } } async exportAllData() { if (!this.db) { console.error("Database not available"); return; } try { const conversations = await this.db.getAllConversations(); const preferencesObj = await this.db.getAllPreferences(); // Export preferences as an array of {key,value} so export is directly re-importable const preferences = Array.isArray(preferencesObj) ? preferencesObj : Object.keys(preferencesObj).map(k => ({ key: k, value: preferencesObj[k] })); const personalityTraits = await this.db.getAllPersonalityTraits(); const models = await this.db.getAllLLMModels(); const memories = await this.db.getAllMemories(); const exportData = { version: "1.0", exportDate: new Date().toISOString(), conversations: conversations, preferences: preferences, personalityTraits: personalityTraits, models: models, memories: memories, metadata: { totalConversations: conversations.length, totalPreferences: Object.keys(preferences).length, totalTraits: Object.keys(personalityTraits).length, totalModels: models.length, totalMemories: memories.length } }; const dataStr = JSON.stringify(exportData, null, 2); const dataBlob = new Blob([dataStr], { type: "application/json" }); const url = URL.createObjectURL(dataBlob); const a = document.createElement("a"); a.href = url; a.download = `kimi-backup-${new Date().toISOString().split("T")[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } catch (error) { console.error("Error during export:", error); } } async importData(event) { const file = event.target.files[0]; if (!file) { alert("No file selected."); return; } const reader = new FileReader(); reader.onload = async e => { try { const data = JSON.parse(e.target.result); try { console.log("Import file keys:", Object.keys(data)); } catch (ex) {} if (data.preferences) { try { const isArray = Array.isArray(data.preferences); const len = isArray ? data.preferences.length : Object.keys(data.preferences).length; console.log("Import: preferences type=", isArray ? "array" : "object", "length=", len); } catch (ex) {} await this.db.setPreferencesBatch(data.preferences); } else { console.log("Import: no preferences found"); } if (data.conversations) { try { console.log( "Import: conversations length=", Array.isArray(data.conversations) ? data.conversations.length : "not-array" ); } catch (ex) {} await this.db.setConversationsBatch(data.conversations); } else { console.log("Import: no conversations found"); } if (data.personalityTraits) { try { console.log("Import: personalityTraits type=", typeof data.personalityTraits); } catch (ex) {} await this.db.setPersonalityBatch(data.personalityTraits); } else { console.log("Import: no personalityTraits found"); } if (data.models) { try { console.log("Import: models length=", Array.isArray(data.models) ? data.models.length : "not-array"); } catch (ex) {} await this.db.setLLMModelsBatch(data.models); } else { console.log("Import: no models found"); } if (data.memories) { try { console.log( "Import: memories length=", Array.isArray(data.memories) ? data.memories.length : "not-array" ); } catch (ex) {} await this.db.setAllMemories(data.memories); } else { console.log("Import: no memories found"); } alert("Import successful!"); await this.updateStorageInfo(); // Reload the page to ensure all UI state is rebuilt from the newly imported DB setTimeout(() => { location.reload(); }, 200); } catch (err) { console.error("Import failed:", err); alert("Import failed. Invalid file or format."); } }; reader.readAsText(file); } async cleanOldData() { if (!this.db) { console.error("Database not available"); return; } const confirmClean = confirm("Do you want to delete ALL conversations?\n\nThis action is irreversible!"); if (!confirmClean) { return; } try { // Centralized: use kimi-database.js cleanOldConversations for all deletion logic await this.db.cleanOldConversations(); if (typeof window.loadChatHistory === "function") { window.loadChatHistory(); } const chatMessages = document.getElementById("chat-messages"); if (chatMessages) { chatMessages.textContent = ""; } await this.updateStorageInfo(); } catch (error) { console.error("Error during cleaning:", error); } } async resetAllData() { if (!this.db) { console.error("Database not available"); return; } const confirmReset = confirm( "WARNING!\n\n" + "Do you REALLY want to delete ALL data?\n\n" + "• All conversations\n" + "• All preferences\n" + "• All configured models\n" + "• All personality traits\n\n" + "This action is IRREVERSIBLE!" ); if (!confirmReset) { return; } try { if (this.db.db) { this.db.db.close(); } const deleteRequest = indexedDB.deleteDatabase(this.db.dbName); deleteRequest.onsuccess = () => { setTimeout(() => { alert("The page will reload to complete the reset."); location.reload(); }, 500); }; deleteRequest.onerror = () => { alert("Error while deleting the database. Please try again."); }; } catch (error) { console.error("Error during reset:", error); alert("Error during reset. Please try again."); } } async updateStorageInfo() { if (!this.db) return; try { // Add a small delay to ensure database operations are complete await new Promise(resolve => setTimeout(resolve, 100)); const stats = await this.db.getStorageStats(); const dbSizeEl = document.getElementById("db-size"); const storageUsedEl = document.getElementById("storage-used"); if (dbSizeEl) { dbSizeEl.textContent = this.formatFileSize(stats.totalSize || 0); } if (storageUsedEl) { const estimate = navigator.storage && navigator.storage.estimate ? await navigator.storage.estimate() : null; if (estimate) { storageUsedEl.textContent = this.formatFileSize(estimate.usage || 0); } else { storageUsedEl.textContent = "N/A"; } } } catch (error) { console.error("Error while calculating storage:", error); const dbSizeEl = document.getElementById("db-size"); const storageUsedEl = document.getElementById("storage-used"); if (dbSizeEl) dbSizeEl.textContent = "Error"; if (storageUsedEl) storageUsedEl.textContent = "Error"; } } } // Global exposure (legacy pattern). Will be phased out; prefer: import { KimiDataManager } from "./kimi-data-manager.js"; window.KimiDataManager = KimiDataManager; // DEPRECATED access path (kept for backward compatibility) export { KimiDataManager };