// ===== KIMI MEMORY UI MANAGER ===== class KimiMemoryUI { constructor() { this.memorySystem = null; this.isInitialized = false; } 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()); } // Memory modal close const memoryClose = document.getElementById("memory-close"); if (memoryClose) { memoryClose.addEventListener("click", () => this.closeMemoryModal()); } // 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()); } // 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(); } }); } } 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 { 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 = ""; 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 += `
${memory.type === "auto_extracted" ? "🤖 Auto" : "✋ Manual"} ${confidence}% ${isLongContent ? `${wordCount} mots` : ""} ${importanceLevel.charAt(0).toUpperCase() + importanceLevel.slice(1)}
${this.highlightMemoryContent(previewText)}
${ isLongContent ? ` ` : "" }
${tagsHtml}
${this.formatDate(memory.timestamp)} ${ memory.sourceText ? `📝 Extrait de conversation` : `📝 Ajouté manuellement` }
`; }); html += `
`; }); memoryList.innerHTML = html; } // Map importance value [0..1] to level string getImportanceLevelFromValue(value) { if (value >= 0.8) return "high"; if (value >= 0.6) return "medium"; return "low"; } // Render tags as compact chips; show up to 4 then "+N" renderTags(tags) { if (!Array.isArray(tags) || tags.length === 0) return ""; const maxVisible = 4; const visible = tags.slice(0, maxVisible); const moreCount = tags.length - visible.length; const escape = txt => window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml ? window.KimiValidationUtils.escapeHtml(String(txt)) : String(txt); const classify = tag => { if (tag.startsWith("relationship:")) return "tag-relationship"; if (tag.startsWith("boundary:")) return "tag-boundary"; if (tag.startsWith("time:")) return "tag-time"; if (tag.startsWith("type:")) return "tag-type"; return "tag-generic"; }; const chips = visible .map(tag => `${escape(tag)}`) .join(""); const moreChip = moreCount > 0 ? `+${moreCount}` : ""; return `
${chips}${moreChip}
`; } formatCategoryName(category) { const names = { personal: "Personal Information", preferences: "Likes & Dislikes", relationships: "Relationships & People", activities: "Activities & Hobbies", goals: "Goals & Aspirations", experiences: "Shared Experiences", important: "Important Events" }; return names[category] || category.charAt(0).toUpperCase() + category.slice(1); } getConfidenceLevel(confidence) { if (confidence >= 80) return "high"; if (confidence >= 60) return "medium"; return "low"; } formatDate(timestamp) { const date = new Date(timestamp); const now = new Date(); const diffTime = now - date; const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); if (diffDays === 0) return "Today"; if (diffDays === 1) return "Yesterday"; if (diffDays < 7) return `${diffDays} days ago`; return date.toLocaleDateString(); } highlightMemoryContent(content) { // Escape HTML first using centralized util const escapedContent = window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml ? window.KimiValidationUtils.escapeHtml(content) : content; // Simple highlighting for search terms if there's a search active const searchInput = document.getElementById("memory-search"); if (searchInput && searchInput.value.trim()) { const searchTerm = searchInput.value.trim(); const regex = new RegExp(`(${searchTerm})`, "gi"); return escapedContent.replace( regex, '$1' ); } return escapedContent; } // Removed duplicate escapeHtml; use window.KimiValidationUtils.escapeHtml instead getCategoryIcon(category) { const icons = { personal: "👤", preferences: "❤️", relationships: "👨‍👩‍👧‍👦", activities: "🎯", goals: "🎯", experiences: "⭐", important: "📌" }; return icons[category] || "📝"; } toggleMemoryContent(memoryId) { const previewShort = document.getElementById(`preview-${memoryId}`); const previewFull = document.getElementById(`full-${memoryId}`); const icon = document.getElementById(`icon-${memoryId}`); const expandBtn = icon?.closest(".memory-expand-btn"); if (!previewShort || !previewFull || !icon || !expandBtn) return; const isExpanded = previewFull.style.display !== "none"; if (isExpanded) { previewShort.style.display = "block"; previewFull.style.display = "none"; icon.className = "fas fa-chevron-down"; expandBtn.innerHTML = ' Voir plus'; } else { previewShort.style.display = "none"; previewFull.style.display = "block"; icon.className = "fas fa-chevron-up"; expandBtn.innerHTML = ' Voir moins'; } } async editMemory(memoryId) { if (!this.memorySystem) return; try { // Get the memory to edit const memories = await this.memorySystem.getAllMemories(); const memory = memories.find(m => m.id == memoryId); if (!memory) { this.showFeedback("Memory not found", "error"); return; } // Create edit dialog const overlay = document.createElement("div"); overlay.className = "memory-edit-overlay"; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 10001; `; const dialog = document.createElement("div"); dialog.className = "memory-edit-dialog"; dialog.style.cssText = ` background: var(--background-secondary); border-radius: 12px; padding: 24px; width: 90%; max-width: 500px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); `; dialog.innerHTML = `

Edit Memory

`; overlay.appendChild(dialog); document.body.appendChild(overlay); // Handle buttons dialog.querySelector("#cancel-edit").addEventListener("click", () => { document.body.removeChild(overlay); }); dialog.querySelector("#save-edit").addEventListener("click", async () => { const newCategory = dialog.querySelector("#edit-memory-category").value; const newContent = dialog.querySelector("#edit-memory-content").value.trim(); if (!newContent) { this.showFeedback("Le contenu ne peut pas être vide", "error"); return; } console.log(`🔄 Tentative de mise à jour de la mémoire ID: ${memoryId}`); console.log("Nouvelles données:", { category: newCategory, content: newContent }); try { const result = await this.memorySystem.updateMemory(memoryId, { category: newCategory, content: newContent }); console.log("Résultat de l'update:", result); if (result === true) { // Fermer le modal document.body.removeChild(overlay); // Forcer le rechargement complet await this.loadMemories(); await this.updateMemoryStats(); this.showFeedback("Mémoire mise à jour avec succès"); console.log("✅ Interface mise à jour"); } else { this.showFeedback("Erreur: Impossible de mettre à jour la mémoire", "error"); console.error("❌ Update échoué, résultat:", result); } } catch (error) { console.error("Error updating memory:", error); this.showFeedback("Erreur lors de la mise à jour de la mémoire", "error"); } }); // Close on overlay click overlay.addEventListener("click", e => { if (e.target === overlay) { document.body.removeChild(overlay); } }); } catch (error) { console.error("Error editing memory:", error); this.showFeedback("Error loading memory for editing", "error"); } } async deleteMemory(memoryId) { if (!confirm("Are you sure you want to delete this memory?")) return; try { await this.memorySystem.deleteMemory(memoryId); await this.loadMemories(); await this.updateMemoryStats(); this.showFeedback("Memory deleted"); } catch (error) { console.error("Error deleting memory:", error); this.showFeedback("Error deleting memory", "error"); } } async exportMemories() { if (!this.memorySystem) return; try { const exportData = await this.memorySystem.exportMemories(); if (exportData) { const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `kimi-memories-${new Date().toISOString().split("T")[0]}.json`; a.click(); URL.revokeObjectURL(url); this.showFeedback("Memories exported successfully"); } } catch (error) { console.error("Error exporting memories:", error); this.showFeedback("Error exporting memories", "error"); } } async updateMemoryStats() { if (!this.memorySystem) return; try { const stats = await this.memorySystem.getMemoryStats(); const memoryCount = document.getElementById("memory-count"); const memoryToggle = document.getElementById("memory-toggle"); if (memoryCount) { memoryCount.textContent = `${stats.total} memories`; } // Update toggle state if (memoryToggle) { const enabled = this.memorySystem.memoryEnabled; memoryToggle.setAttribute("aria-checked", enabled.toString()); memoryToggle.classList.toggle("active", enabled); // Add visual indicator for memory status const indicator = memoryToggle.querySelector(".memory-indicator") || document.createElement("div"); if (!memoryToggle.querySelector(".memory-indicator")) { indicator.className = "memory-indicator"; memoryToggle.appendChild(indicator); } indicator.style.cssText = ` position: absolute; top: -2px; right: -2px; width: 8px; height: 8px; border-radius: 50%; background: ${enabled ? "#27ae60" : "#e74c3c"}; border: 2px solid white; `; } } catch (error) { console.error("Error updating memory stats:", error); } } // Force refresh de l'interface (utile pour debug) async forceRefresh() { console.log("🔄 Force refresh de l'interface mémoire..."); try { if (this.memorySystem) { // Migrer les IDs si nécessaire await this.memorySystem.migrateIncompatibleIDs(); // Recharger les mémoires await this.loadMemories(); await this.updateMemoryStats(); console.log("✅ Refresh forcé terminé"); } } catch (error) { console.error("❌ Erreur lors du refresh forcé:", error); } } showFeedback(message, type = "success") { // Create feedback element const feedback = document.createElement("div"); feedback.className = `memory-feedback memory-feedback-${type}`; feedback.textContent = message; // Style the feedback based on type let backgroundColor; switch (type) { case "error": backgroundColor = "#e74c3c"; break; case "info": backgroundColor = "#3498db"; break; default: backgroundColor = "#27ae60"; } // Style the feedback Object.assign(feedback.style, { position: "fixed", top: "20px", right: "20px", padding: "12px 20px", borderRadius: "6px", color: "white", backgroundColor: backgroundColor, boxShadow: "0 4px 12px rgba(0,0,0,0.2)", zIndex: "10000", fontSize: "14px", fontWeight: "500", opacity: "0", transform: "translateX(100%)", transition: "all 0.3s ease" }); document.body.appendChild(feedback); // Animate in setTimeout(() => { feedback.style.opacity = "1"; feedback.style.transform = "translateX(0)"; }, 10); // Remove after delay (longer for info messages, shorter for others) const delay = type === "info" ? 2000 : 3000; setTimeout(() => { feedback.style.opacity = "0"; feedback.style.transform = "translateX(100%)"; setTimeout(() => { if (feedback.parentNode) { feedback.parentNode.removeChild(feedback); } }, 300); }, delay); } } // Initialize memory UI when DOM is ready document.addEventListener("DOMContentLoaded", async () => { window.kimiMemoryUI = new KimiMemoryUI(); // Wait for memory system to be ready const waitForMemorySystem = () => { if (window.kimiMemorySystem) { window.kimiMemoryUI.init(); } else { setTimeout(waitForMemorySystem, 100); } }; setTimeout(waitForMemorySystem, 1000); // Give time for initialization }); window.KimiMemoryUI = KimiMemoryUI;