🔀 fix: Rerender Edge Cases After Migration to Shared Package (#8713)

* fix: render issues in PromptForm by decoupling nested dependencies as a result of @librechat/client components

* fix: MemoryViewer flicker by moving EditMemoryButton and DeleteMemoryButton outside of rendering

* fix: CategorySelector to use DropdownPopup for improved mobile compatibility

* chore: imports
This commit is contained in:
Danny Avila 2025-07-28 15:14:37 -04:00 committed by GitHub
parent 8e6eef04ab
commit a4ca4b7d9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 401 additions and 268 deletions

View file

@ -1,8 +1,10 @@
import React, { useCallback } from 'react';
import { Trash2 } from 'lucide-react';
import { useDeletePrompt } from '~/data-provider';
import { Button, OGDialog, OGDialogTrigger, Label, OGDialogTemplate } from '@librechat/client';
import { useLocalize } from '~/hooks';
const DeleteVersion = ({
const DeleteConfirmDialog = ({
name,
disabled,
selectHandler,
@ -58,4 +60,42 @@ const DeleteVersion = ({
);
};
export default DeleteVersion;
interface DeletePromptProps {
promptId?: string;
groupId: string;
promptName: string;
disabled: boolean;
}
const DeletePrompt = React.memo(
({ promptId, groupId, promptName, disabled }: DeletePromptProps) => {
const deletePromptMutation = useDeletePrompt();
const handleDelete = useCallback(() => {
if (!promptId) {
console.warn('No prompt ID provided for deletion');
return;
}
deletePromptMutation.mutate({
_id: promptId,
groupId,
});
}, [promptId, groupId, deletePromptMutation]);
if (!promptId) {
return null;
}
return (
<DeleteConfirmDialog
name={promptName}
disabled={disabled || !promptId}
selectHandler={handleDelete}
/>
);
},
);
DeletePrompt.displayName = 'DeletePrompt';
export default DeletePrompt;

View file

@ -1,10 +1,13 @@
import React, { useMemo } from 'react';
import { Dropdown } from '@librechat/client';
import React, { useMemo, useState } from 'react';
import * as Ariakit from '@ariakit/react';
import { useTranslation } from 'react-i18next';
import { useFormContext, Controller } from 'react-hook-form';
import { DropdownPopup } from '@librechat/client';
import { LocalStorageKeys } from 'librechat-data-provider';
import { useFormContext, Controller } from 'react-hook-form';
import type { MenuItemProps } from '@librechat/client';
import type { ReactNode } from 'react';
import { useCategories } from '~/hooks';
import { cn } from '~/utils';
interface CategorySelectorProps {
currentCategory?: string;
@ -20,10 +23,11 @@ const CategorySelector: React.FC<CategorySelectorProps> = ({
const { t } = useTranslation();
const formContext = useFormContext();
const { categories, emptyCategory } = useCategories();
const [isOpen, setIsOpen] = useState(false);
const control = formContext.control;
const watch = formContext.watch;
const setValue = formContext.setValue;
const control = formContext?.control;
const watch = formContext?.watch;
const setValue = formContext?.setValue;
const watchedCategory = watch ? watch('category') : currentCategory;
@ -46,53 +50,71 @@ const CategorySelector: React.FC<CategorySelectorProps> = ({
return categoryOption;
}, [categoryOption, t]);
const menuItems: MenuItemProps[] = useMemo(() => {
if (!categories) return [];
return categories.map((category) => ({
id: category.value,
label: category.label,
icon: 'icon' in category ? category.icon : undefined,
onClick: () => {
const value = category.value || '';
if (formContext && setValue) {
setValue('category', value, { shouldDirty: false });
}
localStorage.setItem(LocalStorageKeys.LAST_PROMPT_CATEGORY, value);
onValueChange?.(value);
setIsOpen(false);
},
}));
}, [categories, formContext, setValue, onValueChange]);
const trigger = (
<Ariakit.MenuButton
className={cn(
'focus:ring-offset-ring-offset relative inline-flex items-center justify-between rounded-xl border border-input bg-background px-3 py-2 text-sm text-text-primary transition-all duration-200 ease-in-out hover:bg-accent hover:text-accent-foreground focus:ring-ring-primary',
'w-fit gap-2',
className,
)}
onClick={() => setIsOpen(!isOpen)}
aria-label="Prompt's category selector"
aria-labelledby="category-selector-label"
>
<div className="flex items-center space-x-2">
{'icon' in displayCategory && displayCategory.icon != null && (
<span>{displayCategory.icon as ReactNode}</span>
)}
<span>{displayCategory.value ? displayCategory.label : t('com_ui_category')}</span>
</div>
<Ariakit.MenuButtonArrow />
</Ariakit.MenuButton>
);
return formContext ? (
<Controller
name="category"
control={control}
render={() => (
<Dropdown
value={displayCategory.value ?? ''}
label={displayCategory.value ? undefined : t('com_ui_category')}
onChange={(value: string) => {
setValue('category', value, { shouldDirty: false });
localStorage.setItem(LocalStorageKeys.LAST_PROMPT_CATEGORY, value);
onValueChange?.(value);
}}
aria-labelledby="category-selector-label"
ariaLabel="Prompt's category selector"
className={className}
options={categories || []}
renderValue={() => (
<div className="flex items-center space-x-2">
{'icon' in displayCategory && displayCategory.icon != null && (
<span>{displayCategory.icon as ReactNode}</span>
)}
<span>{displayCategory.label}</span>
</div>
)}
<DropdownPopup
trigger={trigger}
items={menuItems}
isOpen={isOpen}
setIsOpen={setIsOpen}
menuId="category-selector-menu"
className="mt-2"
portal={true}
/>
)}
/>
) : (
<Dropdown
value={currentCategory ?? ''}
onChange={(value: string) => {
localStorage.setItem(LocalStorageKeys.LAST_PROMPT_CATEGORY, value);
onValueChange?.(value);
}}
aria-labelledby="category-selector-label"
ariaLabel="Prompt's category selector"
className={className}
options={categories || []}
renderValue={() => (
<div className="flex items-center space-x-2">
{'icon' in displayCategory && displayCategory.icon != null && (
<span>{displayCategory.icon as ReactNode}</span>
)}
<span>{displayCategory.label}</span>
</div>
)}
<DropdownPopup
trigger={trigger}
items={menuItems}
isOpen={isOpen}
setIsOpen={setIsOpen}
menuId="category-selector-menu"
className="mt-2"
portal={true}
/>
);
};

View file

@ -1,4 +1,5 @@
import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
import React from 'react';
import debounce from 'lodash/debounce';
import { useRecoilValue } from 'recoil';
import { Menu, Rocket } from 'lucide-react';
@ -6,14 +7,13 @@ import { useForm, FormProvider } from 'react-hook-form';
import { useParams, useOutletContext } from 'react-router-dom';
import { Button, Skeleton, useToastContext } from '@librechat/client';
import { SystemRoles, PermissionTypes, Permissions } from 'librechat-data-provider';
import type { TCreatePrompt } from 'librechat-data-provider';
import type { TCreatePrompt, TPrompt, TPromptGroup } from 'librechat-data-provider';
import {
useCreatePrompt,
useGetPrompts,
useCreatePrompt,
useGetPromptGroup,
useUpdatePromptGroup,
useMakePromptProduction,
useDeletePrompt,
} from '~/data-provider';
import { useAuthContext, usePromptGroupsNav, useHasAccess, useLocalize } from '~/hooks';
import CategorySelector from './Groups/CategorySelector';
@ -22,7 +22,7 @@ import PromptVariables from './PromptVariables';
import { cn, findPromptGroup } from '~/utils';
import PromptVersions from './PromptVersions';
import { PromptsEditorMode } from '~/common';
import DeleteConfirm from './DeleteVersion';
import DeleteVersion from './DeleteVersion';
import PromptDetails from './PromptDetails';
import PromptEditor from './PromptEditor';
import SkeletonForm from './SkeletonForm';
@ -32,16 +32,136 @@ import PromptName from './PromptName';
import Command from './Command';
import store from '~/store';
interface RightPanelProps {
group: TPromptGroup;
prompts: TPrompt[];
selectedPrompt: any;
selectionIndex: number;
selectedPromptId?: string;
isLoadingPrompts: boolean;
setSelectionIndex: React.Dispatch<React.SetStateAction<number>>;
}
const RightPanel = React.memo(
({
group,
prompts,
selectedPrompt,
selectedPromptId,
isLoadingPrompts,
selectionIndex,
setSelectionIndex,
}: RightPanelProps) => {
const localize = useLocalize();
const { showToast } = useToastContext();
const editorMode = useRecoilValue(store.promptsEditorMode);
const hasShareAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.SHARED_GLOBAL,
});
const updateGroupMutation = useUpdatePromptGroup({
onError: () => {
showToast({
status: 'error',
message: localize('com_ui_prompt_update_error'),
});
},
});
const makeProductionMutation = useMakePromptProduction();
const groupId = group?._id || '';
const groupName = group?.name || '';
const groupCategory = group?.category || '';
const isLoadingGroup = !group;
return (
<div
className="h-full w-full overflow-y-auto bg-surface-primary px-4"
style={{ maxHeight: 'calc(100vh - 100px)' }}
>
<div className="mb-2 flex flex-col lg:flex-row lg:items-center lg:justify-center lg:gap-x-2 xl:flex-row xl:space-y-0">
<CategorySelector
currentCategory={groupCategory}
onValueChange={(value) =>
updateGroupMutation.mutate({
id: groupId,
payload: { name: groupName, category: value },
})
}
/>
<div className="mt-2 flex flex-row items-center justify-center gap-x-2 lg:mt-0">
{hasShareAccess && <SharePrompt group={group} disabled={isLoadingGroup} />}
{editorMode === PromptsEditorMode.ADVANCED && (
<Button
variant="submit"
size="sm"
aria-label="Make prompt production"
className="h-10 w-10 border border-transparent p-0.5 transition-all"
onClick={() => {
if (!selectedPrompt) {
console.warn('No prompt is selected');
return;
}
const { _id: promptVersionId = '', prompt } = selectedPrompt;
makeProductionMutation.mutate({
id: promptVersionId,
groupId,
productionPrompt: { prompt },
});
}}
disabled={
isLoadingGroup ||
!selectedPrompt ||
selectedPrompt._id === group?.productionId ||
makeProductionMutation.isLoading
}
>
<Rocket className="size-5 cursor-pointer text-white" />
</Button>
)}
<DeleteVersion
promptId={selectedPromptId}
groupId={groupId}
promptName={groupName}
disabled={isLoadingGroup}
/>
</div>
</div>
{editorMode === PromptsEditorMode.ADVANCED &&
(isLoadingPrompts
? Array.from({ length: 6 }).map((_, index: number) => (
<div key={index} className="my-2">
<Skeleton className="h-[72px] w-full" />
</div>
))
: prompts.length > 0 && (
<PromptVersions
group={group}
prompts={prompts}
selectionIndex={selectionIndex}
setSelectionIndex={setSelectionIndex}
/>
))}
</div>
);
},
);
RightPanel.displayName = 'RightPanel';
const PromptForm = () => {
const params = useParams();
const localize = useLocalize();
const { user } = useAuthContext();
const alwaysMakeProd = useRecoilValue(store.alwaysMakeProd);
const { showToast } = useToastContext();
const alwaysMakeProd = useRecoilValue(store.alwaysMakeProd);
const promptId = params.promptId || '';
const [selectionIndex, setSelectionIndex] = useState<number>(0);
const editorMode = useRecoilValue(store.promptsEditorMode);
const [selectionIndex, setSelectionIndex] = useState<number>(0);
const prevIsEditingRef = useRef(false);
const [isEditing, setIsEditing] = useState(false);
const [initialLoad, setInitialLoad] = useState(true);
@ -72,11 +192,9 @@ const PromptForm = () => {
[prompts, selectionIndex],
);
const selectedPromptId = useMemo(() => selectedPrompt?._id, [selectedPrompt?._id]);
const { groupsQuery } = useOutletContext<ReturnType<typeof usePromptGroupsNav>>();
const hasShareAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.SHARED_GLOBAL,
});
const updateGroupMutation = useUpdatePromptGroup({
onError: () => {
@ -88,7 +206,6 @@ const PromptForm = () => {
});
const makeProductionMutation = useMakePromptProduction();
const deletePromptMutation = useDeletePrompt();
const createPromptMutation = useCreatePrompt({
onMutate: (variables) => {
@ -177,24 +294,40 @@ const PromptForm = () => {
return () => window.removeEventListener('resize', handleResize);
}, []);
const debouncedUpdateOneliner = useCallback(
debounce((oneliner: string) => {
if (!group || !group._id) {
return console.warn('Group not found');
}
updateGroupMutation.mutate({ id: group._id, payload: { oneliner } });
}, 950),
[updateGroupMutation, group],
const debouncedUpdateOneliner = useMemo(
() =>
debounce((groupId: string, oneliner: string, mutate: any) => {
mutate({ id: groupId, payload: { oneliner } });
}, 950),
[],
);
const debouncedUpdateCommand = useCallback(
debounce((command: string) => {
const debouncedUpdateCommand = useMemo(
() =>
debounce((groupId: string, command: string, mutate: any) => {
mutate({ id: groupId, payload: { command } });
}, 950),
[],
);
const handleUpdateOneliner = useCallback(
(oneliner: string) => {
if (!group || !group._id) {
return console.warn('Group not found');
}
updateGroupMutation.mutate({ id: group._id, payload: { command } });
}, 950),
[updateGroupMutation, group],
debouncedUpdateOneliner(group._id, oneliner, updateGroupMutation.mutate);
},
[group, updateGroupMutation.mutate, debouncedUpdateOneliner],
);
const handleUpdateCommand = useCallback(
(command: string) => {
if (!group || !group._id) {
return console.warn('Group not found');
}
debouncedUpdateCommand(group._id, command, updateGroupMutation.mutate);
},
[group, updateGroupMutation.mutate, debouncedUpdateCommand],
);
if (initialLoad) {
@ -217,89 +350,7 @@ const PromptForm = () => {
return null;
}
const groupId = group._id;
const groupName = group.name;
const groupCategory = group.category;
const RightPanel = () => (
<div
className="h-full w-full overflow-y-auto bg-surface-primary px-4"
style={{ maxHeight: 'calc(100vh - 100px)' }}
>
<div className="mb-2 flex flex-col lg:flex-row lg:items-center lg:justify-center lg:gap-x-2 xl:flex-row xl:space-y-0">
<CategorySelector
currentCategory={groupCategory}
onValueChange={(value) =>
updateGroupMutation.mutate({
id: groupId,
payload: { name: groupName, category: value },
})
}
/>
<div className="mt-2 flex flex-row items-center justify-center gap-x-2 lg:mt-0">
{hasShareAccess && <SharePrompt group={group} disabled={isLoadingGroup} />}
{editorMode === PromptsEditorMode.ADVANCED && (
<Button
variant="submit"
size="sm"
aria-label="Make prompt production"
className="h-10 w-10 border border-transparent p-0.5 transition-all"
onClick={() => {
if (!selectedPrompt) {
console.warn('No prompt is selected');
return;
}
const { _id: promptVersionId = '', prompt } = selectedPrompt;
makeProductionMutation.mutate({
id: promptVersionId,
groupId,
productionPrompt: { prompt },
});
}}
disabled={
isLoadingGroup ||
!selectedPrompt ||
selectedPrompt._id === group.productionId ||
makeProductionMutation.isLoading
}
>
<Rocket className="size-5 cursor-pointer text-white" />
</Button>
)}
<DeleteConfirm
name={groupName}
disabled={isLoadingGroup}
selectHandler={() => {
if (!selectedPrompt || !selectedPrompt._id) {
console.warn('No prompt is selected or prompt _id is missing');
return;
}
deletePromptMutation.mutate({
_id: selectedPrompt._id,
groupId,
});
}}
/>
</div>
</div>
{editorMode === PromptsEditorMode.ADVANCED &&
(isLoadingPrompts
? Array.from({ length: 6 }).map((_, index: number) => (
<div key={index} className="my-2">
<Skeleton className="h-[72px] w-full" />
</div>
))
: prompts.length > 0 && (
<PromptVersions
group={group}
prompts={prompts}
selectionIndex={selectionIndex}
setSelectionIndex={setSelectionIndex}
/>
))}
</div>
);
return (
<FormProvider {...methods}>
@ -339,7 +390,17 @@ const PromptForm = () => {
<Menu className="size-5" />
</Button>
<div className="hidden lg:block">
{editorMode === PromptsEditorMode.SIMPLE && <RightPanel />}
{editorMode === PromptsEditorMode.SIMPLE && (
<RightPanel
group={group}
prompts={prompts}
selectedPrompt={selectedPrompt}
selectionIndex={selectionIndex}
selectedPromptId={selectedPromptId}
isLoadingPrompts={isLoadingPrompts}
setSelectionIndex={setSelectionIndex}
/>
)}
</div>
</>
)}
@ -352,11 +413,11 @@ const PromptForm = () => {
<PromptVariables promptText={promptText} />
<Description
initialValue={group.oneliner ?? ''}
onValueChange={debouncedUpdateOneliner}
onValueChange={handleUpdateOneliner}
/>
<Command
initialValue={group.command ?? ''}
onValueChange={debouncedUpdateCommand}
onValueChange={handleUpdateCommand}
/>
</div>
)}
@ -364,7 +425,15 @@ const PromptForm = () => {
{editorMode === PromptsEditorMode.ADVANCED && (
<div className="hidden w-1/4 border-l border-border-light lg:block">
<RightPanel />
<RightPanel
group={group}
prompts={prompts}
selectionIndex={selectionIndex}
selectedPrompt={selectedPrompt}
selectedPromptId={selectedPromptId}
isLoadingPrompts={isLoadingPrompts}
setSelectionIndex={setSelectionIndex}
/>
</div>
)}
</div>
@ -395,7 +464,15 @@ const PromptForm = () => {
>
<div className="h-full">
<div className="h-full overflow-auto">
<RightPanel />
<RightPanel
group={group}
prompts={prompts}
selectionIndex={selectionIndex}
selectedPrompt={selectedPrompt}
selectedPromptId={selectedPromptId}
isLoadingPrompts={isLoadingPrompts}
setSelectionIndex={setSelectionIndex}
/>
</div>
</div>
</div>

View file

@ -4,18 +4,18 @@ import { Plus } from 'lucide-react';
import { matchSorter } from 'match-sorter';
import { SystemRoles, PermissionTypes, Permissions } from 'librechat-data-provider';
import {
Spinner,
EditIcon,
TrashIcon,
Table,
Input,
Label,
Button,
Switch,
Spinner,
TableRow,
OGDialog,
EditIcon,
TableHead,
TableBody,
TrashIcon,
TableCell,
TableHeader,
TooltipAnchor,
@ -25,10 +25,10 @@ import {
} from '@librechat/client';
import type { TUserMemory } from 'librechat-data-provider';
import {
useGetUserQuery,
useMemoriesQuery,
useDeleteMemoryMutation,
useUpdateMemoryPreferencesMutation,
useDeleteMemoryMutation,
useMemoriesQuery,
useGetUserQuery,
} from '~/data-provider';
import { useLocalize, useAuthContext, useHasAccess } from '~/hooks';
import MemoryCreateDialog from './MemoryCreateDialog';
@ -36,18 +36,114 @@ 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<HTMLDivElement>(null);
return (
<MemoryEditDialog
open={open}
memory={memory}
onOpenChange={setOpen}
triggerRef={triggerRef as React.MutableRefObject<HTMLButtonElement | null>}
>
<OGDialogTrigger asChild>
<TooltipAnchor
description={localize('com_ui_edit_memory')}
render={
<Button
variant="ghost"
aria-label={localize('com_ui_bookmarks_edit')}
onClick={() => setOpen(!open)}
className="h-8 w-8 p-0"
>
<EditIcon />
</Button>
}
/>
</OGDialogTrigger>
</MemoryEditDialog>
);
};
const DeleteMemoryButton = ({ memory }: { memory: TUserMemory }) => {
const localize = useLocalize();
const { showToast } = useToastContext();
const [open, setOpen] = useState(false);
const { mutate: deleteMemory } = useDeleteMemoryMutation();
const [deletingKey, setDeletingKey] = useState<string | null>(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 (
<OGDialog open={open} onOpenChange={setOpen}>
<OGDialogTrigger asChild>
<TooltipAnchor
description={localize('com_ui_delete_memory')}
render={
<Button
variant="ghost"
aria-label={localize('com_ui_delete')}
onClick={() => setOpen(!open)}
className="h-8 w-8 p-0"
>
{deletingKey === memory.key ? (
<Spinner className="size-4 animate-spin" />
) : (
<TrashIcon className="size-4" />
)}
</Button>
}
/>
</OGDialogTrigger>
<OGDialogTemplate
showCloseButton={false}
title={localize('com_ui_delete_memory')}
className="w-11/12 max-w-lg"
main={
<Label className="text-left text-sm font-medium">
{localize('com_ui_delete_confirm')} &quot;{memory.key}&quot;?
</Label>
}
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'),
}}
/>
</OGDialog>
);
};
const pageSize = 10;
export default function MemoryViewer() {
const localize = useLocalize();
const { user } = useAuthContext();
const { data: userData } = useGetUserQuery();
const { data: memData, isLoading } = useMemoriesQuery();
const { mutate: deleteMemory } = useDeleteMemoryMutation();
const { showToast } = useToastContext();
const [pageIndex, setPageIndex] = useState(0);
const [searchQuery, setSearchQuery] = useState('');
const pageSize = 10;
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [deletingKey, setDeletingKey] = useState<string | null>(null);
const [referenceSavedMemories, setReferenceSavedMemories] = useState(true);
const updateMemoryPreferencesMutation = useUpdateMemoryPreferencesMutation({
@ -119,108 +215,6 @@ export default function MemoryViewer() {
return 'stroke-green-500';
};
const EditMemoryButton = ({ memory }: { memory: TUserMemory }) => {
const [open, setOpen] = useState(false);
const triggerRef = useRef<HTMLDivElement>(null);
// Only show edit button if user has UPDATE permission
if (!hasUpdateAccess) {
return null;
}
return (
<MemoryEditDialog
open={open}
memory={memory}
onOpenChange={setOpen}
triggerRef={triggerRef as React.MutableRefObject<HTMLButtonElement | null>}
>
<OGDialogTrigger asChild>
<TooltipAnchor
description={localize('com_ui_edit_memory')}
render={
<Button
variant="ghost"
aria-label={localize('com_ui_bookmarks_edit')}
onClick={() => setOpen(!open)}
className="h-8 w-8 p-0"
>
<EditIcon />
</Button>
}
/>
</OGDialogTrigger>
</MemoryEditDialog>
);
};
const DeleteMemoryButton = ({ memory }: { memory: TUserMemory }) => {
const [open, setOpen] = useState(false);
if (!hasUpdateAccess) {
return 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 (
<OGDialog open={open} onOpenChange={setOpen}>
<OGDialogTrigger asChild>
<TooltipAnchor
description={localize('com_ui_delete_memory')}
render={
<Button
variant="ghost"
aria-label={localize('com_ui_delete')}
onClick={() => setOpen(!open)}
className="h-8 w-8 p-0"
>
{deletingKey === memory.key ? (
<Spinner className="size-4 animate-spin" />
) : (
<TrashIcon className="size-4" />
)}
</Button>
}
/>
</OGDialogTrigger>
<OGDialogTemplate
showCloseButton={false}
title={localize('com_ui_delete_memory')}
className="w-11/12 max-w-lg"
main={
<Label className="text-left text-sm font-medium">
{localize('com_ui_delete_confirm')} &quot;{memory.key}&quot;?
</Label>
}
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'),
}}
/>
</OGDialog>
);
};
if (isLoading) {
return (
<div className="flex h-full w-full items-center justify-center p-4">