import React, { useState, useEffect, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { Calendar, Trash2, Plus } from "lucide-react"; import { Button } from "@/components/ui/button"; import { FullScreenPanel } from "@/components/common/FullScreenPanel"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import MarkdownEditor from "@/components/MarkdownEditor"; import { workspaceApi, type DailyMemoryFileInfo } from "@/lib/api/workspace"; interface DailyMemoryPanelProps { isOpen: boolean; onClose: () => void; } function getTodayFilename(): string { const now = new Date(); const y = now.getFullYear(); const m = String(now.getMonth() + 1).padStart(2, "0"); const d = String(now.getDate()).padStart(2, "0"); return `${y}-${m}-${d}.md`; } function formatFileSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } const DailyMemoryPanel: React.FC = ({ isOpen, onClose, }) => { const { t } = useTranslation(); // List state const [files, setFiles] = useState([]); const [loadingList, setLoadingList] = useState(false); // Edit state const [editingFile, setEditingFile] = useState(null); const [content, setContent] = useState(""); const [loadingContent, setLoadingContent] = useState(false); const [saving, setSaving] = useState(false); // Delete state const [deletingFile, setDeletingFile] = useState(null); // Dark mode const [isDarkMode, setIsDarkMode] = useState(false); useEffect(() => { setIsDarkMode(document.documentElement.classList.contains("dark")); const observer = new MutationObserver(() => { setIsDarkMode(document.documentElement.classList.contains("dark")); }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"], }); return () => observer.disconnect(); }, []); // Load file list const loadFiles = useCallback(async () => { setLoadingList(true); try { const list = await workspaceApi.listDailyMemoryFiles(); setFiles(list); } catch (err) { console.error("Failed to load daily memory files:", err); toast.error(t("workspace.dailyMemory.loadFailed")); } finally { setLoadingList(false); } }, [t]); useEffect(() => { if (isOpen) { void loadFiles(); } }, [isOpen, loadFiles]); // Open file for editing const openFile = useCallback( async (filename: string) => { setLoadingContent(true); setEditingFile(filename); try { const data = await workspaceApi.readDailyMemoryFile(filename); setContent(data ?? ""); } catch (err) { console.error("Failed to read daily memory file:", err); toast.error(t("workspace.dailyMemory.loadFailed")); setEditingFile(null); } finally { setLoadingContent(false); } }, [t], ); // Create today's note (deferred — file is only persisted on save) const handleCreateToday = useCallback(async () => { const filename = getTodayFilename(); // Check if already exists in the list const existing = files.find((f) => f.filename === filename); if (existing) { // Just open it await openFile(filename); return; } // Open editor with empty content — no file created until user saves setEditingFile(filename); setContent(""); }, [files, openFile]); // Save current file const handleSave = useCallback(async () => { if (!editingFile) return; setSaving(true); try { await workspaceApi.writeDailyMemoryFile(editingFile, content); toast.success(t("workspace.saveSuccess")); } catch (err) { console.error("Failed to save daily memory file:", err); toast.error(t("workspace.saveFailed")); } finally { setSaving(false); } }, [editingFile, content, t]); // Delete file const handleDelete = useCallback(async () => { if (!deletingFile) return; try { await workspaceApi.deleteDailyMemoryFile(deletingFile); toast.success(t("workspace.dailyMemory.deleteSuccess")); setDeletingFile(null); // If we were editing this file, go back to list if (editingFile === deletingFile) { setEditingFile(null); } await loadFiles(); } catch (err) { console.error("Failed to delete daily memory file:", err); toast.error(t("workspace.dailyMemory.deleteFailed")); setDeletingFile(null); } }, [deletingFile, editingFile, loadFiles, t]); // Back from edit mode to list mode const handleBackToList = useCallback(() => { setEditingFile(null); setContent(""); void loadFiles(); }, [loadFiles]); // Close panel entirely const handleClose = useCallback(() => { setEditingFile(null); setContent(""); onClose(); }, [onClose]); // --- Edit mode --- if (editingFile) { return ( <> {saving ? t("common.saving") : t("common.save")} } > {loadingContent ? (
{t("prompts.loading")}
) : ( )}
setDeletingFile(null)} /> ); } // --- List mode --- return ( <>
{/* Header with path and create button */}

~/.openclaw/workspace/memory/

{/* File list */} {loadingList ? (
{t("prompts.loading")}
) : files.length === 0 ? (

{t("workspace.dailyMemory.empty")}

) : (
{files.map((file) => ( ))}
)}
setDeletingFile(null)} /> ); }; export default DailyMemoryPanel;