mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
🖼️ style: Conversation Menu and Dialogs update (#3601)
* feat: new dropdown * fix: maintain popover active when open * fix: update DeleteButton and ShareButton component to use useState for managing dialog state * BREAKING: style improvement of base Button component * style: update export button * a11y: ExportAndShareButton * add border * quick style fix * fix: flick issue on convo * fix: DropDown opens when renaming * chore: update radix-ui/react-dropdown-menu to latest * small fix * style: bookmarks update * reorder export modal * feat: imporved dropdowns * style: a lot of changes; header, bookmarks, export, nav, convo, convoOptions * fix: small style issues * fix: button * fix: bookmarks header menu * fix: dropdown close glitch * feat: Improve accessibility and keyboard navigation in ModelSpec component * fix: Nav related type issues * style: ConvoOptions theming and focus ring --------- Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
7f50d2f7c0
commit
96581d56df
62 changed files with 2627 additions and 1821 deletions
|
@ -136,13 +136,13 @@ const adjustPositions = async (user, oldPosition, newPosition) => {
|
|||
const position =
|
||||
oldPosition < newPosition
|
||||
? {
|
||||
$gt: Math.min(oldPosition, newPosition),
|
||||
$lte: Math.max(oldPosition, newPosition),
|
||||
}
|
||||
$gt: Math.min(oldPosition, newPosition),
|
||||
$lte: Math.max(oldPosition, newPosition),
|
||||
}
|
||||
: {
|
||||
$gte: Math.min(oldPosition, newPosition),
|
||||
$lt: Math.max(oldPosition, newPosition),
|
||||
};
|
||||
$gte: Math.min(oldPosition, newPosition),
|
||||
$lt: Math.max(oldPosition, newPosition),
|
||||
};
|
||||
|
||||
await ConversationTag.updateMany(
|
||||
{
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"@radix-ui/react-checkbox": "^1.0.3",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.0.5",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.0",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import React, { useRef, useState, Dispatch, SetStateAction } from 'react';
|
||||
import { TConversationTag, TConversation } from 'librechat-data-provider';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { OGDialog, OGDialogTrigger, OGDialogClose } from '~/components/ui/';
|
||||
import { OGDialog, OGDialogClose } from '~/components/ui/';
|
||||
import BookmarkForm from './BookmarkForm';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { Spinner } from '../svg';
|
||||
|
@ -11,18 +11,20 @@ type BookmarkEditDialogProps = {
|
|||
conversation?: TConversation;
|
||||
tags?: string[];
|
||||
setTags?: (tags: string[]) => void;
|
||||
trigger: React.ReactNode;
|
||||
open: boolean;
|
||||
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
const BookmarkEditDialog = ({
|
||||
bookmark,
|
||||
conversation,
|
||||
tags,
|
||||
setTags,
|
||||
trigger,
|
||||
open,
|
||||
setOpen,
|
||||
}: BookmarkEditDialogProps) => {
|
||||
const localize = useLocalize();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
const handleSubmitForm = () => {
|
||||
|
@ -33,10 +35,8 @@ const BookmarkEditDialog = ({
|
|||
|
||||
return (
|
||||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<OGDialogTrigger asChild>{trigger}</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
title="Bookmark"
|
||||
className="w-11/12 sm:w-1/4"
|
||||
showCloseButton={false}
|
||||
main={
|
||||
<BookmarkForm
|
||||
|
|
|
@ -170,7 +170,7 @@ const BookmarkForm = ({
|
|||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
value={field.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import { MenuItem } from '@headlessui/react';
|
||||
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
||||
import type { FC } from 'react';
|
||||
import { Spinner } from '~/components/svg';
|
||||
|
@ -7,25 +8,19 @@ import { cn } from '~/utils';
|
|||
type MenuItemProps = {
|
||||
tag: string | React.ReactNode;
|
||||
selected: boolean;
|
||||
ctx: 'header' | 'nav';
|
||||
count?: number;
|
||||
handleSubmit: (tag: string) => Promise<void>;
|
||||
handleSubmit: (tag?: string) => Promise<void>;
|
||||
icon?: React.ReactNode;
|
||||
highlightSelected?: boolean;
|
||||
};
|
||||
|
||||
const BookmarkItem: FC<MenuItemProps> = ({
|
||||
tag,
|
||||
ctx,
|
||||
selected,
|
||||
count,
|
||||
handleSubmit,
|
||||
icon,
|
||||
highlightSelected,
|
||||
...rest
|
||||
}) => {
|
||||
const BookmarkItem: FC<MenuItemProps> = ({ tag, selected, handleSubmit, icon, ...rest }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const clickHandler = async () => {
|
||||
if (tag === 'New Bookmark') {
|
||||
await handleSubmit();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
await handleSubmit(tag as string);
|
||||
setIsLoading(false);
|
||||
|
@ -49,20 +44,15 @@ const BookmarkItem: FC<MenuItemProps> = ({
|
|||
return <BookmarkIcon className="size-4" />;
|
||||
};
|
||||
|
||||
const ariaLabel =
|
||||
ctx === 'header' ? `${selected ? 'Remove' : 'Add'} bookmark for ${tag}` : (tag as string);
|
||||
|
||||
return (
|
||||
<button
|
||||
aria-label={ariaLabel}
|
||||
role="menuitem"
|
||||
<MenuItem
|
||||
aria-label={tag as string}
|
||||
className={cn(
|
||||
'group m-1.5 flex w-[225px] cursor-pointer gap-2 rounded bg-transparent px-2 py-2.5 !pr-3 text-sm !opacity-100 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50',
|
||||
highlightSelected && selected ? 'bg-surface-secondary' : '',
|
||||
ctx === 'header' ? 'hover:bg-header-hover' : 'hover:bg-surface-hover',
|
||||
'group flex w-full gap-2 rounded-lg p-2.5 text-sm text-text-primary transition-colors duration-200',
|
||||
selected ? 'bg-surface-hover' : 'data-[focus]:bg-surface-hover',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
{...rest}
|
||||
as="button"
|
||||
onClick={clickHandler}
|
||||
>
|
||||
<div className="flex grow items-center justify-between gap-2">
|
||||
|
@ -70,19 +60,8 @@ const BookmarkItem: FC<MenuItemProps> = ({
|
|||
{renderIcon()}
|
||||
<div style={breakWordStyle}>{tag}</div>
|
||||
</div>
|
||||
|
||||
{count !== undefined && (
|
||||
<div className="flex items-center justify-end">
|
||||
<span
|
||||
className="ml-auto w-7 min-w-max whitespace-nowrap rounded-md bg-surface-secondary px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-text-secondary"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -2,35 +2,24 @@ import type { FC } from 'react';
|
|||
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import BookmarkItem from './BookmarkItem';
|
||||
interface BookmarkItemsProps {
|
||||
ctx: 'header' | 'nav';
|
||||
tags: string[];
|
||||
handleSubmit: (tag: string) => Promise<void>;
|
||||
handleSubmit: (tag?: string) => Promise<void>;
|
||||
header: React.ReactNode;
|
||||
highlightSelected?: boolean;
|
||||
}
|
||||
|
||||
const BookmarkItems: FC<BookmarkItemsProps> = ({
|
||||
ctx,
|
||||
tags,
|
||||
handleSubmit,
|
||||
header,
|
||||
highlightSelected,
|
||||
}) => {
|
||||
const BookmarkItems: FC<BookmarkItemsProps> = ({ tags, handleSubmit, header }) => {
|
||||
const { bookmarks } = useBookmarkContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
{header}
|
||||
<div className="my-1.5 h-px" role="none" />
|
||||
{bookmarks.length > 0 && <div className="my-1.5 h-px" role="none" />}
|
||||
{bookmarks.map((bookmark) => (
|
||||
<BookmarkItem
|
||||
ctx={ctx}
|
||||
key={bookmark.tag}
|
||||
tag={bookmark.tag}
|
||||
selected={tags.includes(bookmark.tag)}
|
||||
count={bookmark.count}
|
||||
handleSubmit={handleSubmit}
|
||||
highlightSelected={highlightSelected}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
|
|
@ -46,7 +46,7 @@ const DeleteBookmarkButton: FC<{
|
|||
</Label>
|
||||
}
|
||||
confirm={confirmDelete}
|
||||
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"
|
||||
className="transition-color flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
||||
icon={<TrashIcon className="size-4" />}
|
||||
tabIndex={tabIndex}
|
||||
onFocus={onFocus}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import type { TConversationTag } from 'librechat-data-provider';
|
||||
import BookmarkEditDialog from './BookmarkEditDialog';
|
||||
|
@ -12,30 +13,31 @@ const EditBookmarkButton: FC<{
|
|||
onBlur?: () => void;
|
||||
}> = ({ bookmark, tabIndex = 0, onFocus, onBlur }) => {
|
||||
const localize = useLocalize();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<BookmarkEditDialog
|
||||
bookmark={bookmark}
|
||||
trigger={
|
||||
<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}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<EditIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_edit')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
<>
|
||||
<BookmarkEditDialog bookmark={bookmark} open={open} setOpen={setOpen} />
|
||||
<button
|
||||
type="button"
|
||||
className="transition-color flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
||||
tabIndex={tabIndex}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<EditIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_edit')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { isAssistantsEndpoint } from 'librechat-data-provider';
|
|||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { useChatContext, useAddedChatContext } from '~/Providers';
|
||||
import { mainTextareaId } from '~/common';
|
||||
import { Button } from '~/components/ui';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
function AddMultiConvo({ className = '' }: { className?: string }) {
|
||||
|
@ -32,16 +33,15 @@ function AddMultiConvo({ className = '' }: { className?: string }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
<Button
|
||||
id="add-multi-conversation-button"
|
||||
aria-label="Add multi-conversation"
|
||||
onClick={clickHandler}
|
||||
className={cn(
|
||||
'group m-1.5 flex w-fit cursor-pointer items-center rounded text-sm hover:bg-border-medium focus-visible:bg-border-medium focus-visible:outline-offset-2',
|
||||
className,
|
||||
)}
|
||||
variant="outline"
|
||||
className={cn('h-10 w-10 p-0 transition-all duration-300 ease-in-out', className)}
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,64 +1,95 @@
|
|||
import { useState } from 'react';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { Upload, Share2 } from 'lucide-react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import DropDownMenu from '~/components/Conversations/DropDownMenu';
|
||||
import ShareButton from '~/components/Conversations/ShareButton';
|
||||
import HoverToggle from '~/components/Conversations/HoverToggle';
|
||||
import { ShareButton } from '~/components/Conversations/ConvoOptions';
|
||||
import { Button, DropdownPopup } from '~/components/ui';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import ExportButton from './ExportButton';
|
||||
import { ExportModal } from '../Nav';
|
||||
import store from '~/store';
|
||||
|
||||
export default function ExportAndShareMenu({
|
||||
isSharedButtonEnabled,
|
||||
className = '',
|
||||
}: {
|
||||
isSharedButtonEnabled: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
||||
const conversation = useRecoilValue(store.conversationByIndex(0));
|
||||
const [isPopoverActive, setIsPopoverActive] = useState(false);
|
||||
const [showExports, setShowExports] = useState(false);
|
||||
const [showShareDialog, setShowShareDialog] = useState(false);
|
||||
|
||||
const exportable =
|
||||
conversation &&
|
||||
conversation.conversationId &&
|
||||
conversation.conversationId != null &&
|
||||
conversation.conversationId !== 'new' &&
|
||||
conversation.conversationId !== 'search';
|
||||
|
||||
if (!exportable) {
|
||||
if (exportable === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isActiveConvo = exportable;
|
||||
const onOpenChange = (value: boolean) => {
|
||||
setShowExports(value);
|
||||
};
|
||||
|
||||
const shareHandler = () => {
|
||||
setIsPopoverActive(false);
|
||||
setShowShareDialog(true);
|
||||
};
|
||||
|
||||
const exportHandler = () => {
|
||||
setIsPopoverActive(false);
|
||||
setShowExports(true);
|
||||
};
|
||||
|
||||
const dropdownItems = [
|
||||
{
|
||||
label: localize('com_endpoint_export'),
|
||||
onClick: exportHandler,
|
||||
icon: <Upload className="icon-md mr-2 dark:text-gray-300" />,
|
||||
},
|
||||
{
|
||||
label: localize('com_ui_share'),
|
||||
onClick: shareHandler,
|
||||
icon: <Share2 className="icon-md mr-2 dark:text-gray-300" />,
|
||||
show: isSharedButtonEnabled,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<HoverToggle
|
||||
isActiveConvo={!!isActiveConvo}
|
||||
isPopoverActive={isPopoverActive}
|
||||
setIsPopoverActive={setIsPopoverActive}
|
||||
className={className}
|
||||
>
|
||||
<DropDownMenu
|
||||
icon={<Upload />}
|
||||
tooltip={localize('com_endpoint_export_share')}
|
||||
className="pointer-cursor relative z-50 flex h-[40px] min-w-4 flex-none flex-col items-center justify-center rounded-md border border-gray-100 bg-white px-3 text-left hover:bg-gray-50 focus:outline-none focus:ring-0 focus:ring-offset-0 radix-state-open:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:radix-state-open:bg-gray-700 sm:text-sm"
|
||||
>
|
||||
{conversation && conversation.conversationId && (
|
||||
<>
|
||||
<ExportButton conversation={conversation} setPopoverActive={setIsPopoverActive} />
|
||||
{isSharedButtonEnabled && (
|
||||
<ShareButton
|
||||
conversationId={conversation.conversationId}
|
||||
title={conversation.title ?? ''}
|
||||
appendLabel={true}
|
||||
className="mb-[3.5px]"
|
||||
setPopoverActive={setIsPopoverActive}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropDownMenu>
|
||||
</HoverToggle>
|
||||
<>
|
||||
<DropdownPopup
|
||||
isOpen={isPopoverActive}
|
||||
setIsOpen={setIsPopoverActive}
|
||||
trigger={
|
||||
<Button
|
||||
id="export-menu-button"
|
||||
aria-label="Export options"
|
||||
variant="outline"
|
||||
className="mr-4 h-10 w-10 p-0 transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<Upload className="icon-md dark:text-gray-300" aria-hidden="true" focusable="false" />
|
||||
</Button>
|
||||
}
|
||||
items={dropdownItems}
|
||||
anchor="bottom end"
|
||||
/>
|
||||
{showShareDialog && conversation.conversationId != null && (
|
||||
<ShareButton
|
||||
conversationId={conversation.conversationId}
|
||||
title={conversation.title ?? ''}
|
||||
showShareDialog={showShareDialog}
|
||||
setShowShareDialog={setShowShareDialog}
|
||||
/>
|
||||
)}
|
||||
{showExports && (
|
||||
<ExportModal
|
||||
open={showExports}
|
||||
onOpenChange={onOpenChange}
|
||||
conversation={conversation}
|
||||
aria-label="Export conversation modal"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { useState } from 'react';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { ExportModal } from '../Nav';
|
||||
|
||||
function ExportButton({
|
||||
conversation,
|
||||
setPopoverActive,
|
||||
}: {
|
||||
conversation: TConversation;
|
||||
setPopoverActive: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
||||
const [showExports, setShowExports] = useState(false);
|
||||
|
||||
const clickHandler = () => {
|
||||
setShowExports(true);
|
||||
};
|
||||
|
||||
const onOpenChange = (value: boolean) => {
|
||||
setShowExports(value);
|
||||
setPopoverActive(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={clickHandler}
|
||||
className="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"
|
||||
>
|
||||
<Upload size={16} /> {localize('com_nav_export')}
|
||||
</button>
|
||||
{showExports && (
|
||||
<ExportModal open={showExports} onOpenChange={onOpenChange} conversation={conversation} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExportButton;
|
|
@ -12,31 +12,31 @@ export default function Footer({ className }: { className?: string }) {
|
|||
const privacyPolicy = config?.interface?.privacyPolicy;
|
||||
const termsOfService = config?.interface?.termsOfService;
|
||||
|
||||
const privacyPolicyRender = privacyPolicy?.externalUrl && (
|
||||
const privacyPolicyRender = privacyPolicy?.externalUrl != null && (
|
||||
<a
|
||||
className=" text-gray-600 underline dark:text-gray-300"
|
||||
className="text-text-secondary underline"
|
||||
href={privacyPolicy.externalUrl}
|
||||
target={privacyPolicy.openNewTab ? '_blank' : undefined}
|
||||
target={privacyPolicy.openNewTab === true ? '_blank' : undefined}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{localize('com_ui_privacy_policy')}
|
||||
</a>
|
||||
);
|
||||
|
||||
const termsOfServiceRender = termsOfService?.externalUrl && (
|
||||
const termsOfServiceRender = termsOfService?.externalUrl != null && (
|
||||
<a
|
||||
className=" text-gray-600 underline dark:text-gray-300"
|
||||
className="text-text-secondary underline"
|
||||
href={termsOfService.externalUrl}
|
||||
target={termsOfService.openNewTab ? '_blank' : undefined}
|
||||
target={termsOfService.openNewTab === true ? '_blank' : undefined}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{localize('com_ui_terms_of_service')}
|
||||
</a>
|
||||
);
|
||||
|
||||
if (config?.analyticsGtmId) {
|
||||
if (config?.analyticsGtmId != null) {
|
||||
const tagManagerArgs = {
|
||||
gtmId: config?.analyticsGtmId,
|
||||
gtmId: config.analyticsGtmId,
|
||||
};
|
||||
TagManager.initialize(tagManagerArgs);
|
||||
}
|
||||
|
@ -54,19 +54,22 @@ export default function Footer({ className }: { className?: string }) {
|
|||
<React.Fragment key={`main-content-part-${index}`}>
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
a: (props) => {
|
||||
const { ['node']: _, href, ...otherProps } = props;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
a: ({ node: _n, href, children, ...otherProps }) => {
|
||||
return (
|
||||
<a
|
||||
className=" text-gray-600 underline dark:text-gray-300"
|
||||
className="text-text-secondary underline"
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
{...otherProps}
|
||||
/>
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
p: ({ node, ...props }) => <span {...props} />,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
p: ({ node: _n, ...props }) => <span {...props} />,
|
||||
}}
|
||||
>
|
||||
{text.trim()}
|
||||
|
@ -81,8 +84,8 @@ export default function Footer({ className }: { className?: string }) {
|
|||
return (
|
||||
<div
|
||||
className={
|
||||
className ||
|
||||
'relative flex items-center justify-center gap-2 px-2 py-2 text-center text-xs text-gray-600 dark:text-gray-300 md:px-[60px]'
|
||||
className ??
|
||||
'relative flex items-center justify-center gap-2 px-2 py-2 text-center text-xs text-text-primary md:px-[60px]'
|
||||
}
|
||||
role="contentinfo"
|
||||
>
|
||||
|
@ -92,7 +95,7 @@ export default function Footer({ className }: { className?: string }) {
|
|||
<React.Fragment key={`footer-element-${index}`}>
|
||||
{contentRender}
|
||||
{!isLastElement && (
|
||||
<div key={`separator-${index}`} className="h-2 border-r-[1px] border-gray-300" />
|
||||
<div key={`separator-${index}`} className="h-2 border-r-[1px] border-border-medium" />
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
@ -29,17 +29,16 @@ export default function Header() {
|
|||
<div className="flex items-center gap-2">
|
||||
{!navVisible && <HeaderNewChat />}
|
||||
{interfaceConfig.endpointsMenu && <EndpointsMenu />}
|
||||
{modelSpecs?.length > 0 && <ModelSpecsMenu modelSpecs={modelSpecs} />}
|
||||
{modelSpecs.length > 0 && <ModelSpecsMenu modelSpecs={modelSpecs} />}
|
||||
{<HeaderOptions interfaceConfig={interfaceConfig} />}
|
||||
{interfaceConfig.presets && <PresetsMenu />}
|
||||
<BookmarkMenu />
|
||||
<AddMultiConvo />
|
||||
{isSmallScreen && (
|
||||
<ExportAndShareMenu
|
||||
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
|
||||
className="pl-0"
|
||||
/>
|
||||
)}
|
||||
<BookmarkMenu />
|
||||
<AddMultiConvo />
|
||||
</div>
|
||||
{!isSmallScreen && (
|
||||
<ExportAndShareMenu isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false} />
|
||||
|
|
|
@ -33,12 +33,12 @@ export default function OptionsPopover({
|
|||
(_target) => {
|
||||
const target = _target as Element;
|
||||
if (
|
||||
target?.id === 'presets-button' ||
|
||||
(target?.parentNode instanceof Element && target.parentNode.id === 'presets-button')
|
||||
target.id === 'presets-button' ||
|
||||
(target.parentNode instanceof Element && target.parentNode.id === 'presets-button')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const tagName = target?.tagName;
|
||||
const tagName = target.tagName;
|
||||
return tagName === 'path' || tagName === 'svg' || tagName === 'circle';
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,106 +1,128 @@
|
|||
import { useState, type FC } from 'react';
|
||||
import { useState, type FC, useCallback } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
||||
import { Menu, MenuButton, MenuItems } from '@headlessui/react';
|
||||
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
||||
import { useConversationTagsQuery, useTagConversationMutation } from '~/data-provider';
|
||||
import { BookmarkMenuItems } from './Bookmarks/BookmarkMenuItems';
|
||||
import { BookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import { useLocalize, useBookmarkSuccess } from '~/hooks';
|
||||
import { BookmarkEditDialog } from '~/components/Bookmarks';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { useBookmarkSuccess } from '~/hooks';
|
||||
import { Spinner } from '~/components';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
const BookmarkMenu: FC = () => {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
|
||||
const conversation = useRecoilValue(store.conversationByIndex(0));
|
||||
const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined;
|
||||
const conversationId = conversation?.conversationId ?? '';
|
||||
const onSuccess = useBookmarkSuccess(conversationId);
|
||||
const [tags, setTags] = useState<string[]>(conversation?.tags || []);
|
||||
|
||||
const [open, setIsOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { mutateAsync, isLoading } = useTagConversationMutation(conversationId);
|
||||
|
||||
const { data } = useConversationTagsQuery();
|
||||
|
||||
const isActiveConvo =
|
||||
const isActiveConvo = Boolean(
|
||||
conversation &&
|
||||
conversationId &&
|
||||
conversationId !== Constants.NEW_CONVO &&
|
||||
conversationId !== 'search';
|
||||
conversationId &&
|
||||
conversationId !== Constants.NEW_CONVO &&
|
||||
conversationId !== 'search',
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (tag?: string): Promise<void> => {
|
||||
if (tag === undefined || tag === '' || !conversationId) {
|
||||
showToast({
|
||||
message: 'Invalid tag or conversationId',
|
||||
severity: NotificationSeverity.ERROR,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newTags = tags.includes(tag) ? tags.filter((t) => t !== tag) : [...tags, tag];
|
||||
await mutateAsync(
|
||||
{
|
||||
tags: newTags,
|
||||
},
|
||||
{
|
||||
onSuccess: (newTags: string[]) => {
|
||||
setTags(newTags);
|
||||
onSuccess(newTags);
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: 'Error adding bookmark',
|
||||
severity: NotificationSeverity.ERROR,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
[tags, conversationId, mutateAsync, setTags, onSuccess, showToast],
|
||||
);
|
||||
|
||||
if (!isActiveConvo) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const onOpenChange = async (open: boolean) => {
|
||||
if (!open) {
|
||||
setIsOpen(open);
|
||||
return;
|
||||
}
|
||||
if (open && tags && tags.length > 0) {
|
||||
setIsOpen(open);
|
||||
} else {
|
||||
if (conversation && conversationId) {
|
||||
await mutateAsync(
|
||||
{
|
||||
tags: [Constants.SAVED_TAG as 'Saved'],
|
||||
},
|
||||
{
|
||||
onSuccess: (newTags: string[]) => {
|
||||
setTags(newTags);
|
||||
onSuccess(newTags);
|
||||
},
|
||||
onError: () => {
|
||||
console.error('Error adding bookmark');
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderButtonContent = () => {
|
||||
if (isLoading) {
|
||||
return <Spinner />;
|
||||
return <Spinner aria-label="Spinner" />;
|
||||
}
|
||||
if (tags && tags.length > 0) {
|
||||
return <BookmarkFilledIcon className="icon-sm" />;
|
||||
if (tags.length > 0) {
|
||||
return <BookmarkFilledIcon className="icon-sm" aria-label="Filled Bookmark" />;
|
||||
}
|
||||
return <BookmarkIcon className="icon-sm" />;
|
||||
return <BookmarkIcon className="icon-sm" aria-label="Bookmark" />;
|
||||
};
|
||||
|
||||
const handleToggleOpen = () => {
|
||||
setOpen(!open);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return (
|
||||
<Root open={open} onOpenChange={onOpenChange}>
|
||||
<Trigger asChild>
|
||||
<button
|
||||
id="header-bookmarks-menu"
|
||||
className={cn(
|
||||
'pointer-cursor relative flex flex-col rounded-md border border-border-light bg-transparent text-left focus:outline-none focus:ring-0 sm:text-sm',
|
||||
'hover:bg-header-button-hover radix-state-open:bg-header-button-hover',
|
||||
'z-50 flex h-[40px] min-w-4 flex-none items-center justify-center px-3 focus:outline-offset-2 focus:ring-0 focus-visible:ring-2 focus-visible:ring-ring-primary ',
|
||||
)}
|
||||
title={localize('com_ui_bookmarks')}
|
||||
>
|
||||
{renderButtonContent()}
|
||||
</button>
|
||||
</Trigger>
|
||||
<Portal>
|
||||
<Content
|
||||
className="mt-2 grid max-h-[500px] w-full min-w-[240px] overflow-y-auto rounded-lg border border-border-medium bg-header-primary text-text-primary shadow-lg"
|
||||
side="bottom"
|
||||
align="start"
|
||||
>
|
||||
{data && conversation && (
|
||||
<BookmarkContext.Provider value={{ bookmarks: data }}>
|
||||
<BookmarkMenuItems conversation={conversation} tags={tags ?? []} setTags={setTags} />
|
||||
</BookmarkContext.Provider>
|
||||
)}
|
||||
</Content>
|
||||
</Portal>
|
||||
</Root>
|
||||
<>
|
||||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<MenuButton
|
||||
aria-label="Add bookmarks"
|
||||
className={cn(
|
||||
'mt-text-sm flex size-10 items-center justify-center gap-2 rounded-lg border border-border-light text-sm transition-colors duration-200 hover:bg-surface-hover',
|
||||
open ? 'bg-surface-hover' : '',
|
||||
)}
|
||||
data-testid="bookmark-menu"
|
||||
>
|
||||
{renderButtonContent()}
|
||||
</MenuButton>
|
||||
<MenuItems
|
||||
anchor="bottom start"
|
||||
className="overflow-hidden rounded-lg bg-header-primary p-1.5 shadow-lg outline-none"
|
||||
>
|
||||
<BookmarkContext.Provider value={{ bookmarks: data || [] }}>
|
||||
<BookmarkMenuItems
|
||||
handleToggleOpen={handleToggleOpen}
|
||||
tags={tags}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
</BookmarkContext.Provider>
|
||||
</MenuItems>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
<BookmarkEditDialog
|
||||
conversation={conversation}
|
||||
tags={tags}
|
||||
setTags={setTags}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,78 +1,34 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import { BookmarkPlusIcon } from 'lucide-react';
|
||||
import type { FC } from 'react';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { BookmarkItems, BookmarkEditDialog } from '~/components/Bookmarks';
|
||||
import { useTagConversationMutation } from '~/data-provider';
|
||||
import { useLocalize, useBookmarkSuccess } from '~/hooks';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { BookmarkItems, BookmarkItem } from '~/components/Bookmarks';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export const BookmarkMenuItems: FC<{
|
||||
conversation: TConversation;
|
||||
tags: string[];
|
||||
setTags: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
}> = ({ conversation, tags, setTags }) => {
|
||||
const { showToast } = useToastContext();
|
||||
handleToggleOpen?: () => Promise<void>;
|
||||
handleSubmit: (tag?: string) => Promise<void>;
|
||||
}> = ({
|
||||
tags,
|
||||
handleSubmit,
|
||||
handleToggleOpen = async () => {
|
||||
('');
|
||||
},
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
|
||||
const conversationId = conversation?.conversationId ?? '';
|
||||
const onSuccess = useBookmarkSuccess(conversationId);
|
||||
|
||||
const { mutateAsync } = useTagConversationMutation(conversationId);
|
||||
const handleSubmit = useCallback(
|
||||
async (tag: string): Promise<void> => {
|
||||
if (tags !== undefined && conversationId) {
|
||||
const newTags = tags.includes(tag) ? tags.filter((t) => t !== tag) : [...tags, tag];
|
||||
await mutateAsync(
|
||||
{
|
||||
tags: newTags,
|
||||
},
|
||||
{
|
||||
onSuccess: (newTags: string[]) => {
|
||||
setTags(newTags);
|
||||
onSuccess(newTags);
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: 'Error adding bookmark',
|
||||
severity: NotificationSeverity.ERROR,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
[tags, conversationId, mutateAsync, setTags, onSuccess, showToast],
|
||||
);
|
||||
|
||||
return (
|
||||
<BookmarkItems
|
||||
ctx="header"
|
||||
tags={tags}
|
||||
handleSubmit={handleSubmit}
|
||||
header={
|
||||
<div>
|
||||
<BookmarkEditDialog
|
||||
conversation={conversation}
|
||||
tags={tags}
|
||||
setTags={setTags}
|
||||
trigger={
|
||||
<div
|
||||
role="menuitem"
|
||||
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-header-hover focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div className="flex grow items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookmarkPlusIcon className="size-4" />
|
||||
<div className="break-all">{localize('com_ui_bookmarks_new')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<BookmarkItem
|
||||
tag={localize('com_ui_bookmarks_new')}
|
||||
data-testid="bookmark-item-new"
|
||||
handleSubmit={handleToggleOpen}
|
||||
selected={false}
|
||||
icon={<BookmarkPlusIcon className="size-4" aria-label="Add Bookmark" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -36,7 +36,7 @@ const MenuItem: FC<MenuItemProps> = ({
|
|||
const expiryTime = getExpiry();
|
||||
|
||||
const clickHandler = () => {
|
||||
if (!expiryTime) {
|
||||
if (expiryTime == null) {
|
||||
setDialogOpen(true);
|
||||
}
|
||||
if (onClick) {
|
||||
|
@ -60,6 +60,12 @@ const MenuItem: FC<MenuItemProps> = ({
|
|||
{...rest}
|
||||
onClick={clickHandler}
|
||||
aria-label={title}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
clickHandler();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex grow items-center justify-between gap-2">
|
||||
<div>
|
||||
|
@ -78,7 +84,7 @@ const MenuItem: FC<MenuItemProps> = ({
|
|||
className={cn(
|
||||
'invisible flex gap-x-1 group-hover:visible',
|
||||
selected ? 'visible' : '',
|
||||
expiryTime
|
||||
expiryTime != null
|
||||
? 'w-full rounded-lg p-2 hover:bg-gray-200 dark:hover:bg-gray-900'
|
||||
: '',
|
||||
)}
|
||||
|
@ -88,10 +94,15 @@ const MenuItem: FC<MenuItemProps> = ({
|
|||
setDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className={cn('invisible group-hover:visible', expiryTime ? 'text-xs' : '')}>
|
||||
<div
|
||||
className={cn(
|
||||
'invisible group-hover:visible',
|
||||
expiryTime != null ? 'text-xs' : '',
|
||||
)}
|
||||
>
|
||||
{localize('com_endpoint_config_key')}
|
||||
</div>
|
||||
<Settings className={cn(expiryTime ? 'icon-sm' : 'icon-md stroke-1')} />
|
||||
<Settings className={cn(expiryTime != null ? 'icon-sm' : 'icon-md stroke-1')} />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
@ -45,7 +45,7 @@ const PresetItems: FC<{
|
|||
<div className="flex h-full grow items-center justify-end gap-2">
|
||||
<label
|
||||
htmlFor="default-preset"
|
||||
className="w-40 truncate rounded bg-transparent py-1 text-xs font-medium font-normal text-gray-600 transition-colors dark:bg-transparent dark:text-gray-300 sm:w-72"
|
||||
className="w-40 truncate rounded bg-transparent py-1 text-xs font-medium text-gray-600 transition-colors dark:bg-transparent dark:text-gray-300 sm:w-72"
|
||||
>
|
||||
{defaultPreset
|
||||
? `${localize('com_endpoint_preset_default_item')} ${defaultPreset.title}`
|
||||
|
@ -55,7 +55,7 @@ const PresetItems: FC<{
|
|||
<DialogTrigger asChild>
|
||||
<label
|
||||
htmlFor="file-upload"
|
||||
className="mr-1 flex h-[32px] cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-medium font-normal text-gray-600 transition-colors hover:bg-gray-100 hover:text-red-700 dark:bg-transparent dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-green-500 dark:hover:text-red-700"
|
||||
className="mr-1 flex h-[32px] cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-red-700 dark:bg-transparent dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-red-700"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
|
@ -159,7 +159,7 @@ const PresetItems: FC<{
|
|||
<PinIcon unpin={defaultPreset?.presetId === preset.presetId} />
|
||||
</button>
|
||||
<button
|
||||
className="m-0 h-full rounded-md p-2 text-gray-400 hover:text-gray-700 dark:bg-gray-600 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
||||
className="m-0 h-full rounded-md p-2 text-gray-400 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -169,7 +169,7 @@ const PresetItems: FC<{
|
|||
<EditIcon />
|
||||
</button>
|
||||
<button
|
||||
className="m-0 h-full rounded-md p-2 text-gray-400 hover:text-gray-600 dark:bg-gray-600 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
||||
className="m-0 h-full rounded-md p-2 text-gray-400 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useState, useRef, useMemo } from 'react';
|
||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
||||
import { useConversations, useNavigateToConvo, useMediaQuery } from '~/hooks';
|
||||
import { useUpdateConversationMutation } from '~/data-provider';
|
||||
import EndpointIcon from '~/components/Endpoints/EndpointIcon';
|
||||
import { useConversations, useNavigateToConvo } from '~/hooks';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { ArchiveIcon } from '~/components/svg';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import ArchiveButton from './ArchiveButton';
|
||||
import DropDownMenu from './DropDownMenu';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import RenameButton from './RenameButton';
|
||||
import HoverToggle from './HoverToggle';
|
||||
import ShareButton from './ShareButton';
|
||||
import { ConvoOptions } from './ConvoOptions';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
|
@ -28,7 +24,6 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
|
|||
const activeConvos = useRecoilValue(store.allConversationsSelector);
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
const { navigateWithLastTools } = useNavigateToConvo();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { refreshConversations } = useConversations();
|
||||
const { showToast } = useToastContext();
|
||||
const { conversationId, title } = conversation;
|
||||
|
@ -36,6 +31,7 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
|
|||
const [titleInput, setTitleInput] = useState(title);
|
||||
const [renaming, setRenaming] = useState(false);
|
||||
const [isPopoverActive, setIsPopoverActive] = useState(false);
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
const clickHandler = async (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
if (event.button === 0 && (event.ctrlKey || event.metaKey)) {
|
||||
|
@ -44,7 +40,7 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
|
|||
}
|
||||
|
||||
event.preventDefault();
|
||||
if (currentConvoId === conversationId) {
|
||||
if (currentConvoId === conversationId || isPopoverActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -57,17 +53,17 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
|
|||
};
|
||||
|
||||
const renameHandler = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
setIsPopoverActive(false);
|
||||
setTitleInput(title);
|
||||
setRenaming(true);
|
||||
setTimeout(() => {
|
||||
if (!inputRef.current) {
|
||||
return;
|
||||
}
|
||||
inputRef.current.focus();
|
||||
}, 25);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (renaming && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [renaming]);
|
||||
|
||||
const onRename = (e: MouseEvent<HTMLButtonElement> | FocusEvent<HTMLInputElement> | KeyEvent) => {
|
||||
e.preventDefault();
|
||||
setRenaming(false);
|
||||
|
@ -99,6 +95,12 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
|
|||
}
|
||||
};
|
||||
|
||||
const cancelRename = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
setTitleInput(title);
|
||||
setRenaming(false);
|
||||
};
|
||||
|
||||
const isActiveConvo =
|
||||
currentConvoId === conversationId ||
|
||||
(isLatestConvo && currentConvoId === 'new' && activeConvos[0] && activeConvos[0] !== 'new');
|
||||
|
@ -106,95 +108,77 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'hover:bg-token-sidebar-surface-secondary group relative rounded-lg active:opacity-90',
|
||||
'group relative mt-2 flex h-9 items-center rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700',
|
||||
isActiveConvo ? 'bg-gray-200 dark:bg-gray-700' : '',
|
||||
isSmallScreen ? 'h-12' : '',
|
||||
)}
|
||||
>
|
||||
{renaming ? (
|
||||
<div className="absolute inset-0 z-50 flex w-full items-center rounded-lg bg-gray-200 p-1.5 dark:bg-gray-700">
|
||||
<div className="absolute inset-0 z-20 flex w-full items-center rounded-lg bg-gray-200 p-1.5 dark:bg-gray-700">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="w-full rounded border border-blue-500 bg-transparent p-0.5 text-sm leading-tight outline-none"
|
||||
className="w-full rounded bg-transparent p-0.5 text-sm leading-tight outline-none"
|
||||
value={titleInput}
|
||||
onChange={(e) => setTitleInput(e.target.value)}
|
||||
onBlur={onRename}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<div className="flex gap-1">
|
||||
<button onClick={cancelRename}>
|
||||
<X className="transition-color h-4 w-4 duration-200 ease-in-out hover:opacity-70" />
|
||||
</button>
|
||||
<button onClick={onRename}>
|
||||
<Check className="transition-color h-4 w-4 duration-200 ease-in-out hover:opacity-70" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<HoverToggle
|
||||
isActiveConvo={isActiveConvo}
|
||||
<a
|
||||
href={`/c/${conversationId}`}
|
||||
data-testid="convo-item"
|
||||
onClick={clickHandler}
|
||||
className={cn(
|
||||
'flex grow cursor-pointer items-center gap-2 overflow-hidden whitespace-nowrap break-all rounded-lg px-2 py-2',
|
||||
isActiveConvo ? 'bg-gray-200 dark:bg-gray-700' : '',
|
||||
)}
|
||||
title={title}
|
||||
>
|
||||
<EndpointIcon
|
||||
conversation={conversation}
|
||||
endpointsConfig={endpointsConfig}
|
||||
size={20}
|
||||
context="menu-item"
|
||||
/>
|
||||
{!renaming && (
|
||||
<div className="relative line-clamp-1 flex-1 grow overflow-hidden">{title}</div>
|
||||
)}
|
||||
{isActiveConvo ? (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute bottom-0 right-0 top-0 w-20 rounded-r-lg bg-gradient-to-l',
|
||||
!renaming ? 'from-gray-200 from-40% to-transparent dark:from-gray-700' : '',
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute bottom-0 right-0 top-0 w-20 rounded-r-lg bg-gradient-to-l from-gray-50 from-0% to-transparent group-hover:from-gray-200 group-hover:from-40% dark:from-gray-850 dark:group-hover:from-gray-700" />
|
||||
)}
|
||||
</a>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
'mr-2',
|
||||
isPopoverActive || isActiveConvo ? 'flex' : 'hidden group-hover:flex',
|
||||
)}
|
||||
>
|
||||
<ConvoOptions
|
||||
conversation={conversation}
|
||||
retainView={retainView}
|
||||
renameHandler={renameHandler}
|
||||
isPopoverActive={isPopoverActive}
|
||||
setIsPopoverActive={setIsPopoverActive}
|
||||
>
|
||||
<DropDownMenu>
|
||||
{startupConfig && startupConfig.sharedLinksEnabled && (
|
||||
<ShareButton
|
||||
conversationId={conversationId}
|
||||
title={title}
|
||||
appendLabel={true}
|
||||
className="mb-[3.5px]"
|
||||
setPopoverActive={setIsPopoverActive}
|
||||
/>
|
||||
)}
|
||||
|
||||
<RenameButton
|
||||
renaming={renaming}
|
||||
onRename={onRename}
|
||||
renameHandler={renameHandler}
|
||||
appendLabel={true}
|
||||
className="mb-[3.5px]"
|
||||
/>
|
||||
<DeleteButton
|
||||
conversationId={conversationId}
|
||||
retainView={retainView}
|
||||
renaming={renaming}
|
||||
title={title}
|
||||
appendLabel={true}
|
||||
className="group m-1.5 mt-[3.5px] 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"
|
||||
/>
|
||||
</DropDownMenu>
|
||||
<ArchiveButton
|
||||
className="z-50 hover:text-black dark:hover:text-white"
|
||||
conversationId={conversationId}
|
||||
retainView={retainView}
|
||||
shouldArchive={true}
|
||||
icon={<ArchiveIcon className="hover:text-gray-400" />}
|
||||
/>
|
||||
</HoverToggle>
|
||||
)}
|
||||
<a
|
||||
href={`/c/${conversationId}`}
|
||||
data-testid="convo-item"
|
||||
onClick={clickHandler}
|
||||
className={cn(
|
||||
isActiveConvo || isPopoverActive
|
||||
? 'group relative mt-2 flex cursor-pointer items-center gap-2 break-all rounded-lg bg-gray-200 px-2 py-2 active:opacity-50 dark:bg-gray-700'
|
||||
: 'group relative mt-2 flex grow cursor-pointer items-center gap-2 overflow-hidden whitespace-nowrap break-all rounded-lg px-2 py-2 hover:bg-gray-200 active:opacity-50 dark:hover:bg-gray-700',
|
||||
!isActiveConvo && !renaming ? 'peer-hover:bg-gray-200 dark:peer-hover:bg-gray-800' : '',
|
||||
)}
|
||||
title={title}
|
||||
>
|
||||
<EndpointIcon
|
||||
conversation={conversation}
|
||||
endpointsConfig={endpointsConfig}
|
||||
size={20}
|
||||
context="menu-item"
|
||||
isActiveConvo={isActiveConvo}
|
||||
/>
|
||||
{!renaming && (
|
||||
<div className="relative line-clamp-1 max-h-5 flex-1 grow overflow-hidden">{title}</div>
|
||||
)}
|
||||
{isActiveConvo ? (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute bottom-0 right-0 top-0 w-20 rounded-r-lg bg-gradient-to-l',
|
||||
!renaming ? 'from-gray-200 from-60% to-transparent dark:from-gray-700' : '',
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute bottom-0 right-0 top-0 w-20 rounded-r-lg bg-gradient-to-l from-gray-50 from-0% to-transparent group-hover:from-gray-200 group-hover:from-60% dark:from-gray-850 dark:group-hover:from-gray-700" />
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
||||
|
@ -7,19 +8,19 @@ import { NotificationSeverity } from '~/common';
|
|||
import { useToastContext } from '~/Providers';
|
||||
|
||||
type ArchiveButtonProps = {
|
||||
children?: React.ReactNode;
|
||||
conversationId: string;
|
||||
retainView: () => void;
|
||||
shouldArchive: boolean;
|
||||
icon: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
export default function ArchiveButton({
|
||||
conversationId,
|
||||
retainView,
|
||||
shouldArchive,
|
||||
icon,
|
||||
className = '',
|
||||
}: ArchiveButtonProps) {
|
||||
|
||||
export function useArchiveHandler(
|
||||
conversationId: string,
|
||||
shouldArchive: boolean,
|
||||
retainView: () => void,
|
||||
) {
|
||||
const localize = useLocalize();
|
||||
const navigate = useNavigate();
|
||||
const { showToast } = useToastContext();
|
||||
|
@ -29,14 +30,11 @@ export default function ArchiveButton({
|
|||
|
||||
const archiveConvoMutation = useArchiveConversationMutation(conversationId);
|
||||
|
||||
const label = shouldArchive ? 'archive' : 'unarchive';
|
||||
const archiveHandler = (
|
||||
e:
|
||||
| MouseEvent<HTMLButtonElement>
|
||||
| FocusEvent<HTMLInputElement>
|
||||
| KeyboardEvent<HTMLInputElement>,
|
||||
) => {
|
||||
e.preventDefault();
|
||||
return async (e?: MouseEvent | FocusEvent | KeyboardEvent) => {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
const label = shouldArchive ? 'archive' : 'unarchive';
|
||||
archiveConvoMutation.mutate(
|
||||
{ conversationId, isArchived: shouldArchive },
|
||||
{
|
||||
|
@ -58,6 +56,17 @@ export default function ArchiveButton({
|
|||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default function ArchiveButton({
|
||||
conversationId,
|
||||
retainView,
|
||||
shouldArchive,
|
||||
icon,
|
||||
className = '',
|
||||
}: ArchiveButtonProps) {
|
||||
const localize = useLocalize();
|
||||
const archiveHandler = useArchiveHandler(conversationId, shouldArchive, retainView);
|
||||
|
||||
return (
|
||||
<button type="button" className={className} onClick={archiveHandler}>
|
||||
|
@ -67,10 +76,12 @@ export default function ArchiveButton({
|
|||
<span className="h-5 w-5">{icon}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize(`com_ui_${label}`)}
|
||||
{localize(`com_ui_${shouldArchive ? 'archive' : 'unarchive'}`)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export { useArchiveHandler as archiveHandler };
|
|
@ -0,0 +1,101 @@
|
|||
import { useState } from 'react';
|
||||
import { Ellipsis, Share2, Archive, Pen, Trash } from 'lucide-react';
|
||||
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import { Button } from '~/components/ui';
|
||||
import { useArchiveHandler } from './ArchiveButton';
|
||||
import { DropdownPopup } from '~/components/ui';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import ShareButton from './ShareButton';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function ConvoOptions({
|
||||
conversation,
|
||||
retainView,
|
||||
renameHandler,
|
||||
isPopoverActive,
|
||||
setIsPopoverActive,
|
||||
isActiveConvo,
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { conversationId, title } = conversation;
|
||||
const [showShareDialog, setShowShareDialog] = useState(false);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const archiveHandler = useArchiveHandler(conversationId, true, retainView);
|
||||
|
||||
const shareHandler = () => {
|
||||
setIsPopoverActive(false);
|
||||
setShowShareDialog(true);
|
||||
};
|
||||
|
||||
const deleteHandler = () => {
|
||||
setIsPopoverActive(false);
|
||||
setShowDeleteDialog(true);
|
||||
};
|
||||
|
||||
const dropdownItems = [
|
||||
{
|
||||
label: localize('com_ui_rename'),
|
||||
onClick: renameHandler,
|
||||
icon: <Pen className="icon-md mr-2 text-text-secondary" />,
|
||||
},
|
||||
{
|
||||
label: localize('com_ui_share'),
|
||||
onClick: shareHandler,
|
||||
icon: <Share2 className="icon-md mr-2 text-text-secondary" />,
|
||||
show: startupConfig && startupConfig.sharedLinksEnabled,
|
||||
},
|
||||
{
|
||||
label: localize('com_ui_archive'),
|
||||
onClick: archiveHandler,
|
||||
icon: <Archive className="icon-md mr-2 text-text-secondary" />,
|
||||
},
|
||||
{
|
||||
label: localize('com_ui_delete'),
|
||||
onClick: deleteHandler,
|
||||
icon: <Trash className="icon-md mr-2 text-text-secondary" />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownPopup
|
||||
isOpen={isPopoverActive}
|
||||
setIsOpen={setIsPopoverActive}
|
||||
trigger={
|
||||
<Button
|
||||
id="conversation-menu-button"
|
||||
aria-label="conversation-menu-button"
|
||||
variant="link"
|
||||
className="z-10 h-7 w-7 border-none p-0 transition-all duration-200 ease-in-out"
|
||||
>
|
||||
<Ellipsis className="icon-md text-text-secondary" />
|
||||
</Button>
|
||||
}
|
||||
items={dropdownItems}
|
||||
className={`${
|
||||
isActiveConvo === true
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 focus:opacity-100 group-hover:opacity-100 data-[open]:opacity-100'
|
||||
}`}
|
||||
/>
|
||||
{showShareDialog && (
|
||||
<ShareButton
|
||||
conversationId={conversationId}
|
||||
title={title}
|
||||
showShareDialog={showShareDialog}
|
||||
setShowShareDialog={setShowShareDialog}
|
||||
/>
|
||||
)}
|
||||
{showDeleteDialog && (
|
||||
<DeleteButton
|
||||
conversationId={conversationId}
|
||||
retainView={retainView}
|
||||
title={title}
|
||||
showDeleteDialog={showDeleteDialog}
|
||||
setShowDeleteDialog={setShowDeleteDialog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
@ -14,22 +14,32 @@ import {
|
|||
TooltipTrigger,
|
||||
} from '~/components/ui';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { TrashIcon, CrossIcon } from '~/components/svg';
|
||||
import { TrashIcon } from '~/components/svg';
|
||||
import { useLocalize, useNewConvo } from '~/hooks';
|
||||
|
||||
type DeleteButtonProps = {
|
||||
conversationId: string;
|
||||
retainView: () => void;
|
||||
title: string;
|
||||
className?: string;
|
||||
showDeleteDialog?: boolean;
|
||||
setShowDeleteDialog?: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export default function DeleteButton({
|
||||
conversationId,
|
||||
renaming,
|
||||
retainView,
|
||||
title,
|
||||
appendLabel = false,
|
||||
className = '',
|
||||
}) {
|
||||
showDeleteDialog,
|
||||
setShowDeleteDialog,
|
||||
}: DeleteButtonProps) {
|
||||
const localize = useLocalize();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { newConversation } = useNewConvo();
|
||||
const { conversationId: currentConvoId } = useParams();
|
||||
const [open, setOpen] = useState(false);
|
||||
const deleteConvoMutation = useDeleteConversationMutation({
|
||||
onSuccess: () => {
|
||||
if (currentConvoId === conversationId || currentConvoId === 'new') {
|
||||
|
@ -47,57 +57,56 @@ export default function DeleteButton({
|
|||
deleteConvoMutation.mutate({ conversationId, thread_id, source: 'button' });
|
||||
}, [conversationId, deleteConvoMutation, queryClient]);
|
||||
|
||||
const renderDeleteButton = () => {
|
||||
if (appendLabel) {
|
||||
return (
|
||||
const dialogContent = (
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_conversation')}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<>
|
||||
<TrashIcon /> {localize('com_ui_delete')}
|
||||
<div className="flex w-full flex-col items-center gap-2">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="dialog-confirm-delete" className="text-left text-sm font-medium">
|
||||
{localize('com_ui_delete_confirm')} <strong>{title}</strong>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
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'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (showDeleteDialog !== undefined && setShowDeleteDialog !== undefined) {
|
||||
return (
|
||||
<OGDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
{dialogContent}
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
<OGDialogTrigger asChild>
|
||||
<TooltipTrigger asChild>
|
||||
<button>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
</OGDialogTrigger>
|
||||
<TooltipContent side="top" sideOffset={0} className={className}>
|
||||
{localize('com_ui_delete')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<OGDialog>
|
||||
<OGDialogTrigger asChild>
|
||||
<button className={className}>{renaming ? <CrossIcon /> : renderDeleteButton()}</button>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_conversation')}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<>
|
||||
<div className="flex w-full flex-col items-center gap-2">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="dialog-confirm-delete" className="text-left text-sm font-medium">
|
||||
{localize('com_ui_delete_confirm')} <strong>{title}</strong>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
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'),
|
||||
}}
|
||||
/>
|
||||
{dialogContent}
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
100
client/src/components/Conversations/ConvoOptions/ShareButton.tsx
Normal file
100
client/src/components/Conversations/ConvoOptions/ShareButton.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { OGDialog } from '~/components/ui';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import type { TSharedLink } from 'librechat-data-provider';
|
||||
import { useCreateSharedLinkMutation } from '~/data-provider';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import SharedLinkButton from './SharedLinkButton';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { Spinner } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function ShareButton({
|
||||
conversationId,
|
||||
title,
|
||||
showShareDialog,
|
||||
setShowShareDialog,
|
||||
}: {
|
||||
conversationId: string;
|
||||
title: string;
|
||||
showShareDialog: boolean;
|
||||
setShowShareDialog: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const { mutate, isLoading } = useCreateSharedLinkMutation();
|
||||
const [share, setShare] = useState<TSharedLink | null>(null);
|
||||
const [isUpdated, setIsUpdated] = useState(false);
|
||||
const [isNewSharedLink, setIsNewSharedLink] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || share) {
|
||||
return;
|
||||
}
|
||||
const data = {
|
||||
conversationId,
|
||||
title,
|
||||
isAnonymous: true,
|
||||
};
|
||||
|
||||
mutate(data, {
|
||||
onSuccess: (result) => {
|
||||
setShare(result);
|
||||
setIsNewSharedLink(!result.isPublic);
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: localize('com_ui_share_error'),
|
||||
severity: NotificationSeverity.ERROR,
|
||||
showIcon: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// mutation.mutate should only be called once
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const buttons = share && (
|
||||
<SharedLinkButton
|
||||
share={share}
|
||||
conversationId={conversationId}
|
||||
setShare={setShare}
|
||||
isUpdated={isUpdated}
|
||||
setIsUpdated={setIsUpdated}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<OGDialog open={showShareDialog} onOpenChange={setShowShareDialog}>
|
||||
<OGDialogTemplate
|
||||
buttons={buttons}
|
||||
showCloseButton={true}
|
||||
showCancelButton={false}
|
||||
title={localize('com_ui_share_link_to_chat')}
|
||||
className="max-w-[550px]"
|
||||
main={
|
||||
<div>
|
||||
<div className="h-full py-2 text-gray-400 dark:text-gray-200">
|
||||
{(() => {
|
||||
if (isLoading) {
|
||||
return <Spinner className="m-auto h-14 animate-spin" />;
|
||||
}
|
||||
|
||||
if (isUpdated) {
|
||||
return isNewSharedLink
|
||||
? localize('com_ui_share_created_message')
|
||||
: localize('com_ui_share_updated_message');
|
||||
}
|
||||
|
||||
return share?.isPublic
|
||||
? localize('com_ui_share_update_message')
|
||||
: localize('com_ui_share_create_message');
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export { default as ArchiveButton } from './ArchiveButton';
|
||||
export { default as DeleteButton } from './DeleteButton';
|
||||
export { default as ShareButton } from './ShareButton';
|
||||
export { default as SharedLinkButton } from './SharedLinkButton';
|
||||
export { default as ConvoOptions } from './ConvoOptions';
|
|
@ -1,46 +0,0 @@
|
|||
import type { MouseEvent, ReactElement } from 'react';
|
||||
import { EditIcon, CheckMark } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
interface RenameButtonProps {
|
||||
renaming: boolean;
|
||||
renameHandler: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
onRename?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
appendLabel?: boolean;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function RenameButton({
|
||||
renaming,
|
||||
onRename,
|
||||
renameHandler,
|
||||
className = '',
|
||||
disabled = false,
|
||||
appendLabel = false,
|
||||
}: RenameButtonProps): ReactElement {
|
||||
const localize = useLocalize();
|
||||
const handler = renaming ? onRename : renameHandler;
|
||||
|
||||
return (
|
||||
<button
|
||||
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',
|
||||
className,
|
||||
)}
|
||||
disabled={disabled}
|
||||
onClick={handler}
|
||||
>
|
||||
{renaming ? (
|
||||
<CheckMark />
|
||||
) : appendLabel ? (
|
||||
<>
|
||||
<EditIcon /> {localize('com_ui_rename')}
|
||||
</>
|
||||
) : (
|
||||
<EditIcon />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
import { useState } from 'react';
|
||||
import {
|
||||
OGDialog,
|
||||
Tooltip,
|
||||
OGDialogTrigger,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
TooltipProvider,
|
||||
} from '~/components/ui';
|
||||
import { Share2Icon } from 'lucide-react';
|
||||
import type { TSharedLink } from 'librechat-data-provider';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import SharedLinkButton from './SharedLinkButton';
|
||||
import ShareDialog from './ShareDialog';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export default function ShareButton({
|
||||
conversationId,
|
||||
title,
|
||||
className,
|
||||
appendLabel = false,
|
||||
setPopoverActive,
|
||||
}: {
|
||||
conversationId: string;
|
||||
title: string;
|
||||
className?: string;
|
||||
appendLabel?: boolean;
|
||||
setPopoverActive: (isActive: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [share, setShare] = useState<TSharedLink | null>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isUpdated, setIsUpdated] = useState(false);
|
||||
|
||||
const classProp: { className?: string } = {
|
||||
className: 'p-1 hover:text-black dark:hover:text-white',
|
||||
};
|
||||
if (className) {
|
||||
classProp.className = className;
|
||||
}
|
||||
const renderShareButton = () => {
|
||||
if (appendLabel) {
|
||||
return (
|
||||
<>
|
||||
<Share2Icon className="h-4 w-4" /> {localize('com_ui_share')}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Share2Icon />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_share')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const buttons = share && (
|
||||
<SharedLinkButton
|
||||
share={share}
|
||||
conversationId={conversationId}
|
||||
setShare={setShare}
|
||||
isUpdated={isUpdated}
|
||||
setIsUpdated={setIsUpdated}
|
||||
/>
|
||||
);
|
||||
|
||||
const onOpenChange = (open: boolean) => {
|
||||
setPopoverActive(open);
|
||||
setOpen(open);
|
||||
};
|
||||
return (
|
||||
<OGDialog open={open} onOpenChange={onOpenChange}>
|
||||
<OGDialogTrigger asChild>
|
||||
<button
|
||||
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',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{renderShareButton()}
|
||||
</button>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
buttons={buttons}
|
||||
showCloseButton={true}
|
||||
showCancelButton={false}
|
||||
title={localize('com_ui_share_link_to_chat')}
|
||||
className="max-w-[550px]"
|
||||
main={
|
||||
<>
|
||||
<ShareDialog
|
||||
setDialogOpen={setOpen}
|
||||
conversationId={conversationId}
|
||||
title={title}
|
||||
share={share}
|
||||
setShare={setShare}
|
||||
isUpdated={isUpdated}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import { useLocalize } from '~/hooks';
|
||||
|
||||
import { useCreateSharedLinkMutation } from '~/data-provider';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TSharedLink } from 'librechat-data-provider';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { Spinner } from '~/components/svg';
|
||||
|
||||
export default function ShareDialog({
|
||||
conversationId,
|
||||
title,
|
||||
share,
|
||||
setShare,
|
||||
setDialogOpen,
|
||||
isUpdated,
|
||||
}: {
|
||||
conversationId: string;
|
||||
title: string;
|
||||
share: TSharedLink | null;
|
||||
setShare: (share: TSharedLink | null) => void;
|
||||
setDialogOpen: (open: boolean) => void;
|
||||
isUpdated: boolean;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const { mutate, isLoading } = useCreateSharedLinkMutation();
|
||||
const [isNewSharedLink, setIsNewSharedLink] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || share) {
|
||||
return;
|
||||
}
|
||||
const data = {
|
||||
conversationId,
|
||||
title,
|
||||
isAnonymous: true,
|
||||
};
|
||||
|
||||
mutate(data, {
|
||||
onSuccess: (result) => {
|
||||
setShare(result);
|
||||
setIsNewSharedLink(!result.isPublic);
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: localize('com_ui_share_error'),
|
||||
severity: NotificationSeverity.ERROR,
|
||||
showIcon: true,
|
||||
});
|
||||
setDialogOpen(false);
|
||||
},
|
||||
});
|
||||
|
||||
// mutation.mutate should only be called once
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="h-full py-2 text-gray-400 dark:text-gray-200">
|
||||
{(() => {
|
||||
if (isLoading) {
|
||||
return <Spinner className="m-auto h-14 animate-spin" />;
|
||||
}
|
||||
|
||||
if (isUpdated) {
|
||||
return isNewSharedLink
|
||||
? localize('com_ui_share_created_message')
|
||||
: localize('com_ui_share_updated_message');
|
||||
}
|
||||
|
||||
return share?.isPublic
|
||||
? localize('com_ui_share_update_message')
|
||||
: localize('com_ui_share_create_message');
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export { default as Fork } from './Fork';
|
||||
export { default as Pages } from './Pages';
|
||||
export { default as RenameButton } from './RenameButton';
|
||||
export { default as Conversations } from './Conversations';
|
||||
export * from './ConvoOptions';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useState, type FC } from 'react';
|
||||
import { type FC } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { TConversation } from 'librechat-data-provider';
|
||||
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
||||
import { Menu, MenuButton, MenuItems } from '@headlessui/react';
|
||||
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
||||
import { BookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import { useGetConversationTags } from '~/data-provider';
|
||||
|
@ -25,8 +25,6 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps)
|
|||
const activeConvo = useRecoilValue(store.conversationByIndex(0));
|
||||
const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation);
|
||||
|
||||
const [open, setIsOpen] = useState(false);
|
||||
|
||||
let conversation: TConversation | null | undefined;
|
||||
if (location.state?.from?.pathname.includes('/chat')) {
|
||||
conversation = globalConvo;
|
||||
|
@ -35,38 +33,29 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps)
|
|||
}
|
||||
|
||||
return (
|
||||
<Root open={open} onOpenChange={setIsOpen}>
|
||||
<Trigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'relative mt-1 flex h-10 w-full cursor-pointer items-center gap-1 rounded-lg border-border-light bg-transparent px-1 py-2 text-text-primary transition-colors duration-200 focus-within:bg-surface-hover hover:bg-surface-hover',
|
||||
open ? 'bg-surface-hover' : '',
|
||||
)}
|
||||
id="show-bookmarks"
|
||||
data-testid="show-bookmarks"
|
||||
title={localize('com_ui_bookmarks')}
|
||||
>
|
||||
<div className="relative flex h-8 w-8 items-center justify-center rounded-full p-1 text-text-primary">
|
||||
{tags.length > 0 ? (
|
||||
<BookmarkFilledIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<BookmarkIcon className="h-5 w-5" />
|
||||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<MenuButton
|
||||
className={cn(
|
||||
'mt-text-sm flex h-10 w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors duration-200 hover:bg-surface-hover',
|
||||
open ? 'bg-surface-hover' : '',
|
||||
)}
|
||||
</div>
|
||||
<div className="grow overflow-hidden whitespace-nowrap text-left text-sm text-text-primary">
|
||||
{tags.length > 0 ? tags.join(', ') : localize('com_ui_bookmarks')}
|
||||
</div>
|
||||
</button>
|
||||
</Trigger>
|
||||
<Portal>
|
||||
<div className="fixed left-0 top-0 z-auto translate-x-[268px] translate-y-[50px]">
|
||||
<Content
|
||||
side="bottom"
|
||||
align="start"
|
||||
className="mt-2 max-h-96 min-w-[240px] overflow-y-auto rounded-lg border border-border-medium bg-surface-primary-alt text-text-primary shadow-lg lg:max-h-96"
|
||||
data-testid="bookmark-menu"
|
||||
>
|
||||
<div className="relative flex h-8 w-8 items-center justify-center rounded-full p-1 text-text-primary">
|
||||
{tags.length > 0 ? (
|
||||
<BookmarkFilledIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<BookmarkIcon className="h-5 w-5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="grow overflow-hidden whitespace-nowrap text-left text-sm text-text-primary">
|
||||
{tags.length > 0 ? tags.join(', ') : localize('com_ui_bookmarks')}
|
||||
</div>
|
||||
</MenuButton>
|
||||
<MenuItems className="absolute left-0 top-full z-[100] mt-1 w-full translate-y-0 overflow-hidden rounded-lg bg-header-primary p-1.5 shadow-lg outline-none">
|
||||
{data && conversation && (
|
||||
// Display bookmarks and highlight the selected tag
|
||||
<BookmarkContext.Provider value={{ bookmarks: data.filter((tag) => tag.count > 0) }}>
|
||||
<BookmarkNavItems
|
||||
// Currently selected conversation
|
||||
|
@ -78,10 +67,10 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps)
|
|||
/>
|
||||
</BookmarkContext.Provider>
|
||||
)}
|
||||
</Content>
|
||||
</div>
|
||||
</Portal>
|
||||
</Root>
|
||||
</MenuItems>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -43,9 +43,8 @@ const BookmarkNavItems: FC<{
|
|||
return (
|
||||
<div className="flex flex-col">
|
||||
<BookmarkItem
|
||||
ctx="nav"
|
||||
tag={localize('com_ui_no_bookmarks')}
|
||||
data-testid="bookmark-item-clear"
|
||||
data-testid="bookmark-item-no-bookmarks"
|
||||
handleSubmit={() => Promise.resolve()}
|
||||
selected={false}
|
||||
icon={'🤔'}
|
||||
|
@ -57,18 +56,15 @@ const BookmarkNavItems: FC<{
|
|||
return (
|
||||
<div className="flex flex-col">
|
||||
<BookmarkItems
|
||||
ctx="nav"
|
||||
tags={tags}
|
||||
handleSubmit={handleSubmit}
|
||||
highlightSelected={true}
|
||||
header={
|
||||
<BookmarkItem
|
||||
ctx="nav"
|
||||
tag="Clear all"
|
||||
tag={localize('com_ui_clear_all')}
|
||||
data-testid="bookmark-item-clear"
|
||||
handleSubmit={clear}
|
||||
selected={false}
|
||||
icon={<CrossCircledIcon className="h-4 w-4" />}
|
||||
icon={<CrossCircledIcon className="size-4" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import filenamify from 'filenamify';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { Dialog, DialogButton, Input, Label, Checkbox, Dropdown } from '~/components/ui';
|
||||
import { OGDialog, Button, Input, Label, Checkbox, Dropdown } from '~/components/ui';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { useLocalize, useExportConversation } from '~/hooks';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { cn, defaultTextProps } from '~/utils';
|
||||
|
||||
export default function ExportModal({
|
||||
|
@ -62,8 +62,8 @@ export default function ExportModal({
|
|||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogTemplate
|
||||
<OGDialog open={open} onOpenChange={onOpenChange}>
|
||||
<OGDialogTemplate
|
||||
title={localize('com_nav_export_conversation')}
|
||||
className="max-w-full sm:max-w-2xl"
|
||||
main={
|
||||
|
@ -164,16 +164,13 @@ export default function ExportModal({
|
|||
}
|
||||
buttons={
|
||||
<>
|
||||
<DialogButton
|
||||
onClick={exportConversation}
|
||||
className="dark:hover:gray-400 border-gray-700 bg-green-500 text-white hover:bg-green-600 dark:hover:bg-green-600"
|
||||
>
|
||||
<Button onClick={exportConversation} variant="success">
|
||||
{localize('com_endpoint_export')}
|
||||
</DialogButton>
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
selection={undefined}
|
||||
/>
|
||||
</Dialog>
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,13 @@ import NewChat from './NewChat';
|
|||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
const Nav = ({ navVisible, setNavVisible }) => {
|
||||
const Nav = ({
|
||||
navVisible,
|
||||
setNavVisible,
|
||||
}: {
|
||||
navVisible: boolean;
|
||||
setNavVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const { conversationId } = useParams();
|
||||
const { isAuthenticated } = useAuthContext();
|
||||
|
@ -80,7 +86,9 @@ const Nav = ({ navVisible, setNavVisible }) => {
|
|||
setShowLoading,
|
||||
hasNextPage: searchQuery ? searchQueryRes?.hasNextPage : hasNextPage,
|
||||
fetchNextPage: searchQuery ? searchQueryRes?.fetchNextPage : fetchNextPage,
|
||||
isFetchingNextPage: searchQuery ? searchQueryRes?.isFetchingNextPage : isFetchingNextPage,
|
||||
isFetchingNextPage: searchQuery
|
||||
? searchQueryRes?.isFetchingNextPage ?? false
|
||||
: isFetchingNextPage,
|
||||
});
|
||||
|
||||
const conversations = useMemo(
|
||||
|
@ -155,24 +163,37 @@ const Nav = ({ navVisible, setNavVisible }) => {
|
|||
onMouseLeave={handleMouseLeave}
|
||||
ref={containerRef}
|
||||
>
|
||||
<NewChat
|
||||
toggleNav={itemToggleNav}
|
||||
subHeaders={
|
||||
<>
|
||||
{isSearchEnabled && <SearchBar clearSearch={clearSearch} />}
|
||||
<BookmarkNav tags={tags} setTags={setTags} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{isSmallScreen == true ? (
|
||||
<div className="pt-3.5">
|
||||
{isSearchEnabled === true && (
|
||||
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
|
||||
)}
|
||||
<BookmarkNav tags={tags} setTags={setTags} />
|
||||
</div>
|
||||
) : (
|
||||
<NewChat
|
||||
toggleNav={itemToggleNav}
|
||||
subHeaders={
|
||||
<>
|
||||
{isSearchEnabled === true && (
|
||||
<SearchBar
|
||||
clearSearch={clearSearch}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
)}
|
||||
<BookmarkNav tags={tags} setTags={setTags} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Conversations
|
||||
conversations={conversations}
|
||||
moveToTop={moveToTop}
|
||||
toggleNav={itemToggleNav}
|
||||
/>
|
||||
{(isFetchingNextPage || showLoading) && (
|
||||
<Spinner
|
||||
className={cn('m-1 mx-auto mb-4 h-4 w-4 text-black dark:text-white')}
|
||||
/>
|
||||
<Spinner className={cn('m-1 mx-auto mb-4 h-4 w-4 text-text-primary')} />
|
||||
)}
|
||||
</div>
|
||||
<NavLinks />
|
||||
|
|
|
@ -54,7 +54,7 @@ function NavLinks() {
|
|||
<UserIcon />
|
||||
</div>
|
||||
) : (
|
||||
<img className="rounded-full" src={user?.avatar || avatarSrc} alt="avatar" />
|
||||
<img className="rounded-full" src={user.avatar || avatarSrc} alt="avatar" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -68,7 +68,7 @@ function NavLinks() {
|
|||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-110 transform"
|
||||
enter="transition ease-out duration-100 transform"
|
||||
enterFrom="translate-y-2 opacity-0"
|
||||
enterTo="translate-y-0 opacity-100"
|
||||
leave="transition ease-in duration-100 transform"
|
||||
|
|
|
@ -82,14 +82,14 @@ export default function NewChat({
|
|||
return (
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<div className="sticky left-0 right-0 top-0 z-20 bg-gray-50 pt-3.5 dark:bg-gray-850">
|
||||
<div className="sticky left-0 right-0 top-0 z-20 bg-surface-primary-alt pt-3.5">
|
||||
<div className="pb-0.5 last:pb-0" style={{ transform: 'none' }}>
|
||||
<a
|
||||
href="/"
|
||||
tabIndex={0}
|
||||
data-testid="nav-new-chat"
|
||||
onClick={clickHandler}
|
||||
className="group flex h-10 items-center gap-2 rounded-lg px-2 font-medium hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
className="group flex h-10 items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
>
|
||||
<NewChatButtonIcon conversation={conversation} />
|
||||
|
@ -104,7 +104,7 @@ export default function NewChat({
|
|||
aria-label="nav-new-chat-btn"
|
||||
className="text-token-text-primary"
|
||||
>
|
||||
<NewChatIcon className="h-[18px] w-[18px]" />
|
||||
<NewChatIcon className="size-5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={20}>
|
||||
|
|
|
@ -10,10 +10,11 @@ import store from '~/store';
|
|||
|
||||
type SearchBarProps = {
|
||||
clearSearch: () => void;
|
||||
isSmallScreen?: boolean;
|
||||
};
|
||||
|
||||
const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) => {
|
||||
const { clearSearch } = props;
|
||||
const { clearSearch, isSmallScreen } = props;
|
||||
const queryClient = useQueryClient();
|
||||
const clearConvoState = store.useClearConvoState();
|
||||
const setSearchQuery = useSetRecoilState(store.searchQuery);
|
||||
|
@ -58,7 +59,10 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
|||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="relative mt-1 flex h-10 cursor-pointer items-center gap-3 rounded-lg border-border-medium px-3 py-2 text-text-primary transition-colors duration-200 focus-within:bg-surface-hover hover:bg-surface-hover dark:focus-within:bg-surface-hover"
|
||||
className={cn(
|
||||
'relative mt-1 flex h-10 cursor-pointer items-center gap-3 rounded-lg border-border-medium px-3 py-2 text-text-primary transition-colors duration-200 focus-within:bg-surface-hover hover:bg-surface-hover dark:focus-within:bg-surface-hover',
|
||||
isSmallScreen === true ? 'h-16 rounded-2xl' : '',
|
||||
)}
|
||||
>
|
||||
{<Search className="absolute left-3 h-4 w-4" />}
|
||||
<input
|
||||
|
@ -77,8 +81,9 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
|||
/>
|
||||
<X
|
||||
className={cn(
|
||||
'absolute right-[7px] h-5 w-5 cursor-pointer transition-opacity duration-1000',
|
||||
'absolute right-[7px] h-5 w-5 cursor-pointer transition-opacity duration-200',
|
||||
showClearIcon ? 'opacity-100' : 'opacity-0',
|
||||
isSmallScreen === true ? 'right-[16px]' : '',
|
||||
)}
|
||||
onClick={clearText}
|
||||
/>
|
||||
|
|
|
@ -94,7 +94,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<Tabs.Trigger
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
|
||||
: 'bg-surface-tertiary-alt',
|
||||
|
@ -108,7 +108,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<Tabs.Trigger
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
|
||||
: 'bg-surface-tertiary-alt',
|
||||
|
@ -122,7 +122,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<Tabs.Trigger
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
|
||||
: 'bg-surface-tertiary-alt',
|
||||
|
@ -136,7 +136,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<Tabs.Trigger
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap text-sm text-text-secondary'
|
||||
: 'bg-surface-tertiary-alt',
|
||||
|
@ -150,7 +150,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<Tabs.Trigger
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
|
||||
: 'bg-surface-tertiary-alt',
|
||||
|
@ -164,7 +164,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<Tabs.Trigger
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
|
||||
: 'bg-surface-tertiary-alt',
|
||||
|
@ -178,7 +178,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<Tabs.Trigger
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
|
||||
: 'bg-surface-tertiary-alt',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useLocalize } from '~/hooks';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { Dialog, DialogTrigger } from '~/components/ui';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { OGDialog, OGDialogTrigger } from '~/components/ui';
|
||||
|
||||
import ArchivedChatsTable from './ArchivedChatsTable';
|
||||
|
||||
|
@ -10,19 +10,19 @@ export default function ArchivedChats() {
|
|||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_archived_chats')}</div>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<OGDialog>
|
||||
<OGDialogTrigger asChild>
|
||||
<button className="btn btn-neutral relative ">
|
||||
{localize('com_nav_archived_chats_manage')}
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogTemplate
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
title={localize('com_nav_archived_chats')}
|
||||
className="max-w-[1000px]"
|
||||
showCancelButton={false}
|
||||
main={<ArchivedChatsTable />}
|
||||
/>
|
||||
</Dialog>
|
||||
</OGDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ import { useMemo, useState } from 'react';
|
|||
import { MessageCircle, ArchiveRestore } from 'lucide-react';
|
||||
import { useConversationsInfiniteQuery } from '~/data-provider';
|
||||
import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks';
|
||||
import ArchiveButton from '~/components/Conversations/ArchiveButton';
|
||||
import DeleteButton from '~/components/Conversations/DeleteButton';
|
||||
import ArchiveButton from '~/components/Conversations/ConvoOptions/ArchiveButton';
|
||||
import DeleteButton from '~/components/Conversations/ConvoOptions/DeleteButton';
|
||||
import { Spinner } from '~/components/svg';
|
||||
import { cn } from '~/utils';
|
||||
import { ConversationListResponse } from 'librechat-data-provider';
|
||||
|
@ -90,10 +90,7 @@ export default function ArchivedChatsTable({ className }: { className?: string }
|
|||
<DeleteButton
|
||||
conversationId={conversation.conversationId}
|
||||
retainView={moveToTop}
|
||||
renaming={false}
|
||||
title={conversation.title}
|
||||
appendLabel={false}
|
||||
className="group ml-4 flex w-full cursor-pointer items-center items-center gap-2 rounded 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"
|
||||
title={conversation.title ?? ''}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -152,7 +152,7 @@ export default function FilterPrompts({
|
|||
setDisplayName(e.target.value);
|
||||
setName(e.target.value);
|
||||
}}
|
||||
className="max-w-xs border-border-light focus:bg-surface-tertiary"
|
||||
className="w-full border-border-light"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -24,10 +24,15 @@ function PanelNavigation({
|
|||
{!isChatRoute && <ThemeSelector returnThemeOnly={true} />}
|
||||
</div>
|
||||
<div className="mb-2 flex gap-2">
|
||||
<Button variant="outline" onClick={() => prevPage()} disabled={!hasPreviousPage}>
|
||||
<Button variant="outline" size="sm" onClick={() => prevPage()} disabled={!hasPreviousPage}>
|
||||
{localize('com_ui_prev')}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => nextPage()} disabled={!hasNextPage || isFetching}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => nextPage()}
|
||||
disabled={!hasNextPage || isFetching}
|
||||
>
|
||||
{localize('com_ui_next')}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { BookmarkPlusIcon } from 'lucide-react';
|
||||
import { useConversationTagsQuery } from '~/data-provider';
|
||||
import { Button } from '~/components/ui';
|
||||
|
@ -9,20 +10,18 @@ import { useLocalize } from '~/hooks';
|
|||
const BookmarkPanel = () => {
|
||||
const localize = useLocalize();
|
||||
const { data } = useConversationTagsQuery();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="h-auto max-w-full overflow-x-hidden">
|
||||
<BookmarkContext.Provider value={{ bookmarks: data || [] }}>
|
||||
<BookmarkTable />
|
||||
<div className="flex justify-between gap-2">
|
||||
<BookmarkEditDialog
|
||||
trigger={
|
||||
<Button variant="outline" className="w-full text-sm">
|
||||
<BookmarkPlusIcon className="mr-1 size-4" />
|
||||
<div className="break-all">{localize('com_ui_bookmarks_new')}</div>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<BookmarkEditDialog open={open} setOpen={setOpen} />
|
||||
<Button variant="outline" className="w-full text-sm" onClick={() => setOpen(!open)}>
|
||||
<BookmarkPlusIcon className="mr-1 size-4" />
|
||||
<div className="break-all">{localize('com_ui_bookmarks_new')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
</BookmarkContext.Provider>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@ const BookmarkTable = () => {
|
|||
useEffect(() => {
|
||||
setRows(
|
||||
bookmarks
|
||||
?.map((item) => ({ id: item.tag, ...item }))
|
||||
.map((item) => ({ id: item.tag, ...item }))
|
||||
.sort((a, b) => a.position - b.position) || [],
|
||||
);
|
||||
}, [bookmarks]);
|
||||
|
@ -50,7 +50,7 @@ const BookmarkTable = () => {
|
|||
placeholder={localize('com_ui_bookmarks_filter')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full dark:border-gray-700"
|
||||
className="w-full border-border-light"
|
||||
/>
|
||||
</div>
|
||||
<div className="overflow-y-auto rounded-md border border-black/10 dark:border-white/10">
|
||||
|
|
|
@ -77,7 +77,7 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
|
|||
placeholder={localize('com_files_filter')}
|
||||
value={(table.getColumn('filename')?.getFilterValue() as string) ?? ''}
|
||||
onChange={(event) => table.getColumn('filename')?.setFilterValue(event.target.value)}
|
||||
className="w-full dark:border-gray-700"
|
||||
className="w-full border-border-light"
|
||||
/>
|
||||
</div>
|
||||
<div className="overflow-y-auto rounded-md border border-black/10 dark:border-white/10">
|
||||
|
|
|
@ -84,7 +84,7 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr
|
|||
variant === 'default'
|
||||
? 'dark:bg-muted dark:hover:bg-muted dark:text-white dark:hover:text-white'
|
||||
: '',
|
||||
'hover:bg-gray-50 data-[state=open]:bg-gray-50 data-[state=open]:text-black dark:hover:bg-gray-700 dark:data-[state=open]:bg-gray-700 dark:data-[state=open]:text-white',
|
||||
'hover:bg-gray-200 data-[state=open]:bg-gray-200 data-[state=open]:text-black dark:hover:bg-gray-700 dark:data-[state=open]:bg-gray-700 dark:data-[state=open]:text-white',
|
||||
'w-full justify-start rounded-md border dark:border-gray-700',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
|
|
|
@ -3,26 +3,50 @@ import { VariantProps, cva } from 'class-variance-authority';
|
|||
import { cn } from '~/utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'rounded-md inline-flex items-center justify-center text-sm font-medium transition-colors dark:hover:bg-gray-700 dark:hover:text-gray-100 disabled:opacity-50 disabled:pointer-events-none data-[state=open]:bg-gray-100 dark:data-[state=open]:bg-gray-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-500',
|
||||
'rounded-md inline-flex items-center justify-center text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-gray-850 text-white hover:bg-gray-800 dark:bg-gray-50 dark:text-gray-900',
|
||||
destructive: 'bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600',
|
||||
default:
|
||||
'bg-gray-600 text-white hover:bg-gray-800 dark:bg-gray-200 dark:text-gray-900 dark:hover:bg-gray-300',
|
||||
destructive: 'bg-red-600 text-white hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-700',
|
||||
outline:
|
||||
'bg-transparent border border-gray-200 hover:bg-gray-100 dark:border-gray-700 dark:text-gray-100',
|
||||
subtle: 'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-100',
|
||||
'bg-transparent border border-gray-200 text-gray-700 hover:bg-gray-200 dark:border-gray-700 dark:text-gray-100 dark:hover:bg-gray-700',
|
||||
subtle:
|
||||
'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-100 dark:hover:bg-gray-600',
|
||||
ghost:
|
||||
'bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 dark:text-gray-100 dark:hover:text-gray-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent',
|
||||
link: 'bg-transparent underline-offset-4 hover:underline text-gray-900 dark:text-gray-100 hover:bg-transparent dark:hover:bg-transparent',
|
||||
'bg-transparent text-gray-900 hover:bg-gray-100 dark:text-gray-100 dark:hover:bg-gray-800 data-[state=open]:bg-transparent',
|
||||
link: 'bg-transparent underline-offset-4 hover:underline text-gray-600 dark:text-gray-400 hover:bg-transparent dark:hover:bg-transparent',
|
||||
success:
|
||||
'bg-green-500 text-white hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-700',
|
||||
warning:
|
||||
'bg-yellow-500 text-white hover:bg-yellow-600 dark:bg-yellow-600 dark:hover:bg-yellow-700',
|
||||
info: 'bg-blue-500 text-white hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700',
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 py-2 px-4',
|
||||
sm: 'h-9 px-2 rounded-md',
|
||||
lg: 'h-11 px-8 rounded-md',
|
||||
sm: 'h-8 px-3 rounded',
|
||||
lg: 'h-12 px-6 rounded-md',
|
||||
xl: 'h-14 px-8 rounded-lg text-base',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
fullWidth: {
|
||||
true: 'w-full',
|
||||
},
|
||||
loading: {
|
||||
true: 'opacity-80 pointer-events-none',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
variant: ['default', 'destructive', 'success', 'warning', 'info'],
|
||||
className: 'focus-visible:ring-white focus-visible:ring-offset-2',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
className: 'focus-visible:ring-gray-400 dark:focus-visible:ring-gray-500',
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
|
@ -32,17 +56,63 @@ const buttonVariants = cva(
|
|||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {}
|
||||
VariantProps<typeof buttonVariants> {
|
||||
loading?: boolean;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps & { customId?: string }>(
|
||||
({ className, variant, size, customId, ...props }, ref) => {
|
||||
(
|
||||
{
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
fullWidth,
|
||||
loading,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
children,
|
||||
customId,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<button
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
className={cn(buttonVariants({ variant, size, fullWidth, loading, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
id={customId ?? props.id ?? 'shadcn-button'}
|
||||
/>
|
||||
disabled={props.disabled || loading}
|
||||
aria-busy={loading}
|
||||
>
|
||||
{loading && (
|
||||
<svg
|
||||
className="-ml-1 mr-3 h-5 w-5 animate-spin text-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
)}
|
||||
{leftIcon && <span className="mr-2">{leftIcon}</span>}
|
||||
{children}
|
||||
{rightIcon && <span className="ml-2">{rightIcon}</span>}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
100
client/src/components/ui/DropdownPopup.tsx
Normal file
100
client/src/components/ui/DropdownPopup.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import React from 'react';
|
||||
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react';
|
||||
|
||||
interface DropdownProps {
|
||||
trigger: React.ReactNode;
|
||||
items: {
|
||||
label?: string;
|
||||
onClick?: () => void;
|
||||
icon?: React.ReactNode;
|
||||
kbd?: string;
|
||||
show?: boolean;
|
||||
disabled?: boolean;
|
||||
separate?: boolean;
|
||||
}[];
|
||||
isOpen: boolean;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
className?: string;
|
||||
anchor?: string;
|
||||
}
|
||||
|
||||
const DropdownPopup: React.FC<DropdownProps> = ({
|
||||
trigger,
|
||||
items,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
className,
|
||||
anchor = { x: 'bottom', y: 'start' },
|
||||
}) => {
|
||||
const handleButtonClick = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<MenuButton
|
||||
onClick={handleButtonClick}
|
||||
className={`inline-flex items-center gap-2 rounded-md ${className}`}
|
||||
>
|
||||
{trigger}
|
||||
</MenuButton>
|
||||
<Transition
|
||||
show={open}
|
||||
enter="transition-opacity duration-150"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-150"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
afterLeave={() => setIsOpen(false)}
|
||||
>
|
||||
<div className={`${isOpen ? 'visible' : 'invisible'}`}>
|
||||
{open && (
|
||||
<MenuItems
|
||||
static
|
||||
// @ts-ignore
|
||||
anchor={anchor}
|
||||
className="mt-2 overflow-hidden rounded-lg bg-header-primary p-1.5 shadow-lg outline-none focus-visible:ring-2 focus-visible:ring-ring-primary"
|
||||
>
|
||||
<div>
|
||||
{items
|
||||
.filter((item) => item.show !== false)
|
||||
.map((item, index) =>
|
||||
item.separate ? (
|
||||
<div key={index} className="my-1 h-px bg-white/10" />
|
||||
) : (
|
||||
<MenuItem key={index}>
|
||||
<button
|
||||
onClick={item.onClick}
|
||||
className="group flex w-full gap-2 rounded-lg p-2.5 text-sm text-text-primary transition-colors duration-200 data-[focus]:bg-surface-hover"
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.icon && (
|
||||
<span className="mr-2 h-5 w-5" aria-hidden="true">
|
||||
{item.icon}
|
||||
</span>
|
||||
)}
|
||||
{item.label}
|
||||
{item.kbd && (
|
||||
<kbd className="ml-auto hidden font-sans text-xs text-black/50 group-data-[focus]:inline dark:text-white/50">
|
||||
⌘{item.kbd}
|
||||
</kbd>
|
||||
)}
|
||||
</button>
|
||||
</MenuItem>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</MenuItems>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownPopup;
|
|
@ -15,8 +15,8 @@ const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, S
|
|||
className={cn('relative flex w-full touch-none select-none items-center', className ?? '')}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="relative h-1 w-full grow overflow-hidden rounded-full bg-gray-200 dark:bg-gray-800">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-gray-400 dark:bg-gray-400" />
|
||||
<SliderPrimitive.Track className="relative h-1 w-full grow overflow-hidden rounded-full bg-gray-200 dark:bg-gray-850">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-gray-850 dark:bg-white" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb
|
||||
onClick={
|
||||
|
@ -25,7 +25,7 @@ const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, S
|
|||
return;
|
||||
})
|
||||
}
|
||||
className="block h-4 w-4 cursor-pointer rounded-full border-2 border-gray-400 bg-white transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:border-gray-200 dark:bg-gray-400 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-800"
|
||||
className="block h-4 w-4 cursor-pointer rounded-full border border-border-medium-alt bg-white shadow ring-ring-primary transition-colors focus-visible:ring-1 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50 dark:border-none"
|
||||
/>
|
||||
</SliderPrimitive.Root>
|
||||
),
|
||||
|
|
|
@ -31,3 +31,4 @@ export { default as SelectDropDown } from './SelectDropDown';
|
|||
export { default as MultiSelectPop } from './MultiSelectPop';
|
||||
export { default as SelectDropDownPop } from './SelectDropDownPop';
|
||||
export { default as MultiSelectDropDown } from './MultiSelectDropDown';
|
||||
export { default as DropdownPopup } from './DropdownPopup';
|
||||
|
|
|
@ -570,7 +570,7 @@ export const useUploadFileMutation = (
|
|||
|
||||
return {
|
||||
...prev,
|
||||
data: prev?.data.map((assistant) => {
|
||||
data: prev.data.map((assistant) => {
|
||||
if (assistant.id !== assistant_id) {
|
||||
return assistant;
|
||||
}
|
||||
|
@ -817,7 +817,7 @@ export const useUpdateAssistantMutation = (
|
|||
({ assistant_id, data }: { assistant_id: string; data: t.AssistantUpdateParams }) => {
|
||||
const { endpoint } = data;
|
||||
const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
const version = endpointsConfig?.[endpoint].version ?? defaultAssistantsVersion[endpoint];
|
||||
return dataService.updateAssistant({
|
||||
data,
|
||||
version,
|
||||
|
@ -1020,7 +1020,7 @@ export const useDeleteAction = (
|
|||
|
||||
return {
|
||||
...prev,
|
||||
data: prev?.data.map((assistant) => {
|
||||
data: prev.data.map((assistant) => {
|
||||
if (assistant.id === variables.assistant_id) {
|
||||
return {
|
||||
...assistant,
|
||||
|
|
|
@ -123,7 +123,7 @@ export default function useChatFunctions({
|
|||
conversationId = null;
|
||||
}
|
||||
|
||||
const parentMessage = currentMessages?.find(
|
||||
const parentMessage = currentMessages.find(
|
||||
(msg) => msg.messageId === latestMessage?.parentMessageId,
|
||||
);
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import { useEffect, useState, useCallback } from 'react';
|
|||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useGetSearchEnabledQuery } from 'librechat-data-provider/react-query';
|
||||
import type { UseInfiniteQueryResult } from '@tanstack/react-query';
|
||||
import type { ConversationListResponse } from 'librechat-data-provider';
|
||||
import { useSearchInfiniteQuery } from '~/data-provider';
|
||||
import useConversation from './useConversation';
|
||||
import store from '~/store';
|
||||
|
@ -19,7 +21,7 @@ export default function useSearchMessages({ isAuthenticated }: { isAuthenticated
|
|||
const searchQueryRes = useSearchInfiniteQuery(
|
||||
{ pageNumber: pageNumber.toString(), searchQuery: searchQuery, isArchived: false },
|
||||
{ enabled: isAuthenticated && !!searchQuery.length },
|
||||
);
|
||||
) as UseInfiniteQueryResult<ConversationListResponse, unknown> | undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery && searchQuery.length > 0) {
|
||||
|
@ -36,7 +38,7 @@ export default function useSearchMessages({ isAuthenticated }: { isAuthenticated
|
|||
}, [navigate, searchQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchEnabledQuery.data) {
|
||||
if (searchEnabledQuery.data === true) {
|
||||
setIsSearchEnabled(searchEnabledQuery.data);
|
||||
} else if (searchEnabledQuery.isError) {
|
||||
console.error('Failed to get search enabled', searchEnabledQuery.error);
|
||||
|
@ -55,10 +57,10 @@ export default function useSearchMessages({ isAuthenticated }: { isAuthenticated
|
|||
|
||||
useEffect(() => {
|
||||
//we use isInitialLoading here instead of isLoading because query is disabled by default
|
||||
if (searchQueryRes.data) {
|
||||
if (searchQueryRes?.data) {
|
||||
onSearchSuccess();
|
||||
}
|
||||
}, [searchQueryRes.data, searchQueryRes.isInitialLoading, onSearchSuccess]);
|
||||
}, [searchQueryRes?.data, searchQueryRes?.isInitialLoading, onSearchSuccess]);
|
||||
|
||||
return {
|
||||
pageNumber,
|
||||
|
|
|
@ -31,7 +31,7 @@ export default function useSubmitMessage(helpers?: { clearDraft?: () => void })
|
|||
}
|
||||
const rootMessages = getMessages();
|
||||
const isLatestInRootMessages = rootMessages?.some(
|
||||
(message) => message?.messageId === latestMessage?.messageId,
|
||||
(message) => message.messageId === latestMessage?.messageId,
|
||||
);
|
||||
if (!isLatestInRootMessages && latestMessage) {
|
||||
setMessages([...(rootMessages || []), latestMessage]);
|
||||
|
@ -86,7 +86,7 @@ export default function useSubmitMessage(helpers?: { clearDraft?: () => void })
|
|||
}
|
||||
|
||||
const currentText = methods.getValues('text');
|
||||
const newText = currentText?.trim()?.length > 1 ? `\n${parsedText}` : parsedText;
|
||||
const newText = currentText.trim().length > 1 ? `\n${parsedText}` : parsedText;
|
||||
setActivePrompt(newText);
|
||||
},
|
||||
[autoSendPrompts, submitMessage, setActivePrompt, methods, user],
|
||||
|
|
|
@ -11,16 +11,18 @@ export default function useNavScrolling<TData>({
|
|||
hasNextPage?: boolean;
|
||||
isFetchingNextPage: boolean;
|
||||
setShowLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
fetchNextPage: (
|
||||
options?: FetchNextPageOptions | undefined,
|
||||
) => Promise<InfiniteQueryObserverResult<TData, unknown>>;
|
||||
fetchNextPage:
|
||||
| ((
|
||||
options?: FetchNextPageOptions | undefined,
|
||||
) => Promise<InfiniteQueryObserverResult<TData, unknown>>)
|
||||
| undefined;
|
||||
}) {
|
||||
const scrollPositionRef = useRef<number | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const fetchNext = useCallback(
|
||||
throttle(() => fetchNextPage(), 750, { leading: true }),
|
||||
throttle(() => (fetchNextPage != null ? fetchNextPage() : () => ({})), 750, { leading: true }),
|
||||
[fetchNextPage],
|
||||
);
|
||||
|
||||
|
@ -29,7 +31,7 @@ export default function useNavScrolling<TData>({
|
|||
const { scrollTop, clientHeight, scrollHeight } = containerRef.current;
|
||||
const nearBottomOfList = scrollTop + clientHeight >= scrollHeight * 0.97;
|
||||
|
||||
if (nearBottomOfList && hasNextPage && !isFetchingNextPage) {
|
||||
if (nearBottomOfList && hasNextPage === true && !isFetchingNextPage) {
|
||||
setShowLoading(true);
|
||||
fetchNext();
|
||||
} else {
|
||||
|
|
|
@ -310,6 +310,7 @@ export default {
|
|||
com_ui_bookmarks_add_to_conversation: 'Add to current conversation',
|
||||
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_ui_no_conversation_id: 'No conversation ID found',
|
||||
com_auth_error_login:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -591,6 +592,7 @@ export default {
|
|||
com_ui_drag_drop_file: 'Drag and drop a file here',
|
||||
com_ui_upload_image: 'Upload an image',
|
||||
com_ui_select_a_category: 'No category selected',
|
||||
com_ui_clear_all: 'Clear all',
|
||||
com_nav_tool_dialog_description: 'Assistant must be saved to persist tool selections.',
|
||||
com_show_agent_settings: 'Show Agent Settings',
|
||||
com_show_completion_settings: 'Show Completion Settings',
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Nav, MobileNav } from '~/components/Nav';
|
|||
|
||||
export default function Root() {
|
||||
const { isAuthenticated } = useAuthContext();
|
||||
const [navVisible, setNavVisible] = useState(() => {
|
||||
const [navVisible, setNavVisible] = useState<boolean>(() => {
|
||||
const savedNavVisible = localStorage.getItem('navVisible');
|
||||
return savedNavVisible !== null ? JSON.parse(savedNavVisible) : true;
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ const localStorageAtoms = {
|
|||
enterToSend: atomWithLocalStorage('enterToSend', true),
|
||||
chatDirection: atomWithLocalStorage('chatDirection', 'LTR'),
|
||||
showCode: atomWithLocalStorage('showCode', false),
|
||||
saveDrafts: atomWithLocalStorage('saveDrafts', false),
|
||||
saveDrafts: atomWithLocalStorage('saveDrafts', true),
|
||||
forkSetting: atomWithLocalStorage('forkSetting', ''),
|
||||
splitAtTarget: atomWithLocalStorage('splitAtTarget', false),
|
||||
|
||||
|
|
1886
client/src/style.css
1886
client/src/style.css
File diff suppressed because it is too large
Load diff
|
@ -21,7 +21,7 @@ export const getLatestText = (message?: TMessage | null, includeIndex?: boolean)
|
|||
if (message.content?.length) {
|
||||
for (let i = message.content.length - 1; i >= 0; i--) {
|
||||
const part = message.content[i];
|
||||
if (part.type === ContentTypes.TEXT && part[ContentTypes.TEXT]?.value?.length > 0) {
|
||||
if (part.type === ContentTypes.TEXT && part[ContentTypes.TEXT].value.length > 0) {
|
||||
const text = part[ContentTypes.TEXT].value;
|
||||
if (includeIndex) {
|
||||
return `${text}-${i}`;
|
||||
|
|
|
@ -16,7 +16,7 @@ module.exports = {
|
|||
// },
|
||||
extend: {
|
||||
width: {
|
||||
'authPageWidth': '370px',
|
||||
authPageWidth: '370px',
|
||||
},
|
||||
keyframes: {
|
||||
'accordion-down': {
|
||||
|
|
689
package-lock.json
generated
689
package-lock.json
generated
|
@ -1126,7 +1126,7 @@
|
|||
"@radix-ui/react-checkbox": "^1.0.3",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.0.5",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.0",
|
||||
|
@ -8359,24 +8359,24 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz",
|
||||
"integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz",
|
||||
"integrity": "sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-menu": "2.0.6",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-menu": "2.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
|
@ -8387,6 +8387,149 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
|
||||
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
|
||||
"integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz",
|
||||
"integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
|
||||
"integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
|
||||
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
||||
"integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
|
||||
"integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
|
||||
"integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz",
|
||||
|
@ -8510,35 +8653,35 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz",
|
||||
"integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.1.tgz",
|
||||
"integrity": "sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||
"@radix-ui/react-focus-guards": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.4",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-popper": "1.1.3",
|
||||
"@radix-ui/react-portal": "1.0.4",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-roving-focus": "1.0.4",
|
||||
"@radix-ui/react-slot": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-collection": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.0",
|
||||
"@radix-ui/react-focus-guards": "1.1.0",
|
||||
"@radix-ui/react-focus-scope": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-popper": "1.2.0",
|
||||
"@radix-ui/react-portal": "1.1.1",
|
||||
"@radix-ui/react-presence": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-roving-focus": "1.1.0",
|
||||
"@radix-ui/react-slot": "1.1.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
"react-remove-scroll": "2.5.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
|
@ -8549,6 +8692,476 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
|
||||
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
|
||||
"integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
|
||||
"integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-slot": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
|
||||
"integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz",
|
||||
"integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-direction": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
||||
"integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz",
|
||||
"integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz",
|
||||
"integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz",
|
||||
"integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
|
||||
"integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
|
||||
"integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^2.0.0",
|
||||
"@radix-ui/react-arrow": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0",
|
||||
"@radix-ui/react-use-rect": "1.1.0",
|
||||
"@radix-ui/react-use-size": "1.1.0",
|
||||
"@radix-ui/rect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz",
|
||||
"integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz",
|
||||
"integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
|
||||
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz",
|
||||
"integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-collection": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
||||
"integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
|
||||
"integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
|
||||
"integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
|
||||
"integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-rect": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
|
||||
"integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/rect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-size": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
|
||||
"integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/rect": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
|
||||
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": {
|
||||
"version": "2.5.7",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz",
|
||||
"integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.4",
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.1.0",
|
||||
"use-callback-ref": "^1.3.0",
|
||||
"use-sidecar": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popover": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz",
|
||||
|
@ -30878,7 +31491,7 @@
|
|||
},
|
||||
"packages/data-provider": {
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.7.41",
|
||||
"version": "0.7.41.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
|
|
|
@ -16,7 +16,7 @@ export type ConversationListParams = {
|
|||
before?: string | null;
|
||||
after?: string | null;
|
||||
order?: 'asc' | 'desc';
|
||||
pageNumber: string; // Add this line
|
||||
pageNumber: string;
|
||||
conversationId?: string;
|
||||
isArchived?: boolean;
|
||||
tags?: string[];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue