mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-11 04:58:51 +01:00
180 lines
5.8 KiB
TypeScript
180 lines
5.8 KiB
TypeScript
|
|
import React, { useState, useEffect } from 'react';
|
||
|
|
import { PermissionTypes, Permissions } from 'librechat-data-provider';
|
||
|
|
import type { TUserMemory } from 'librechat-data-provider';
|
||
|
|
import { OGDialog, OGDialogTemplate, Button, Label, Input } from '~/components/ui';
|
||
|
|
import { useUpdateMemoryMutation, useMemoriesQuery } from '~/data-provider';
|
||
|
|
import { useLocalize, useHasAccess } from '~/hooks';
|
||
|
|
import { useToastContext } from '~/Providers';
|
||
|
|
import { Spinner } from '~/components/svg';
|
||
|
|
|
||
|
|
interface MemoryEditDialogProps {
|
||
|
|
memory: TUserMemory | null;
|
||
|
|
open: boolean;
|
||
|
|
onOpenChange: (open: boolean) => void;
|
||
|
|
children: React.ReactNode;
|
||
|
|
triggerRef?: React.MutableRefObject<HTMLButtonElement | null>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function MemoryEditDialog({
|
||
|
|
memory,
|
||
|
|
open,
|
||
|
|
onOpenChange,
|
||
|
|
children,
|
||
|
|
triggerRef,
|
||
|
|
}: MemoryEditDialogProps) {
|
||
|
|
const localize = useLocalize();
|
||
|
|
const { showToast } = useToastContext();
|
||
|
|
const { data: memData } = useMemoriesQuery();
|
||
|
|
|
||
|
|
const hasUpdateAccess = useHasAccess({
|
||
|
|
permissionType: PermissionTypes.MEMORIES,
|
||
|
|
permission: Permissions.UPDATE,
|
||
|
|
});
|
||
|
|
|
||
|
|
const { mutate: updateMemory, isLoading } = useUpdateMemoryMutation({
|
||
|
|
onMutate: () => {
|
||
|
|
onOpenChange(false);
|
||
|
|
setTimeout(() => {
|
||
|
|
triggerRef?.current?.focus();
|
||
|
|
}, 0);
|
||
|
|
},
|
||
|
|
onSuccess: () => {
|
||
|
|
showToast({
|
||
|
|
message: localize('com_ui_saved'),
|
||
|
|
status: 'success',
|
||
|
|
});
|
||
|
|
},
|
||
|
|
onError: () => {
|
||
|
|
showToast({
|
||
|
|
message: localize('com_ui_error'),
|
||
|
|
status: 'error',
|
||
|
|
});
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const [key, setKey] = useState('');
|
||
|
|
const [value, setValue] = useState('');
|
||
|
|
const [originalKey, setOriginalKey] = useState('');
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (memory) {
|
||
|
|
setKey(memory.key);
|
||
|
|
setValue(memory.value);
|
||
|
|
setOriginalKey(memory.key);
|
||
|
|
}
|
||
|
|
}, [memory]);
|
||
|
|
|
||
|
|
const handleSave = () => {
|
||
|
|
if (!hasUpdateAccess || !memory) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!key.trim() || !value.trim()) {
|
||
|
|
showToast({
|
||
|
|
message: localize('com_ui_field_required'),
|
||
|
|
status: 'error',
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
updateMemory({
|
||
|
|
key: key.trim(),
|
||
|
|
value: value.trim(),
|
||
|
|
...(originalKey !== key.trim() && { originalKey }),
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||
|
|
if (e.key === 'Enter' && e.ctrlKey && hasUpdateAccess) {
|
||
|
|
handleSave();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<OGDialog open={open} onOpenChange={onOpenChange} triggerRef={triggerRef}>
|
||
|
|
{children}
|
||
|
|
<OGDialogTemplate
|
||
|
|
title={hasUpdateAccess ? localize('com_ui_edit_memory') : localize('com_ui_view_memory')}
|
||
|
|
showCloseButton={false}
|
||
|
|
className="w-11/12 md:max-w-lg"
|
||
|
|
main={
|
||
|
|
<div className="space-y-4">
|
||
|
|
{memory && (
|
||
|
|
<div className="space-y-2">
|
||
|
|
<div className="flex items-center justify-between text-xs text-text-secondary">
|
||
|
|
<div>
|
||
|
|
{localize('com_ui_date')}:{' '}
|
||
|
|
{new Date(memory.updated_at).toLocaleDateString(undefined, {
|
||
|
|
month: 'short',
|
||
|
|
day: 'numeric',
|
||
|
|
year: 'numeric',
|
||
|
|
hour: '2-digit',
|
||
|
|
minute: '2-digit',
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
{/* Token Information */}
|
||
|
|
{memory.tokenCount !== undefined && (
|
||
|
|
<div>
|
||
|
|
{memory.tokenCount.toLocaleString()}
|
||
|
|
{memData?.tokenLimit && ` / ${memData.tokenLimit.toLocaleString()}`}{' '}
|
||
|
|
{localize('com_ui_tokens')}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
{/* Overall Memory Usage */}
|
||
|
|
{memData?.tokenLimit && memData?.usagePercentage !== null && (
|
||
|
|
<div className="text-xs text-text-secondary">
|
||
|
|
{localize('com_ui_usage')}: {memData.usagePercentage}%{' '}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="memory-key" className="text-sm font-medium">
|
||
|
|
{localize('com_ui_key')}
|
||
|
|
</Label>
|
||
|
|
<Input
|
||
|
|
id="memory-key"
|
||
|
|
value={key}
|
||
|
|
onChange={(e) => hasUpdateAccess && setKey(e.target.value)}
|
||
|
|
onKeyDown={handleKeyPress}
|
||
|
|
placeholder={localize('com_ui_enter_key')}
|
||
|
|
className="w-full"
|
||
|
|
disabled={!hasUpdateAccess}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="memory-value" className="text-sm font-medium">
|
||
|
|
{localize('com_ui_value')}
|
||
|
|
</Label>
|
||
|
|
<textarea
|
||
|
|
id="memory-value"
|
||
|
|
value={value}
|
||
|
|
onChange={(e) => hasUpdateAccess && setValue(e.target.value)}
|
||
|
|
onKeyDown={handleKeyPress}
|
||
|
|
placeholder={localize('com_ui_enter_value')}
|
||
|
|
className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||
|
|
rows={3}
|
||
|
|
disabled={!hasUpdateAccess}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
buttons={
|
||
|
|
hasUpdateAccess ? (
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="submit"
|
||
|
|
onClick={handleSave}
|
||
|
|
disabled={isLoading || !key.trim() || !value.trim()}
|
||
|
|
className="text-white"
|
||
|
|
>
|
||
|
|
{isLoading ? <Spinner className="size-4" /> : localize('com_ui_save')}
|
||
|
|
</Button>
|
||
|
|
) : null
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
</OGDialog>
|
||
|
|
);
|
||
|
|
}
|