Revert "⌨️ feat: Add Shift-Key Shortcuts for Instant Conversation Actions (#10732)"

This reverts commit 41c0a96d39.
This commit is contained in:
Danny Avila 2025-12-15 17:02:16 -05:00
parent dcd9273700
commit 03ced7a894
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
4 changed files with 43 additions and 176 deletions

View file

@ -6,7 +6,7 @@ import { useToastContext, useMediaQuery } from '@librechat/client';
import type { TConversation } from 'librechat-data-provider'; import type { TConversation } from 'librechat-data-provider';
import { useUpdateConversationMutation } from '~/data-provider'; import { useUpdateConversationMutation } from '~/data-provider';
import EndpointIcon from '~/components/Endpoints/EndpointIcon'; import EndpointIcon from '~/components/Endpoints/EndpointIcon';
import { useNavigateToConvo, useLocalize, useShiftKey } from '~/hooks'; import { useNavigateToConvo, useLocalize } from '~/hooks';
import { useGetEndpointsQuery } from '~/data-provider'; import { useGetEndpointsQuery } from '~/data-provider';
import { NotificationSeverity } from '~/common'; import { NotificationSeverity } from '~/common';
import { ConvoOptions } from './ConvoOptions'; import { ConvoOptions } from './ConvoOptions';
@ -31,7 +31,6 @@ export default function Conversation({ conversation, retainView, toggleNav }: Co
const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? ''); const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? '');
const activeConvos = useRecoilValue(store.allConversationsSelector); const activeConvos = useRecoilValue(store.allConversationsSelector);
const isSmallScreen = useMediaQuery('(max-width: 768px)'); const isSmallScreen = useMediaQuery('(max-width: 768px)');
const isShiftHeld = useShiftKey();
const { conversationId, title = '' } = conversation; const { conversationId, title = '' } = conversation;
const [titleInput, setTitleInput] = useState(title || ''); const [titleInput, setTitleInput] = useState(title || '');
@ -195,9 +194,8 @@ export default function Conversation({ conversation, retainView, toggleNav }: Co
className={cn( className={cn(
'mr-2 flex origin-left', 'mr-2 flex origin-left',
isPopoverActive || isActiveConvo isPopoverActive || isActiveConvo
? 'pointer-events-auto scale-x-100 opacity-100' ? 'pointer-events-auto max-w-[28px] scale-x-100 opacity-100'
: 'pointer-events-none max-w-0 scale-x-0 opacity-0 group-focus-within:pointer-events-auto group-focus-within:max-w-[60px] group-focus-within:scale-x-100 group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:max-w-[60px] group-hover:scale-x-100 group-hover:opacity-100', : 'pointer-events-none max-w-0 scale-x-0 opacity-0 group-focus-within:pointer-events-auto group-focus-within:max-w-[28px] group-focus-within:scale-x-100 group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:max-w-[28px] group-hover:scale-x-100 group-hover:opacity-100',
(isPopoverActive || isActiveConvo) && (isShiftHeld ? 'max-w-[60px]' : 'max-w-[28px]'),
)} )}
// Removing aria-hidden to fix accessibility issue: ARIA hidden element must not be focusable or contain focusable elements // Removing aria-hidden to fix accessibility issue: ARIA hidden element must not be focusable or contain focusable elements
// but not sure what its original purpose was, so leaving the property commented out until it can be cleared safe to delete. // but not sure what its original purpose was, so leaving the property commented out until it can be cleared safe to delete.

View file

@ -1,19 +1,15 @@
import { useState, useId, useRef, memo, useCallback, useMemo } from 'react'; import { useState, useId, useRef, memo, useCallback, useMemo } from 'react';
import * as Ariakit from '@ariakit/react'; import * as Ariakit from '@ariakit/react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { QueryKeys } from 'librechat-data-provider';
import { useQueryClient } from '@tanstack/react-query';
import { DropdownPopup, Spinner, useToastContext } from '@librechat/client'; import { DropdownPopup, Spinner, useToastContext } from '@librechat/client';
import { Ellipsis, Share2, CopyPlus, Archive, Pen, Trash } from 'lucide-react'; import { Ellipsis, Share2, CopyPlus, Archive, Pen, Trash } from 'lucide-react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import type { TMessage } from 'librechat-data-provider';
import { import {
useDuplicateConversationMutation, useDuplicateConversationMutation,
useDeleteConversationMutation,
useGetStartupConfig, useGetStartupConfig,
useArchiveConvoMutation, useArchiveConvoMutation,
} from '~/data-provider'; } from '~/data-provider';
import { useLocalize, useNavigateToConvo, useNewConvo, useShiftKey } from '~/hooks'; import { useLocalize, useNavigateToConvo, useNewConvo } from '~/hooks';
import { NotificationSeverity } from '~/common'; import { NotificationSeverity } from '~/common';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import DeleteButton from './DeleteButton'; import DeleteButton from './DeleteButton';
@ -38,8 +34,6 @@ function ConvoOptions({
isActiveConvo: boolean; isActiveConvo: boolean;
}) { }) {
const localize = useLocalize(); const localize = useLocalize();
const queryClient = useQueryClient();
const isShiftHeld = useShiftKey();
const { index } = useChatContext(); const { index } = useChatContext();
const { data: startupConfig } = useGetStartupConfig(); const { data: startupConfig } = useGetStartupConfig();
const { navigateToConvo } = useNavigateToConvo(index); const { navigateToConvo } = useNavigateToConvo(index);
@ -58,28 +52,6 @@ function ConvoOptions({
const archiveConvoMutation = useArchiveConvoMutation(); const archiveConvoMutation = useArchiveConvoMutation();
const deleteMutation = useDeleteConversationMutation({
onSuccess: () => {
if (currentConvoId === conversationId || currentConvoId === 'new') {
newConversation();
navigate('/c/new', { replace: true });
}
retainView();
showToast({
message: localize('com_ui_convo_delete_success'),
severity: NotificationSeverity.SUCCESS,
showIcon: true,
});
},
onError: () => {
showToast({
message: localize('com_ui_convo_delete_error'),
severity: NotificationSeverity.ERROR,
showIcon: true,
});
},
});
const duplicateConversation = useDuplicateConversationMutation({ const duplicateConversation = useDuplicateConversationMutation({
onSuccess: (data) => { onSuccess: (data) => {
navigateToConvo(data.conversation); navigateToConvo(data.conversation);
@ -105,7 +77,6 @@ function ConvoOptions({
const isDuplicateLoading = duplicateConversation.isLoading; const isDuplicateLoading = duplicateConversation.isLoading;
const isArchiveLoading = archiveConvoMutation.isLoading; const isArchiveLoading = archiveConvoMutation.isLoading;
const isDeleteLoading = deleteMutation.isLoading;
const shareHandler = useCallback(() => { const shareHandler = useCallback(() => {
setShowShareDialog(true); setShowShareDialog(true);
@ -115,70 +86,47 @@ function ConvoOptions({
setShowDeleteDialog(true); setShowDeleteDialog(true);
}, []); }, []);
const handleInstantDelete = useCallback( const handleArchiveClick = useCallback(async () => {
(e: MouseEvent) => { const convoId = conversationId ?? '';
e.stopPropagation(); if (!convoId) {
const convoId = conversationId ?? ''; return;
if (!convoId) { }
return;
}
const messages = queryClient.getQueryData<TMessage[]>([QueryKeys.messages, convoId]); archiveConvoMutation.mutate(
const thread_id = messages?.[messages.length - 1]?.thread_id; { conversationId: convoId, isArchived: true },
const endpoint = messages?.[messages.length - 1]?.endpoint; {
onSuccess: () => {
deleteMutation.mutate({ conversationId: convoId, thread_id, endpoint, source: 'button' }); setAnnouncement(localize('com_ui_convo_archived'));
}, setTimeout(() => {
[conversationId, deleteMutation, queryClient], setAnnouncement('');
); }, 10000);
if (currentConvoId === convoId || currentConvoId === 'new') {
const handleArchiveClick = useCallback( newConversation();
async (e?: MouseEvent) => { navigate('/c/new', { replace: true });
e?.stopPropagation(); }
const convoId = conversationId ?? ''; retainView();
if (!convoId) { setIsPopoverActive(false);
return;
}
archiveConvoMutation.mutate(
{ conversationId: convoId, isArchived: true },
{
onSuccess: () => {
setAnnouncement(localize('com_ui_convo_archived'));
setTimeout(() => {
setAnnouncement('');
}, 10000);
if (currentConvoId === convoId || currentConvoId === 'new') {
newConversation();
navigate('/c/new', { replace: true });
}
retainView();
setIsPopoverActive(false);
},
onError: () => {
showToast({
message: localize('com_ui_archive_error'),
severity: NotificationSeverity.ERROR,
showIcon: true,
});
},
}, },
); onError: () => {
}, showToast({
[ message: localize('com_ui_archive_error'),
conversationId, severity: NotificationSeverity.ERROR,
currentConvoId, showIcon: true,
archiveConvoMutation, });
navigate, },
newConversation, },
retainView, );
setIsPopoverActive, }, [
showToast, conversationId,
localize, currentConvoId,
], archiveConvoMutation,
); navigate,
newConversation,
retainView,
setIsPopoverActive,
showToast,
localize,
]);
const handleDuplicateClick = useCallback(() => { const handleDuplicateClick = useCallback(() => {
duplicateConversation.mutate({ duplicateConversation.mutate({
@ -250,44 +198,6 @@ function ConvoOptions({
], ],
); );
const buttonClassName = cn(
'inline-flex h-7 w-7 items-center justify-center rounded-md border-none p-0 text-sm font-medium ring-ring-primary transition-all duration-200 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50',
isActiveConvo === true || isPopoverActive
? 'opacity-100'
: 'opacity-0 focus:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100 data-[open]:opacity-100',
);
if (isShiftHeld) {
return (
<div className="flex items-center gap-0.5">
<button
aria-label={localize('com_ui_archive')}
className={cn(buttonClassName, 'hover:bg-surface-hover')}
onClick={handleArchiveClick}
disabled={isArchiveLoading}
>
{isArchiveLoading ? (
<Spinner className="size-4" />
) : (
<Archive className="icon-md text-text-secondary" aria-hidden={true} />
)}
</button>
<button
aria-label={localize('com_ui_delete')}
className={cn(buttonClassName, 'hover:bg-surface-hover')}
onClick={handleInstantDelete}
disabled={isDeleteLoading}
>
{isDeleteLoading ? (
<Spinner className="size-4" />
) : (
<Trash className="icon-md text-text-secondary" aria-hidden={true} />
)}
</button>
</div>
);
}
return ( return (
<> <>
<span className="sr-only" aria-live="polite" aria-atomic="true"> <span className="sr-only" aria-live="polite" aria-atomic="true">

View file

@ -1,2 +1 @@
export * from './useLazyEffect'; export * from './useLazyEffect';
export { default as useShiftKey } from './useShiftKey';

View file

@ -1,40 +0,0 @@
import { useState, useEffect } from 'react';
/**
* Hook to track whether the shift key is currently being held down
* @returns boolean indicating if shift key is pressed
*/
export default function useShiftKey(): boolean {
const [isShiftHeld, setIsShiftHeld] = useState(false);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Shift') {
setIsShiftHeld(true);
}
};
const handleKeyUp = (e: KeyboardEvent) => {
if (e.key === 'Shift') {
setIsShiftHeld(false);
}
};
// Reset shift state when window loses focus
const handleBlur = () => {
setIsShiftHeld(false);
};
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);
window.addEventListener('blur', handleBlur);
return () => {
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keyup', handleKeyUp);
window.removeEventListener('blur', handleBlur);
};
}, []);
return isShiftHeld;
}