mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
🎨 style: overall UI improvements (#3576)
* stlye: new dialogs * style: DashGroupItem * style: bookmarks update
This commit is contained in:
parent
5c99d93744
commit
cf393b1308
11 changed files with 172 additions and 121 deletions
|
|
@ -5,7 +5,7 @@ import { Spinner } from '~/components/svg';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
type MenuItemProps = {
|
type MenuItemProps = {
|
||||||
tag: string;
|
tag: string | React.ReactNode;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
count?: number;
|
count?: number;
|
||||||
handleSubmit: (tag: string) => Promise<void>;
|
handleSubmit: (tag: string) => Promise<void>;
|
||||||
|
|
@ -25,14 +25,20 @@ const BookmarkItem: FC<MenuItemProps> = ({
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const clickHandler = async () => {
|
const clickHandler = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await handleSubmit(tag);
|
await handleSubmit(tag as string);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const breakWordStyle: React.CSSProperties = {
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
className={cn(
|
className={cn(
|
||||||
'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',
|
'group m-1.5 flex w-[225px] 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',
|
||||||
)}
|
)}
|
||||||
|
|
@ -51,14 +57,14 @@ const BookmarkItem: FC<MenuItemProps> = ({
|
||||||
) : (
|
) : (
|
||||||
<BookmarkIcon className="size-4" />
|
<BookmarkIcon className="size-4" />
|
||||||
)}
|
)}
|
||||||
<div className="break-all">{tag}</div>
|
<div style={breakWordStyle}>{tag}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{count !== undefined && (
|
{count !== undefined && (
|
||||||
<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-md bg-white px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600',
|
'ml-auto w-7 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:bg-gray-800 dark:text-white',
|
||||||
)}
|
)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
||||||
import BookmarkItem from './BookmarkItem';
|
import BookmarkItem from './BookmarkItem';
|
||||||
|
interface BookmarkItemsProps {
|
||||||
const BookmarkItems: FC<{
|
|
||||||
tags: string[];
|
tags: string[];
|
||||||
handleSubmit: (tag: string) => Promise<void>;
|
handleSubmit: (tag: string) => Promise<void>;
|
||||||
header: React.ReactNode;
|
header: React.ReactNode;
|
||||||
highlightSelected?: boolean;
|
highlightSelected?: boolean;
|
||||||
}> = ({ tags, handleSubmit, header, highlightSelected }) => {
|
}
|
||||||
|
|
||||||
|
const BookmarkItems: FC<BookmarkItemsProps> = ({
|
||||||
|
tags,
|
||||||
|
handleSubmit,
|
||||||
|
header,
|
||||||
|
highlightSelected,
|
||||||
|
}) => {
|
||||||
const { bookmarks } = useBookmarkContext();
|
const { bookmarks } = useBookmarkContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{header}
|
{header}
|
||||||
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
||||||
{bookmarks.length > 0 &&
|
{bookmarks.map((bookmark) => (
|
||||||
bookmarks.map((bookmark) => (
|
|
||||||
<BookmarkItem
|
<BookmarkItem
|
||||||
key={bookmark.tag}
|
key={bookmark.tag}
|
||||||
tag={bookmark.tag}
|
tag={bookmark.tag}
|
||||||
|
|
@ -27,4 +33,5 @@ const BookmarkItems: FC<{
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BookmarkItems;
|
export default BookmarkItems;
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,15 @@ import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import type { TMessage } from 'librechat-data-provider';
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
import { useDeleteConversationMutation } from '~/data-provider';
|
import { useDeleteConversationMutation } from '~/data-provider';
|
||||||
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 { TrashIcon, CrossIcon } from '~/components/svg';
|
import { TrashIcon, CrossIcon } from '~/components/svg';
|
||||||
import { useLocalize, useNewConvo } from '~/hooks';
|
import { useLocalize, useNewConvo } from '~/hooks';
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ export default function DeleteButton({
|
||||||
|
|
||||||
const confirmDelete = useCallback(() => {
|
const confirmDelete = useCallback(() => {
|
||||||
const messages = queryClient.getQueryData<TMessage[]>([QueryKeys.messages, conversationId]);
|
const messages = queryClient.getQueryData<TMessage[]>([QueryKeys.messages, conversationId]);
|
||||||
const thread_id = messages?.[messages?.length - 1]?.thread_id;
|
const thread_id = messages?.[messages.length - 1]?.thread_id;
|
||||||
|
|
||||||
deleteConvoMutation.mutate({ conversationId, thread_id, source: 'button' });
|
deleteConvoMutation.mutate({ conversationId, thread_id, source: 'button' });
|
||||||
}, [conversationId, deleteConvoMutation, queryClient]);
|
}, [conversationId, deleteConvoMutation, queryClient]);
|
||||||
|
|
@ -72,11 +72,11 @@ export default function DeleteButton({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<OGDialog>
|
||||||
<DialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
<button className={className}>{renaming ? <CrossIcon /> : renderDeleteButton()}</button>
|
<button className={className}>{renaming ? <CrossIcon /> : renderDeleteButton()}</button>
|
||||||
</DialogTrigger>
|
</OGDialogTrigger>
|
||||||
<DialogTemplate
|
<OGDialogTemplate
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
title={localize('com_ui_delete_conversation')}
|
title={localize('com_ui_delete_conversation')}
|
||||||
className="max-w-[450px]"
|
className="max-w-[450px]"
|
||||||
|
|
@ -98,6 +98,6 @@ export default function DeleteButton({
|
||||||
selectText: localize('com_ui_delete'),
|
selectText: localize('com_ui_delete'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</OGDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
OGDialog,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
DialogTrigger,
|
OGDialogTrigger,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
} from '~/components/ui';
|
} from '~/components/ui';
|
||||||
import { Share2Icon } from 'lucide-react';
|
import { Share2Icon } from 'lucide-react';
|
||||||
import type { TSharedLink } from 'librechat-data-provider';
|
import type { TSharedLink } from 'librechat-data-provider';
|
||||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import SharedLinkButton from './SharedLinkButton';
|
import SharedLinkButton from './SharedLinkButton';
|
||||||
import ShareDialog from './ShareDialog';
|
import ShareDialog from './ShareDialog';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
@ -78,8 +78,8 @@ export default function ShareButton({
|
||||||
setOpen(open);
|
setOpen(open);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<OGDialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'group m-1.5 flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600',
|
'group m-1.5 flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600',
|
||||||
|
|
@ -88,8 +88,8 @@ export default function ShareButton({
|
||||||
>
|
>
|
||||||
{renderShareButton()}
|
{renderShareButton()}
|
||||||
</button>
|
</button>
|
||||||
</DialogTrigger>
|
</OGDialogTrigger>
|
||||||
<DialogTemplate
|
<OGDialogTemplate
|
||||||
buttons={buttons}
|
buttons={buttons}
|
||||||
showCloseButton={true}
|
showCloseButton={true}
|
||||||
showCancelButton={false}
|
showCancelButton={false}
|
||||||
|
|
@ -108,6 +108,6 @@ export default function ShareButton({
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</OGDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export default function SharedLinkButton({
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (share?.isPublic) {
|
if (share.isPublic) {
|
||||||
return {
|
return {
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await updateSharedLink();
|
await updateSharedLink();
|
||||||
|
|
@ -107,12 +107,12 @@ export default function SharedLinkButton({
|
||||||
|
|
||||||
const handlers = getHandler();
|
const handlers = getHandler();
|
||||||
return (
|
return (
|
||||||
<Button
|
<button
|
||||||
disabled={isLoading || isCopying}
|
disabled={isLoading || isCopying}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlers.handler();
|
handlers.handler();
|
||||||
}}
|
}}
|
||||||
className="min-w-32 whitespace-nowrap bg-green-500 text-white hover:bg-green-600 dark:bg-green-600 dark:text-white dark:hover:bg-green-800"
|
className="btn btn-primary flex items-center"
|
||||||
>
|
>
|
||||||
{isCopying && (
|
{isCopying && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -122,6 +122,6 @@ export default function SharedLinkButton({
|
||||||
)}
|
)}
|
||||||
{!isCopying && !isLoading && handlers.label}
|
{!isCopying && !isLoading && handlers.label}
|
||||||
{!isCopying && isLoading && <Spinner className="h-4 w-4" />}
|
{!isCopying && isLoading && <Spinner className="h-4 w-4" />}
|
||||||
</Button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ type BookmarkNavProps = {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
setTags: (tags: string[]) => void;
|
setTags: (tags: string[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps) => {
|
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
@ -33,38 +34,26 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps)
|
||||||
conversation = activeConvo;
|
conversation = activeConvo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide the button if there are no tags
|
|
||||||
if (!data || !data.some((tag) => tag.count > 0)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Root open={open} onOpenChange={setIsOpen}>
|
<Root open={open} onOpenChange={setIsOpen}>
|
||||||
<Trigger asChild>
|
<Trigger asChild>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'group-ui-open:bg-gray-100 dark:group-ui-open:bg-gray-700 duration-350 mt-text-sm flex h-auto w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-800',
|
'relative mt-1 flex h-10 w-full cursor-pointer items-center gap-1 rounded-lg border-white bg-gray-50 px-1 py-2 text-black transition-colors duration-200 focus-within:bg-gray-200 hover:bg-gray-200 dark:bg-gray-850 dark:text-white dark:focus-within:bg-gray-800 dark:hover:bg-gray-800',
|
||||||
open ? 'bg-gray-100 dark:bg-gray-800' : '',
|
open ? 'bg-gray-200 dark:bg-gray-800' : '',
|
||||||
)}
|
)}
|
||||||
id="presets-button"
|
id="presets-button"
|
||||||
data-testid="presets-button"
|
data-testid="presets-button"
|
||||||
title={localize('com_endpoint_examples')}
|
title={localize('com_endpoint_examples')}
|
||||||
>
|
>
|
||||||
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
|
|
||||||
<div className="relative flex">
|
|
||||||
<div className="relative flex h-8 w-8 items-center justify-center rounded-full p-1 dark:text-white">
|
<div className="relative flex h-8 w-8 items-center justify-center rounded-full p-1 dark:text-white">
|
||||||
{tags.length > 0 ? (
|
{tags.length > 0 ? (
|
||||||
<BookmarkFilledIcon className="h-6 w-6" />
|
<BookmarkFilledIcon className="h-5 w-5" />
|
||||||
) : (
|
) : (
|
||||||
<BookmarkIcon className="h-6 w-6" />
|
<BookmarkIcon className="h-5 w-5" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="grow overflow-hidden whitespace-nowrap text-left text-sm text-black dark:text-gray-100">
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="mt-2 grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-black dark:text-gray-100"
|
|
||||||
style={{ marginTop: '0', marginLeft: '0' }}
|
|
||||||
>
|
|
||||||
{tags.length > 0 ? tags.join(',') : localize('com_ui_bookmarks')}
|
{tags.length > 0 ? tags.join(',') : localize('com_ui_bookmarks')}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -76,7 +65,7 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps)
|
||||||
align="start"
|
align="start"
|
||||||
className="mt-2 max-h-96 min-w-[240px] overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white lg:max-h-96"
|
className="mt-2 max-h-96 min-w-[240px] overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white lg:max-h-96"
|
||||||
>
|
>
|
||||||
{data && conversation && data.some((tag) => tag.count > 0) && (
|
{data && conversation && (
|
||||||
// Display bookmarks and highlight the selected tag
|
// Display bookmarks and highlight the selected tag
|
||||||
<BookmarkContext.Provider value={{ bookmarks: data.filter((tag) => tag.count > 0) }}>
|
<BookmarkContext.Provider value={{ bookmarks: data.filter((tag) => tag.count > 0) }}>
|
||||||
<BookmarkNavItems
|
<BookmarkNavItems
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { useEffect, useState, type FC } from 'react';
|
import { useEffect, useState, type FC } from 'react';
|
||||||
import { CrossCircledIcon } from '@radix-ui/react-icons';
|
import { CrossCircledIcon } from '@radix-ui/react-icons';
|
||||||
import type { TConversation } from 'librechat-data-provider';
|
import type { TConversation } from 'librechat-data-provider';
|
||||||
|
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
||||||
import { BookmarkItems, BookmarkItem } from '~/components/Bookmarks';
|
import { BookmarkItems, BookmarkItem } from '~/components/Bookmarks';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
const BookmarkNavItems: FC<{
|
const BookmarkNavItems: FC<{
|
||||||
conversation: TConversation;
|
conversation: TConversation;
|
||||||
|
|
@ -9,6 +11,8 @@ const BookmarkNavItems: FC<{
|
||||||
setTags: (tags: string[]) => void;
|
setTags: (tags: string[]) => void;
|
||||||
}> = ({ conversation, tags, setTags }) => {
|
}> = ({ conversation, tags, setTags }) => {
|
||||||
const [currentConversation, setCurrentConversation] = useState<TConversation>();
|
const [currentConversation, setCurrentConversation] = useState<TConversation>();
|
||||||
|
const { bookmarks } = useBookmarkContext();
|
||||||
|
const localize = useLocalize();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentConversation) {
|
if (!currentConversation) {
|
||||||
|
|
@ -35,8 +39,24 @@ const BookmarkNavItems: FC<{
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('bookmarks', bookmarks);
|
||||||
|
|
||||||
|
if (bookmarks.length === 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-col">
|
||||||
|
<BookmarkItem
|
||||||
|
tag={localize('com_ui_no_bookmarks')}
|
||||||
|
data-testid="bookmark-item-clear"
|
||||||
|
handleSubmit={() => Promise.resolve()}
|
||||||
|
selected={false}
|
||||||
|
icon={'🤔'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
<BookmarkItems
|
<BookmarkItems
|
||||||
tags={tags}
|
tags={tags}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
|
|
@ -51,7 +71,7 @@ const BookmarkNavItems: FC<{
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,12 @@ const Nav = ({ navVisible, setNavVisible }) => {
|
||||||
>
|
>
|
||||||
<NewChat
|
<NewChat
|
||||||
toggleNav={itemToggleNav}
|
toggleNav={itemToggleNav}
|
||||||
subHeaders={isSearchEnabled && <SearchBar clearSearch={clearSearch} />}
|
subHeaders={
|
||||||
|
<>
|
||||||
|
{isSearchEnabled && <SearchBar clearSearch={clearSearch} />}
|
||||||
|
<BookmarkNav tags={tags} setTags={setTags} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Conversations
|
<Conversations
|
||||||
conversations={conversations}
|
conversations={conversations}
|
||||||
|
|
@ -170,7 +175,6 @@ const Nav = ({ navVisible, setNavVisible }) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<BookmarkNav tags={tags} setTags={setTags} />
|
|
||||||
<NavLinks />
|
<NavLinks />
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Button, Dialog, DialogTrigger, Label } from '~/components/ui';
|
import { Button, OGDialog, OGDialogTrigger, Label } from '~/components/ui';
|
||||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import { TrashIcon } from '~/components/svg';
|
import { TrashIcon } from '~/components/svg';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
|
|
@ -15,8 +15,8 @@ const DeleteVersion = ({
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<OGDialog>
|
||||||
<DialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
className="h-10 w-10 border border-transparent bg-red-600 text-red-500 transition-all hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-800"
|
className="h-10 w-10 border border-transparent bg-red-600 text-red-500 transition-all hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-800"
|
||||||
|
|
@ -27,8 +27,8 @@ const DeleteVersion = ({
|
||||||
>
|
>
|
||||||
<TrashIcon className="icon-lg cursor-pointer text-white dark:text-white" />
|
<TrashIcon className="icon-lg cursor-pointer text-white dark:text-white" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</OGDialogTrigger>
|
||||||
<DialogTemplate
|
<OGDialogTemplate
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
title={localize('com_ui_delete_prompt')}
|
title={localize('com_ui_delete_prompt')}
|
||||||
className="max-w-[450px]"
|
className="max-w-[450px]"
|
||||||
|
|
@ -53,7 +53,7 @@ const DeleteVersion = ({
|
||||||
selectText: localize('com_ui_delete'),
|
selectText: localize('com_ui_delete'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</OGDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,16 @@ import {
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
OGDialog,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DialogTrigger,
|
OGDialogTrigger,
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuItem,
|
||||||
} from '~/components/ui';
|
} from '~/components/ui';
|
||||||
import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon';
|
import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon';
|
||||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import { RenameButton } from '~/components/Conversations';
|
|
||||||
import { useLocalize, useAuthContext } from '~/hooks';
|
import { useLocalize, useAuthContext } from '~/hooks';
|
||||||
import { TrashIcon } from '~/components/svg';
|
import { TrashIcon } from '~/components/svg';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
@ -36,9 +36,9 @@ export default function DashGroupItem({
|
||||||
const blurTimeoutRef = useRef<NodeJS.Timeout>();
|
const blurTimeoutRef = useRef<NodeJS.Timeout>();
|
||||||
const [nameEditFlag, setNameEditFlag] = useState(false);
|
const [nameEditFlag, setNameEditFlag] = useState(false);
|
||||||
const [nameInputField, setNameInputField] = useState(group.name);
|
const [nameInputField, setNameInputField] = useState(group.name);
|
||||||
const isOwner = useMemo(() => user?.id === group?.author, [user, group]);
|
const isOwner = useMemo(() => user?.id === group.author, [user, group]);
|
||||||
const groupIsGlobal = useMemo(
|
const groupIsGlobal = useMemo(
|
||||||
() => instanceProjectId && group?.projectIds?.includes(instanceProjectId),
|
() => instanceProjectId && group.projectIds?.includes(instanceProjectId),
|
||||||
[group, instanceProjectId],
|
[group, instanceProjectId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ export default function DashGroupItem({
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveRename = () => {
|
const saveRename = () => {
|
||||||
updateGroup.mutate({ payload: { name: nameInputField }, id: group?._id || '' });
|
updateGroup.mutate({ payload: { name: nameInputField }, id: group._id || '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
|
|
@ -70,6 +70,22 @@ export default function DashGroupItem({
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
navigate(`/d/prompts/${group._id}`, { replace: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRename = (e: React.MouseEvent | React.KeyboardEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setNameEditFlag(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
deletePromptGroupMutation.mutate({ id: group._id || '' });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -77,27 +93,30 @@ export default function DashGroupItem({
|
||||||
params.promptId === group._id && 'bg-gray-100/50 dark:bg-gray-600 ',
|
params.promptId === group._id && 'bg-gray-100/50 dark:bg-gray-600 ',
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (nameEditFlag) {
|
if (!nameEditFlag) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
navigate(`/d/prompts/${group._id}`, { replace: true });
|
navigate(`/d/prompts/${group._id}`, { replace: true });
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={`${group.name} prompt group`}
|
||||||
>
|
>
|
||||||
<div className="flex w-full flex-row items-center justify-start truncate">
|
<div className="flex w-full flex-row items-center justify-start truncate">
|
||||||
{/* <Checkbox /> */}
|
|
||||||
<div className="relative flex w-full cursor-pointer flex-col gap-1 text-start align-top">
|
<div className="relative flex w-full cursor-pointer flex-col gap-1 text-start align-top">
|
||||||
{nameEditFlag ? (
|
{nameEditFlag ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex w-full gap-2">
|
<div className="flex w-full gap-2">
|
||||||
|
<Label htmlFor="group-name-input" className="sr-only">
|
||||||
|
{localize('com_ui_rename_group')}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
defaultValue={nameInputField}
|
id="group-name-input"
|
||||||
|
value={nameInputField}
|
||||||
|
tabIndex={0}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={(e) => {
|
onClick={(e) => e.stopPropagation()}
|
||||||
e.stopPropagation();
|
onChange={(e) => setNameInputField(e.target.value)}
|
||||||
}}
|
|
||||||
onChange={(e) => {
|
|
||||||
setNameInputField(e.target.value);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
cancelRename();
|
cancelRename();
|
||||||
|
|
@ -106,17 +125,18 @@ export default function DashGroupItem({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
|
aria-label={localize('com_ui_rename_group')}
|
||||||
/>
|
/>
|
||||||
<Button
|
<button
|
||||||
variant="subtle"
|
className="btn btn-primary"
|
||||||
className="w-min bg-green-500 text-white hover:bg-green-600 dark:bg-green-400 dark:hover:bg-green-500"
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
saveRename();
|
saveRename();
|
||||||
}}
|
}}
|
||||||
|
aria-label={localize('com_ui_save')}
|
||||||
>
|
>
|
||||||
{localize('com_ui_save')}
|
{localize('com_ui_save')}
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="break-word line-clamp-3 text-balance text-sm text-gray-600 dark:text-gray-400">
|
<div className="break-word line-clamp-3 text-balance text-sm text-gray-600 dark:text-gray-400">
|
||||||
{localize('com_ui_renaming_var', group.name)}
|
{localize('com_ui_renaming_var', group.name)}
|
||||||
|
|
@ -126,13 +146,22 @@ export default function DashGroupItem({
|
||||||
<>
|
<>
|
||||||
<div className="flex w-full justify-between">
|
<div className="flex w-full justify-between">
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<CategoryIcon category={group.category ?? ''} className="icon-md" />
|
<CategoryIcon
|
||||||
|
category={group.category ?? ''}
|
||||||
|
className="icon-md"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<h3 className="break-word text-balance text-sm font-semibold text-gray-800 dark:text-gray-200">
|
<h3 className="break-word text-balance text-sm font-semibold text-gray-800 dark:text-gray-200">
|
||||||
{group.name}
|
{group.name}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-1">
|
<div className="flex flex-row items-center gap-1">
|
||||||
{groupIsGlobal && <EarthIcon className="icon-md text-green-400" />}
|
{groupIsGlobal && (
|
||||||
|
<EarthIcon
|
||||||
|
className="icon-md text-green-400"
|
||||||
|
aria-label={localize('com_ui_global_group')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{(isOwner || user?.role === SystemRoles.ADMIN) && (
|
{(isOwner || user?.role === SystemRoles.ADMIN) && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
|
@ -140,39 +169,36 @@ export default function DashGroupItem({
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-7 w-7 p-0 hover:bg-gray-200 dark:bg-gray-800/50 dark:text-gray-400 dark:hover:border-gray-400 dark:focus:border-gray-500"
|
className="h-7 w-7 p-0 hover:bg-gray-200 dark:bg-gray-800/50 dark:text-gray-400 dark:hover:border-gray-400 dark:focus:border-gray-500"
|
||||||
|
aria-label={localize('com_ui_more_options')}
|
||||||
>
|
>
|
||||||
<MenuIcon className="icon-md dark:text-gray-300" />
|
<MenuIcon className="icon-md dark:text-gray-300" aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="mt-2 w-36 rounded-lg" collisionPadding={2}>
|
<DropdownMenuContent className="mt-2 w-36 rounded-lg" collisionPadding={2}>
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<RenameButton
|
<DropdownMenuItem onSelect={handleRename}>
|
||||||
renaming={false}
|
{localize('com_ui_rename')}
|
||||||
renameHandler={(e) => {
|
</DropdownMenuItem>
|
||||||
e.stopPropagation();
|
|
||||||
setNameEditFlag(true);
|
|
||||||
}}
|
|
||||||
appendLabel={true}
|
|
||||||
className={cn('m-0 w-full p-2')}
|
|
||||||
/>
|
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Dialog>
|
<OGDialog>
|
||||||
<DialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-7 w-7 p-0 hover:bg-gray-200 dark:bg-gray-800/50 dark:text-gray-400 dark:hover:border-gray-400 dark:focus:border-gray-500',
|
'h-7 w-7 p-0 hover:bg-gray-200 dark:bg-gray-800/50 dark:text-gray-400 dark:hover:border-gray-400 dark:focus:border-gray-500',
|
||||||
)}
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => e.stopPropagation()}
|
||||||
e.stopPropagation();
|
aria-label={localize('com_ui_delete_prompt')}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<TrashIcon className="icon-md text-gray-600 dark:text-gray-300" />
|
<TrashIcon
|
||||||
|
className="icon-md text-gray-600 dark:text-gray-300"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</OGDialogTrigger>
|
||||||
<DialogTemplate
|
<OGDialogTemplate
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
title={localize('com_ui_delete_prompt')}
|
title={localize('com_ui_delete_prompt')}
|
||||||
className="max-w-[450px]"
|
className="max-w-[450px]"
|
||||||
|
|
@ -181,7 +207,7 @@ export default function DashGroupItem({
|
||||||
<div className="flex w-full flex-col items-center gap-2">
|
<div className="flex w-full flex-col items-center gap-2">
|
||||||
<div className="grid w-full items-center gap-2">
|
<div className="grid w-full items-center gap-2">
|
||||||
<Label
|
<Label
|
||||||
htmlFor="chatGptLabel"
|
htmlFor="confirm-delete"
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
{localize('com_ui_delete_confirm')}{' '}
|
{localize('com_ui_delete_confirm')}{' '}
|
||||||
|
|
@ -192,21 +218,19 @@ export default function DashGroupItem({
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
selection={{
|
selection={{
|
||||||
selectHandler: () => {
|
selectHandler: handleDelete,
|
||||||
deletePromptGroupMutation.mutate({ id: group?._id || '' });
|
|
||||||
},
|
|
||||||
selectClasses:
|
selectClasses:
|
||||||
'bg-red-600 dark:bg-red-600 hover:bg-red-700 dark:hover:bg-red-800 text-white',
|
'bg-red-600 dark:bg-red-600 hover:bg-red-700 dark:hover:bg-red-800 text-white',
|
||||||
selectText: localize('com_ui_delete'),
|
selectText: localize('com_ui_delete'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</OGDialog>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ellipsis text-balance text-sm text-gray-600 dark:text-gray-400">
|
<div className="ellipsis text-balance text-sm text-gray-600 dark:text-gray-400">
|
||||||
{group.oneliner ? group.oneliner : group?.productionPrompt?.prompt ?? ''}
|
{group.oneliner ? group.oneliner : group.productionPrompt?.prompt ?? ''}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,7 @@ export default {
|
||||||
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_ui_bookmarks_filter: 'Filter bookmarks...',
|
||||||
|
com_ui_no_bookmarks: 'it seems like you have no bookmarks yet. Click on a chat and add a new one',
|
||||||
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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue