🎨 style: bookmarks UI update (#3479)

* style: bookmarks update; style(Files): minor update

* style: update conversation bookmarks

* style: fix w TableCell
This commit is contained in:
Marco Beretta 2024-07-29 19:25:36 -04:00 committed by GitHub
parent 3fd25920d4
commit 2d5f704695
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 223 additions and 235 deletions

View file

@ -1,8 +1,7 @@
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { DialogTrigger } from '@radix-ui/react-dialog';
import { TConversationTag, TConversation } from 'librechat-data-provider'; import { TConversationTag, TConversation } from 'librechat-data-provider';
import DialogTemplate from '~/components/ui/DialogTemplate'; import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
import { Dialog, DialogButton } from '~/components/ui/'; import { OGDialog, OGDialogTrigger, OGDialogClose } from '~/components/ui/';
import BookmarkForm from './BookmarkForm'; import BookmarkForm from './BookmarkForm';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import { Spinner } from '../svg'; import { Spinner } from '../svg';
@ -33,9 +32,9 @@ const BookmarkEditDialog = ({
}; };
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <OGDialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{trigger}</DialogTrigger> <OGDialogTrigger asChild>{trigger}</OGDialogTrigger>
<DialogTemplate <OGDialogTemplate
title="Bookmark" title="Bookmark"
className="w-11/12 sm:w-1/4" className="w-11/12 sm:w-1/4"
showCloseButton={false} showCloseButton={false}
@ -51,18 +50,19 @@ const BookmarkEditDialog = ({
/> />
} }
buttons={ buttons={
<div className="mb-6 md:mb-2"> <OGDialogClose asChild>
<DialogButton <button
type="submit"
disabled={isLoading} disabled={isLoading}
onClick={handleSubmitForm} onClick={handleSubmitForm}
className="bg-green-500 text-white hover:bg-green-600 dark:hover:bg-green-600" className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
> >
{isLoading ? <Spinner /> : localize('com_ui_save')} {isLoading ? <Spinner /> : localize('com_ui_save')}
</DialogButton> </button>
</div> </OGDialogClose>
} }
/> />
</Dialog> </OGDialog>
); );
}; };

View file

