/* Memories */ import { useMemo, useState, useRef, useEffect } from 'react'; import { Plus } from 'lucide-react'; import { matchSorter } from 'match-sorter'; import { SystemRoles, PermissionTypes, Permissions } from 'librechat-data-provider'; import { Table, Input, Label, Button, Switch, Spinner, TableRow, OGDialog, EditIcon, TableHead, TableBody, TrashIcon, TableCell, TableHeader, TooltipAnchor, useToastContext, OGDialogTrigger, OGDialogTemplate, } from '@librechat/client'; import type { TUserMemory } from 'librechat-data-provider'; import { useUpdateMemoryPreferencesMutation, useDeleteMemoryMutation, useMemoriesQuery, useGetUserQuery, } from '~/data-provider'; import { useLocalize, useAuthContext, useHasAccess } from '~/hooks'; import MemoryCreateDialog from './MemoryCreateDialog'; import MemoryEditDialog from './MemoryEditDialog'; import AdminSettings from './AdminSettings'; import { cn } from '~/utils'; const EditMemoryButton = ({ memory }: { memory: TUserMemory }) => { const localize = useLocalize(); const [open, setOpen] = useState(false); const triggerRef = useRef(null); return ( } > setOpen(!open)} className="h-8 w-8 p-0" > } /> ); }; const DeleteMemoryButton = ({ memory }: { memory: TUserMemory }) => { const localize = useLocalize(); const { showToast } = useToastContext(); const [open, setOpen] = useState(false); const { mutate: deleteMemory } = useDeleteMemoryMutation(); const [deletingKey, setDeletingKey] = useState(null); const confirmDelete = async () => { setDeletingKey(memory.key); deleteMemory(memory.key, { onSuccess: () => { showToast({ message: localize('com_ui_deleted'), status: 'success', }); setOpen(false); }, onError: () => showToast({ message: localize('com_ui_error'), status: 'error', }), onSettled: () => setDeletingKey(null), }); }; return ( setOpen(!open)} className="h-8 w-8 p-0" > {deletingKey === memory.key ? ( ) : ( )} } /> {localize('com_ui_delete_confirm')} "{memory.key}"? } selection={{ selectHandler: confirmDelete, selectClasses: 'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 text-white', selectText: localize('com_ui_delete'), }} /> ); }; const pageSize = 10; export default function MemoryViewer() { const localize = useLocalize(); const { user } = useAuthContext(); const { data: userData } = useGetUserQuery(); const { data: memData, isLoading } = useMemoriesQuery(); const { showToast } = useToastContext(); const [pageIndex, setPageIndex] = useState(0); const [searchQuery, setSearchQuery] = useState(''); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [referenceSavedMemories, setReferenceSavedMemories] = useState(true); const updateMemoryPreferencesMutation = useUpdateMemoryPreferencesMutation({ onSuccess: () => { showToast({ message: localize('com_ui_preferences_updated'), status: 'success', }); }, onError: () => { showToast({ message: localize('com_ui_error_updating_preferences'), status: 'error', }); setReferenceSavedMemories((prev) => !prev); }, }); useEffect(() => { if (userData?.personalization?.memories !== undefined) { setReferenceSavedMemories(userData.personalization.memories); } }, [userData?.personalization?.memories]); const handleMemoryToggle = (checked: boolean) => { setReferenceSavedMemories(checked); updateMemoryPreferencesMutation.mutate({ memories: checked }); }; const hasReadAccess = useHasAccess({ permissionType: PermissionTypes.MEMORIES, permission: Permissions.READ, }); const hasUpdateAccess = useHasAccess({ permissionType: PermissionTypes.MEMORIES, permission: Permissions.UPDATE, }); const hasCreateAccess = useHasAccess({ permissionType: PermissionTypes.MEMORIES, permission: Permissions.CREATE, }); const hasOptOutAccess = useHasAccess({ permissionType: PermissionTypes.MEMORIES, permission: Permissions.OPT_OUT, }); const memories: TUserMemory[] = useMemo(() => memData?.memories ?? [], [memData]); const filteredMemories = useMemo(() => { return matchSorter(memories, searchQuery, { keys: ['key', 'value'], }); }, [memories, searchQuery]); const currentRows = useMemo(() => { return filteredMemories.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize); }, [filteredMemories, pageIndex]); const getProgressBarColor = (percentage: number): string => { if (percentage > 90) { return 'stroke-red-500'; } if (percentage > 75) { return 'stroke-yellow-500'; } return 'stroke-green-500'; }; if (isLoading) { return (
); } if (!hasReadAccess) { return (

{localize('com_ui_no_read_access')}

); } return (
setSearchQuery(e.target.value)} aria-label={localize('com_ui_memories_filter')} />
{/* Memory Usage and Toggle Display */} {(memData?.tokenLimit || hasOptOutAccess) && (
{/* Usage Display */} {memData?.tokenLimit && (
{memData.usagePercentage}%
{localize('com_ui_usage')}
)} {/* Memory Toggle */} {hasOptOutAccess && (
{localize('com_ui_use_memory')}
)}
)} {/* Create Memory Button */} {hasCreateAccess && (
)}
{localize('com_ui_memory')}
{hasUpdateAccess && (
{localize('com_assistants_actions')}
)}
{currentRows.length ? ( currentRows.map((memory: TUserMemory, idx: number) => (
{memory.value}
{hasUpdateAccess && (
)}
)) ) : ( {localize('com_ui_no_memories')} )}
{/* Pagination controls */} {filteredMemories.length > pageSize && (
{`${pageIndex + 1} / ${Math.ceil(filteredMemories.length / pageSize)}`}
)} {/* Admin Settings */} {user?.role === SystemRoles.ADMIN && (
)}
); }