import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { agentMemoriesApi, type AgentMemory, type AgentMemoryCreateRequest, type AgentMemoryUpdateRequest, } from "../api/agentMemories"; import { projectsApi } from "../api/projects"; import { queryKeys } from "../lib/queryKeys"; import { useToast } from "../context/ToastContext"; import { cn } from "../lib/utils"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Brain, Plus, Pencil, Trash2, Globe, FolderOpen } from "lucide-react"; const CATEGORIES = ["pattern", "preference", "decision", "learning", "feedback"] as const; const SOURCES = ["self", "ceo", "board", "human"] as const; const categoryColors: Record = { pattern: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300", preference: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300", decision: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300", learning: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300", feedback: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300", }; const sourceColors: Record = { self: "bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300", ceo: "bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-300", board: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300", human: "bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300", }; interface AgentMemoryTabProps { agentId: string; companyId: string; } interface MemoryFormState { title: string; content: string; scope: "global" | "project"; projectId: string | null; category: AgentMemory["category"]; source: AgentMemory["source"]; confidence: number; } const emptyForm: MemoryFormState = { title: "", content: "", scope: "global", projectId: null, category: "learning", source: "human", confidence: 0.8, }; export function AgentMemoryTab({ agentId, companyId }: AgentMemoryTabProps) { const queryClient = useQueryClient(); const { pushToast } = useToast(); const [selectedProjectId, setSelectedProjectId] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const [editingMemory, setEditingMemory] = useState(null); const [form, setForm] = useState(emptyForm); const [deleteConfirm, setDeleteConfirm] = useState(null); const memoriesQuery = useQuery({ queryKey: queryKeys.agentMemories.list(agentId), queryFn: () => agentMemoriesApi.list(agentId), }); const projectsQuery = useQuery({ queryKey: queryKeys.projects.list(companyId), queryFn: () => projectsApi.list(companyId), }); const createMutation = useMutation({ mutationFn: (data: AgentMemoryCreateRequest) => agentMemoriesApi.create(agentId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.agentMemories.list(agentId) }); pushToast({ title: "Memory created" }); closeDialog(); }, onError: () => pushToast({ tone: "warn", title: "Failed to create memory" }), }); const updateMutation = useMutation({ mutationFn: ({ memoryId, data }: { memoryId: string; data: AgentMemoryUpdateRequest }) => agentMemoriesApi.update(agentId, memoryId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.agentMemories.list(agentId) }); pushToast({ title: "Memory updated" }); closeDialog(); }, onError: () => pushToast({ tone: "warn", title: "Failed to update memory" }), }); const deleteMutation = useMutation({ mutationFn: (memoryId: string) => agentMemoriesApi.delete(agentId, memoryId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.agentMemories.list(agentId) }); pushToast({ title: "Memory deleted" }); setDeleteConfirm(null); }, onError: () => pushToast({ tone: "warn", title: "Failed to delete memory" }), }); const memories = memoriesQuery.data ?? []; const globalMemories = memories.filter((m) => m.scope === "global"); const projectMemories = memories.filter( (m) => m.scope === "project" && (!selectedProjectId || m.projectId === selectedProjectId), ); const projects = projectsQuery.data ?? []; function openCreate() { setEditingMemory(null); setForm(emptyForm); setDialogOpen(true); } function openEdit(memory: AgentMemory) { setEditingMemory(memory); setForm({ title: memory.title, content: memory.content, scope: memory.scope, projectId: memory.projectId, category: memory.category, source: memory.source, confidence: memory.confidence, }); setDialogOpen(true); } function closeDialog() { setDialogOpen(false); setEditingMemory(null); setForm(emptyForm); } function handleSave() { const payload = { title: form.title, content: form.content, scope: form.scope, projectId: form.scope === "project" ? form.projectId : null, category: form.category, source: form.source, confidence: form.confidence, }; if (editingMemory) { updateMutation.mutate({ memoryId: editingMemory.id, data: payload }); } else { createMutation.mutate(payload); } } const isSaving = createMutation.isPending || updateMutation.isPending; return (
{/* Global Memory */}

Global Memory

({globalMemories.length})
{globalMemories.length === 0 ? ( ) : (
{globalMemories.map((memory) => ( openEdit(memory)} onDelete={() => setDeleteConfirm(memory.id)} /> ))}
)}
{/* Project Memory */}

Project Memory

({projectMemories.length})
{projectMemories.length === 0 ? ( ) : (
{projectMemories.map((memory) => ( openEdit(memory)} onDelete={() => setDeleteConfirm(memory.id)} /> ))}
)}
{/* Create/Edit Dialog */} !open && closeDialog()}> {editingMemory ? "Edit Memory" : "Add Memory"} {editingMemory ? "Update this memory entry." : "Add a new memory to help this agent learn and improve."}
setForm({ ...form, title: e.target.value })} placeholder="Memory title" />