@ -130,7 +130,7 @@ const BookmarkForm = ({
aria-invalid={!!errors.tag} aria-invalid={!!errors.tag}
className={cn( className={cn(
defaultTextProps, defaultTextProps,
'flex h-10 max-h-10 w-full resize-none border-gray-100 px-3 py-2 dark:border-gray-600', 'flex h-10 max-h-10 w-full resize-none px-3 py-2',
removeFocusOutlines, removeFocusOutlines,
)} )}
placeholder=" " placeholder=" "
@ -158,7 +158,7 @@ const BookmarkForm = ({
/> />
</div> </div>
{conversation && ( {conversation && (
<div className="flex w-full items-center"> <div className="mt-2 flex w-full items-center">
<Controller <Controller
name="addToConversation" name="addToConversation"
control={control} control={control}

View file

@ -32,7 +32,7 @@ const BookmarkItem: FC<MenuItemProps> = ({
<div <div
role="menuitem" role="menuitem"
className={cn( className={cn(
'group m-1.5 flex cursor-pointer gap-2 rounded px-1 py-2.5 !pr-3 text-sm !opacity-100 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50', 'group m-1.5 flex cursor-pointer gap-2 rounded px-2 py-2.5 !pr-3 text-sm !opacity-100 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50',
'hover:bg-black/5 dark:hover:bg-white/5', 'hover:bg-black/5 dark:hover:bg-white/5',
highlightSelected && selected && 'bg-black/5 dark:bg-white/5', highlightSelected && selected && 'bg-black/5 dark:bg-white/5',
)} )}
@ -58,8 +58,8 @@ const BookmarkItem: FC<MenuItemProps> = ({
<div className="flex items-center justify-end"> <div className="flex items-center justify-end">
<span <span
className={cn( className={cn(
'ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-white px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600 ring-1 ring-inset ring-gray-200', 'ml-auto w-9 min-w-max whitespace-nowrap rounded-md bg-white px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600',
'dark:bg-gray-800 dark:text-white dark:ring-gray-100/50', 'dark:bg-gray-800 dark:text-white',
)} )}
aria-hidden="true" aria-hidden="true"
> >

View file

@ -8,7 +8,12 @@ import { TrashIcon } from '~/components/svg';
import { Label } from '~/components/ui'; import { Label } from '~/components/ui';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
const DeleteBookmarkButton: FC<{ bookmark: string }> = ({ bookmark }) => { const DeleteBookmarkButton: FC<{
bookmark: string;
tabIndex?: number;
onFocus?: () => void;
onBlur?: () => void;
}> = ({ bookmark, tabIndex = 0, onFocus, onBlur }) => {
const localize = useLocalize(); const localize = useLocalize();
const { showToast } = useToastContext(); const { showToast } = useToastContext();
@ -37,13 +42,17 @@ const DeleteBookmarkButton: FC<{ bookmark: string }> = ({ bookmark }) => {
title="Delete Bookmark" title="Delete Bookmark"
confirmMessage={ confirmMessage={
<Label htmlFor="bookmark" className="text-left text-sm font-medium"> <Label htmlFor="bookmark" className="text-left text-sm font-medium">
{localize('com_ui_bookmark_delete_confirm')} : {bookmark} {localize('com_ui_bookmark_delete_confirm')} {bookmark}
</Label> </Label>
} }
confirm={confirmDelete} confirm={confirmDelete}
className="hover:text-gray-300 focus-visible:bg-gray-100 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600" className="transition-color flex h-7 w-7 min-w-7 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
icon={<TrashIcon className="size-4" />} icon={<TrashIcon className="size-4" />}
tabIndex={tabIndex}
onFocus={onFocus}
onBlur={onBlur}
/> />
); );
}; };
export default DeleteBookmarkButton; export default DeleteBookmarkButton;

View file

@ -3,21 +3,30 @@ import type { TConversationTag } from 'librechat-data-provider';
import BookmarkEditDialog from './BookmarkEditDialog'; import BookmarkEditDialog from './BookmarkEditDialog';
import { EditIcon } from '~/components/svg'; import { EditIcon } from '~/components/svg';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
const EditBookmarkButton: FC<{ bookmark: TConversationTag }> = ({ bookmark }) => {
const EditBookmarkButton: FC<{
bookmark: TConversationTag;
tabIndex?: number;
onFocus?: () => void;
onBlur?: () => void;
}> = ({ bookmark, tabIndex = 0, onFocus, onBlur }) => {
const localize = useLocalize(); const localize = useLocalize();
return ( return (
<BookmarkEditDialog <BookmarkEditDialog
bookmark={bookmark} bookmark={bookmark}
trigger={ trigger={
<button className="size-4 hover:text-gray-300 focus-visible:bg-gray-100 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600"> <button
type="button"
className="transition-color flex h-7 w-7 min-w-7 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
tabIndex={tabIndex}
onFocus={onFocus}
onBlur={onBlur}
>
<TooltipProvider delayDuration={250}> <TooltipProvider delayDuration={250}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<span> <EditIcon />
<EditIcon />
</span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="top" sideOffset={0}> <TooltipContent side="top" sideOffset={0}>
{localize('com_ui_edit')} {localize('com_ui_edit')}

View file

@ -58,7 +58,7 @@ export const BookmarkMenuItems: FC<{
trigger={ trigger={
<div <div
role="menuitem" role="menuitem"
className="group m-1.5 flex cursor-pointer gap-2 rounded px-1 !pr-3.5 pb-2.5 pt-3 text-sm !opacity-100 hover:bg-black/5 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-white/5" className="group m-1.5 flex cursor-pointer gap-2 rounded px-2 !pr-3.5 pb-2.5 pt-3 text-sm !opacity-100 hover:bg-black/5 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-white/5"
tabIndex={-1} tabIndex={-1}
> >
<div className="flex grow items-center justify-between gap-2"> <div className="flex grow items-center justify-between gap-2">

View file

@ -1,19 +1,14 @@
import { BookmarkPlusIcon } from 'lucide-react'; import { BookmarkPlusIcon } from 'lucide-react';
import type { FC } from 'react';
import { useConversationTagsQuery, useRebuildConversationTagsMutation } from '~/data-provider'; import { useConversationTagsQuery, useRebuildConversationTagsMutation } from '~/data-provider';
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui'; import { Button } from '~/components/ui';
import { BookmarkContext } from '~/Providers/BookmarkContext'; import { BookmarkContext } from '~/Providers/BookmarkContext';
import { BookmarkEditDialog } from '~/components/Bookmarks'; import { BookmarkEditDialog } from '~/components/Bookmarks';
import BookmarkTable from './BookmarkTable'; import BookmarkTable from './BookmarkTable';
import { Spinner } from '~/components/svg'; import { Spinner } from '~/components/svg';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import { cn } from '~/utils/';
import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings'; import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings';
const BookmarkPanel: FC<{ open: boolean; onOpenChange: (open: boolean) => void }> = ({ const BookmarkPanel = () => {
open,
onOpenChange,
}) => {
const localize = useLocalize(); const localize = useLocalize();
const { mutate, isLoading } = useRebuildConversationTagsMutation(); const { mutate, isLoading } = useRebuildConversationTagsMutation();
const { data } = useConversationTagsQuery(); const { data } = useConversationTagsQuery();
@ -21,52 +16,31 @@ const BookmarkPanel: FC<{ open: boolean; onOpenChange: (open: boolean) => void }
mutate({}); mutate({});
}; };
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <div className="h-auto max-w-full overflow-x-hidden">
<DialogContent <BookmarkContext.Provider value={{ bookmarks: data || [] }}>
showCloseButton={true} <BookmarkTable />
className={cn( <div className="flex justify-between gap-2">
'overflow-x-auto shadow-2xl dark:bg-gray-700 dark:text-white md:max-h-[600px] md:min-h-[373px] md:w-[680px]', <Button variant="outline" onClick={rebuildTags} className="w-50 text-sm">
)} {isLoading ? (
> <Spinner />
<DialogHeader> ) : (
<DialogTitle className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
{localize('com_ui_bookmarks')}
</DialogTitle>
</DialogHeader>
<BookmarkContext.Provider value={{ bookmarks: data || [] }}>
<div className="p-0 sm:p-6 sm:pt-4">
<BookmarkTable />
<div className="mt-5 sm:mt-4" />
<div className="flex justify-between gap-2 pr-2 sm:pr-0">
<Button variant="outline" onClick={rebuildTags} className="text-sm">
{isLoading ? (
<Spinner />
) : (
<div className="flex gap-2">
{localize('com_ui_bookmarks_rebuild')}
<HoverCardSettings side="bottom" text="com_nav_info_bookmarks_rebuild" />
</div>
)}
</Button>
<div className="flex gap-2"> <div className="flex gap-2">
<BookmarkEditDialog {localize('com_ui_bookmarks_rebuild')}
trigger={ <HoverCardSettings side="top" text="com_nav_info_bookmarks_rebuild" />
<Button variant="outline" onClick={rebuildTags} className="text-sm">
<BookmarkPlusIcon className="mr-1 size-4" />
<div className="break-all">{localize('com_ui_bookmarks_new')}</div>
</Button>
}
/>
<Button variant="subtle" onClick={() => onOpenChange(!open)} className="text-sm">
{localize('com_ui_close')}
</Button>
</div> </div>
</div> )}
</div> </Button>
</BookmarkContext.Provider> <BookmarkEditDialog
</DialogContent> trigger={
</Dialog> <Button variant="outline" onClick={rebuildTags} className="w-full text-sm">
<BookmarkPlusIcon className="mr-1 size-4" />
<div className="break-all">{localize('com_ui_bookmarks_new')}</div>
</Button>
}
/>
</div>
</BookmarkContext.Provider>
</div>
); );
}; };
export default BookmarkPanel; export default BookmarkPanel;

View file

@ -1,13 +1,16 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import type { ConversationTagsResponse, TConversationTag } from 'librechat-data-provider'; import type { ConversationTagsResponse, TConversationTag } from 'librechat-data-provider';
import { Table, TableHeader, TableBody, TableRow, TableCell, Input, Button } from '~/components/ui';
import { BookmarkContext, useBookmarkContext } from '~/Providers/BookmarkContext'; import { BookmarkContext, useBookmarkContext } from '~/Providers/BookmarkContext';
import BookmarkTableRow from './BookmarkTableRow'; import BookmarkTableRow from './BookmarkTableRow';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
const BookmarkTable = () => { const BookmarkTable = () => {
const localize = useLocalize(); const localize = useLocalize();
const [rows, setRows] = useState<ConversationTagsResponse>([]); const [rows, setRows] = useState<ConversationTagsResponse>([]);
const [pageIndex, setPageIndex] = useState(0);
const [searchQuery, setSearchQuery] = useState('');
const pageSize = 10;
const { bookmarks } = useBookmarkContext(); const { bookmarks } = useBookmarkContext();
useEffect(() => { useEffect(() => {
@ -27,30 +30,65 @@ const BookmarkTable = () => {
return <BookmarkTableRow key={row.tag} moveRow={moveRow} row={row} position={position} />; return <BookmarkTableRow key={row.tag} moveRow={moveRow} row={row} position={position} />;
}, []); }, []);
const filteredRows = rows.filter((row) =>
row.tag.toLowerCase().includes(searchQuery.toLowerCase()),
);
const currentRows = filteredRows.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
return ( return (
<BookmarkContext.Provider value={{ bookmarks }}> <BookmarkContext.Provider value={{ bookmarks }}>
<div <div className="flex items-center gap-4 py-4">
className={cn( <Input
'container', placeholder={localize('com_ui_bookmarks_filter')}
'relative h-[300px] overflow-auto', value={searchQuery}
'-mx-4 w-auto ring-1 ring-gray-300 sm:mx-0 sm:rounded-lg', onChange={(e) => setSearchQuery(e.target.value)}
)} className="w-full dark:border-gray-700"
> />
<table className="min-w-full divide-gray-300"> </div>
<thead className="sticky top-0 z-10 border-b bg-white"> <div className="overflow-y-auto rounded-md border border-black/10 dark:border-white/10">
<tr className="text-left text-sm font-semibold text-gray-900"> <Table className="table-fixed border-separate border-spacing-0">
<th className="w-96 px-3 py-3.5 pl-6"> <TableHeader>
<TableRow>
<TableCell className="w-full px-3 py-3.5 pl-6 dark:bg-gray-700">
<div>{localize('com_ui_bookmarks_title')}</div> <div>{localize('com_ui_bookmarks_title')}</div>
</th> </TableCell>
<th className="w-28 px-3 py-3.5 sm:pl-6"> <TableCell className="w-full px-3 py-3.5 dark:bg-gray-700 sm:pl-6">
<div>{localize('com_ui_bookmarks_count')}</div> <div>{localize('com_ui_bookmarks_count')}</div>
</th> </TableCell>
</TableRow>
<th className="flex-grow px-3 py-3.5 sm:pl-6"> </th> </TableHeader>
</tr> <TableBody>{currentRows.map((row, i) => renderRow(row, i))}</TableBody>
</thead> </Table>
<tbody className="text-sm">{rows.map((row, i) => renderRow(row, i))}</tbody> </div>
</table> <div className="flex items-center justify-between py-4">
<div className="pl-1 text-gray-400">
{localize('com_ui_showing')} {pageIndex * pageSize + 1} -{' '}
{Math.min((pageIndex + 1) * pageSize, filteredRows.length)} {localize('com_ui_of')}{' '}
{filteredRows.length}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setPageIndex((prev) => Math.max(prev - 1, 0))}
disabled={pageIndex === 0}
>
{localize('com_ui_prev')}
</Button>
<Button
variant="outline"
size="sm"
onClick={() =>
setPageIndex((prev) =>
(prev + 1) * pageSize < filteredRows.length ? prev + 1 : prev,
)
}
disabled={(pageIndex + 1) * pageSize >= filteredRows.length}
>
{localize('com_ui_next')}
</Button>
</div>
</div> </div>
</BookmarkContext.Provider> </BookmarkContext.Provider>
); );

View file

@ -1,64 +1,22 @@
import { useRef } from 'react'; import React, { useState } from 'react';
import { useDrag, useDrop } from 'react-dnd'; import { useDrag, useDrop } from 'react-dnd';
import type { FC } from 'react';
import type { Identifier, XYCoord } from 'dnd-core';
import type { TConversationTag } from 'librechat-data-provider'; import type { TConversationTag } from 'librechat-data-provider';
import { DeleteBookmarkButton, EditBookmarkButton } from '~/components/Bookmarks'; import { DeleteBookmarkButton, EditBookmarkButton } from '~/components/Bookmarks';
import { useConversationTagMutation } from '~/data-provider'; import { TableRow, TableCell } from '~/components/ui';
import { NotificationSeverity } from '~/common';
import { useToastContext } from '~/Providers';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
export const ItemTypes = { interface BookmarkTableRowProps {
CARD: 'card',
};
export interface BookmarkItemProps {
position: number;
moveRow: (dragIndex: number, hoverIndex: number) => void;
row: TConversationTag; row: TConversationTag;
moveRow: (dragIndex: number, hoverIndex: number) => void;
position: number;
} }
interface DragItem { const BookmarkTableRow: React.FC<BookmarkTableRowProps> = ({ row, moveRow, position }) => {
index: number; const [isHovered, setIsHovered] = useState(false);
id: string; const ref = React.useRef<HTMLTableRowElement>(null);
type: string;
}
const BookmarkTableRow: FC<BookmarkItemProps> = ({ position, moveRow, row, ...rest }) => { const [, drop] = useDrop({
const ref = useRef<HTMLTableRowElement>(null); accept: 'bookmark',
hover(item: { index: number }) {
const mutation = useConversationTagMutation(row.tag);
const localize = useLocalize();
const { showToast } = useToastContext();
const handleDrop = (item: DragItem) => {
const data = {
...row,
position: item.index,
};
mutation.mutate(data, {
onError: () => {
showToast({
message: localize('com_endpoint_preset_save_error'),
severity: NotificationSeverity.ERROR,
});
},
});
};
const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
accept: ItemTypes.CARD,
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
};
},
drop(item: DragItem, monitor) {
handleDrop(item);
},
hover(item: DragItem, monitor) {
if (!ref.current) { if (!ref.current) {
return; return;
} }
@ -67,68 +25,60 @@ const BookmarkTableRow: FC<BookmarkItemProps> = ({ position, moveRow, row, ...re
if (dragIndex === hoverIndex) { if (dragIndex === hoverIndex) {
return; return;
} }
const hoverBoundingRect = ref.current?.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
moveRow(dragIndex, hoverIndex); moveRow(dragIndex, hoverIndex);
item.index = hoverIndex; item.index = hoverIndex;
}, },
}); });
const [{ isDragging }, drag] = useDrag({ const [{ isDragging }, drag] = useDrag({
type: ItemTypes.CARD, type: 'bookmark',
item: () => { item: { index: position },
return { id: row.tag, index: position };
},
collect: (monitor) => ({ collect: (monitor) => ({
isDragging: monitor.isDragging(), isDragging: monitor.isDragging(),
}), }),
}); });
if (position > 0) { drag(drop(ref));
drag(drop(ref));
}
return ( return (
<tr <TableRow
className={cn(
'group cursor-pointer gap-2 rounded text-sm hover:bg-black/5 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-white/5',
isDragging ? 'opacity-0' : 'opacity-100',
)}
key={row.tag}
ref={ref} ref={ref}
data-handler-id={handlerId} className="cursor-move hover:bg-gray-100 dark:hover:bg-gray-800"
role="menuitem" style={{ opacity: isDragging ? 0.5 : 1 }}
tabIndex={-1} onMouseEnter={() => setIsHovered(true)}
{...rest} onMouseLeave={() => setIsHovered(false)}
> >
<td className="w-96 py-2 pl-6 pr-3">{row.tag}</td> <TableCell className="w-full px-3 py-3.5 pl-6">
<td className={cn('w-28 py-2 pl-4 pr-3 sm:pl-6')}> <div className="truncate">{row.tag}</div>
<span className="py-1">{row.count}</span> </TableCell>
</td> <TableCell className="w-full px-3 py-3.5 sm:pl-6">
<td className="flex-grow py-2 pl-4 pr-4 sm:pl-6"> <div className="flex items-center justify-between py-1">
{position > 0 && ( <div>{row.count}</div>
<div className="flex w-full items-center justify-end gap-2 py-1 text-gray-400"> <div
<EditBookmarkButton bookmark={row} /> className="flex items-center gap-2"
<DeleteBookmarkButton bookmark={row.tag} /> style={{
opacity: isHovered ? 1 : 0,
transition: 'opacity 0.1s ease-in-out',
}}
onFocus={() => setIsHovered(true)}
onBlur={() => setIsHovered(false)}
>
<EditBookmarkButton
bookmark={row}
tabIndex={0}
onFocus={() => setIsHovered(true)}
onBlur={() => setIsHovered(false)}
/>
<DeleteBookmarkButton
bookmark={row.tag}
tabIndex={0}
onFocus={() => setIsHovered(true)}
onBlur={() => setIsHovered(false)}
/>
</div> </div>
)} </div>
</td> </TableCell>
</tr> </TableRow>
); );
}; };

View file

@ -88,7 +88,6 @@ export default function AssistantTool({
<OGDialogTemplate <OGDialogTemplate
showCloseButton={false} showCloseButton={false}
title={localize('com_ui_delete_tool')} title={localize('com_ui_delete_tool')}
mainClassName="px-0"
className="max-w-[450px]" className="max-w-[450px]"
main={ main={
<Label className="text-left text-sm font-medium"> <Label className="text-left text-sm font-medium">

View file

@ -72,12 +72,12 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
return ( return (
<> <>
<div className="flex items-center gap-4 px-2 py-4"> <div className="flex items-center gap-4 py-4">
<Input <Input
placeholder={localize('com_files_filter')} placeholder={localize('com_files_filter')}
value={(table.getColumn('filename')?.getFilterValue() as string) ?? ''} value={(table.getColumn('filename')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('filename')?.setFilterValue(event.target.value)} onChange={(event) => table.getColumn('filename')?.setFilterValue(event.target.value)}
className="max-w-xs dark:border-gray-700" className="w-full dark:border-gray-700"
/> />
</div> </div>
<div className="overflow-y-auto rounded-md border border-black/10 dark:border-white/10"> <div className="overflow-y-auto rounded-md border border-black/10 dark:border-white/10">
@ -90,7 +90,7 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
<TableHead <TableHead
key={header.id} key={header.id}
style={{ width: index === 0 ? '75%' : '25%' }} style={{ width: index === 0 ? '75%' : '25%' }}
className="sticky top-0 h-auto border-b border-black/10 bg-white py-1 text-left font-medium text-gray-700 dark:border-white/10 dark:bg-gray-800 dark:text-gray-100" className="sticky top-0 h-auto bg-white py-1 text-left font-medium text-gray-700 dark:bg-gray-700 dark:text-gray-100"
> >
{header.isPlaceholder {header.isPlaceholder
? null ? null
@ -133,17 +133,19 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
</TableBody> </TableBody>
</Table> </Table>
</div> </div>
<div className="flex items-center justify-around space-x-2 py-4"> <div className="flex items-center justify-between py-4">
<Button <div className="flex items-center">
variant="outline" <Button
size="sm" variant="outline"
onClick={() => setShowFiles(true)} size="sm"
className="flex gap-2" onClick={() => setShowFiles(true)}
> className="flex gap-2"
<LucideArrowUpLeft className="icon-sm" /> >
{localize('com_sidepanel_manage_files')} <LucideArrowUpLeft className="icon-sm" />
</Button> {localize('com_sidepanel_manage_files')}
<div className="flex gap-2"> </Button>
</div>
<div className="flex items-center gap-2">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"

View file

@ -66,7 +66,7 @@ const OGDialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDi
<OGDialogTitle>{title}</OGDialogTitle> <OGDialogTitle>{title}</OGDialogTitle>
{description && <OGDialogDescription className="">{description}</OGDialogDescription>} {description && <OGDialogDescription className="">{description}</OGDialogDescription>}
</OGDialogHeader> </OGDialogHeader>
<div className={cn('px-6', mainClassName)}>{main ? main : null}</div> <div className={cn('px-0', mainClassName)}>{main ? main : null}</div>
<OGDialogFooter className={footerClassName}> <OGDialogFooter className={footerClassName}>
<div>{leftButtons ? leftButtons : null}</div> <div>{leftButtons ? leftButtons : null}</div>
<div className="flex h-auto gap-3"> <div className="flex h-auto gap-3">

View file

@ -1,14 +1,14 @@
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { import {
Dialog, OGDialog,
DialogTrigger, OGDialogTrigger,
Label, Label,
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from '~/components/ui'; } from '~/components/ui';
import DialogTemplate from '~/components/ui/DialogTemplate'; import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
import { CrossIcon } from '~/components/svg'; import { CrossIcon } from '~/components/svg';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
@ -20,6 +20,9 @@ export default function TooltipIcon({
confirm, confirm,
confirmMessage, confirmMessage,
icon, icon,
tabIndex,
onFocus,
onBlur,
}: { }: {
disabled: boolean; disabled: boolean;
title: string; title: string;
@ -28,6 +31,9 @@ export default function TooltipIcon({
confirm?: () => void; confirm?: () => void;
confirmMessage?: ReactElement; confirmMessage?: ReactElement;
icon?: ReactElement; icon?: ReactElement;
tabIndex?: number;
onFocus?: () => void;
onBlur?: () => void;
}) { }) {
const localize = useLocalize(); const localize = useLocalize();
@ -55,27 +61,29 @@ export default function TooltipIcon({
if (!confirmMessage) { if (!confirmMessage) {
return ( return (
<button className={className} onClick={confirm}> <button
className={className}
onClick={confirm}
tabIndex={tabIndex}
onFocus={onFocus}
onBlur={onBlur}
>
{disabled ? <CrossIcon /> : renderDeleteButton()} {disabled ? <CrossIcon /> : renderDeleteButton()}
</button> </button>
); );
} }
return ( return (
<Dialog> <OGDialog>
<DialogTrigger asChild> <OGDialogTrigger asChild>
<button className={className}>{disabled ? <CrossIcon /> : renderDeleteButton()}</button> <button className={className} tabIndex={tabIndex} onFocus={onFocus} onBlur={onBlur}>
</DialogTrigger> {disabled ? <CrossIcon /> : renderDeleteButton()}
<DialogTemplate </button>
</OGDialogTrigger>
<OGDialogTemplate
showCloseButton={false} showCloseButton={false}
title={title} title={title}
className="max-w-[450px]" className="max-w-[450px]"
main={ main={<Label className="text-left text-sm font-medium">{confirmMessage}</Label>}
<>
<div className="flex w-full flex-col items-center gap-2">
<div className="grid w-full items-center gap-2">{confirmMessage}</div>
</div>
</>
}
selection={{ selection={{
selectHandler: confirm, selectHandler: confirm,
selectClasses: selectClasses:
@ -83,6 +91,6 @@ export default function TooltipIcon({
selectText: localize('com_ui_delete'), selectText: localize('com_ui_delete'),
}} }}
/> />
</Dialog> </OGDialog>
); );
} }

View file

@ -13,6 +13,7 @@ import {
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import type { TConfig, TInterfaceConfig } from 'librechat-data-provider'; import type { TConfig, TInterfaceConfig } from 'librechat-data-provider';
import type { NavLink } from '~/common'; import type { NavLink } from '~/common';
import BookmarkPanel from '~/components/SidePanel/Bookmarks/BookmarkPanel';
import PanelSwitch from '~/components/SidePanel/Builder/PanelSwitch'; import PanelSwitch from '~/components/SidePanel/Builder/PanelSwitch';
import PromptsAccordion from '~/components/Prompts/PromptsAccordion'; import PromptsAccordion from '~/components/Prompts/PromptsAccordion';
// import Parameters from '~/components/SidePanel/Parameters/Panel'; // import Parameters from '~/components/SidePanel/Parameters/Panel';
@ -26,14 +27,12 @@ export default function useSideNavLinks({
keyProvided, keyProvided,
endpoint, endpoint,
interfaceConfig, interfaceConfig,
manageBookmarks,
}: { }: {
hidePanel: () => void; hidePanel: () => void;
assistants?: TConfig | null; assistants?: TConfig | null;
keyProvided: boolean; keyProvided: boolean;
endpoint?: EModelEndpoint | null; endpoint?: EModelEndpoint | null;
interfaceConfig: Partial<TInterfaceConfig>; interfaceConfig: Partial<TInterfaceConfig>;
manageBookmarks: (e?: React.MouseEvent) => void;
}) { }) {
const hasAccessToPrompts = useHasAccess({ const hasAccessToPrompts = useHasAccess({
permissionType: PermissionTypes.PROMPTS, permissionType: PermissionTypes.PROMPTS,
@ -80,8 +79,8 @@ export default function useSideNavLinks({
title: 'com_sidepanel_conversation_tags', title: 'com_sidepanel_conversation_tags',
label: '', label: '',
icon: Bookmark, icon: Bookmark,
onClick: manageBookmarks,
id: 'bookmarks', id: 'bookmarks',
Component: BookmarkPanel,
}); });
links.push({ links.push({
@ -100,7 +99,6 @@ export default function useSideNavLinks({
endpoint, endpoint,
interfaceConfig.parameters, interfaceConfig.parameters,
hasAccessToPrompts, hasAccessToPrompts,
manageBookmarks,
]); ]);
return Links; return Links;

View file

@ -307,6 +307,7 @@ export default {
com_ui_bookmarks_update_error: 'There was an error updating the bookmark', com_ui_bookmarks_update_error: 'There was an error updating the bookmark',
com_ui_bookmarks_delete_error: 'There was an error deleting the bookmark', com_ui_bookmarks_delete_error: 'There was an error deleting the bookmark',
com_ui_bookmarks_add_to_conversation: 'Add to current conversation', com_ui_bookmarks_add_to_conversation: 'Add to current conversation',
com_ui_bookmarks_filter: 'Filter bookmarks...',
com_auth_error_login: com_auth_error_login:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
com_auth_error_login_rl: com_auth_error_login_rl: