mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +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",
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.4.8",
|
||||
"@ariakit/react": "^0.4.11",
|
||||
"@codesandbox/sandpack-react": "^2.18.2",
|
||||
"@dicebear/collection": "^7.0.4",
|
||||
"@dicebear/core": "^7.0.4",
|
||||
|
|
@ -63,6 +63,7 @@
|
|||
"downloadjs": "^1.4.7",
|
||||
"export-from-json": "^1.7.2",
|
||||
"filenamify": "^6.0.0",
|
||||
"framer-motion": "^11.5.4",
|
||||
"html-to-image": "^1.11.11",
|
||||
"image-blob-reduce": "^4.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { Label, OGDialog, OGDialogTrigger, TooltipAnchor } from '~/components/ui';
|
||||
import { useDeleteConversationTagMutation } from '~/data-provider';
|
||||
import TooltipIcon from '~/components/ui/TooltipIcon';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { TrashIcon } from '~/components/svg';
|
||||
import { Label } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const DeleteBookmarkButton: FC<{
|
||||
|
|
@ -16,6 +16,7 @@ const DeleteBookmarkButton: FC<{
|
|||
}> = ({ bookmark, tabIndex = 0, onFocus, onBlur }) => {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const deleteBookmarkMutation = useDeleteConversationTagMutation({
|
||||
onSuccess: () => {
|
||||
|
|
@ -35,23 +36,48 @@ const DeleteBookmarkButton: FC<{
|
|||
await deleteBookmarkMutation.mutateAsync(bookmark);
|
||||
}, [bookmark, deleteBookmarkMutation]);
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setOpen(!open);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipIcon
|
||||
disabled={false}
|
||||
appendLabel={false}
|
||||
title="Delete Bookmark"
|
||||
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" />}
|
||||
tabIndex={tabIndex}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
<>
|
||||
<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}
|
||||
onFocus={onFocus}
|
||||
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 { TConversationTag } from 'librechat-data-provider';
|
||||
import BookmarkEditDialog from './BookmarkEditDialog';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { EditIcon } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
|
||||
|
||||
const EditBookmarkButton: FC<{
|
||||
bookmark: TConversationTag;
|
||||
|
|
@ -15,6 +15,12 @@ const EditBookmarkButton: FC<{
|
|||
const localize = useLocalize();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
setOpen(!open);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BookmarkEditDialog
|
||||
|
|
@ -23,25 +29,17 @@ const EditBookmarkButton: FC<{
|
|||
open={open}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="transition-colors flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_edit')}
|
||||
tabIndex={tabIndex}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
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 />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_edit')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</button>
|
||||
<EditIcon />
|
||||
</TooltipAnchor>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ import { PlusCircle } from 'lucide-react';
|
|||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { useChatContext, useAddedChatContext } from '~/Providers';
|
||||
import { TooltipAnchor } from '~/components';
|
||||
import { mainTextareaId } from '~/common';
|
||||
import { Button } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
function AddMultiConvo({ className = '' }: { className?: string }) {
|
||||
function AddMultiConvo() {
|
||||
const { conversation } = useChatContext();
|
||||
const { setConversation: setAddedConvo } = useAddedChatContext();
|
||||
const localize = useLocalize();
|
||||
|
||||
const clickHandler = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
|
@ -33,15 +35,18 @@ function AddMultiConvo({ className = '' }: { className?: string }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
<TooltipAnchor
|
||||
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}
|
||||
variant="outline"
|
||||
className={cn('h-10 w-10 p-0 transition-all duration-300 ease-in-out', className)}
|
||||
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"
|
||||
>
|
||||
<PlusCircle size={16} />
|
||||
</Button>
|
||||
<PlusCircle size={16} aria-label="Plus Icon" />
|
||||
</TooltipAnchor>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect } from 'react';
|
||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
||||
import { ListeningIcon, Spinner } from '~/components/svg';
|
||||
import { useLocalize, useSpeechToText } from '~/hooks';
|
||||
import { useChatFormContext } from '~/Providers';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { globalAudioId } from '~/common';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -74,29 +74,20 @@ export default function AudioRecorder({
|
|||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
id="audio-recorder"
|
||||
aria-label={localize('com_ui_use_micrphone')}
|
||||
onClick={isListening ? handleStopRecording : handleStartRecording}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'absolute flex h-[30px] w-[30px] items-center justify-center rounded-lg p-0.5 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700',
|
||||
isRTL
|
||||
? 'bottom-1.5 left-4 md:bottom-3 md:left-12'
|
||||
: 'bottom-1.5 right-12 md:bottom-3 md:right-12',
|
||||
)}
|
||||
type="button"
|
||||
>
|
||||
{renderIcon()}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={10}>
|
||||
{localize('com_ui_use_micrphone')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipAnchor
|
||||
id="audio-recorder"
|
||||
aria-label={localize('com_ui_use_micrphone')}
|
||||
onClick={isListening ? handleStopRecording : handleStartRecording}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'absolute flex h-[30px] w-[30px] items-center justify-center rounded-lg p-0.5 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700',
|
||||
isRTL
|
||||
? 'bottom-1.5 left-4 md:bottom-3 md:left-12'
|
||||
: 'bottom-1.5 right-12 md:bottom-3 md:right-12',
|
||||
)}
|
||||
description={localize('com_ui_use_micrphone')}
|
||||
>
|
||||
{renderIcon()}
|
||||
</TooltipAnchor>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import {
|
|||
fileConfig as defaultFileConfig,
|
||||
mergeFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import { FileUpload, TooltipAnchor } from '~/components/ui';
|
||||
import { useFileHandling, useLocalize } from '~/hooks';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
import { AttachmentIcon } from '~/components/svg';
|
||||
import { FileUpload } from '~/components/ui';
|
||||
import { useFileHandling } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const AttachFile = ({
|
||||
|
|
@ -22,6 +22,7 @@ const AttachFile = ({
|
|||
isRTL: boolean;
|
||||
disabled?: boolean | null;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const { handleFileChange } = useFileHandling();
|
||||
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
|
||||
select: (data) => mergeFileConfig(data),
|
||||
|
|
@ -42,17 +43,18 @@ const AttachFile = ({
|
|||
)}
|
||||
>
|
||||
<FileUpload handleFileChange={handleFileChange} className="flex">
|
||||
<button
|
||||
<TooltipAnchor
|
||||
id="audio-recorder"
|
||||
aria-label={localize('com_sidepanel_attach_files')}
|
||||
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"
|
||||
aria-label="Attach files"
|
||||
style={{ padding: 0 }}
|
||||
description={localize('com_sidepanel_attach_files')}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
<AttachmentIcon />
|
||||
</div>
|
||||
</button>
|
||||
</TooltipAnchor>
|
||||
</FileUpload>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@ import { useState, useEffect, useMemo } from 'react';
|
|||
import { tPresetUpdateSchema, EModelEndpoint, paramEndpoints } from 'librechat-data-provider';
|
||||
import type { TPreset, TInterfaceConfig } from 'librechat-data-provider';
|
||||
import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints';
|
||||
import { PluginStoreDialog, TooltipAnchor } from '~/components';
|
||||
import { ModelSelect } from '~/components/Input/ModelSelect';
|
||||
import { PluginStoreDialog } from '~/components';
|
||||
import { useSetIndexOptions, useLocalize } from '~/hooks';
|
||||
import OptionsPopover from './OptionsPopover';
|
||||
import PopoverButtons from './PopoverButtons';
|
||||
import { useSetIndexOptions } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { Button } from '~/components/ui';
|
||||
import store from '~/store';
|
||||
|
||||
export default function HeaderOptions({
|
||||
|
|
@ -23,6 +22,7 @@ export default function HeaderOptions({
|
|||
const [showPluginStoreDialog, setShowPluginStoreDialog] = useRecoilState(
|
||||
store.showPluginStoreDialog,
|
||||
);
|
||||
const localize = useLocalize();
|
||||
|
||||
const { showPopover, conversation, latestMessage, setShowPopover, setShowBingToneSetting } =
|
||||
useChatContext();
|
||||
|
|
@ -84,17 +84,18 @@ export default function HeaderOptions({
|
|||
{!noSettings[endpoint] &&
|
||||
interfaceConfig?.parameters === true &&
|
||||
!paramEndpoints.has(endpoint) && (
|
||||
<Button
|
||||
aria-label="Settings/parameters"
|
||||
<TooltipAnchor
|
||||
id="parameters-button"
|
||||
data-testid="parameters-button"
|
||||
type="button"
|
||||
variant="outline"
|
||||
aria-label={localize('com_ui_model_parameters')}
|
||||
description={localize('com_ui_model_parameters')}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
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" />
|
||||
</Button>
|
||||
<Settings2 size={16} aria-label="Settings/Parameters Icon" />
|
||||
</TooltipAnchor>
|
||||
)}
|
||||
</div>
|
||||
{interfaceConfig?.parameters === true && !paramEndpoints.has(endpoint) && (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
import { useWatch } 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 { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
|
@ -17,33 +17,29 @@ const SubmitButton = React.memo(
|
|||
(props: { disabled: boolean; isRTL: boolean }, ref: React.ForwardedRef<HTMLButtonElement>) => {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
ref={ref}
|
||||
aria-label={localize('com_nav_send_message')}
|
||||
id="send-button"
|
||||
disabled={props.disabled}
|
||||
className={cn(
|
||||
'absolute rounded-lg border border-black p-0.5 text-white outline-offset-4 transition-colors enabled:bg-black disabled:bg-black disabled:text-gray-400 disabled:opacity-10 dark:border-white dark:bg-white dark:disabled:bg-white',
|
||||
props.isRTL
|
||||
? 'bottom-1.5 left-2 md:bottom-3 md:left-3'
|
||||
: 'bottom-1.5 right-2 md:bottom-3 md:right-3',
|
||||
)}
|
||||
data-testid="send-button"
|
||||
type="submit"
|
||||
>
|
||||
<span className="" data-state="closed">
|
||||
<SendIcon size={24} />
|
||||
</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={10}>
|
||||
{localize('com_nav_send_message')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipAnchor
|
||||
description={localize('com_nav_send_message')}
|
||||
render={
|
||||
<button
|
||||
ref={ref}
|
||||
aria-label={localize('com_nav_send_message')}
|
||||
id="send-button"
|
||||
disabled={props.disabled}
|
||||
className={cn(
|
||||
'absolute rounded-lg border border-black p-0.5 text-white outline-offset-4 transition-colors enabled:bg-black disabled:bg-black disabled:text-gray-400 disabled:opacity-10 dark:border-white dark:bg-white dark:disabled:bg-white',
|
||||
props.isRTL
|
||||
? 'bottom-1.5 left-2 md:bottom-3 md:left-3'
|
||||
: 'bottom-1.5 right-2 md:bottom-3 md:right-3',
|
||||
)}
|
||||
data-testid="send-button"
|
||||
type="submit"
|
||||
>
|
||||
<span className="" data-state="closed">
|
||||
<SendIcon size={24} />
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
></TooltipAnchor>
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import { useMemo } from 'react';
|
|||
import { EModelEndpoint, isAssistantsEndpoint, Constants } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import type { ReactNode } from 'react';
|
||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { useGetAssistantDocsQuery } from '~/data-provider';
|
||||
import ConvoIcon from '~/components/Endpoints/ConvoIcon';
|
||||
import { useLocalize, useSubmitMessage } from '~/hooks';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { BirthdayIcon } from '~/components/svg';
|
||||
import { getIconEndpoint, cn } from '~/utils';
|
||||
import ConvoStarter from './ConvoStarter';
|
||||
|
|
@ -58,66 +58,58 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
|||
const sendConversationStarter = (text: string) => submitMessage({ text });
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={50}>
|
||||
<Tooltip>
|
||||
<div className="relative h-full">
|
||||
<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={cn('relative h-12 w-12', assistantName && avatar ? 'mb-0' : 'mb-3')}>
|
||||
<ConvoIcon
|
||||
conversation={conversation}
|
||||
assistantMap={assistantMap}
|
||||
endpointsConfig={endpointsConfig}
|
||||
containerClassName={containerClassName}
|
||||
context="landing"
|
||||
className="h-2/3 w-2/3"
|
||||
size={41}
|
||||
/>
|
||||
{startupConfig?.showBirthdayIcon === true ? (
|
||||
<div>
|
||||
<TooltipTrigger>
|
||||
<BirthdayIcon className="absolute bottom-8 right-2.5" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={110} className="">
|
||||
{localize('com_ui_happy_birthday')}
|
||||
</TooltipContent>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{assistantName ? (
|
||||
<div className="flex flex-col items-center gap-0 p-2">
|
||||
<div className="text-center text-2xl font-medium dark:text-white">
|
||||
{assistantName}
|
||||
</div>
|
||||
<div className="max-w-md text-center text-sm font-normal text-text-primary ">
|
||||
{assistantDesc ? assistantDesc : localize('com_nav_welcome_message')}
|
||||
</div>
|
||||
{/* <div className="mt-1 flex items-center gap-1 text-token-text-tertiary">
|
||||
<div className="text-sm text-token-text-tertiary">By Daniel Avila</div>
|
||||
</div> */}
|
||||
</div>
|
||||
) : (
|
||||
<h2 className="mb-5 max-w-[75vh] px-12 text-center text-lg font-medium dark:text-white md:px-0 md:text-2xl">
|
||||
{isAssistant
|
||||
? conversation?.greeting ?? localize('com_nav_welcome_assistant')
|
||||
: conversation?.greeting ?? localize('com_nav_welcome_message')}
|
||||
</h2>
|
||||
)}
|
||||
<div className="mt-8 flex flex-wrap justify-center gap-3 px-4">
|
||||
{conversation_starters.length > 0 &&
|
||||
conversation_starters
|
||||
.slice(0, Constants.MAX_CONVO_STARTERS)
|
||||
.map((text, index) => (
|
||||
<ConvoStarter
|
||||
key={index}
|
||||
text={text}
|
||||
onClick={() => sendConversationStarter(text)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative h-full">
|
||||
<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={cn('relative h-12 w-12', assistantName && avatar ? 'mb-0' : 'mb-3')}>
|
||||
<ConvoIcon
|
||||
conversation={conversation}
|
||||
assistantMap={assistantMap}
|
||||
endpointsConfig={endpointsConfig}
|
||||
containerClassName={containerClassName}
|
||||
context="landing"
|
||||
className="h-2/3 w-2/3"
|
||||
size={41}
|
||||
/>
|
||||
{startupConfig?.showBirthdayIcon === true ? (
|
||||
<TooltipAnchor
|
||||
className="absolute bottom-8 right-2.5"
|
||||
description={localize('com_ui_happy_birthday')}
|
||||
>
|
||||
<BirthdayIcon />
|
||||
</TooltipAnchor>
|
||||
) : null}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{assistantName ? (
|
||||
<div className="flex flex-col items-center gap-0 p-2">
|
||||
<div className="text-center text-2xl font-medium dark:text-white">{assistantName}</div>
|
||||
<div className="max-w-md text-center text-sm font-normal text-text-primary ">
|
||||
{assistantDesc ? assistantDesc : localize('com_nav_welcome_message')}
|
||||
</div>
|
||||
{/* <div className="mt-1 flex items-center gap-1 text-token-text-tertiary">
|
||||
<div className="text-sm text-token-text-tertiary">By Daniel Avila</div>
|
||||
</div> */}
|
||||
</div>
|
||||
) : (
|
||||
<h2 className="mb-5 max-w-[75vh] px-12 text-center text-lg font-medium dark:text-white md:px-0 md:text-2xl">
|
||||
{isAssistant
|
||||
? conversation?.greeting ?? localize('com_nav_welcome_assistant')
|
||||
: conversation?.greeting ?? localize('com_nav_welcome_message')}
|
||||
</h2>
|
||||
)}
|
||||
<div className="mt-8 flex flex-wrap justify-center gap-3 px-4">
|
||||
{conversation_starters.length > 0 &&
|
||||
conversation_starters
|
||||
.slice(0, Constants.MAX_CONVO_STARTERS)
|
||||
.map((text, index) => (
|
||||
<ConvoStarter
|
||||
key={index}
|
||||
text={text}
|
||||
onClick={() => sendConversationStarter(text)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
|||
import { EditPresetDialog, PresetItems } from './Presets';
|
||||
import { useLocalize, usePresets } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { Button } from '~/components/ui';
|
||||
import { TooltipAnchor } from '~/components';
|
||||
|
||||
const PresetsMenu: FC = () => {
|
||||
const localize = useLocalize();
|
||||
|
|
@ -25,17 +25,17 @@ const PresetsMenu: FC = () => {
|
|||
return (
|
||||
<Root>
|
||||
<Trigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="flex h-[40px] min-w-4 px-3 radix-state-open:bg-surface-hover"
|
||||
<TooltipAnchor
|
||||
id="presets-button"
|
||||
data-testid="presets-button"
|
||||
title={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" />
|
||||
</Button>
|
||||
<BookCopy size={16} aria-label="Preset Icon" />
|
||||
</TooltipAnchor>
|
||||
</Trigger>
|
||||
<Portal>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ import { useState, useId } from 'react';
|
|||
import * as Ariakit from '@ariakit/react';
|
||||
import { Ellipsis, Share2, Archive, Pen, Trash } from 'lucide-react';
|
||||
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import { useArchiveHandler } from './ArchiveButton';
|
||||
import { useLocalize, useArchiveHandler } from '~/hooks';
|
||||
import { DropdownPopup } from '~/components/ui';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import ShareButton from './ShareButton';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export default function ConvoOptions({
|
||||
|
|
|
|||
|
|
@ -4,15 +4,7 @@ import { useQueryClient } from '@tanstack/react-query';
|
|||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import { useDeleteConversationMutation } from '~/data-provider';
|
||||
import {
|
||||
OGDialog,
|
||||
OGDialogTrigger,
|
||||
Label,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '~/components/ui';
|
||||
import { OGDialog, OGDialogTrigger, Label, TooltipAnchor } from '~/components/ui';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { TrashIcon } from '~/components/svg';
|
||||
import { useLocalize, useNewConvo } from '~/hooks';
|
||||
|
|
@ -21,7 +13,6 @@ type DeleteButtonProps = {
|
|||
conversationId: string;
|
||||
retainView: () => void;
|
||||
title: string;
|
||||
className?: string;
|
||||
showDeleteDialog?: boolean;
|
||||
setShowDeleteDialog?: (value: boolean) => void;
|
||||
};
|
||||
|
|
@ -30,7 +21,6 @@ export default function DeleteButton({
|
|||
conversationId,
|
||||
retainView,
|
||||
title,
|
||||
className = '',
|
||||
showDeleteDialog,
|
||||
setShowDeleteDialog,
|
||||
}: DeleteButtonProps) {
|
||||
|
|
@ -92,20 +82,13 @@ export default function DeleteButton({
|
|||
|
||||
return (
|
||||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<OGDialogTrigger asChild>
|
||||
<TooltipTrigger asChild>
|
||||
<button>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
</OGDialogTrigger>
|
||||
<TooltipContent side="top" sideOffset={0} className={className}>
|
||||
{localize('com_ui_delete')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipAnchor description={localize('com_ui_delete')}>
|
||||
<OGDialogTrigger asChild>
|
||||
<button>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</OGDialogTrigger>
|
||||
</TooltipAnchor>
|
||||
{dialogContent}
|
||||
</OGDialog>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
export { default as ArchiveButton } from './ArchiveButton';
|
||||
export { default as DeleteButton } from './DeleteButton';
|
||||
export { default as ShareButton } from './ShareButton';
|
||||
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 = {
|
||||
tags: string[];
|
||||
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 location = useLocation();
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps)
|
|||
className={cn(
|
||||
'mt-text-sm flex h-10 w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors duration-200 hover:bg-surface-hover',
|
||||
open ? 'bg-surface-hover' : '',
|
||||
isSmallScreen ? 'h-14 rounded-2xl' : '',
|
||||
)}
|
||||
data-testid="bookmark-menu"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ export default function MobileNav({
|
|||
const { title = 'New Chat' } = conversation || {};
|
||||
|
||||
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
|
||||
type="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={() =>
|
||||
setNavVisible((prev) => {
|
||||
localStorage.setItem('navVisible', JSON.stringify(!prev));
|
||||
|
|
@ -49,7 +49,7 @@ export default function MobileNav({
|
|||
</h1>
|
||||
<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()}
|
||||
>
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {
|
|||
useConversations,
|
||||
} from '~/hooks';
|
||||
import { useConversationsInfiniteQuery } from '~/data-provider';
|
||||
import { TooltipProvider, Tooltip } from '~/components/ui';
|
||||
import { Conversations } from '~/components/Conversations';
|
||||
import BookmarkNav from './Bookmarks/BookmarkNav';
|
||||
import AccountSettings from './AccountSettings';
|
||||
|
|
@ -130,111 +129,112 @@ const Nav = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<div
|
||||
data-testid="nav"
|
||||
className={
|
||||
'nav active max-w-[320px] flex-shrink-0 overflow-x-hidden bg-surface-primary-alt md:max-w-[260px]'
|
||||
}
|
||||
style={{
|
||||
width: navVisible ? navWidth : '0px',
|
||||
visibility: navVisible ? 'visible' : 'hidden',
|
||||
transition: 'width 0.2s, visibility 0.2s',
|
||||
}}
|
||||
>
|
||||
<div className="h-full w-[320px] md:w-[260px]">
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<>
|
||||
<div
|
||||
data-testid="nav"
|
||||
className={
|
||||
'nav active max-w-[320px] flex-shrink-0 overflow-x-hidden bg-surface-primary-alt md:max-w-[260px]'
|
||||
}
|
||||
style={{
|
||||
width: navVisible ? navWidth : '0px',
|
||||
visibility: navVisible ? 'visible' : 'hidden',
|
||||
transition: 'width 0.2s, visibility 0.2s',
|
||||
}}
|
||||
>
|
||||
<div className="h-full w-[320px] md:w-[260px]">
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-full min-h-0 flex-col transition-opacity',
|
||||
isToggleHovering && !isSmallScreen ? 'opacity-50' : 'opacity-100',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-full min-h-0 flex-col transition-opacity',
|
||||
isToggleHovering && !isSmallScreen ? 'opacity-50' : 'opacity-100',
|
||||
'scrollbar-trigger relative h-full w-full flex-1 items-start border-white/20',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'scrollbar-trigger relative h-full w-full flex-1 items-start border-white/20',
|
||||
)}
|
||||
<nav
|
||||
id="chat-history-nav"
|
||||
aria-label={localize('com_ui_chat_history')}
|
||||
className="flex h-full w-full flex-col px-3 pb-3.5"
|
||||
>
|
||||
<nav
|
||||
id="chat-history-nav"
|
||||
aria-label={localize('com_ui_chat_history')}
|
||||
className="flex h-full w-full flex-col px-3 pb-3.5"
|
||||
<div
|
||||
className={cn(
|
||||
'-mr-2 flex-1 flex-col overflow-y-auto pr-2 transition-opacity duration-500',
|
||||
isHovering ? '' : 'scrollbar-transparent',
|
||||
)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={containerRef}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'-mr-2 flex-1 flex-col overflow-y-auto pr-2 transition-opacity duration-500',
|
||||
isHovering ? '' : 'scrollbar-transparent',
|
||||
)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={containerRef}
|
||||
>
|
||||
{isSmallScreen == true ? (
|
||||
<div className="pt-3.5">
|
||||
{isSearchEnabled === true && (
|
||||
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
|
||||
)}
|
||||
{hasAccessToBookmarks === true && (
|
||||
<BookmarkNav tags={tags} setTags={setTags} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<NewChat
|
||||
toggleNav={itemToggleNav}
|
||||
subHeaders={
|
||||
<>
|
||||
{isSearchEnabled === true && (
|
||||
<SearchBar
|
||||
clearSearch={clearSearch}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
)}
|
||||
{hasAccessToBookmarks === true && (
|
||||
<BookmarkNav tags={tags} setTags={setTags} />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Conversations
|
||||
conversations={conversations}
|
||||
moveToTop={moveToTop}
|
||||
{isSmallScreen == true ? (
|
||||
<div className="pt-3.5">
|
||||
{isSearchEnabled === true && (
|
||||
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
|
||||
)}
|
||||
{hasAccessToBookmarks === true && (
|
||||
<BookmarkNav
|
||||
tags={tags}
|
||||
setTags={setTags}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<NewChat
|
||||
toggleNav={itemToggleNav}
|
||||
subHeaders={
|
||||
<>
|
||||
{isSearchEnabled === true && (
|
||||
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
|
||||
)}
|
||||
<BookmarkNav
|
||||
tags={tags}
|
||||
setTags={setTags}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{(isFetchingNextPage || showLoading) && (
|
||||
<Spinner className={cn('m-1 mx-auto mb-4 h-4 w-4 text-text-primary')} />
|
||||
)}
|
||||
</div>
|
||||
<AccountSettings />
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Conversations
|
||||
conversations={conversations}
|
||||
moveToTop={moveToTop}
|
||||
toggleNav={itemToggleNav}
|
||||
/>
|
||||
{(isFetchingNextPage || showLoading) && (
|
||||
<Spinner className={cn('m-1 mx-auto mb-4 h-4 w-4 text-text-primary')} />
|
||||
)}
|
||||
</div>
|
||||
<AccountSettings />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NavToggle
|
||||
isHovering={isToggleHovering}
|
||||
setIsHovering={setIsToggleHovering}
|
||||
onToggle={toggleNavVisible}
|
||||
navVisible={navVisible}
|
||||
className="fixed left-0 top-1/2 z-40 hidden md:flex"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={`nav-mask ${navVisible ? 'active' : ''}`}
|
||||
onClick={toggleNavVisible}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
toggleNavVisible();
|
||||
}
|
||||
}}
|
||||
aria-label="Toggle navigation"
|
||||
/>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<NavToggle
|
||||
isHovering={isToggleHovering}
|
||||
setIsHovering={setIsToggleHovering}
|
||||
onToggle={toggleNavVisible}
|
||||
navVisible={navVisible}
|
||||
className="fixed left-0 top-1/2 z-40 hidden md:flex"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={`nav-mask ${navVisible ? 'active' : ''}`}
|
||||
onClick={toggleNavVisible}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
toggleNavVisible();
|
||||
}
|
||||
}}
|
||||
aria-label="Toggle navigation"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { TooltipTrigger, TooltipContent } from '~/components/ui';
|
||||
import { useLocalize, useLocalStorage } from '~/hooks';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export default function NavToggle({
|
||||
|
|
@ -15,7 +15,6 @@ export default function NavToggle({
|
|||
const transition = {
|
||||
transition: 'transform 0.3s ease, opacity 0.2s ease',
|
||||
};
|
||||
const [newUser] = useLocalStorage('newUser', true);
|
||||
|
||||
const rotationDegree = 15;
|
||||
const rotation = isHovering || !navVisible ? `${rotationDegree}deg` : '0deg';
|
||||
|
|
@ -33,46 +32,44 @@ export default function NavToggle({
|
|||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={onToggle}
|
||||
id={`toggle-${side}-nav`}
|
||||
aria-label={`toggle-${side === 'left' ? 'chat-history' : 'controls'}-nav`}
|
||||
>
|
||||
<span className="" data-state="closed">
|
||||
<div
|
||||
className="flex h-[72px] w-8 items-center justify-center"
|
||||
style={{ ...transition, opacity: isHovering ? 1 : 0.25 }}
|
||||
>
|
||||
<div className="flex h-6 w-6 flex-col items-center">
|
||||
{/* Top bar */}
|
||||
<div
|
||||
className="h-3 w-1 rounded-full bg-black dark:bg-white"
|
||||
style={{
|
||||
...transition,
|
||||
transform: `translateY(0.15rem) rotate(${topBarRotation}) translateZ(0px)`,
|
||||
}}
|
||||
/>
|
||||
{/* Bottom bar */}
|
||||
<div
|
||||
className="h-3 w-1 rounded-full bg-black dark:bg-white"
|
||||
style={{
|
||||
...transition,
|
||||
transform: `translateY(-0.15rem) rotate(${bottomBarRotation}) translateZ(0px)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TooltipAnchor
|
||||
side={side === 'right' ? 'left' : 'right'}
|
||||
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">
|
||||
<div
|
||||
className="flex h-[72px] w-8 items-center justify-center"
|
||||
style={{ ...transition, opacity: isHovering ? 1 : 0.25 }}
|
||||
>
|
||||
<div className="flex h-6 w-6 flex-col items-center">
|
||||
{/* Top bar */}
|
||||
<div
|
||||
className="h-3 w-1 rounded-full bg-black dark:bg-white"
|
||||
style={{
|
||||
...transition,
|
||||
transform: `translateY(0.15rem) rotate(${topBarRotation}) translateZ(0px)`,
|
||||
}}
|
||||
/>
|
||||
{/* Bottom bar */}
|
||||
<div
|
||||
className="h-3 w-1 rounded-full bg-black dark:bg-white"
|
||||
style={{
|
||||
...transition,
|
||||
transform: `translateY(-0.15rem) rotate(${bottomBarRotation}) translateZ(0px)`,
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
</div>
|
||||
</span>
|
||||
</TooltipAnchor>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ import { Search } from 'lucide-react';
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
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 { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
||||
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
|
||||
import { useLocalize, useNewConvo } from '~/hooks';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { NewChatIcon } from '~/components/svg';
|
||||
import store from '~/store';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
|
||||
const NewChatButtonIcon = ({ conversation }: { conversation: TConversation | null }) => {
|
||||
const searchQuery = useRecoilValue(store.searchQuery);
|
||||
|
|
@ -80,43 +80,36 @@ export default function NewChat({
|
|||
};
|
||||
|
||||
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="pb-0.5 last:pb-0" style={{ transform: 'none' }}>
|
||||
<a
|
||||
href="/"
|
||||
tabIndex={0}
|
||||
data-testid="nav-new-chat"
|
||||
onClick={clickHandler}
|
||||
className="group flex h-10 items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
>
|
||||
<NewChatButtonIcon conversation={conversation} />
|
||||
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-sm text-text-primary">
|
||||
{localize('com_ui_new_chat')}
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<span className="flex items-center" data-state="closed">
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
id="nav-new-chat-btn"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
className="text-text-primary"
|
||||
>
|
||||
<NewChatIcon className="size-5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={20}>
|
||||
{localize('com_ui_new_chat')}
|
||||
</TooltipContent>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<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' }}>
|
||||
<a
|
||||
href="/"
|
||||
tabIndex={0}
|
||||
data-testid="nav-new-chat"
|
||||
onClick={clickHandler}
|
||||
className="group flex h-10 items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
>
|
||||
<NewChatButtonIcon conversation={conversation} />
|
||||
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-sm text-text-primary">
|
||||
{localize('com_ui_new_chat')}
|
||||
</div>
|
||||
{subHeaders != null ? subHeaders : null}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<div className="flex gap-3">
|
||||
<span className="flex items-center" data-state="closed">
|
||||
<TooltipAnchor
|
||||
side="right"
|
||||
id="nav-new-chat-btn"
|
||||
aria-label="nav-new-chat-btn"
|
||||
description={localize('com_ui_new_chat')}
|
||||
className="text-text-primary"
|
||||
>
|
||||
<NewChatIcon className="size-5" />
|
||||
</TooltipAnchor>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{subHeaders != null ? subHeaders : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
|||
ref={ref}
|
||||
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',
|
||||
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 { MessageSquare, Link as LinkIcon } from 'lucide-react';
|
||||
import { Link as LinkIcon } from 'lucide-react';
|
||||
import type { SharedLinksResponse, TSharedLink } from 'librechat-data-provider';
|
||||
import { useDeleteSharedLinkMutation, useSharedLinksInfiniteQuery } from '~/data-provider';
|
||||
import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks';
|
||||
import { Spinner, TooltipAnchor, TrashIcon } from '~/components';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { cn } from '~/utils';
|
||||
import {
|
||||
Spinner,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
TrashIcon,
|
||||
} from '~/components';
|
||||
|
||||
function SharedLinkDeleteButton({
|
||||
shareId,
|
||||
|
|
@ -36,7 +29,7 @@ function SharedLinkDeleteButton({
|
|||
},
|
||||
});
|
||||
|
||||
const handleDelete = async (e: MouseEvent<HTMLButtonElement>) => {
|
||||
const handleDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
if (mutation.isLoading) {
|
||||
return;
|
||||
|
|
@ -46,36 +39,14 @@ function SharedLinkDeleteButton({
|
|||
setIsDeleting(false);
|
||||
};
|
||||
return (
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button id="delete-shared-link" aria-label="Delete shared link" onClick={handleDelete}>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{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>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_delete')}
|
||||
id="delete-shared-link"
|
||||
aria-label="Delete shared link"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
</TooltipAnchor>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -114,15 +85,12 @@ function ShareLinkRow({ sharedLink }: { sharedLink: TSharedLink }) {
|
|||
)}
|
||||
>
|
||||
{sharedLink.conversationId && (
|
||||
<>
|
||||
<SourceChatButton conversationId={sharedLink.conversationId} />
|
||||
<div className={cn('h-4 w-4 cursor-pointer', !isDeleting && 'hover:text-gray-300')}>
|
||||
<SharedLinkDeleteButton
|
||||
shareId={sharedLink.shareId}
|
||||
setIsDeleting={setIsDeleting}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div className={cn('cursor-pointer', !isDeleting && 'hover:text-gray-300')}>
|
||||
<SharedLinkDeleteButton
|
||||
shareId={sharedLink.shareId}
|
||||
setIsDeleting={setIsDeleting}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useLocalize } from '~/hooks';
|
||||
import { Dialog, DialogTrigger } from '~/components/ui';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { OGDialog, OGDialogTrigger } from '~/components/ui';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
|
||||
import ShareLinkTable from './SharedLinkTable';
|
||||
|
||||
|
|
@ -11,19 +11,19 @@ export default function SharedLinks() {
|
|||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_shared_links')}</div>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<OGDialog>
|
||||
<OGDialogTrigger asChild>
|
||||
<button className="btn btn-neutral relative ">
|
||||
{localize('com_nav_shared_links_manage')}
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogTemplate
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
title={localize('com_nav_shared_links')}
|
||||
className="max-w-[1000px]"
|
||||
showCancelButton={false}
|
||||
main={<ShareLinkTable />}
|
||||
/>
|
||||
</Dialog>
|
||||
</OGDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { useMemo, useState, useCallback } from 'react';
|
||||
import { MessageCircle, ArchiveRestore } from 'lucide-react';
|
||||
import { useConversationsInfiniteQuery } from '~/data-provider';
|
||||
import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks';
|
||||
import ArchiveButton from '~/components/Conversations/ConvoOptions/ArchiveButton';
|
||||
import DeleteButton from '~/components/Conversations/ConvoOptions/DeleteButton';
|
||||
import { ConversationListResponse } from 'librechat-data-provider';
|
||||
import { useAuthContext, useLocalize, useNavScrolling, useArchiveHandler } from '~/hooks';
|
||||
import { DeleteButton } from '~/components/Conversations/ConvoOptions';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { Spinner } from '~/components/svg';
|
||||
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 { isAuthenticated } = useAuthContext();
|
||||
const [showLoading, setShowLoading] = useState(false);
|
||||
const [conversationId, setConversationId] = useState<string | null>(null);
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useConversationsInfiniteQuery(
|
||||
{ pageNumber: '1', isArchived: true },
|
||||
|
|
@ -30,14 +31,9 @@ export default function ArchivedChatsTable({ className }: { className?: string }
|
|||
[data],
|
||||
);
|
||||
|
||||
const classProp: { className?: string } = {
|
||||
className: 'p-1 hover:text-black dark:hover:text-white',
|
||||
};
|
||||
if (className) {
|
||||
classProp.className = className;
|
||||
}
|
||||
const archiveHandler = useArchiveHandler(conversationId ?? '', false, moveToTop);
|
||||
|
||||
if (!conversations || conversations.length === 0) {
|
||||
if (!data || conversations.length === 0) {
|
||||
return <div className="text-gray-300">{localize('com_nav_archived_chats_empty')}</div>;
|
||||
}
|
||||
|
||||
|
|
@ -58,48 +54,52 @@ export default function ArchivedChatsTable({ className }: { className?: string }
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{conversations.map((conversation) => (
|
||||
<tr
|
||||
key={conversation.conversationId}
|
||||
className="border-b border-gray-200 text-sm font-normal dark:border-white/10"
|
||||
>
|
||||
<td className="flex items-center py-3 text-blue-800/70 dark:text-blue-500">
|
||||
<MessageCircle className="mr-1 h-5 w-5" />
|
||||
{conversation.title}
|
||||
</td>
|
||||
<td className="p-1">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-start dark:text-gray-200">
|
||||
{new Date(conversation.createdAt).toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</div>
|
||||
<div className="ml-auto mr-4 flex items-center justify-end gap-1 text-gray-400">
|
||||
{conversation.conversationId && (
|
||||
<>
|
||||
<ArchiveButton
|
||||
className="hover:text-black dark:hover:text-white"
|
||||
{conversations.map((conversation) => {
|
||||
if (!conversation.conversationId) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<tr
|
||||
key={conversation.conversationId}
|
||||
className="border-b border-gray-200 text-sm font-normal dark:border-white/10"
|
||||
>
|
||||
<td className="flex items-center py-3 text-blue-800/70 dark:text-blue-500">
|
||||
<MessageCircle className="mr-1 h-5 w-5" />
|
||||
{conversation.title}
|
||||
</td>
|
||||
<td className="p-1">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-start dark:text-gray-200">
|
||||
{new Date(conversation.createdAt).toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</div>
|
||||
<div className="ml-auto mr-4 flex items-center justify-end gap-1 text-gray-400">
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_unarchive')}
|
||||
onClick={() => {
|
||||
setConversationId(conversation.conversationId);
|
||||
archiveHandler();
|
||||
}}
|
||||
className="cursor-pointer hover:text-black dark:hover:text-white"
|
||||
>
|
||||
<ArchiveRestore className="size-4 hover:text-gray-300" />
|
||||
</TooltipAnchor>
|
||||
<div className="size-5 hover:text-gray-300">
|
||||
<DeleteButton
|
||||
conversationId={conversation.conversationId}
|
||||
retainView={moveToTop}
|
||||
shouldArchive={false}
|
||||
icon={<ArchiveRestore className="h-4 w-4 hover:text-gray-300" />}
|
||||
title={conversation.title ?? ''}
|
||||
/>
|
||||
<div className="h-5 w-5 hover:text-gray-300">
|
||||
<DeleteButton
|
||||
conversationId={conversation.conversationId}
|
||||
retainView={moveToTop}
|
||||
title={conversation.title ?? ''}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{(isFetchingNextPage || showLoading) && (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react';
|
|||
import { Plus, X } from 'lucide-react';
|
||||
import { Transition } from 'react-transition-group';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface AssistantConversationStartersProps {
|
||||
|
|
@ -106,25 +106,19 @@ const AssistantConversationStarters: React.FC<AssistantConversationStartersProps
|
|||
}}
|
||||
className="absolute right-1 top-1"
|
||||
>
|
||||
<TooltipProvider delayDuration={1000}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={handleAddStarter}
|
||||
disabled={hasReachedMax}
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{hasReachedMax
|
||||
? localize('com_assistants_max_starters_reached')
|
||||
: localize('com_ui_add')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipAnchor
|
||||
side="top"
|
||||
description={
|
||||
hasReachedMax
|
||||
? 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"
|
||||
onClick={handleAddStarter}
|
||||
disabled={hasReachedMax}
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
</TooltipAnchor>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
|
|
@ -143,22 +137,14 @@ const AssistantConversationStarters: React.FC<AssistantConversationStartersProps
|
|||
type="text"
|
||||
maxLength={64}
|
||||
/>
|
||||
<TooltipProvider delayDuration={1000}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<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"
|
||||
onClick={() => handleDeleteStarter(index)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_delete')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipAnchor
|
||||
side="top"
|
||||
description={localize('com_ui_delete')}
|
||||
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)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</TooltipAnchor>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { useState } from 'react';
|
|||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
import type { NavLink, NavProps } from '~/common';
|
||||
import { Accordion, AccordionItem, AccordionContent } from '~/components/ui/Accordion';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '~/components/ui/Tooltip';
|
||||
import { buttonVariants } from '~/components/ui/Button';
|
||||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function Nav({ links, isCollapsed, resize, defaultActive }: NavProps) {
|
||||
|
|
@ -30,42 +30,30 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr
|
|||
{links.map((link, index) => {
|
||||
const variant = getVariant(link);
|
||||
return isCollapsed ? (
|
||||
<Tooltip key={index} delayDuration={0}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
buttonVariants({ variant, size: 'icon' }),
|
||||
removeFocusOutlines,
|
||||
'h-9 w-9',
|
||||
variant === 'default'
|
||||
? 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white'
|
||||
: '',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick(e);
|
||||
setActive('');
|
||||
return;
|
||||
}
|
||||
setActive(link.id);
|
||||
resize && resize(25);
|
||||
}}
|
||||
>
|
||||
<link.icon className="h-4 w-4" />
|
||||
<span className="sr-only">{link.title}</span>
|
||||
</button>
|
||||
</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>
|
||||
<TooltipAnchor
|
||||
className={cn(
|
||||
buttonVariants({ variant, size: 'icon' }),
|
||||
removeFocusOutlines,
|
||||
'h-9 w-9 cursor-pointer',
|
||||
variant === 'default'
|
||||
? 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted bg-surface-terniary dark:hover:text-white'
|
||||
: '',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick(e);
|
||||
setActive('');
|
||||
return;
|
||||
}
|
||||
setActive(link.id);
|
||||
resize && resize(25);
|
||||
}}
|
||||
description={localize(link.title)}
|
||||
side="left"
|
||||
>
|
||||
<link.icon className="h-4 w-4" />
|
||||
<span className="sr-only">{link.title}</span>
|
||||
</TooltipAnchor>
|
||||
) : (
|
||||
<Accordion
|
||||
key={index}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ import {
|
|||
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
||||
import type { TEndpointsConfig } from 'librechat-data-provider';
|
||||
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 useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
||||
import NavToggle from '~/components/Nav/NavToggle';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import Switcher from './Switcher';
|
||||
|
|
@ -176,116 +175,109 @@ const SidePanel = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
onLayout={(sizes) => throttledSaveLayout(sizes)}
|
||||
className="transition-width relative h-full w-full flex-1 overflow-auto bg-white dark:bg-gray-800"
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
onLayout={(sizes) => throttledSaveLayout(sizes)}
|
||||
className="transition-width relative h-full w-full flex-1 overflow-auto bg-white dark:bg-gray-800"
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={currentLayout[0]}
|
||||
minSize={minSizeMain}
|
||||
order={1}
|
||||
id="messages-view"
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={currentLayout[0]}
|
||||
minSize={minSizeMain}
|
||||
order={1}
|
||||
id="messages-view"
|
||||
>
|
||||
{children}
|
||||
</ResizablePanel>
|
||||
{artifacts != null && (
|
||||
<>
|
||||
<ResizableHandleAlt withHandle className="ml-3 bg-border-medium dark:text-white" />
|
||||
<ResizablePanel
|
||||
defaultSize={currentLayout[1]}
|
||||
minSize={minSizeMain}
|
||||
order={2}
|
||||
id="artifacts-panel"
|
||||
>
|
||||
{artifacts}
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
<TooltipProvider delayDuration={400}>
|
||||
<Tooltip>
|
||||
<div
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
className="relative flex w-px items-center justify-center"
|
||||
>
|
||||
<NavToggle
|
||||
navVisible={!isCollapsed}
|
||||
isHovering={isHovering}
|
||||
onToggle={toggleNavVisible}
|
||||
setIsHovering={setIsHovering}
|
||||
className={cn(
|
||||
'fixed top-1/2',
|
||||
(isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse
|
||||
? 'mr-9'
|
||||
: 'mr-16',
|
||||
)}
|
||||
translateX={false}
|
||||
side="right"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && (
|
||||
<ResizableHandleAlt withHandle className="bg-transparent dark:text-white" />
|
||||
)}
|
||||
<ResizablePanel
|
||||
tagName="nav"
|
||||
id="controls-nav"
|
||||
order={artifacts != null ? 3 : 2}
|
||||
aria-label={localize('com_ui_controls')}
|
||||
role="region"
|
||||
collapsedSize={collapsedSize}
|
||||
defaultSize={currentLayout[currentLayout.length - 1]}
|
||||
collapsible={true}
|
||||
minSize={minSize}
|
||||
maxSize={40}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
transition: 'width 0.2s ease, visibility 0s linear 0.2s',
|
||||
}}
|
||||
onExpand={() => {
|
||||
setIsCollapsed(false);
|
||||
localStorage.setItem('react-resizable-panels:collapsed', 'false');
|
||||
}}
|
||||
onCollapse={() => {
|
||||
setIsCollapsed(true);
|
||||
localStorage.setItem('react-resizable-panels:collapsed', 'true');
|
||||
}}
|
||||
{children}
|
||||
</ResizablePanel>
|
||||
{artifacts != null && (
|
||||
<>
|
||||
<ResizableHandleAlt withHandle className="ml-3 bg-border-medium dark:text-white" />
|
||||
<ResizablePanel
|
||||
defaultSize={currentLayout[1]}
|
||||
minSize={minSizeMain}
|
||||
order={2}
|
||||
id="artifacts-panel"
|
||||
>
|
||||
{artifacts}
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
className="relative flex w-px items-center justify-center"
|
||||
>
|
||||
<NavToggle
|
||||
navVisible={!isCollapsed}
|
||||
isHovering={isHovering}
|
||||
onToggle={toggleNavVisible}
|
||||
setIsHovering={setIsHovering}
|
||||
className={cn(
|
||||
'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]',
|
||||
(isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) ||
|
||||
fullCollapse
|
||||
? 'hidden min-w-0'
|
||||
: 'opacity-100',
|
||||
'fixed top-1/2',
|
||||
(isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse
|
||||
? 'mr-9'
|
||||
: 'mr-16',
|
||||
)}
|
||||
>
|
||||
{interfaceConfig.modelSelect && (
|
||||
<div
|
||||
className={cn(
|
||||
'sticky left-0 right-0 top-0 z-[100] flex h-[52px] flex-wrap items-center justify-center bg-surface-primary-alt',
|
||||
isCollapsed ? 'h-[52px]' : 'px-2',
|
||||
)}
|
||||
>
|
||||
<Switcher
|
||||
isCollapsed={isCollapsed}
|
||||
endpointKeyProvided={keyProvided}
|
||||
endpoint={endpoint}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Nav
|
||||
resize={panelRef.current?.resize}
|
||||
isCollapsed={isCollapsed}
|
||||
defaultActive={defaultActive}
|
||||
links={Links}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</TooltipProvider>
|
||||
translateX={false}
|
||||
side="right"
|
||||
/>
|
||||
</div>
|
||||
{(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && (
|
||||
<ResizableHandleAlt withHandle className="bg-transparent dark:text-white" />
|
||||
)}
|
||||
<ResizablePanel
|
||||
tagName="nav"
|
||||
id="controls-nav"
|
||||
order={artifacts != null ? 3 : 2}
|
||||
aria-label={localize('com_ui_controls')}
|
||||
role="region"
|
||||
collapsedSize={collapsedSize}
|
||||
defaultSize={currentLayout[currentLayout.length - 1]}
|
||||
collapsible={true}
|
||||
minSize={minSize}
|
||||
maxSize={40}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
transition: 'width 0.2s ease, visibility 0s linear 0.2s',
|
||||
}}
|
||||
onExpand={() => {
|
||||
setIsCollapsed(false);
|
||||
localStorage.setItem('react-resizable-panels:collapsed', 'false');
|
||||
}}
|
||||
onCollapse={() => {
|
||||
setIsCollapsed(true);
|
||||
localStorage.setItem('react-resizable-panels:collapsed', 'true');
|
||||
}}
|
||||
className={cn(
|
||||
'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]',
|
||||
(isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse
|
||||
? 'hidden min-w-0'
|
||||
: 'opacity-100',
|
||||
)}
|
||||
>
|
||||
{interfaceConfig.modelSelect && (
|
||||
<div
|
||||
className={cn(
|
||||
'sticky left-0 right-0 top-0 z-[100] flex h-[52px] flex-wrap items-center justify-center bg-surface-primary-alt',
|
||||
isCollapsed ? 'h-[52px]' : 'px-2',
|
||||
)}
|
||||
>
|
||||
<Switcher
|
||||
isCollapsed={isCollapsed}
|
||||
endpointKeyProvided={keyProvided}
|
||||
endpoint={endpoint}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Nav
|
||||
resize={panelRef.current?.resize}
|
||||
isCollapsed={isCollapsed}
|
||||
defaultActive={defaultActive}
|
||||
links={Links}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
<button
|
||||
aria-label="Close right side panel"
|
||||
className={`nav-mask ${!isCollapsed ? 'active' : ''}`}
|
||||
|
|
|
|||
|
|
@ -3,25 +3,18 @@ import { cn } from '~/utils/';
|
|||
export default function Spinner({ className = 'm-auto', size = '1em' }) {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={cn(className, 'animate-spin text-center')}
|
||||
height={size}
|
||||
width={size}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn('animate-spin', className)}
|
||||
>
|
||||
<line x1="12" y1="2" x2="12" y2="6" />
|
||||
<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" />
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ function SelectDropDownPop({
|
|||
<button
|
||||
data-testid="select-dropdown-button"
|
||||
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',
|
||||
)}
|
||||
aria-label={`Select ${title}`}
|
||||
|
|
|
|||
|
|
@ -1,46 +1,65 @@
|
|||
import * as React from 'react';
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
import { cn } from '~/utils';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
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<
|
||||
React.ElementRef<typeof TooltipPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>
|
||||
>((props, ref) => <TooltipPrimitive.Trigger ref={ref} {...props} />);
|
||||
TooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName;
|
||||
export const TooltipAnchor = forwardRef<HTMLDivElement, TooltipAnchorProps>(function TooltipAnchor(
|
||||
{ description, side = 'top', role, ...props },
|
||||
ref,
|
||||
) {
|
||||
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<
|
||||
React.ElementRef<typeof TooltipPrimitive.Arrow>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Arrow>
|
||||
>((props, ref) => <TooltipPrimitive.Arrow ref={ref} {...props} />);
|
||||
TooltipArrow.displayName = TooltipPrimitive.Arrow.displayName;
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (role === 'button' && event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
(event.target as HTMLDivElement).click();
|
||||
}
|
||||
};
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className = '', forceMount, children, ...props }, ref) => (
|
||||
<TooltipPortal forceMount={forceMount}>
|
||||
<TooltipPrimitive.Content
|
||||
className={cn(
|
||||
'shadow-xs relative z-[1000] max-w-xs rounded-lg border border-gray-900/10 bg-gray-900 p-1 transition-opacity',
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<span className="flex items-center whitespace-pre-wrap px-2 py-1 text-center text-sm font-medium normal-case text-white">
|
||||
{children}
|
||||
<TooltipArrow className="TooltipArrow" />
|
||||
</span>
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPortal>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipPortal, TooltipContent, TooltipArrow, TooltipProvider };
|
||||
return (
|
||||
<Ariakit.TooltipProvider store={tooltip} hideTimeout={0}>
|
||||
<Ariakit.TooltipAnchor {...props} ref={ref} role={role} onKeyDown={handleKeyDown} />
|
||||
<AnimatePresence>
|
||||
{mounted && (
|
||||
<Ariakit.Tooltip
|
||||
gutter={4}
|
||||
alwaysVisible
|
||||
className="tooltip"
|
||||
render={
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x, y }}
|
||||
animate={{ opacity: 1, x: 0, y: 0 }}
|
||||
exit={{ opacity: 0, x, y }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Ariakit.TooltipArrow />
|
||||
{description}
|
||||
</Ariakit.Tooltip>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Ariakit.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 useGenerateConvo } from './useGenerateConvo';
|
||||
export { default as useConversations } from './useConversations';
|
||||
export { default as useArchiveHandler } from './useArchiveHandler';
|
||||
export { default as useDebouncedInput } from './useDebouncedInput';
|
||||
export { default as useBookmarkSuccess } from './useBookmarkSuccess';
|
||||
export { default as useNavigateToConvo } from './useNavigateToConvo';
|
||||
|
|
|
|||
|
|
@ -1,22 +1,13 @@
|
|||
import React from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
||||
import { useConversations, useLocalize, useNewConvo } from '~/hooks';
|
||||
import { useArchiveConversationMutation } from '~/data-provider';
|
||||
import useConversations from './useConversations';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import useLocalize from '../useLocalize';
|
||||
import useNewConvo from '../useNewConvo';
|
||||
|
||||
type ArchiveButtonProps = {
|
||||
children?: React.ReactNode;
|
||||
conversationId: string;
|
||||
retainView: () => void;
|
||||
shouldArchive: boolean;
|
||||
icon?: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function useArchiveHandler(
|
||||
export default function useArchiveHandler(
|
||||
conversationId: string,
|
||||
shouldArchive: boolean,
|
||||
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_add_to_conversation: 'Add to current conversation',
|
||||
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_conversation_id: 'No conversation ID found',
|
||||
com_ui_add_multi_conversation: 'Add multi-conversation',
|
||||
com_auth_error_login:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
com_auth_error_login_rl:
|
||||
|
|
|
|||
|
|
@ -207,12 +207,12 @@ export default {
|
|||
com_ui_shared_link_not_found: 'Link condiviso non trovato',
|
||||
com_ui_delete_conversation: 'Eliminare la chat?',
|
||||
com_ui_delete_confirm: 'Questo eliminerà',
|
||||
com_ui_rename: 'Rinominare',
|
||||
com_ui_archive: 'Arsip',
|
||||
com_ui_rename: 'Rinomina',
|
||||
com_ui_archive: 'Archivia',
|
||||
com_ui_archive_error: 'Errore durante l\'archiviazione della conversazione',
|
||||
com_ui_unarchive: 'Disarchivia',
|
||||
com_ui_unarchive_error: 'Impossibile disarchiviare la conversazione',
|
||||
com_ui_more_options: 'Pi',
|
||||
com_ui_more_options: 'Più',
|
||||
com_ui_delete_assistant_confirm:
|
||||
'Sei sicuro di voler eliminare questo Assistente? Questa operazione non può essere annullata.',
|
||||
com_ui_preview: 'Anteprima',
|
||||
|
|
@ -1361,11 +1361,11 @@ export const comparisons = {
|
|||
},
|
||||
com_ui_rename: {
|
||||
english: 'Rename',
|
||||
translated: 'Rinominare',
|
||||
translated: 'Rinomina',
|
||||
},
|
||||
com_ui_archive: {
|
||||
english: 'Archive',
|
||||
translated: 'Arsip',
|
||||
translated: 'Archivia',
|
||||
},
|
||||
com_ui_archive_error: {
|
||||
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**:
|
||||
- **english**: Rename
|
||||
- **translated**: Rinominare
|
||||
- **translated**: Rinomina
|
||||
|
||||
- **com_ui_archive**:
|
||||
- **english**: Archive
|
||||
- **translated**: Arsip
|
||||
- **translated**: Archivia
|
||||
|
||||
- **com_ui_archive_error**:
|
||||
- **english**: Failed to archive conversation
|
||||
|
|
|
|||
|
|
@ -1963,7 +1963,7 @@ button.scroll-convo {
|
|||
|
||||
.prose ol > li::marker,
|
||||
.markdown ol > li::marker {
|
||||
content: counter(list-counter) ". ";
|
||||
content: counter(list-counter) '. ';
|
||||
color: var(--tw-prose-counters);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
|
@ -1982,7 +1982,7 @@ button.scroll-convo {
|
|||
|
||||
.prose 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,
|
||||
|
|
@ -1998,7 +1998,7 @@ button.scroll-convo {
|
|||
|
||||
.prose 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 */
|
||||
|
|
@ -2288,6 +2288,27 @@ button.scroll-convo {
|
|||
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 {
|
||||
outline: none;
|
||||
}
|
||||
|
|
|
|||
53
package-lock.json
generated
53
package-lock.json
generated
|
|
@ -1223,7 +1223,7 @@
|
|||
"version": "v0.7.5-rc2",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.4.8",
|
||||
"@ariakit/react": "^0.4.11",
|
||||
"@codesandbox/sandpack-react": "^2.18.2",
|
||||
"@dicebear/collection": "^7.0.4",
|
||||
"@dicebear/core": "^7.0.4",
|
||||
|
|
@ -1258,6 +1258,7 @@
|
|||
"downloadjs": "^1.4.7",
|
||||
"export-from-json": "^1.7.2",
|
||||
"filenamify": "^6.0.0",
|
||||
"framer-motion": "^11.5.4",
|
||||
"html-to-image": "^1.11.11",
|
||||
"image-blob-reduce": "^4.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
|
|
@ -1398,16 +1399,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@ariakit/core": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.8.tgz",
|
||||
"integrity": "sha512-HQS+9CI7pMqqVlAt5bPGenT0/e65UxXY+PKtgU7Y+0UToBDBRolO5S9+UUSDm8OmJHSnq24owEGm1Mv28l5XCQ=="
|
||||
"version": "0.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.10.tgz",
|
||||
"integrity": "sha512-mX3EabQbfVh5uTjsTJ3+gjj7KGdTNhIN0qZHJd5Z2iPUnKl9NBy23Lgu6PEskpVsKAZ3proirjguD7U9fKMs/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ariakit/react": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.8.tgz",
|
||||
"integrity": "sha512-Bb1vOrp0X52hxi1wE9TEHjjZ/Y08tVq2ZH+RFDwRQB3g04uVwrrhnTccHepC6rsObrDpAOV3/YlJCi4k/lSUaQ==",
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.11.tgz",
|
||||
"integrity": "sha512-nLpPrmNcspqNhk4o+epsgeZfP1+Fkh4uIzNe5yrFkXolRkqHGKAxl4Hi82e0yxIBUbYbZIEwsZQQVceF1L6xrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ariakit/react-core": "0.4.8"
|
||||
"@ariakit/react-core": "0.4.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
|
@ -1419,11 +1422,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@ariakit/react-core": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.8.tgz",
|
||||
"integrity": "sha512-TzsddUWQwWYhrEVWHA/Gf7KCGx8rwFohAHfuljjqidKeZi2kUmuRAImCTG9oga34FWHFf4AdXQbBKclMNt0nrQ==",
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.11.tgz",
|
||||
"integrity": "sha512-i6KedWhjZkNC7tMEKO0eNjjq2HRPiHyGaBS2x2VaWwzBepoYtjyvxRXyqLJ3gaiNdlwckN1TZsRDfD+viy13IQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ariakit/core": "0.4.8",
|
||||
"@ariakit/core": "0.4.10",
|
||||
"@floating-ui/dom": "^1.0.0",
|
||||
"use-sync-external-store": "^1.2.0"
|
||||
},
|
||||
|
|
@ -20907,6 +20911,31 @@
|
|||
"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": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue