| | import React, { useState } from 'react'; |
| | import { PermissionTypes, Permissions } from 'librechat-data-provider'; |
| | import { |
| | OGDialog, |
| | OGDialogTemplate, |
| | Button, |
| | Label, |
| | Input, |
| | Spinner, |
| | useToastContext, |
| | } from '@librechat/client'; |
| | import { useCreateMemoryMutation } from '~/data-provider'; |
| | import { useLocalize, useHasAccess } from '~/hooks'; |
| |
|
| | interface MemoryCreateDialogProps { |
| | open: boolean; |
| | onOpenChange: (open: boolean) => void; |
| | children: React.ReactNode; |
| | triggerRef?: React.MutableRefObject<HTMLButtonElement | null>; |
| | } |
| |
|
| | export default function MemoryCreateDialog({ |
| | open, |
| | onOpenChange, |
| | children, |
| | triggerRef, |
| | }: MemoryCreateDialogProps) { |
| | const localize = useLocalize(); |
| | const { showToast } = useToastContext(); |
| |
|
| | const hasCreateAccess = useHasAccess({ |
| | permissionType: PermissionTypes.MEMORIES, |
| | permission: Permissions.CREATE, |
| | }); |
| |
|
| | const { mutate: createMemory, isLoading } = useCreateMemoryMutation({ |
| | onSuccess: () => { |
| | showToast({ |
| | message: localize('com_ui_memory_created'), |
| | status: 'success', |
| | }); |
| | onOpenChange(false); |
| | setKey(''); |
| | setValue(''); |
| | setTimeout(() => { |
| | triggerRef?.current?.focus(); |
| | }, 0); |
| | }, |
| | onError: (error: Error) => { |
| | let errorMessage = localize('com_ui_error'); |
| |
|
| | if (error && typeof error === 'object' && 'response' in error) { |
| | const axiosError = error as any; |
| | if (axiosError.response?.data?.error) { |
| | errorMessage = axiosError.response.data.error; |
| |
|
| | |
| | if (axiosError.response?.status === 409 || errorMessage.includes('already exists')) { |
| | errorMessage = localize('com_ui_memory_key_exists'); |
| | } |
| | |
| | else if (errorMessage.includes('lowercase letters and underscores')) { |
| | errorMessage = localize('com_ui_memory_key_validation'); |
| | } |
| | } |
| | } else if (error.message) { |
| | errorMessage = error.message; |
| | } |
| |
|
| | showToast({ |
| | message: errorMessage, |
| | status: 'error', |
| | }); |
| | }, |
| | }); |
| |
|
| | const [key, setKey] = useState(''); |
| | const [value, setValue] = useState(''); |
| |
|
| | const handleSave = () => { |
| | if (!hasCreateAccess) { |
| | return; |
| | } |
| |
|
| | if (!key.trim() || !value.trim()) { |
| | showToast({ |
| | message: localize('com_ui_field_required'), |
| | status: 'error', |
| | }); |
| | return; |
| | } |
| |
|
| | createMemory({ |
| | key: key.trim(), |
| | value: value.trim(), |
| | }); |
| | }; |
| |
|
| | const handleKeyPress = (e: React.KeyboardEvent) => { |
| | if (e.key === 'Enter' && e.ctrlKey && hasCreateAccess) { |
| | handleSave(); |
| | } |
| | }; |
| |
|
| | return ( |
| | <OGDialog open={open} onOpenChange={onOpenChange} triggerRef={triggerRef}> |
| | {children} |
| | <OGDialogTemplate |
| | title={localize('com_ui_create_memory')} |
| | showCloseButton={false} |
| | className="w-11/12 md:max-w-lg" |
| | main={ |
| | <div className="space-y-4"> |
| | <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) => setKey(e.target.value)} |
| | onKeyDown={handleKeyPress} |
| | placeholder={localize('com_ui_enter_key')} |
| | className="w-full" |
| | /> |
| | </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) => 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} |
| | /> |
| | </div> |
| | </div> |
| | } |
| | buttons={ |
| | <Button |
| | type="button" |
| | variant="submit" |
| | onClick={handleSave} |
| | disabled={isLoading || !key.trim() || !value.trim()} |
| | className="text-white" |
| | aria-label={localize('com_ui_create_memory')} |
| | > |
| | {isLoading ? <Spinner className="size-4" /> : localize('com_ui_create')} |
| | </Button> |
| | } |
| | /> |
| | </OGDialog> |
| | ); |
| | } |
| |
|