mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
💡 style: switched to Ariakit's tooltip (#3748)
* inital Tooltip implementation and test * style(tooltip): L/R sidePanel and Nav * style(tooltip): unarchive button; refactor: `useArchiveHandler` and `ArchiveButton` * style(tooltip): Delete button * refactor: remove unused className prop in DeleteButton component * style(tooltip): finish final tooltip and fix bookmark edit and delete button * refactor(ui): remove TooltipTest and DropDownMenu component and unused imports * style: update mobile UI * fix: sidePanel icon not showing * feat(AttachFile): add tooltip * fix(NavToggle): remove button without this button, kb users don't have to manually press 2 times to change the focus Also, tooltips with buttons focus don't trigger * fix: right side panel issue with double button * fix: merge issues * fix: sharedLink table issue * chore: update ariakit and framer-motion version * a11y: kb toggle for sidebar * feat: tooltip for some buttons
This commit is contained in:
parent
e293ff63f9
commit
4ef5ae6f71
37 changed files with 747 additions and 967 deletions
|
|
@ -28,7 +28,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://librechat.ai",
|
"homepage": "https://librechat.ai",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ariakit/react": "^0.4.8",
|
"@ariakit/react": "^0.4.11",
|
||||||
"@codesandbox/sandpack-react": "^2.18.2",
|
"@codesandbox/sandpack-react": "^2.18.2",
|
||||||
"@dicebear/collection": "^7.0.4",
|
"@dicebear/collection": "^7.0.4",
|
||||||
"@dicebear/core": "^7.0.4",
|
"@dicebear/core": "^7.0.4",
|
||||||
|
|
@ -63,6 +63,7 @@
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"export-from-json": "^1.7.2",
|
"export-from-json": "^1.7.2",
|
||||||
"filenamify": "^6.0.0",
|
"filenamify": "^6.0.0",
|
||||||
|
"framer-motion": "^11.5.4",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"image-blob-reduce": "^4.1.0",
|
"image-blob-reduce": "^4.1.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
import { Label, OGDialog, OGDialogTrigger, TooltipAnchor } from '~/components/ui';
|
||||||
import { useDeleteConversationTagMutation } from '~/data-provider';
|
import { useDeleteConversationTagMutation } from '~/data-provider';
|
||||||
import TooltipIcon from '~/components/ui/TooltipIcon';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import { NotificationSeverity } from '~/common';
|
import { NotificationSeverity } from '~/common';
|
||||||
import { useToastContext } from '~/Providers';
|
import { useToastContext } from '~/Providers';
|
||||||
import { TrashIcon } from '~/components/svg';
|
import { TrashIcon } from '~/components/svg';
|
||||||
import { Label } from '~/components/ui';
|
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
const DeleteBookmarkButton: FC<{
|
const DeleteBookmarkButton: FC<{
|
||||||
|
|
@ -16,6 +16,7 @@ const DeleteBookmarkButton: FC<{
|
||||||
}> = ({ bookmark, tabIndex = 0, onFocus, onBlur }) => {
|
}> = ({ bookmark, tabIndex = 0, onFocus, onBlur }) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
const { showToast } = useToastContext();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const deleteBookmarkMutation = useDeleteConversationTagMutation({
|
const deleteBookmarkMutation = useDeleteConversationTagMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
@ -35,23 +36,48 @@ const DeleteBookmarkButton: FC<{
|
||||||
await deleteBookmarkMutation.mutateAsync(bookmark);
|
await deleteBookmarkMutation.mutateAsync(bookmark);
|
||||||
}, [bookmark, deleteBookmarkMutation]);
|
}, [bookmark, deleteBookmarkMutation]);
|
||||||
|
|
||||||
return (
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
<TooltipIcon
|
if (event.key === 'Enter') {
|
||||||
disabled={false}
|
event.preventDefault();
|
||||||
appendLabel={false}
|
event.stopPropagation();
|
||||||
title="Delete Bookmark"
|
setOpen(!open);
|
||||||
confirmMessage={
|
|
||||||
<Label htmlFor="bookmark" className="text-left text-sm font-medium">
|
|
||||||
{localize('com_ui_bookmark_delete_confirm')} {bookmark}
|
|
||||||
</Label>
|
|
||||||
}
|
}
|
||||||
confirm={confirmDelete}
|
};
|
||||||
className="transition-colors flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
|
||||||
icon={<TrashIcon className="size-4" />}
|
return (
|
||||||
|
<>
|
||||||
|
<OGDialog open={open} onOpenChange={setOpen}>
|
||||||
|
<OGDialogTrigger asChild>
|
||||||
|
<TooltipAnchor
|
||||||
|
description={localize('com_ui_delete')}
|
||||||
|
className="flex size-7 cursor-pointer items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
>
|
||||||
|
<TrashIcon className="size-4" />
|
||||||
|
</TooltipAnchor>
|
||||||
|
</OGDialogTrigger>
|
||||||
|
<OGDialogTemplate
|
||||||
|
showCloseButton={false}
|
||||||
|
title={localize('com_ui_bookmarks_delete')}
|
||||||
|
className="max-w-[450px]"
|
||||||
|
main={
|
||||||
|
<Label className="text-left text-sm font-medium">
|
||||||
|
{localize('com_ui_bookmark_delete_confirm')} {bookmark}
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
selection={{
|
||||||
|
selectHandler: confirmDelete,
|
||||||
|
selectClasses:
|
||||||
|
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 text-white',
|
||||||
|
selectText: localize('com_ui_delete'),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</OGDialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import { useState } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { TConversationTag } from 'librechat-data-provider';
|
import type { TConversationTag } from 'librechat-data-provider';
|
||||||
import BookmarkEditDialog from './BookmarkEditDialog';
|
import BookmarkEditDialog from './BookmarkEditDialog';
|
||||||
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { EditIcon } from '~/components/svg';
|
import { EditIcon } from '~/components/svg';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
|
|
||||||
|
|
||||||
const EditBookmarkButton: FC<{
|
const EditBookmarkButton: FC<{
|
||||||
bookmark: TConversationTag;
|
bookmark: TConversationTag;
|
||||||
|
|
@ -15,6 +15,12 @@ const EditBookmarkButton: FC<{
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BookmarkEditDialog
|
<BookmarkEditDialog
|
||||||
|
|
@ -23,25 +29,17 @@ const EditBookmarkButton: FC<{
|
||||||
open={open}
|
open={open}
|
||||||
setOpen={setOpen}
|
setOpen={setOpen}
|
||||||
/>
|
/>
|
||||||
<button
|
<TooltipAnchor
|
||||||
type="button"
|
description={localize('com_ui_edit')}
|
||||||
className="transition-colors flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
|
className="flex size-7 cursor-pointer items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
<TooltipProvider delayDuration={250}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</TooltipTrigger>
|
</TooltipAnchor>
|
||||||
<TooltipContent side="top" sideOffset={0}>
|
|
||||||
{localize('com_ui_edit')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</button>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@ import { PlusCircle } from 'lucide-react';
|
||||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||||
import type { TConversation } from 'librechat-data-provider';
|
import type { TConversation } from 'librechat-data-provider';
|
||||||
import { useChatContext, useAddedChatContext } from '~/Providers';
|
import { useChatContext, useAddedChatContext } from '~/Providers';
|
||||||
|
import { TooltipAnchor } from '~/components';
|
||||||
import { mainTextareaId } from '~/common';
|
import { mainTextareaId } from '~/common';
|
||||||
import { Button } from '~/components/ui';
|
import { useLocalize } from '~/hooks';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
function AddMultiConvo({ className = '' }: { className?: string }) {
|
function AddMultiConvo() {
|
||||||
const { conversation } = useChatContext();
|
const { conversation } = useChatContext();
|
||||||
const { setConversation: setAddedConvo } = useAddedChatContext();
|
const { setConversation: setAddedConvo } = useAddedChatContext();
|
||||||
|
const localize = useLocalize();
|
||||||
|
|
||||||
const clickHandler = () => {
|
const clickHandler = () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
|
@ -33,15 +35,18 @@ function AddMultiConvo({ className = '' }: { className?: string }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<TooltipAnchor
|
||||||
id="add-multi-conversation-button"
|
id="add-multi-conversation-button"
|
||||||
aria-label="Add multi-conversation"
|
aria-label={localize('com_ui_add_multi_conversation')}
|
||||||
|
description={localize('com_ui_add_multi_conversation')}
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
variant="outline"
|
data-testid="parameters-button"
|
||||||
className={cn('h-10 w-10 p-0 transition-all duration-300 ease-in-out', className)}
|
className="inline-flex size-10 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
|
||||||
>
|
>
|
||||||
<PlusCircle size={16} />
|
<PlusCircle size={16} aria-label="Plus Icon" />
|
||||||
</Button>
|
</TooltipAnchor>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
|
||||||
import { ListeningIcon, Spinner } from '~/components/svg';
|
import { ListeningIcon, Spinner } from '~/components/svg';
|
||||||
import { useLocalize, useSpeechToText } from '~/hooks';
|
import { useLocalize, useSpeechToText } from '~/hooks';
|
||||||
import { useChatFormContext } from '~/Providers';
|
import { useChatFormContext } from '~/Providers';
|
||||||
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { globalAudioId } from '~/common';
|
import { globalAudioId } from '~/common';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
|
@ -74,10 +74,7 @@ export default function AudioRecorder({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={250}>
|
<TooltipAnchor
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
id="audio-recorder"
|
id="audio-recorder"
|
||||||
aria-label={localize('com_ui_use_micrphone')}
|
aria-label={localize('com_ui_use_micrphone')}
|
||||||
onClick={isListening ? handleStopRecording : handleStartRecording}
|
onClick={isListening ? handleStopRecording : handleStartRecording}
|
||||||
|
|
@ -88,15 +85,9 @@ export default function AudioRecorder({
|
||||||
? 'bottom-1.5 left-4 md:bottom-3 md:left-12'
|
? 'bottom-1.5 left-4 md:bottom-3 md:left-12'
|
||||||
: 'bottom-1.5 right-12 md:bottom-3 md:right-12',
|
: 'bottom-1.5 right-12 md:bottom-3 md:right-12',
|
||||||
)}
|
)}
|
||||||
type="button"
|
description={localize('com_ui_use_micrphone')}
|
||||||
>
|
>
|
||||||
{renderIcon()}
|
{renderIcon()}
|
||||||
</button>
|
</TooltipAnchor>
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" sideOffset={10}>
|
|
||||||
{localize('com_ui_use_micrphone')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import {
|
||||||
fileConfig as defaultFileConfig,
|
fileConfig as defaultFileConfig,
|
||||||
mergeFileConfig,
|
mergeFileConfig,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
|
import { FileUpload, TooltipAnchor } from '~/components/ui';
|
||||||
|
import { useFileHandling, useLocalize } from '~/hooks';
|
||||||
import { useGetFileConfig } from '~/data-provider';
|
import { useGetFileConfig } from '~/data-provider';
|
||||||
import { AttachmentIcon } from '~/components/svg';
|
import { AttachmentIcon } from '~/components/svg';
|
||||||
import { FileUpload } from '~/components/ui';
|
|
||||||
import { useFileHandling } from '~/hooks';
|
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
const AttachFile = ({
|
const AttachFile = ({
|
||||||
|
|
@ -22,6 +22,7 @@ const AttachFile = ({
|
||||||
isRTL: boolean;
|
isRTL: boolean;
|
||||||
disabled?: boolean | null;
|
disabled?: boolean | null;
|
||||||
}) => {
|
}) => {
|
||||||
|
const localize = useLocalize();
|
||||||
const { handleFileChange } = useFileHandling();
|
const { handleFileChange } = useFileHandling();
|
||||||
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
|
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
|
||||||
select: (data) => mergeFileConfig(data),
|
select: (data) => mergeFileConfig(data),
|
||||||
|
|
@ -42,17 +43,18 @@ const AttachFile = ({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FileUpload handleFileChange={handleFileChange} className="flex">
|
<FileUpload handleFileChange={handleFileChange} className="flex">
|
||||||
<button
|
<TooltipAnchor
|
||||||
|
id="audio-recorder"
|
||||||
|
aria-label={localize('com_sidepanel_attach_files')}
|
||||||
disabled={!!disabled}
|
disabled={!!disabled}
|
||||||
type="button"
|
|
||||||
className="btn relative text-black focus:outline-none focus:ring-2 focus:ring-border-xheavy focus:ring-opacity-50 dark:text-white"
|
className="btn relative text-black focus:outline-none focus:ring-2 focus:ring-border-xheavy focus:ring-opacity-50 dark:text-white"
|
||||||
aria-label="Attach files"
|
|
||||||
style={{ padding: 0 }}
|
style={{ padding: 0 }}
|
||||||
|
description={localize('com_sidepanel_attach_files')}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-center gap-2">
|
<div className="flex w-full items-center justify-center gap-2">
|
||||||
<AttachmentIcon />
|
<AttachmentIcon />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</TooltipAnchor>
|
||||||
</FileUpload>
|
</FileUpload>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,12 @@ import { useState, useEffect, useMemo } from 'react';
|
||||||
import { tPresetUpdateSchema, EModelEndpoint, paramEndpoints } from 'librechat-data-provider';
|
import { tPresetUpdateSchema, EModelEndpoint, paramEndpoints } from 'librechat-data-provider';
|
||||||
import type { TPreset, TInterfaceConfig } from 'librechat-data-provider';
|
import type { TPreset, TInterfaceConfig } from 'librechat-data-provider';
|
||||||
import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints';
|
import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints';
|
||||||
|
import { PluginStoreDialog, TooltipAnchor } from '~/components';
|
||||||
import { ModelSelect } from '~/components/Input/ModelSelect';
|
import { ModelSelect } from '~/components/Input/ModelSelect';
|
||||||
import { PluginStoreDialog } from '~/components';
|
import { useSetIndexOptions, useLocalize } from '~/hooks';
|
||||||
import OptionsPopover from './OptionsPopover';
|
import OptionsPopover from './OptionsPopover';
|
||||||
import PopoverButtons from './PopoverButtons';
|
import PopoverButtons from './PopoverButtons';
|
||||||
import { useSetIndexOptions } from '~/hooks';
|
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import { Button } from '~/components/ui';
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function HeaderOptions({
|
export default function HeaderOptions({
|
||||||
|
|
@ -23,6 +22,7 @@ export default function HeaderOptions({
|
||||||
const [showPluginStoreDialog, setShowPluginStoreDialog] = useRecoilState(
|
const [showPluginStoreDialog, setShowPluginStoreDialog] = useRecoilState(
|
||||||
store.showPluginStoreDialog,
|
store.showPluginStoreDialog,
|
||||||
);
|
);
|
||||||
|
const localize = useLocalize();
|
||||||
|
|
||||||
const { showPopover, conversation, latestMessage, setShowPopover, setShowBingToneSetting } =
|
const { showPopover, conversation, latestMessage, setShowPopover, setShowBingToneSetting } =
|
||||||
useChatContext();
|
useChatContext();
|
||||||
|
|
@ -84,17 +84,18 @@ export default function HeaderOptions({
|
||||||
{!noSettings[endpoint] &&
|
{!noSettings[endpoint] &&
|
||||||
interfaceConfig?.parameters === true &&
|
interfaceConfig?.parameters === true &&
|
||||||
!paramEndpoints.has(endpoint) && (
|
!paramEndpoints.has(endpoint) && (
|
||||||
<Button
|
<TooltipAnchor
|
||||||
aria-label="Settings/parameters"
|
|
||||||
id="parameters-button"
|
id="parameters-button"
|
||||||
data-testid="parameters-button"
|
aria-label={localize('com_ui_model_parameters')}
|
||||||
type="button"
|
description={localize('com_ui_model_parameters')}
|
||||||
variant="outline"
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
onClick={triggerAdvancedMode}
|
onClick={triggerAdvancedMode}
|
||||||
className="flex h-[40px] min-w-4 px-3 radix-state-open:bg-surface-hover"
|
data-testid="parameters-button"
|
||||||
|
className="inline-flex size-10 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
|
||||||
>
|
>
|
||||||
<Settings2 className="w-4 text-gray-600 dark:text-white" />
|
<Settings2 size={16} aria-label="Settings/Parameters Icon" />
|
||||||
</Button>
|
</TooltipAnchor>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{interfaceConfig?.parameters === true && !paramEndpoints.has(endpoint) && (
|
{interfaceConfig?.parameters === true && !paramEndpoints.has(endpoint) && (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { forwardRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
import { useWatch } from 'react-hook-form';
|
import { useWatch } from 'react-hook-form';
|
||||||
import type { Control } from 'react-hook-form';
|
import type { Control } from 'react-hook-form';
|
||||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { SendIcon } from '~/components/svg';
|
import { SendIcon } from '~/components/svg';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
@ -17,9 +17,9 @@ const SubmitButton = React.memo(
|
||||||
(props: { disabled: boolean; isRTL: boolean }, ref: React.ForwardedRef<HTMLButtonElement>) => {
|
(props: { disabled: boolean; isRTL: boolean }, ref: React.ForwardedRef<HTMLButtonElement>) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={250}>
|
<TooltipAnchor
|
||||||
<Tooltip>
|
description={localize('com_nav_send_message')}
|
||||||
<TooltipTrigger asChild>
|
render={
|
||||||
<button
|
<button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
aria-label={localize('com_nav_send_message')}
|
aria-label={localize('com_nav_send_message')}
|
||||||
|
|
@ -38,12 +38,8 @@ const SubmitButton = React.memo(
|
||||||
<SendIcon size={24} />
|
<SendIcon size={24} />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
}
|
||||||
<TooltipContent side="top" sideOffset={10}>
|
></TooltipAnchor>
|
||||||
{localize('com_nav_send_message')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import { useMemo } from 'react';
|
||||||
import { EModelEndpoint, isAssistantsEndpoint, Constants } from 'librechat-data-provider';
|
import { EModelEndpoint, isAssistantsEndpoint, Constants } from 'librechat-data-provider';
|
||||||
import { useGetEndpointsQuery, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
import { useGetEndpointsQuery, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
|
||||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||||
import { useGetAssistantDocsQuery } from '~/data-provider';
|
import { useGetAssistantDocsQuery } from '~/data-provider';
|
||||||
import ConvoIcon from '~/components/Endpoints/ConvoIcon';
|
import ConvoIcon from '~/components/Endpoints/ConvoIcon';
|
||||||
import { useLocalize, useSubmitMessage } from '~/hooks';
|
import { useLocalize, useSubmitMessage } from '~/hooks';
|
||||||
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { BirthdayIcon } from '~/components/svg';
|
import { BirthdayIcon } from '~/components/svg';
|
||||||
import { getIconEndpoint, cn } from '~/utils';
|
import { getIconEndpoint, cn } from '~/utils';
|
||||||
import ConvoStarter from './ConvoStarter';
|
import ConvoStarter from './ConvoStarter';
|
||||||
|
|
@ -58,8 +58,6 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
||||||
const sendConversationStarter = (text: string) => submitMessage({ text });
|
const sendConversationStarter = (text: string) => submitMessage({ text });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={50}>
|
|
||||||
<Tooltip>
|
|
||||||
<div className="relative h-full">
|
<div className="relative h-full">
|
||||||
<div className="absolute left-0 right-0">{Header != null ? Header : null}</div>
|
<div className="absolute left-0 right-0">{Header != null ? Header : null}</div>
|
||||||
<div className="flex h-full flex-col items-center justify-center">
|
<div className="flex h-full flex-col items-center justify-center">
|
||||||
|
|
@ -74,21 +72,17 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
||||||
size={41}
|
size={41}
|
||||||
/>
|
/>
|
||||||
{startupConfig?.showBirthdayIcon === true ? (
|
{startupConfig?.showBirthdayIcon === true ? (
|
||||||
<div>
|
<TooltipAnchor
|
||||||
<TooltipTrigger>
|
className="absolute bottom-8 right-2.5"
|
||||||
<BirthdayIcon className="absolute bottom-8 right-2.5" />
|
description={localize('com_ui_happy_birthday')}
|
||||||
</TooltipTrigger>
|
>
|
||||||
<TooltipContent side="top" sideOffset={110} className="">
|
<BirthdayIcon />
|
||||||
{localize('com_ui_happy_birthday')}
|
</TooltipAnchor>
|
||||||
</TooltipContent>
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{assistantName ? (
|
{assistantName ? (
|
||||||
<div className="flex flex-col items-center gap-0 p-2">
|
<div className="flex flex-col items-center gap-0 p-2">
|
||||||
<div className="text-center text-2xl font-medium dark:text-white">
|
<div className="text-center text-2xl font-medium dark:text-white">{assistantName}</div>
|
||||||
{assistantName}
|
|
||||||
</div>
|
|
||||||
<div className="max-w-md text-center text-sm font-normal text-text-primary ">
|
<div className="max-w-md text-center text-sm font-normal text-text-primary ">
|
||||||
{assistantDesc ? assistantDesc : localize('com_nav_welcome_message')}
|
{assistantDesc ? assistantDesc : localize('com_nav_welcome_message')}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -117,7 +111,5 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
||||||
import { EditPresetDialog, PresetItems } from './Presets';
|
import { EditPresetDialog, PresetItems } from './Presets';
|
||||||
import { useLocalize, usePresets } from '~/hooks';
|
import { useLocalize, usePresets } from '~/hooks';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import { Button } from '~/components/ui';
|
import { TooltipAnchor } from '~/components';
|
||||||
|
|
||||||
const PresetsMenu: FC = () => {
|
const PresetsMenu: FC = () => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
@ -25,17 +25,17 @@ const PresetsMenu: FC = () => {
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<Trigger asChild>
|
<Trigger asChild>
|
||||||
<Button
|
<TooltipAnchor
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
className="flex h-[40px] min-w-4 px-3 radix-state-open:bg-surface-hover"
|
|
||||||
id="presets-button"
|
id="presets-button"
|
||||||
data-testid="presets-button"
|
|
||||||
title={localize('com_endpoint_examples')}
|
|
||||||
aria-label={localize('com_endpoint_examples')}
|
aria-label={localize('com_endpoint_examples')}
|
||||||
|
description={localize('com_endpoint_examples')}
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
data-testid="presets-button"
|
||||||
|
className="inline-flex size-10 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
|
||||||
>
|
>
|
||||||
<BookCopy className="icon-sm" id="presets-button" />
|
<BookCopy size={16} aria-label="Preset Icon" />
|
||||||
</Button>
|
</TooltipAnchor>
|
||||||
</Trigger>
|
</Trigger>
|
||||||
<Portal>
|
<Portal>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@ import { useState, useId } from 'react';
|
||||||
import * as Ariakit from '@ariakit/react';
|
import * as Ariakit from '@ariakit/react';
|
||||||
import { Ellipsis, Share2, Archive, Pen, Trash } from 'lucide-react';
|
import { Ellipsis, Share2, Archive, Pen, Trash } from 'lucide-react';
|
||||||
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||||
import { useArchiveHandler } from './ArchiveButton';
|
import { useLocalize, useArchiveHandler } from '~/hooks';
|
||||||
import { DropdownPopup } from '~/components/ui';
|
import { DropdownPopup } from '~/components/ui';
|
||||||
import DeleteButton from './DeleteButton';
|
import DeleteButton from './DeleteButton';
|
||||||
import ShareButton from './ShareButton';
|
import ShareButton from './ShareButton';
|
||||||
import { useLocalize } from '~/hooks';
|
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
export default function ConvoOptions({
|
export default function ConvoOptions({
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,7 @@ import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import type { TMessage } from 'librechat-data-provider';
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
import { useDeleteConversationMutation } from '~/data-provider';
|
import { useDeleteConversationMutation } from '~/data-provider';
|
||||||
import {
|
import { OGDialog, OGDialogTrigger, Label, TooltipAnchor } from '~/components/ui';
|
||||||
OGDialog,
|
|
||||||
OGDialogTrigger,
|
|
||||||
Label,
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '~/components/ui';
|
|
||||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import { TrashIcon } from '~/components/svg';
|
import { TrashIcon } from '~/components/svg';
|
||||||
import { useLocalize, useNewConvo } from '~/hooks';
|
import { useLocalize, useNewConvo } from '~/hooks';
|
||||||
|
|
@ -21,7 +13,6 @@ type DeleteButtonProps = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
retainView: () => void;
|
retainView: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
className?: string;
|
|
||||||
showDeleteDialog?: boolean;
|
showDeleteDialog?: boolean;
|
||||||
setShowDeleteDialog?: (value: boolean) => void;
|
setShowDeleteDialog?: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
@ -30,7 +21,6 @@ export default function DeleteButton({
|
||||||
conversationId,
|
conversationId,
|
||||||
retainView,
|
retainView,
|
||||||
title,
|
title,
|
||||||
className = '',
|
|
||||||
showDeleteDialog,
|
showDeleteDialog,
|
||||||
setShowDeleteDialog,
|
setShowDeleteDialog,
|
||||||
}: DeleteButtonProps) {
|
}: DeleteButtonProps) {
|
||||||
|
|
@ -92,20 +82,13 @@ export default function DeleteButton({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OGDialog open={open} onOpenChange={setOpen}>
|
<OGDialog open={open} onOpenChange={setOpen}>
|
||||||
<TooltipProvider delayDuration={250}>
|
<TooltipAnchor description={localize('com_ui_delete')}>
|
||||||
<Tooltip>
|
|
||||||
<OGDialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button>
|
<button>
|
||||||
<TrashIcon className="h-5 w-5" />
|
<TrashIcon className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
|
||||||
</OGDialogTrigger>
|
</OGDialogTrigger>
|
||||||
<TooltipContent side="top" sideOffset={0} className={className}>
|
</TooltipAnchor>
|
||||||
{localize('com_ui_delete')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
{dialogContent}
|
{dialogContent}
|
||||||
</OGDialog>
|
</OGDialog>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
export { default as ArchiveButton } from './ArchiveButton';
|
|
||||||
export { default as DeleteButton } from './DeleteButton';
|
export { default as DeleteButton } from './DeleteButton';
|
||||||
export { default as ShareButton } from './ShareButton';
|
export { default as ShareButton } from './ShareButton';
|
||||||
export { default as SharedLinkButton } from './SharedLinkButton';
|
export { default as SharedLinkButton } from './SharedLinkButton';
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
import { cloneElement, type FC } from 'react';
|
|
||||||
import { DotsIcon } from '~/components/svg';
|
|
||||||
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
|
||||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
|
||||||
import { useToggle } from './ToggleContext';
|
|
||||||
import { useLocalize } from '~/hooks';
|
|
||||||
import { cn } from '~/utils';
|
|
||||||
|
|
||||||
type DropDownMenuProps = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
icon?: React.ReactElement;
|
|
||||||
tooltip?: string;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
const DropDownMenu: FC<DropDownMenuProps> = ({
|
|
||||||
children,
|
|
||||||
icon = <DotsIcon />,
|
|
||||||
tooltip = 'More',
|
|
||||||
className,
|
|
||||||
}: DropDownMenuProps) => {
|
|
||||||
const localize = useLocalize();
|
|
||||||
const { isPopoverActive, setPopoverActive } = useToggle();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Root open={isPopoverActive} onOpenChange={(open) => setPopoverActive(open)}>
|
|
||||||
<Trigger asChild>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'pointer-cursor relative flex flex-col text-left focus:outline-none focus:ring-0 focus:ring-offset-0 sm:text-sm',
|
|
||||||
'hover:text-gray-400 radix-state-open:text-gray-400 dark:hover:text-gray-400 dark:radix-state-open:text-gray-400',
|
|
||||||
'z-50 flex h-[40px] min-w-4 flex-none items-center justify-center focus:ring-0 focus:ring-offset-0',
|
|
||||||
)}
|
|
||||||
id="edit-menu-button"
|
|
||||||
data-testid="edit-menu-button"
|
|
||||||
title={localize('com_ui_more_options')}
|
|
||||||
>
|
|
||||||
<TooltipProvider delayDuration={500}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button type="button" className={className}>
|
|
||||||
{cloneElement(icon, {
|
|
||||||
className:
|
|
||||||
'h-[18px] w-[18px] flex-shrink-0 text-gray-500 hover:text-gray-400 dark:text-gray-300 dark:hover:text-gray-400',
|
|
||||||
})}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" sideOffset={0}>
|
|
||||||
{tooltip}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</div>
|
|
||||||
</Trigger>
|
|
||||||
<Portal>
|
|
||||||
<Content
|
|
||||||
side="bottom"
|
|
||||||
align="start"
|
|
||||||
className={cn(
|
|
||||||
'popover radix-side-bottom:animate-slideUpAndFade radix-side-left:animate-slideRightAndFade radix-side-right:animate-slideLeftAndFade radix-side-top:animate-slideDownAndFade overflow-hidden rounded-lg shadow-lg',
|
|
||||||
'border border-gray-200 bg-white dark:border-gray-600 dark:bg-gray-700 dark:text-white',
|
|
||||||
'flex min-w-[200px] max-w-xs flex-wrap',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Content>
|
|
||||||
</Portal>
|
|
||||||
</Root>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DropDownMenu;
|
|
||||||
|
|
@ -14,9 +14,10 @@ import store from '~/store';
|
||||||
type BookmarkNavProps = {
|
type BookmarkNavProps = {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
setTags: (tags: string[]) => void;
|
setTags: (tags: string[]) => void;
|
||||||
|
isSmallScreen: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps) => {
|
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags, isSmallScreen }: BookmarkNavProps) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
|
@ -40,6 +41,7 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps)
|
||||||
className={cn(
|
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',
|
'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' : '',
|
open ? 'bg-surface-hover' : '',
|
||||||
|
isSmallScreen ? 'h-14 rounded-2xl' : '',
|
||||||
)}
|
)}
|
||||||
data-testid="bookmark-menu"
|
data-testid="bookmark-menu"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ export default function MobileNav({
|
||||||
const { title = 'New Chat' } = conversation || {};
|
const { title = 'New Chat' } = conversation || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-token-border-medium bg-token-main-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center justify-center border-b bg-white pl-1 dark:bg-gray-800 dark:text-white md:hidden md:hidden">
|
<div className="bg-token-main-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center justify-center bg-white pl-1 dark:bg-gray-800 dark:text-white md:hidden">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-testid="mobile-header-new-chat-button"
|
data-testid="mobile-header-new-chat-button"
|
||||||
className="inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white active:opacity-50 dark:hover:text-white"
|
className="inline-flex size-10 items-center justify-center rounded-full hover:bg-surface-hover"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setNavVisible((prev) => {
|
setNavVisible((prev) => {
|
||||||
localStorage.setItem('navVisible', JSON.stringify(!prev));
|
localStorage.setItem('navVisible', JSON.stringify(!prev));
|
||||||
|
|
@ -49,7 +49,7 @@ export default function MobileNav({
|
||||||
</h1>
|
</h1>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white active:opacity-50 dark:hover:text-white"
|
className="inline-flex size-10 items-center justify-center rounded-full hover:bg-surface-hover"
|
||||||
onClick={() => newConversation()}
|
onClick={() => newConversation()}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import {
|
||||||
useConversations,
|
useConversations,
|
||||||
} from '~/hooks';
|
} from '~/hooks';
|
||||||
import { useConversationsInfiniteQuery } from '~/data-provider';
|
import { useConversationsInfiniteQuery } from '~/data-provider';
|
||||||
import { TooltipProvider, Tooltip } from '~/components/ui';
|
|
||||||
import { Conversations } from '~/components/Conversations';
|
import { Conversations } from '~/components/Conversations';
|
||||||
import BookmarkNav from './Bookmarks/BookmarkNav';
|
import BookmarkNav from './Bookmarks/BookmarkNav';
|
||||||
import AccountSettings from './AccountSettings';
|
import AccountSettings from './AccountSettings';
|
||||||
|
|
@ -130,8 +129,7 @@ const Nav = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={250}>
|
<>
|
||||||
<Tooltip>
|
|
||||||
<div
|
<div
|
||||||
data-testid="nav"
|
data-testid="nav"
|
||||||
className={
|
className={
|
||||||
|
|
@ -176,7 +174,11 @@ const Nav = ({
|
||||||
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
|
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
|
||||||
)}
|
)}
|
||||||
{hasAccessToBookmarks === true && (
|
{hasAccessToBookmarks === true && (
|
||||||
<BookmarkNav tags={tags} setTags={setTags} />
|
<BookmarkNav
|
||||||
|
tags={tags}
|
||||||
|
setTags={setTags}
|
||||||
|
isSmallScreen={isSmallScreen}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -185,14 +187,13 @@ const Nav = ({
|
||||||
subHeaders={
|
subHeaders={
|
||||||
<>
|
<>
|
||||||
{isSearchEnabled === true && (
|
{isSearchEnabled === true && (
|
||||||
<SearchBar
|
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
|
||||||
clearSearch={clearSearch}
|
)}
|
||||||
|
<BookmarkNav
|
||||||
|
tags={tags}
|
||||||
|
setTags={setTags}
|
||||||
isSmallScreen={isSmallScreen}
|
isSmallScreen={isSmallScreen}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{hasAccessToBookmarks === true && (
|
|
||||||
<BookmarkNav tags={tags} setTags={setTags} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -233,8 +234,7 @@ const Nav = ({
|
||||||
}}
|
}}
|
||||||
aria-label="Toggle navigation"
|
aria-label="Toggle navigation"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</>
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { TooltipTrigger, TooltipContent } from '~/components/ui';
|
|
||||||
import { useLocalize, useLocalStorage } from '~/hooks';
|
import { useLocalize, useLocalStorage } from '~/hooks';
|
||||||
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
export default function NavToggle({
|
export default function NavToggle({
|
||||||
|
|
@ -15,7 +15,6 @@ export default function NavToggle({
|
||||||
const transition = {
|
const transition = {
|
||||||
transition: 'transform 0.3s ease, opacity 0.2s ease',
|
transition: 'transform 0.3s ease, opacity 0.2s ease',
|
||||||
};
|
};
|
||||||
const [newUser] = useLocalStorage('newUser', true);
|
|
||||||
|
|
||||||
const rotationDegree = 15;
|
const rotationDegree = 15;
|
||||||
const rotation = isHovering || !navVisible ? `${rotationDegree}deg` : '0deg';
|
const rotation = isHovering || !navVisible ? `${rotationDegree}deg` : '0deg';
|
||||||
|
|
@ -33,11 +32,17 @@ export default function NavToggle({
|
||||||
onMouseEnter={() => setIsHovering(true)}
|
onMouseEnter={() => setIsHovering(true)}
|
||||||
onMouseLeave={() => setIsHovering(false)}
|
onMouseLeave={() => setIsHovering(false)}
|
||||||
>
|
>
|
||||||
<TooltipTrigger asChild>
|
<TooltipAnchor
|
||||||
<button
|
side={side === 'right' ? 'left' : 'right'}
|
||||||
onClick={onToggle}
|
|
||||||
id={`toggle-${side}-nav`}
|
|
||||||
aria-label={`toggle-${side === 'left' ? 'chat-history' : 'controls'}-nav`}
|
aria-label={`toggle-${side === 'left' ? 'chat-history' : 'controls'}-nav`}
|
||||||
|
id={`toggle-${side}-nav`}
|
||||||
|
onClick={onToggle}
|
||||||
|
role="button"
|
||||||
|
description={
|
||||||
|
navVisible ? localize('com_nav_close_sidebar') : localize('com_nav_open_sidebar')
|
||||||
|
}
|
||||||
|
className="flex cursor-pointer items-center justify-center"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<span className="" data-state="closed">
|
<span className="" data-state="closed">
|
||||||
<div
|
<div
|
||||||
|
|
@ -63,16 +68,8 @@ export default function NavToggle({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TooltipContent
|
|
||||||
forceMount={newUser ? true : undefined}
|
|
||||||
side={side === 'right' ? 'left' : 'right'}
|
|
||||||
sideOffset={4}
|
|
||||||
>
|
|
||||||
{navVisible ? localize('com_nav_close_sidebar') : localize('com_nav_open_sidebar')}
|
|
||||||
</TooltipContent>
|
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</TooltipAnchor>
|
||||||
</TooltipTrigger>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ import { Search } from 'lucide-react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
import type { TConversation } from 'librechat-data-provider';
|
||||||
import { getEndpointField, getIconEndpoint, getIconKey } from '~/utils';
|
import { getEndpointField, getIconEndpoint, getIconKey } from '~/utils';
|
||||||
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
||||||
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
|
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
|
||||||
import { useLocalize, useNewConvo } from '~/hooks';
|
import { useLocalize, useNewConvo } from '~/hooks';
|
||||||
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { NewChatIcon } from '~/components/svg';
|
import { NewChatIcon } from '~/components/svg';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
import type { TConversation } from 'librechat-data-provider';
|
|
||||||
|
|
||||||
const NewChatButtonIcon = ({ conversation }: { conversation: TConversation | null }) => {
|
const NewChatButtonIcon = ({ conversation }: { conversation: TConversation | null }) => {
|
||||||
const searchQuery = useRecoilValue(store.searchQuery);
|
const searchQuery = useRecoilValue(store.searchQuery);
|
||||||
|
|
@ -80,8 +80,6 @@ export default function NewChat({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={250}>
|
|
||||||
<Tooltip>
|
|
||||||
<div className="sticky left-0 right-0 top-0 z-50 bg-surface-primary-alt pt-3.5">
|
<div className="sticky left-0 right-0 top-0 z-50 bg-surface-primary-alt pt-3.5">
|
||||||
<div className="pb-0.5 last:pb-0" style={{ transform: 'none' }}>
|
<div className="pb-0.5 last:pb-0" style={{ transform: 'none' }}>
|
||||||
<a
|
<a
|
||||||
|
|
@ -98,25 +96,20 @@ export default function NewChat({
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<span className="flex items-center" data-state="closed">
|
<span className="flex items-center" data-state="closed">
|
||||||
<TooltipTrigger asChild>
|
<TooltipAnchor
|
||||||
<button
|
side="right"
|
||||||
id="nav-new-chat-btn"
|
id="nav-new-chat-btn"
|
||||||
aria-label={localize('com_ui_new_chat')}
|
aria-label="nav-new-chat-btn"
|
||||||
|
description={localize('com_ui_new_chat')}
|
||||||
className="text-text-primary"
|
className="text-text-primary"
|
||||||
>
|
>
|
||||||
<NewChatIcon className="size-5" />
|
<NewChatIcon className="size-5" />
|
||||||
</button>
|
</TooltipAnchor>
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="right" sideOffset={20}>
|
|
||||||
{localize('com_ui_new_chat')}
|
|
||||||
</TooltipContent>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{subHeaders != null ? subHeaders : null}
|
{subHeaders != null ? subHeaders : null}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group 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',
|
'group 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',
|
||||||
isSmallScreen === true ? 'h-16 rounded-2xl' : '',
|
isSmallScreen === true ? 'mb-2 h-14 rounded-2xl' : '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,13 @@
|
||||||
import { useMemo, useState, MouseEvent } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { MessageSquare, Link as LinkIcon } from 'lucide-react';
|
import { Link as LinkIcon } from 'lucide-react';
|
||||||
import type { SharedLinksResponse, TSharedLink } from 'librechat-data-provider';
|
import type { SharedLinksResponse, TSharedLink } from 'librechat-data-provider';
|
||||||
import { useDeleteSharedLinkMutation, useSharedLinksInfiniteQuery } from '~/data-provider';
|
import { useDeleteSharedLinkMutation, useSharedLinksInfiniteQuery } from '~/data-provider';
|
||||||
import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks';
|
import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks';
|
||||||
|
import { Spinner, TooltipAnchor, TrashIcon } from '~/components';
|
||||||
import { NotificationSeverity } from '~/common';
|
import { NotificationSeverity } from '~/common';
|
||||||
import { useToastContext } from '~/Providers';
|
import { useToastContext } from '~/Providers';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
import {
|
|
||||||
Spinner,
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
TrashIcon,
|
|
||||||
} from '~/components';
|
|
||||||
|
|
||||||
function SharedLinkDeleteButton({
|
function SharedLinkDeleteButton({
|
||||||
shareId,
|
shareId,
|
||||||
|
|
@ -36,7 +29,7 @@ function SharedLinkDeleteButton({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = async (e: MouseEvent<HTMLButtonElement>) => {
|
const handleDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (mutation.isLoading) {
|
if (mutation.isLoading) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -46,36 +39,14 @@ function SharedLinkDeleteButton({
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={250}>
|
<TooltipAnchor
|
||||||
<Tooltip>
|
description={localize('com_ui_delete')}
|
||||||
<TooltipTrigger asChild>
|
id="delete-shared-link"
|
||||||
<button id="delete-shared-link" aria-label="Delete shared link" onClick={handleDelete}>
|
aria-label="Delete shared link"
|
||||||
<TrashIcon />
|
onClick={handleDelete}
|
||||||
</button>
|
>
|
||||||
</TooltipTrigger>
|
<TrashIcon className="size-4" />
|
||||||
<TooltipContent side="top" sideOffset={0}>
|
</TooltipAnchor>
|
||||||
{localize('com_ui_delete')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function SourceChatButton({ conversationId }: { conversationId: string }) {
|
|
||||||
const localize = useLocalize();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TooltipProvider delayDuration={250}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Link to={`/c/${conversationId}`} target="_blank" rel="noreferrer">
|
|
||||||
<MessageSquare className="h-4 w-4 hover:text-gray-300" />
|
|
||||||
</Link>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" sideOffset={0}>
|
|
||||||
{localize('com_nav_source_chat')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,15 +85,12 @@ function ShareLinkRow({ sharedLink }: { sharedLink: TSharedLink }) {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{sharedLink.conversationId && (
|
{sharedLink.conversationId && (
|
||||||
<>
|
<div className={cn('cursor-pointer', !isDeleting && 'hover:text-gray-300')}>
|
||||||
<SourceChatButton conversationId={sharedLink.conversationId} />
|
|
||||||
<div className={cn('h-4 w-4 cursor-pointer', !isDeleting && 'hover:text-gray-300')}>
|
|
||||||
<SharedLinkDeleteButton
|
<SharedLinkDeleteButton
|
||||||
shareId={sharedLink.shareId}
|
shareId={sharedLink.shareId}
|
||||||
setIsDeleting={setIsDeleting}
|
setIsDeleting={setIsDeleting}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import { Dialog, DialogTrigger } from '~/components/ui';
|
import { OGDialog, OGDialogTrigger } from '~/components/ui';
|
||||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
|
|
||||||
import ShareLinkTable from './SharedLinkTable';
|
import ShareLinkTable from './SharedLinkTable';
|
||||||
|
|
||||||
|
|
@ -11,19 +11,19 @@ export default function SharedLinks() {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>{localize('com_nav_shared_links')}</div>
|
<div>{localize('com_nav_shared_links')}</div>
|
||||||
|
|
||||||
<Dialog>
|
<OGDialog>
|
||||||
<DialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
<button className="btn btn-neutral relative ">
|
<button className="btn btn-neutral relative ">
|
||||||
{localize('com_nav_shared_links_manage')}
|
{localize('com_nav_shared_links_manage')}
|
||||||
</button>
|
</button>
|
||||||
</DialogTrigger>
|
</OGDialogTrigger>
|
||||||
<DialogTemplate
|
<OGDialogTemplate
|
||||||
title={localize('com_nav_shared_links')}
|
title={localize('com_nav_shared_links')}
|
||||||
className="max-w-[1000px]"
|
className="max-w-[1000px]"
|
||||||
showCancelButton={false}
|
showCancelButton={false}
|
||||||
main={<ShareLinkTable />}
|
main={<ShareLinkTable />}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</OGDialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState, useCallback } from 'react';
|
||||||
import { MessageCircle, ArchiveRestore } from 'lucide-react';
|
import { MessageCircle, ArchiveRestore } from 'lucide-react';
|
||||||
import { useConversationsInfiniteQuery } from '~/data-provider';
|
import { useConversationsInfiniteQuery } from '~/data-provider';
|
||||||
import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks';
|
import { ConversationListResponse } from 'librechat-data-provider';
|
||||||
import ArchiveButton from '~/components/Conversations/ConvoOptions/ArchiveButton';
|
import { useAuthContext, useLocalize, useNavScrolling, useArchiveHandler } from '~/hooks';
|
||||||
import DeleteButton from '~/components/Conversations/ConvoOptions/DeleteButton';
|
import { DeleteButton } from '~/components/Conversations/ConvoOptions';
|
||||||
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { Spinner } from '~/components/svg';
|
import { Spinner } from '~/components/svg';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
import { ConversationListResponse } from 'librechat-data-provider';
|
|
||||||
|
|
||||||
export default function ArchivedChatsTable({ className }: { className?: string }) {
|
export default function ArchivedChatsTable() {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { isAuthenticated } = useAuthContext();
|
const { isAuthenticated } = useAuthContext();
|
||||||
const [showLoading, setShowLoading] = useState(false);
|
const [showLoading, setShowLoading] = useState(false);
|
||||||
|
const [conversationId, setConversationId] = useState<string | null>(null);
|
||||||
|
|
||||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useConversationsInfiniteQuery(
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useConversationsInfiniteQuery(
|
||||||
{ pageNumber: '1', isArchived: true },
|
{ pageNumber: '1', isArchived: true },
|
||||||
|
|
@ -30,14 +31,9 @@ export default function ArchivedChatsTable({ className }: { className?: string }
|
||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
const classProp: { className?: string } = {
|
const archiveHandler = useArchiveHandler(conversationId ?? '', false, moveToTop);
|
||||||
className: 'p-1 hover:text-black dark:hover:text-white',
|
|
||||||
};
|
|
||||||
if (className) {
|
|
||||||
classProp.className = className;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!conversations || conversations.length === 0) {
|
if (!data || conversations.length === 0) {
|
||||||
return <div className="text-gray-300">{localize('com_nav_archived_chats_empty')}</div>;
|
return <div className="text-gray-300">{localize('com_nav_archived_chats_empty')}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +54,11 @@ export default function ArchivedChatsTable({ className }: { className?: string }
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{conversations.map((conversation) => (
|
{conversations.map((conversation) => {
|
||||||
|
if (!conversation.conversationId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={conversation.conversationId}
|
key={conversation.conversationId}
|
||||||
className="border-b border-gray-200 text-sm font-normal dark:border-white/10"
|
className="border-b border-gray-200 text-sm font-normal dark:border-white/10"
|
||||||
|
|
@ -77,29 +77,29 @@ export default function ArchivedChatsTable({ className }: { className?: string }
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto mr-4 flex items-center justify-end gap-1 text-gray-400">
|
<div className="ml-auto mr-4 flex items-center justify-end gap-1 text-gray-400">
|
||||||
{conversation.conversationId && (
|
<TooltipAnchor
|
||||||
<>
|
description={localize('com_ui_unarchive')}
|
||||||
<ArchiveButton
|
onClick={() => {
|
||||||
className="hover:text-black dark:hover:text-white"
|
setConversationId(conversation.conversationId);
|
||||||
conversationId={conversation.conversationId}
|
archiveHandler();
|
||||||
retainView={moveToTop}
|
}}
|
||||||
shouldArchive={false}
|
className="cursor-pointer hover:text-black dark:hover:text-white"
|
||||||
icon={<ArchiveRestore className="h-4 w-4 hover:text-gray-300" />}
|
>
|
||||||
/>
|
<ArchiveRestore className="size-4 hover:text-gray-300" />
|
||||||
<div className="h-5 w-5 hover:text-gray-300">
|
</TooltipAnchor>
|
||||||
|
<div className="size-5 hover:text-gray-300">
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
conversationId={conversation.conversationId}
|
conversationId={conversation.conversationId}
|
||||||
retainView={moveToTop}
|
retainView={moveToTop}
|
||||||
title={conversation.title ?? ''}
|
title={conversation.title ?? ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{(isFetchingNextPage || showLoading) && (
|
{(isFetchingNextPage || showLoading) && (
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react';
|
||||||
import { Plus, X } from 'lucide-react';
|
import { Plus, X } from 'lucide-react';
|
||||||
import { Transition } from 'react-transition-group';
|
import { Transition } from 'react-transition-group';
|
||||||
import { Constants } from 'librechat-data-provider';
|
import { Constants } from 'librechat-data-provider';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
interface AssistantConversationStartersProps {
|
interface AssistantConversationStartersProps {
|
||||||
|
|
@ -106,25 +106,19 @@ const AssistantConversationStarters: React.FC<AssistantConversationStartersProps
|
||||||
}}
|
}}
|
||||||
className="absolute right-1 top-1"
|
className="absolute right-1 top-1"
|
||||||
>
|
>
|
||||||
<TooltipProvider delayDuration={1000}>
|
<TooltipAnchor
|
||||||
<Tooltip>
|
side="top"
|
||||||
<TooltipTrigger asChild>
|
description={
|
||||||
<button
|
hasReachedMax
|
||||||
type="button"
|
? localize('com_assistants_max_starters_reached')
|
||||||
|
: localize('com_ui_add')
|
||||||
|
}
|
||||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||||
onClick={handleAddStarter}
|
onClick={handleAddStarter}
|
||||||
disabled={hasReachedMax}
|
disabled={hasReachedMax}
|
||||||
>
|
>
|
||||||
<Plus className="size-4" />
|
<Plus className="size-4" />
|
||||||
</button>
|
</TooltipAnchor>
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" sideOffset={0}>
|
|
||||||
{hasReachedMax
|
|
||||||
? localize('com_assistants_max_starters_reached')
|
|
||||||
: localize('com_ui_add')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
@ -143,22 +137,14 @@ const AssistantConversationStarters: React.FC<AssistantConversationStartersProps
|
||||||
type="text"
|
type="text"
|
||||||
maxLength={64}
|
maxLength={64}
|
||||||
/>
|
/>
|
||||||
<TooltipProvider delayDuration={1000}>
|
<TooltipAnchor
|
||||||
<Tooltip>
|
side="top"
|
||||||
<TooltipTrigger asChild>
|
description={localize('com_ui_delete')}
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="absolute right-1 top-1 flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
className="absolute right-1 top-1 flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||||
onClick={() => handleDeleteStarter(index)}
|
onClick={() => handleDeleteStarter(index)}
|
||||||
>
|
>
|
||||||
<X className="size-4" />
|
<X className="size-4" />
|
||||||
</button>
|
</TooltipAnchor>
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" sideOffset={0}>
|
|
||||||
{localize('com_ui_delete')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import { useState } from 'react';
|
||||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||||
import type { NavLink, NavProps } from '~/common';
|
import type { NavLink, NavProps } from '~/common';
|
||||||
import { Accordion, AccordionItem, AccordionContent } from '~/components/ui/Accordion';
|
import { Accordion, AccordionItem, AccordionContent } from '~/components/ui/Accordion';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '~/components/ui/Tooltip';
|
|
||||||
import { buttonVariants } from '~/components/ui/Button';
|
import { buttonVariants } from '~/components/ui/Button';
|
||||||
import { cn, removeFocusOutlines } from '~/utils';
|
import { cn, removeFocusOutlines } from '~/utils';
|
||||||
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
export default function Nav({ links, isCollapsed, resize, defaultActive }: NavProps) {
|
export default function Nav({ links, isCollapsed, resize, defaultActive }: NavProps) {
|
||||||
|
|
@ -30,15 +30,13 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr
|
||||||
{links.map((link, index) => {
|
{links.map((link, index) => {
|
||||||
const variant = getVariant(link);
|
const variant = getVariant(link);
|
||||||
return isCollapsed ? (
|
return isCollapsed ? (
|
||||||
<Tooltip key={index} delayDuration={0}>
|
<TooltipAnchor
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({ variant, size: 'icon' }),
|
buttonVariants({ variant, size: 'icon' }),
|
||||||
removeFocusOutlines,
|
removeFocusOutlines,
|
||||||
'h-9 w-9',
|
'h-9 w-9 cursor-pointer',
|
||||||
variant === 'default'
|
variant === 'default'
|
||||||
? 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white'
|
? 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted bg-surface-terniary dark:hover:text-white'
|
||||||
: '',
|
: '',
|
||||||
)}
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
@ -50,22 +48,12 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr
|
||||||
setActive(link.id);
|
setActive(link.id);
|
||||||
resize && resize(25);
|
resize && resize(25);
|
||||||
}}
|
}}
|
||||||
|
description={localize(link.title)}
|
||||||
|
side="left"
|
||||||
>
|
>
|
||||||
<link.icon className="h-4 w-4" />
|
<link.icon className="h-4 w-4" />
|
||||||
<span className="sr-only">{link.title}</span>
|
<span className="sr-only">{link.title}</span>
|
||||||
</button>
|
</TooltipAnchor>
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent
|
|
||||||
side="left"
|
|
||||||
sideOffset={10}
|
|
||||||
className="flex items-center gap-4"
|
|
||||||
>
|
|
||||||
{localize(link.title)}
|
|
||||||
{link.label && (
|
|
||||||
<span className="text-muted-foreground ml-auto">{link.label}</span>
|
|
||||||
)}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
) : (
|
||||||
<Accordion
|
<Accordion
|
||||||
key={index}
|
key={index}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,8 @@ import {
|
||||||
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
||||||
import type { TEndpointsConfig } from 'librechat-data-provider';
|
import type { TEndpointsConfig } from 'librechat-data-provider';
|
||||||
import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable';
|
import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable';
|
||||||
import { TooltipProvider, Tooltip } from '~/components/ui/Tooltip';
|
|
||||||
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
|
||||||
import { useMediaQuery, useLocalStorage, useLocalize } from '~/hooks';
|
import { useMediaQuery, useLocalStorage, useLocalize } from '~/hooks';
|
||||||
|
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
||||||
import NavToggle from '~/components/Nav/NavToggle';
|
import NavToggle from '~/components/Nav/NavToggle';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import Switcher from './Switcher';
|
import Switcher from './Switcher';
|
||||||
|
|
@ -176,7 +175,6 @@ const SidePanel = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TooltipProvider delayDuration={0}>
|
|
||||||
<ResizablePanelGroup
|
<ResizablePanelGroup
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
onLayout={(sizes) => throttledSaveLayout(sizes)}
|
onLayout={(sizes) => throttledSaveLayout(sizes)}
|
||||||
|
|
@ -203,8 +201,6 @@ const SidePanel = ({
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<TooltipProvider delayDuration={400}>
|
|
||||||
<Tooltip>
|
|
||||||
<div
|
<div
|
||||||
onMouseEnter={() => setIsHovering(true)}
|
onMouseEnter={() => setIsHovering(true)}
|
||||||
onMouseLeave={() => setIsHovering(false)}
|
onMouseLeave={() => setIsHovering(false)}
|
||||||
|
|
@ -225,8 +221,6 @@ const SidePanel = ({
|
||||||
side="right"
|
side="right"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
{(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && (
|
{(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && (
|
||||||
<ResizableHandleAlt withHandle className="bg-transparent dark:text-white" />
|
<ResizableHandleAlt withHandle className="bg-transparent dark:text-white" />
|
||||||
)}
|
)}
|
||||||
|
|
@ -257,8 +251,7 @@ const SidePanel = ({
|
||||||
className={cn(
|
className={cn(
|
||||||
'sidenav hide-scrollbar border-l border-border-light bg-surface-primary-alt transition-opacity',
|
'sidenav hide-scrollbar border-l border-border-light bg-surface-primary-alt transition-opacity',
|
||||||
isCollapsed ? 'min-w-[50px]' : 'min-w-[340px] sm:min-w-[352px]',
|
isCollapsed ? 'min-w-[50px]' : 'min-w-[340px] sm:min-w-[352px]',
|
||||||
(isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) ||
|
(isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse
|
||||||
fullCollapse
|
|
||||||
? 'hidden min-w-0'
|
? 'hidden min-w-0'
|
||||||
: 'opacity-100',
|
: 'opacity-100',
|
||||||
)}
|
)}
|
||||||
|
|
@ -285,7 +278,6 @@ const SidePanel = ({
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</TooltipProvider>
|
|
||||||
<button
|
<button
|
||||||
aria-label="Close right side panel"
|
aria-label="Close right side panel"
|
||||||
className={`nav-mask ${!isCollapsed ? 'active' : ''}`}
|
className={`nav-mask ${!isCollapsed ? 'active' : ''}`}
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,18 @@ import { cn } from '~/utils/';
|
||||||
export default function Spinner({ className = 'm-auto', size = '1em' }) {
|
export default function Spinner({ className = 'm-auto', size = '1em' }) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
stroke="currentColor"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
width={size}
|
||||||
strokeWidth="2"
|
height={size}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
className={cn(className, 'animate-spin text-center')}
|
className={cn('animate-spin', className)}
|
||||||
height={size}
|
|
||||||
width={size}
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
>
|
||||||
<line x1="12" y1="2" x2="12" y2="6" />
|
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||||
<line x1="12" y1="18" x2="12" y2="22" />
|
|
||||||
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76" />
|
|
||||||
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07" />
|
|
||||||
<line x1="2" y1="12" x2="6" y2="12" />
|
|
||||||
<line x1="18" y1="12" x2="22" y2="12" />
|
|
||||||
<line x1="4.93" y1="19.07" x2="7.76" y2="16.24" />
|
|
||||||
<line x1="16.24" y1="7.76" x2="19.07" y2="4.93" />
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ function SelectDropDownPop({
|
||||||
<button
|
<button
|
||||||
data-testid="select-dropdown-button"
|
data-testid="select-dropdown-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
'pointer-cursor relative flex flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:ring-0 focus:ring-offset-0 dark:border-gray-700 dark:bg-gray-800 sm:text-sm',
|
'pointer-cursor relative flex flex-col rounded-lg border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:ring-0 focus:ring-offset-0 dark:border-gray-700 dark:bg-gray-800 sm:text-sm',
|
||||||
'hover:bg-gray-50 radix-state-open:bg-gray-50 dark:hover:bg-gray-700 dark:radix-state-open:bg-gray-700',
|
'hover:bg-gray-50 radix-state-open:bg-gray-50 dark:hover:bg-gray-700 dark:radix-state-open:bg-gray-700',
|
||||||
)}
|
)}
|
||||||
aria-label={`Select ${title}`}
|
aria-label={`Select ${title}`}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,65 @@
|
||||||
import * as React from 'react';
|
import * as Ariakit from '@ariakit/react';
|
||||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { cn } from '~/utils';
|
import { forwardRef, useMemo } from 'react';
|
||||||
|
|
||||||
const Tooltip = TooltipPrimitive.Root;
|
interface TooltipAnchorProps extends Ariakit.TooltipAnchorProps {
|
||||||
|
description: string;
|
||||||
|
side?: 'top' | 'bottom' | 'left' | 'right';
|
||||||
|
}
|
||||||
|
|
||||||
const TooltipTrigger = React.forwardRef<
|
export const TooltipAnchor = forwardRef<HTMLDivElement, TooltipAnchorProps>(function TooltipAnchor(
|
||||||
React.ElementRef<typeof TooltipPrimitive.Trigger>,
|
{ description, side = 'top', role, ...props },
|
||||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>
|
ref,
|
||||||
>((props, ref) => <TooltipPrimitive.Trigger ref={ref} {...props} />);
|
) {
|
||||||
TooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName;
|
const tooltip = Ariakit.useTooltipStore({ placement: side });
|
||||||
|
const mounted = Ariakit.useStoreState(tooltip, (state) => state.mounted);
|
||||||
|
const placement = Ariakit.useStoreState(tooltip, (state) => state.placement);
|
||||||
|
|
||||||
const TooltipPortal = TooltipPrimitive.Portal;
|
const { x, y } = useMemo(() => {
|
||||||
|
const dir = placement.split('-')[0];
|
||||||
|
switch (dir) {
|
||||||
|
case 'top':
|
||||||
|
return { x: 0, y: -8 };
|
||||||
|
case 'bottom':
|
||||||
|
return { x: 0, y: 8 };
|
||||||
|
case 'left':
|
||||||
|
return { x: -8, y: 0 };
|
||||||
|
case 'right':
|
||||||
|
return { x: 8, y: 0 };
|
||||||
|
default:
|
||||||
|
return { x: 0, y: 0 };
|
||||||
|
}
|
||||||
|
}, [placement]);
|
||||||
|
|
||||||
const TooltipArrow = React.forwardRef<
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
React.ElementRef<typeof TooltipPrimitive.Arrow>,
|
if (role === 'button' && event.key === 'Enter') {
|
||||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Arrow>
|
event.preventDefault();
|
||||||
>((props, ref) => <TooltipPrimitive.Arrow ref={ref} {...props} />);
|
(event.target as HTMLDivElement).click();
|
||||||
TooltipArrow.displayName = TooltipPrimitive.Arrow.displayName;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const TooltipContent = React.forwardRef<
|
return (
|
||||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
<Ariakit.TooltipProvider store={tooltip} hideTimeout={0}>
|
||||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
<Ariakit.TooltipAnchor {...props} ref={ref} role={role} onKeyDown={handleKeyDown} />
|
||||||
>(({ className = '', forceMount, children, ...props }, ref) => (
|
<AnimatePresence>
|
||||||
<TooltipPortal forceMount={forceMount}>
|
{mounted && (
|
||||||
<TooltipPrimitive.Content
|
<Ariakit.Tooltip
|
||||||
className={cn(
|
gutter={4}
|
||||||
'shadow-xs relative z-[1000] max-w-xs rounded-lg border border-gray-900/10 bg-gray-900 p-1 transition-opacity',
|
alwaysVisible
|
||||||
className,
|
className="tooltip"
|
||||||
)}
|
render={
|
||||||
ref={ref}
|
<motion.div
|
||||||
{...props}
|
initial={{ opacity: 0, x, y }}
|
||||||
style={{ userSelect: 'none' }}
|
animate={{ opacity: 1, x: 0, y: 0 }}
|
||||||
|
exit={{ opacity: 0, x, y }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span className="flex items-center whitespace-pre-wrap px-2 py-1 text-center text-sm font-medium normal-case text-white">
|
<Ariakit.TooltipArrow />
|
||||||
{children}
|
{description}
|
||||||
<TooltipArrow className="TooltipArrow" />
|
</Ariakit.Tooltip>
|
||||||
</span>
|
)}
|
||||||
</TooltipPrimitive.Content>
|
</AnimatePresence>
|
||||||
</TooltipPortal>
|
</Ariakit.TooltipProvider>
|
||||||
));
|
);
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
});
|
||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider;
|
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipPortal, TooltipContent, TooltipArrow, TooltipProvider };
|
|
||||||
|
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
import { ReactElement } from 'react';
|
|
||||||
import {
|
|
||||||
OGDialog,
|
|
||||||
OGDialogTrigger,
|
|
||||||
Label,
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '~/components/ui';
|
|
||||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
|
||||||
import { CrossIcon } from '~/components/svg';
|
|
||||||
import { useLocalize } from '~/hooks';
|
|
||||||
|
|
||||||
export default function TooltipIcon({
|
|
||||||
disabled,
|
|
||||||
appendLabel = false,
|
|
||||||
title,
|
|
||||||
className = '',
|
|
||||||
confirm,
|
|
||||||
confirmMessage,
|
|
||||||
icon,
|
|
||||||
tabIndex,
|
|
||||||
onFocus,
|
|
||||||
onBlur,
|
|
||||||
}: {
|
|
||||||
disabled: boolean;
|
|
||||||
title: string;
|
|
||||||
appendLabel?: boolean;
|
|
||||||
className?: string;
|
|
||||||
confirm?: () => void;
|
|
||||||
confirmMessage?: ReactElement;
|
|
||||||
icon?: ReactElement;
|
|
||||||
tabIndex?: number;
|
|
||||||
onFocus?: () => void;
|
|
||||||
onBlur?: () => void;
|
|
||||||
}) {
|
|
||||||
const localize = useLocalize();
|
|
||||||
|
|
||||||
const renderDeleteButton = () => {
|
|
||||||
if (appendLabel) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{icon} {localize('com_ui_delete')}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TooltipProvider delayDuration={250}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span>{icon}</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" sideOffset={0}>
|
|
||||||
{localize('com_ui_delete')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!confirmMessage) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={className}
|
|
||||||
onClick={confirm}
|
|
||||||
tabIndex={tabIndex}
|
|
||||||
onFocus={onFocus}
|
|
||||||
onBlur={onBlur}
|
|
||||||
>
|
|
||||||
{disabled ? <CrossIcon /> : renderDeleteButton()}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<OGDialog>
|
|
||||||
<OGDialogTrigger asChild>
|
|
||||||
<button className={className} tabIndex={tabIndex} onFocus={onFocus} onBlur={onBlur}>
|
|
||||||
{disabled ? <CrossIcon /> : renderDeleteButton()}
|
|
||||||
</button>
|
|
||||||
</OGDialogTrigger>
|
|
||||||
<OGDialogTemplate
|
|
||||||
showCloseButton={false}
|
|
||||||
title={title}
|
|
||||||
className="max-w-[450px]"
|
|
||||||
main={<Label className="text-left text-sm font-medium">{confirmMessage}</Label>}
|
|
||||||
selection={{
|
|
||||||
selectHandler: confirm,
|
|
||||||
selectClasses:
|
|
||||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 text-white',
|
|
||||||
selectText: localize('com_ui_delete'),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</OGDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -5,6 +5,7 @@ export { default as useDefaultConvo } from './useDefaultConvo';
|
||||||
export { default as useConversation } from './useConversation';
|
export { default as useConversation } from './useConversation';
|
||||||
export { default as useGenerateConvo } from './useGenerateConvo';
|
export { default as useGenerateConvo } from './useGenerateConvo';
|
||||||
export { default as useConversations } from './useConversations';
|
export { default as useConversations } from './useConversations';
|
||||||
|
export { default as useArchiveHandler } from './useArchiveHandler';
|
||||||
export { default as useDebouncedInput } from './useDebouncedInput';
|
export { default as useDebouncedInput } from './useDebouncedInput';
|
||||||
export { default as useBookmarkSuccess } from './useBookmarkSuccess';
|
export { default as useBookmarkSuccess } from './useBookmarkSuccess';
|
||||||
export { default as useNavigateToConvo } from './useNavigateToConvo';
|
export { default as useNavigateToConvo } from './useNavigateToConvo';
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,13 @@
|
||||||
import React from 'react';
|
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
||||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
|
||||||
import { useConversations, useLocalize, useNewConvo } from '~/hooks';
|
|
||||||
import { useArchiveConversationMutation } from '~/data-provider';
|
import { useArchiveConversationMutation } from '~/data-provider';
|
||||||
|
import useConversations from './useConversations';
|
||||||
import { NotificationSeverity } from '~/common';
|
import { NotificationSeverity } from '~/common';
|
||||||
import { useToastContext } from '~/Providers';
|
import { useToastContext } from '~/Providers';
|
||||||
|
import useLocalize from '../useLocalize';
|
||||||
|
import useNewConvo from '../useNewConvo';
|
||||||
|
|
||||||
type ArchiveButtonProps = {
|
export default function useArchiveHandler(
|
||||||
children?: React.ReactNode;
|
|
||||||
conversationId: string;
|
|
||||||
retainView: () => void;
|
|
||||||
shouldArchive: boolean;
|
|
||||||
icon?: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useArchiveHandler(
|
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
shouldArchive: boolean,
|
shouldArchive: boolean,
|
||||||
retainView: () => void,
|
retainView: () => void,
|
||||||
|
|
@ -57,31 +48,3 @@ export function useArchiveHandler(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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}>
|
|
||||||
<TooltipProvider delayDuration={250}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span className="h-5 w-5">{icon}</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" sideOffset={0}>
|
|
||||||
{localize(`com_ui_${shouldArchive ? 'archive' : 'unarchive'}`)}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { useArchiveHandler as archiveHandler };
|
|
||||||
|
|
@ -362,8 +362,10 @@ export default {
|
||||||
com_ui_bookmarks_delete_error: 'There was an error deleting the bookmark',
|
com_ui_bookmarks_delete_error: 'There was an error deleting the bookmark',
|
||||||
com_ui_bookmarks_add_to_conversation: 'Add to current conversation',
|
com_ui_bookmarks_add_to_conversation: 'Add to current conversation',
|
||||||
com_ui_bookmarks_filter: 'Filter bookmarks...',
|
com_ui_bookmarks_filter: 'Filter bookmarks...',
|
||||||
|
com_ui_bookmarks_delete: 'Delete Bookmark',
|
||||||
com_ui_no_bookmarks: 'it seems like you have no bookmarks yet. Click on a chat and add a new one',
|
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_ui_no_conversation_id: 'No conversation ID found',
|
||||||
|
com_ui_add_multi_conversation: 'Add multi-conversation',
|
||||||
com_auth_error_login:
|
com_auth_error_login:
|
||||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||||
com_auth_error_login_rl:
|
com_auth_error_login_rl:
|
||||||
|
|
|
||||||
|
|
@ -207,12 +207,12 @@ export default {
|
||||||
com_ui_shared_link_not_found: 'Link condiviso non trovato',
|
com_ui_shared_link_not_found: 'Link condiviso non trovato',
|
||||||
com_ui_delete_conversation: 'Eliminare la chat?',
|
com_ui_delete_conversation: 'Eliminare la chat?',
|
||||||
com_ui_delete_confirm: 'Questo eliminerà',
|
com_ui_delete_confirm: 'Questo eliminerà',
|
||||||
com_ui_rename: 'Rinominare',
|
com_ui_rename: 'Rinomina',
|
||||||
com_ui_archive: 'Arsip',
|
com_ui_archive: 'Archivia',
|
||||||
com_ui_archive_error: 'Errore durante l\'archiviazione della conversazione',
|
com_ui_archive_error: 'Errore durante l\'archiviazione della conversazione',
|
||||||
com_ui_unarchive: 'Disarchivia',
|
com_ui_unarchive: 'Disarchivia',
|
||||||
com_ui_unarchive_error: 'Impossibile disarchiviare la conversazione',
|
com_ui_unarchive_error: 'Impossibile disarchiviare la conversazione',
|
||||||
com_ui_more_options: 'Pi',
|
com_ui_more_options: 'Più',
|
||||||
com_ui_delete_assistant_confirm:
|
com_ui_delete_assistant_confirm:
|
||||||
'Sei sicuro di voler eliminare questo Assistente? Questa operazione non può essere annullata.',
|
'Sei sicuro di voler eliminare questo Assistente? Questa operazione non può essere annullata.',
|
||||||
com_ui_preview: 'Anteprima',
|
com_ui_preview: 'Anteprima',
|
||||||
|
|
@ -1361,11 +1361,11 @@ export const comparisons = {
|
||||||
},
|
},
|
||||||
com_ui_rename: {
|
com_ui_rename: {
|
||||||
english: 'Rename',
|
english: 'Rename',
|
||||||
translated: 'Rinominare',
|
translated: 'Rinomina',
|
||||||
},
|
},
|
||||||
com_ui_archive: {
|
com_ui_archive: {
|
||||||
english: 'Archive',
|
english: 'Archive',
|
||||||
translated: 'Arsip',
|
translated: 'Archivia',
|
||||||
},
|
},
|
||||||
com_ui_archive_error: {
|
com_ui_archive_error: {
|
||||||
english: 'Failed to archive conversation',
|
english: 'Failed to archive conversation',
|
||||||
|
|
|
||||||
|
|
@ -653,11 +653,11 @@ Write a prompt that is mindful of the nuances in the language with respect to it
|
||||||
|
|
||||||
- **com_ui_rename**:
|
- **com_ui_rename**:
|
||||||
- **english**: Rename
|
- **english**: Rename
|
||||||
- **translated**: Rinominare
|
- **translated**: Rinomina
|
||||||
|
|
||||||
- **com_ui_archive**:
|
- **com_ui_archive**:
|
||||||
- **english**: Archive
|
- **english**: Archive
|
||||||
- **translated**: Arsip
|
- **translated**: Archivia
|
||||||
|
|
||||||
- **com_ui_archive_error**:
|
- **com_ui_archive_error**:
|
||||||
- **english**: Failed to archive conversation
|
- **english**: Failed to archive conversation
|
||||||
|
|
|
||||||
|
|
@ -1963,7 +1963,7 @@ button.scroll-convo {
|
||||||
|
|
||||||
.prose ol > li::marker,
|
.prose ol > li::marker,
|
||||||
.markdown ol > li::marker {
|
.markdown ol > li::marker {
|
||||||
content: counter(list-counter) ". ";
|
content: counter(list-counter) '. ';
|
||||||
color: var(--tw-prose-counters);
|
color: var(--tw-prose-counters);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
@ -1982,7 +1982,7 @@ button.scroll-convo {
|
||||||
|
|
||||||
.prose ol ol > li::marker,
|
.prose ol ol > li::marker,
|
||||||
.markdown ol ol > li::marker {
|
.markdown ol ol > li::marker {
|
||||||
content: counter(list-counter-alpha, lower-alpha) ". ";
|
content: counter(list-counter-alpha, lower-alpha) '. ';
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose ol ol ol,
|
.prose ol ol ol,
|
||||||
|
|
@ -1998,7 +1998,7 @@ button.scroll-convo {
|
||||||
|
|
||||||
.prose ol ol ol > li::marker,
|
.prose ol ol ol > li::marker,
|
||||||
.markdown ol ol ol > li::marker {
|
.markdown ol ol ol > li::marker {
|
||||||
content: counter(list-counter-roman, lower-roman) ". ";
|
content: counter(list-counter-roman, lower-roman) '. ';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Unordered lists */
|
/* Unordered lists */
|
||||||
|
|
@ -2288,6 +2288,27 @@ button.scroll-convo {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
z-index: 50;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.275rem;
|
||||||
|
background-color: var(--bg-gray-600);
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
color: black;
|
||||||
|
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip:where(.dark, .dark *) {
|
||||||
|
background-color: var(--surface-primary);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
package-lock.json
generated
53
package-lock.json
generated
|
|
@ -1223,7 +1223,7 @@
|
||||||
"version": "v0.7.5-rc2",
|
"version": "v0.7.5-rc2",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ariakit/react": "^0.4.8",
|
"@ariakit/react": "^0.4.11",
|
||||||
"@codesandbox/sandpack-react": "^2.18.2",
|
"@codesandbox/sandpack-react": "^2.18.2",
|
||||||
"@dicebear/collection": "^7.0.4",
|
"@dicebear/collection": "^7.0.4",
|
||||||
"@dicebear/core": "^7.0.4",
|
"@dicebear/core": "^7.0.4",
|
||||||
|
|
@ -1258,6 +1258,7 @@
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"export-from-json": "^1.7.2",
|
"export-from-json": "^1.7.2",
|
||||||
"filenamify": "^6.0.0",
|
"filenamify": "^6.0.0",
|
||||||
|
"framer-motion": "^11.5.4",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"image-blob-reduce": "^4.1.0",
|
"image-blob-reduce": "^4.1.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
|
@ -1398,16 +1399,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ariakit/core": {
|
"node_modules/@ariakit/core": {
|
||||||
"version": "0.4.8",
|
"version": "0.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.10.tgz",
|
||||||
"integrity": "sha512-HQS+9CI7pMqqVlAt5bPGenT0/e65UxXY+PKtgU7Y+0UToBDBRolO5S9+UUSDm8OmJHSnq24owEGm1Mv28l5XCQ=="
|
"integrity": "sha512-mX3EabQbfVh5uTjsTJ3+gjj7KGdTNhIN0qZHJd5Z2iPUnKl9NBy23Lgu6PEskpVsKAZ3proirjguD7U9fKMs/A==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@ariakit/react": {
|
"node_modules/@ariakit/react": {
|
||||||
"version": "0.4.8",
|
"version": "0.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.11.tgz",
|
||||||
"integrity": "sha512-Bb1vOrp0X52hxi1wE9TEHjjZ/Y08tVq2ZH+RFDwRQB3g04uVwrrhnTccHepC6rsObrDpAOV3/YlJCi4k/lSUaQ==",
|
"integrity": "sha512-nLpPrmNcspqNhk4o+epsgeZfP1+Fkh4uIzNe5yrFkXolRkqHGKAxl4Hi82e0yxIBUbYbZIEwsZQQVceF1L6xrw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ariakit/react-core": "0.4.8"
|
"@ariakit/react-core": "0.4.11"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
@ -1419,11 +1422,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ariakit/react-core": {
|
"node_modules/@ariakit/react-core": {
|
||||||
"version": "0.4.8",
|
"version": "0.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.11.tgz",
|
||||||
"integrity": "sha512-TzsddUWQwWYhrEVWHA/Gf7KCGx8rwFohAHfuljjqidKeZi2kUmuRAImCTG9oga34FWHFf4AdXQbBKclMNt0nrQ==",
|
"integrity": "sha512-i6KedWhjZkNC7tMEKO0eNjjq2HRPiHyGaBS2x2VaWwzBepoYtjyvxRXyqLJ3gaiNdlwckN1TZsRDfD+viy13IQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ariakit/core": "0.4.8",
|
"@ariakit/core": "0.4.10",
|
||||||
"@floating-ui/dom": "^1.0.0",
|
"@floating-ui/dom": "^1.0.0",
|
||||||
"use-sync-external-store": "^1.2.0"
|
"use-sync-external-store": "^1.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -20907,6 +20911,31 @@
|
||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "11.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.5.4.tgz",
|
||||||
|
"integrity": "sha512-E+tb3/G6SO69POkdJT+3EpdMuhmtCh9EWuK4I1DnIC23L7tFPrl8vxP+LSovwaw6uUr73rUbpb4FgK011wbRJQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue