// ===== KIMI MEMORY UI MANAGER ===== class KimiMemoryUI { constructor() { this.memorySystem = null; this.isInitialized = false; // Debounce helpers for UI refresh to coalesce multiple DB reads this._debounceTimers = {}; } debounce(key, fn, wait = 350) { if (this._debounceTimers[key]) clearTimeout(this._debounceTimers[key]); this._debounceTimers[key] = setTimeout(() => { fn(); delete this._debounceTimers[key]; }, wait); } async init() { if (!window.kimiMemorySystem) { console.warn("Memory system not available"); return; } this.memorySystem = window.kimiMemorySystem; this.setupEventListeners(); await this.updateMemoryStats(); this.isInitialized = true; } setupEventListeners() { // Memory toggle const memoryToggle = document.getElementById("memory-toggle"); if (memoryToggle) { memoryToggle.addEventListener("click", () => this.toggleMemorySystem()); } // View memories button const viewMemoriesBtn = document.getElementById("view-memories"); if (viewMemoriesBtn) { viewMemoriesBtn.addEventListener("click", () => this.openMemoryModal()); } // Add memory button const addMemoryBtn = document.getElementById("add-memory"); if (addMemoryBtn) { addMemoryBtn.addEventListener("click", () => { this.addManualMemory(); ensureVideoNeutralOnUIChange(); }); } // Memory modal close const memoryClose = document.getElementById("memory-close"); if (memoryClose) { memoryClose.addEventListener("click", () => { this.closeMemoryModal(); ensureVideoNeutralOnUIChange(); }); } // Memory export const memoryExport = document.getElementById("memory-export"); if (memoryExport) { memoryExport.addEventListener("click", () => this.exportMemories()); } // Memory filter const memoryFilter = document.getElementById("memory-filter-category"); if (memoryFilter) { memoryFilter.addEventListener("change", () => { this.filterMemories(); ensureVideoNeutralOnUIChange(); }); } // Memory search const memorySearch = document.getElementById("memory-search"); if (memorySearch) { memorySearch.addEventListener("input", () => this.filterMemories()); } // Close modal on overlay click const memoryOverlay = document.getElementById("memory-overlay"); if (memoryOverlay) { memoryOverlay.addEventListener("click", e => { if (e.target === memoryOverlay) { this.closeMemoryModal(); } }); } // Delegated handler for memory-source clicks / touch / keyboard const memoryList = document.getElementById("memory-list"); if (memoryList) { // Click and touch memoryList.addEventListener("click", e => this.handleMemorySourceToggle(e)); memoryList.addEventListener("touchstart", e => this.handleMemorySourceToggle(e)); // General delegated click handler for memory actions (summarize, etc.) memoryList.addEventListener("click", e => this.handleMemoryListClick(e)); // Keyboard accessibility: Enter / Space when focused on .memory-source memoryList.addEventListener("keydown", e => { const target = e.target; if (target && target.classList && target.classList.contains("memory-source")) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); this.toggleSourceContentForElement(target); } } }); } } // Delegated click handler for actions inside the memory list async handleMemoryListClick(e) { try { const summarizeBtn = e.target.closest && e.target.closest("#memory-summarize-btn"); if (summarizeBtn) { e.stopPropagation(); await this.handleSummarizeAction(); return; } } catch (err) { console.error("Error handling memory list click", err); } } async toggleMemorySystem() { if (!this.memorySystem) return; const toggle = document.getElementById("memory-toggle"); const enabled = !this.memorySystem.memoryEnabled; await this.memorySystem.toggleMemorySystem(enabled); if (toggle) { toggle.setAttribute("aria-checked", enabled.toString()); toggle.classList.toggle("active", enabled); } // Show feedback this.showFeedback(enabled ? "Memory system enabled" : "Memory system disabled"); } async addManualMemory() { const categorySelect = document.getElementById("memory-category"); const contentInput = document.getElementById("memory-content"); if (!categorySelect || !contentInput) return; const category = categorySelect.value; const content = contentInput.value.trim(); if (!content) { this.showFeedback("Please enter memory content", "error"); return; } try { await this.memorySystem.addMemory({ category: category, content: content, type: "manual", confidence: 1.0 }); contentInput.value = ""; await this.updateMemoryStats(); this.showFeedback("Memory added successfully"); } catch (error) { console.error("Error adding memory:", error); this.showFeedback("Error adding memory", "error"); } } async openMemoryModal() { const overlay = document.getElementById("memory-overlay"); if (!overlay) return; overlay.style.display = "flex"; await this.loadMemories(); } closeMemoryModal() { const overlay = document.getElementById("memory-overlay"); if (overlay) { overlay.style.display = "none"; // Ensure background video resumes after closing memory modal const kv = window.kimiVideo; if (kv && kv.activeVideo) { try { const v = kv.activeVideo; if (v.ended) { if (typeof kv.returnToNeutral === "function") kv.returnToNeutral(); } else if (v.paused) { // Use centralized video utility for play window.KimiVideoManager.getVideoElement(v) .play() .catch(() => { if (typeof kv.returnToNeutral === "function") kv.returnToNeutral(); }); } } catch {} } } } async loadMemories() { if (!this.memorySystem) return; try { // Use debounce to avoid multiple rapid DB reads this.debounce("loadMemories", async () => { const memories = await this.memorySystem.getAllMemories(); console.log("Loading memories into UI:", memories.length); this.renderMemories(memories); }); } catch (error) { console.error("Error loading memories:", error); } } async filterMemories() { const filterSelect = document.getElementById("memory-filter-category"); const searchInput = document.getElementById("memory-search"); if (!this.memorySystem) return; try { const category = filterSelect?.value; const searchTerm = searchInput?.value.toLowerCase().trim(); let memories; if (category) { memories = await this.memorySystem.getMemoriesByCategory(category); } else { memories = await this.memorySystem.getAllMemories(); } // Apply search filter if search term exists if (searchTerm) { memories = memories.filter( memory => memory.content.toLowerCase().includes(searchTerm) || memory.category.toLowerCase().includes(searchTerm) || (memory.sourceText && memory.sourceText.toLowerCase().includes(searchTerm)) ); } this.renderMemories(memories); } catch (error) { console.error("Error filtering memories:", error); } } renderMemories(memories) { const memoryList = document.getElementById("memory-list"); if (!memoryList) return; console.log("Rendering memories:", memories); // Debug logging if (memories.length === 0) { memoryList.innerHTML = `

No memories found. Start chatting to build memories automatically, or add them manually.

`; return; } // Group memories by category for better organization const groupedMemories = memories.reduce((groups, memory) => { const category = memory.category || "other"; if (!groups[category]) groups[category] = []; groups[category].push(memory); return groups; }, {}); let html = ""; // Toolbar with summarize action html += `
`; Object.entries(groupedMemories).forEach(([category, categoryMemories]) => { html += `

${this.getCategoryIcon(category)} ${this.formatCategoryName(category)} (${categoryMemories.length})

`; categoryMemories.forEach(memory => { const confidence = Math.round(memory.confidence * 100); const isAutomatic = memory.type === "auto_extracted"; const previewLength = 120; const isLongContent = memory.content.length > previewLength; const previewText = isLongContent ? memory.content.substring(0, previewLength) + "..." : memory.content; const wordCount = memory.content.split(/\s+/).length; const importance = typeof memory.importance === "number" ? memory.importance : 0.5; const importanceLevel = this.getImportanceLevelFromValue(importance); const importancePct = Math.round(importance * 100); const tagsHtml = this.renderTags(memory.tags || []); html += `
${window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml ? window.KimiValidationUtils.escapeHtml(memory.title || "") : memory.title || ""}${!memory.title ? "" : ""}
${memory.type === "auto_extracted" ? "๐Ÿค– Auto" : "โœ‹ Manual"} ${confidence}% ${memory.type === "summary" || (memory.tags && memory.tags.includes("summary")) ? `Summary` : ""} ${isLongContent ? `${wordCount} mots` : ""} ${importanceLevel.charAt(0).toUpperCase() + importanceLevel.slice(1)}
${ !memory.title ? `
${this.highlightMemoryContent(previewText)}
${ isLongContent ? ` ` : "" }
` : "" } ${tagsHtml}
${this.formatDate(memory.timestamp)} ${ memory.sourceText ? `` : `` }
${ memory.sourceText ? `` : "" }
`; }); html += `
`; }); // Minimal runtime guard: block accidental