mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-09 12:08:50 +01:00
🪜 fix: Layering Conflicts and UX Polish (#11177)
* 🔧 refactor: Update z-index values for popover components
- Reduced z-index from 50 to 40 across various popover components including Artifacts, ArtifactsSubMenu, MCPSubMenu, CustomMenu, and others to ensure consistent layering and improve UI behavior.
- Adjusted related CSS styles in Dropdown.css and DropdownMenu.tsx to align with the new z-index values, enhancing overall component visibility and interaction.
* chore: remove string template for className concatenation in CustomMenu component
- Improved the readability of the className prop in the CustomMenu component by restructuring the concatenation of class names. This change enhances maintainability and clarity in the styling logic.
* refactor: Simplify button visibility logic in SiblingHeader component
- Updated the button rendering logic in the SiblingHeader component to improve clarity and maintainability. The button is now always rendered, with its visibility controlled by the disabled state based on messageId, agentId, and submission status, enhancing user experience during interactions.
* refactor: Update shift key handling in Conversation and ConvoOptions components
- Modified the handling of the `isShiftHeld` state in both the Conversation and ConvoOptions components to improve clarity and functionality. The logic now ensures that the shift key state is accurately reflected based on the active conversation status, enhancing user interaction during conversations.
- Cleaned up imports in ConvoOptions by removing the unused `useShiftKey` hook, streamlining the component's dependencies.
* refactor: Improve Escape key handling in OriginalDialog component
- Updated the Escape key handling logic to prevent closing the dialog when a tooltip or dropdown menu has focus. This change enhances accessibility by ensuring compliance with WCAG standards for dismissable tooltips.
- Simplified the focus checking mechanism by directly assessing the active element within dropdown menus and tooltips, improving code clarity and maintainability.
* chore: imports
* refactor: Enhance Escape key handling in OriginalDialog component
- Updated the Escape key handling logic to prevent closing the dialog when a trigger with an open popover is focused. This change improves accessibility and user experience by ensuring that the dialog remains open during interactions with popovers, dropdowns, and listboxes.
- Simplified the focus checking mechanism to include additional roles, enhancing the clarity and maintainability of the code.
* refactor: Add dropdownClassName prop to FilterPrompts component
- Enhanced the FilterPrompts component by introducing a new dropdownClassName prop, allowing for customizable styling of the dropdown element.
- Updated the PromptsView component to utilize the new prop, improving the flexibility of the FilterPrompts integration within the UI.
* refactor: Clean up imports and remove unused code in DashBreadcrumb component
- Streamlined the DashBreadcrumb component by removing commented-out imports and unused code, enhancing clarity and maintainability.
- Adjusted the import order for better organization and readability.
* refactor: Update z-index handling in Dropdown component
- Removed the z-index property from Dropdown.css to streamline styling.
- Adjusted the className in Dropdown.tsx to include a new z-40 class for consistent z-index management, enhancing UI layering and interaction.
* refactor: Enhance file type acceptance in AttachFileMenu and useDragHelpers
- Updated the AttachFileMenu component to accept additional image formats (.heif, .heic) alongside existing types, improving file upload flexibility.
- Modified the useDragHelpers hook to utilize inferMimeType for better file type detection, ensuring accurate handling of dragged files.
* refactor: Enhance FloatingThinkingBar with copy functionality
- Added a copy button to the FloatingThinkingBar component, allowing users to copy thoughts to the clipboard.
- Updated the tooltip descriptions for the expand/collapse and copy actions to improve user experience.
- Cleaned up imports and adjusted prop types for better clarity and maintainability.
* refactor: Enhance RunCode component with icon-only mode
- Updated the RunCode component to accept an `iconOnly` prop, allowing for a simplified button display that shows only the icon when desired.
- Adjusted the button rendering logic to improve user experience and maintainability.
- Cleaned up imports and ensured consistent styling in the FloatingCodeBar component.
This commit is contained in:
parent
1e74dc231f
commit
b1a2b96276
26 changed files with 233 additions and 164 deletions
|
|
@ -107,7 +107,7 @@ function Artifacts() {
|
|||
<Ariakit.Menu
|
||||
gutter={4}
|
||||
className={cn(
|
||||
'animate-popover-top-left z-50 flex min-w-[250px] flex-col rounded-xl',
|
||||
'animate-popover-top-left z-40 flex min-w-[250px] flex-col rounded-xl',
|
||||
'border border-border-light bg-surface-secondary shadow-lg',
|
||||
)}
|
||||
portal={true}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ const ArtifactsSubMenu = React.forwardRef<HTMLDivElement, ArtifactsSubMenuProps>
|
|||
portal={true}
|
||||
unmountOnHide={true}
|
||||
className={cn(
|
||||
'animate-popover-left z-50 ml-3 mt-6 flex min-w-[250px] flex-col rounded-xl',
|
||||
'animate-popover-left z-40 ml-3 mt-6 flex min-w-[250px] flex-col rounded-xl',
|
||||
'border border-border-light bg-surface-secondary shadow-lg',
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -94,13 +94,13 @@ const AttachFileMenu = ({
|
|||
}
|
||||
inputRef.current.value = '';
|
||||
if (fileType === 'image') {
|
||||
inputRef.current.accept = 'image/*';
|
||||
inputRef.current.accept = 'image/*,.heif,.heic';
|
||||
} else if (fileType === 'document') {
|
||||
inputRef.current.accept = '.pdf,application/pdf';
|
||||
} else if (fileType === 'image_document') {
|
||||
inputRef.current.accept = 'image/*,.pdf,application/pdf';
|
||||
inputRef.current.accept = 'image/*,.heif,.heic,.pdf,application/pdf';
|
||||
} else if (fileType === 'image_document_video_audio') {
|
||||
inputRef.current.accept = 'image/*,.pdf,application/pdf,video/*,audio/*';
|
||||
inputRef.current.accept = 'image/*,.heif,.heic,.pdf,application/pdf,video/*,audio/*';
|
||||
} else {
|
||||
inputRef.current.accept = '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ const MCPSubMenu = React.forwardRef<HTMLDivElement, MCPSubMenuProps>(
|
|||
unmountOnHide={true}
|
||||
aria-label={localize('com_ui_mcp_servers')}
|
||||
className={cn(
|
||||
'animate-popover-left z-50 ml-3 flex min-w-[260px] max-w-[320px] flex-col rounded-xl',
|
||||
'animate-popover-left z-40 ml-3 flex min-w-[260px] max-w-[320px] flex-col rounded-xl',
|
||||
'border border-border-light bg-presentation p-1.5 shadow-lg',
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ export const CustomMenu = React.forwardRef<HTMLDivElement, CustomMenuProps>(func
|
|||
unmountOnHide
|
||||
gutter={parent ? -4 : 4}
|
||||
className={cn(
|
||||
`${parent ? 'animate-popover-left ml-3' : 'animate-popover'} outline-none! z-50 flex max-h-[min(450px,var(--popover-available-height))] w-full`,
|
||||
parent ? 'animate-popover-left ml-3' : 'animate-popover',
|
||||
'outline-none! z-40 flex max-h-[min(450px,var(--popover-available-height))] w-full',
|
||||
'w-[var(--menu-width,auto)] min-w-[300px] flex-col overflow-auto rounded-xl border border-border-light',
|
||||
'bg-presentation px-3 py-2 text-sm text-text-primary shadow-lg',
|
||||
'max-w-[calc(100vw-4rem)] sm:max-h-[calc(65vh)] sm:max-w-[400px]',
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ const Reasoning = memo(({ reasoning, isLast }: ReasoningProps) => {
|
|||
isVisible={isBarVisible && isExpanded}
|
||||
isExpanded={isExpanded}
|
||||
onClick={handleClick}
|
||||
content={reasoningText}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useState, useMemo, memo, useCallback, useRef } from 'react';
|
||||
import { useState, useMemo, memo, useCallback, useRef, type MouseEvent } from 'react';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Clipboard, CheckMark, TooltipAnchor } from '@librechat/client';
|
||||
import { Lightbulb, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import type { MouseEvent, FocusEvent, FC } from 'react';
|
||||
import type { FocusEvent, FC } from 'react';
|
||||
import { showThinkingAtom } from '~/store/showThinking';
|
||||
import { fontSizeAtom } from '~/store/fontSize';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
|
@ -122,7 +122,7 @@ export const ThinkingButton = memo(
|
|||
);
|
||||
|
||||
/**
|
||||
* FloatingThinkingBar - Floating bar with expand/collapse button
|
||||
* FloatingThinkingBar - Floating bar with expand/collapse and copy buttons
|
||||
* Shows on hover/focus, positioned at bottom right of thinking content
|
||||
* Inspired by CodeBlock's FloatingCodeBar pattern
|
||||
*/
|
||||
|
|
@ -131,16 +131,36 @@ export const FloatingThinkingBar = memo(
|
|||
isVisible,
|
||||
isExpanded,
|
||||
onClick,
|
||||
content,
|
||||
}: {
|
||||
isVisible: boolean;
|
||||
isExpanded: boolean;
|
||||
onClick: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
content?: string;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const tooltipText = isExpanded
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
const handleCopy = useCallback(
|
||||
(e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
if (content) {
|
||||
navigator.clipboard.writeText(content);
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 2000);
|
||||
}
|
||||
},
|
||||
[content],
|
||||
);
|
||||
|
||||
const collapseTooltip = isExpanded
|
||||
? localize('com_ui_collapse_thoughts')
|
||||
: localize('com_ui_expand_thoughts');
|
||||
|
||||
const copyTooltip = isCopied
|
||||
? localize('com_ui_copied_to_clipboard')
|
||||
: localize('com_ui_copy_thoughts_to_clipboard');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -149,13 +169,13 @@ export const FloatingThinkingBar = memo(
|
|||
)}
|
||||
>
|
||||
<TooltipAnchor
|
||||
description={tooltipText}
|
||||
description={collapseTooltip}
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={isVisible ? 0 : -1}
|
||||
onClick={onClick}
|
||||
aria-label={tooltipText}
|
||||
aria-label={collapseTooltip}
|
||||
className={cn(
|
||||
'flex items-center justify-center rounded-lg bg-surface-secondary p-1.5 text-text-secondary-alt shadow-sm',
|
||||
'hover:bg-surface-hover hover:text-text-primary',
|
||||
|
|
@ -170,6 +190,30 @@ export const FloatingThinkingBar = memo(
|
|||
</button>
|
||||
}
|
||||
/>
|
||||
{content && (
|
||||
<TooltipAnchor
|
||||
description={copyTooltip}
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={isVisible ? 0 : -1}
|
||||
onClick={handleCopy}
|
||||
aria-label={copyTooltip}
|
||||
className={cn(
|
||||
'flex items-center justify-center rounded-lg bg-surface-secondary p-1.5 text-text-secondary-alt shadow-sm',
|
||||
'hover:bg-surface-hover hover:text-text-primary',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy',
|
||||
)}
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckMark className="h-[18px] w-[18px]" aria-hidden="true" />
|
||||
) : (
|
||||
<Clipboard size="18" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
@ -265,6 +309,7 @@ const Thinking: React.ElementType = memo(({ children }: { children: React.ReactN
|
|||
isVisible={isBarVisible && isExpanded}
|
||||
isExpanded={isExpanded}
|
||||
onClick={handleClick}
|
||||
content={textContent}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -118,23 +118,22 @@ export default function SiblingHeader({
|
|||
</div>
|
||||
<span className="truncate text-sm font-medium text-text-primary">{displayName}</span>
|
||||
</div>
|
||||
{messageId && agentId && !isSubmitting && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBranch}
|
||||
disabled={isSubmitting || branchMessage.isLoading}
|
||||
className={cn(
|
||||
'flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-md',
|
||||
'text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-primary',
|
||||
'focus:outline-none focus:ring-2 focus:ring-border-medium focus:ring-offset-1',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
)}
|
||||
aria-label={localize('com_ui_branch_message')}
|
||||
title={localize('com_ui_branch_message')}
|
||||
>
|
||||
<GitBranchPlus className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBranch}
|
||||
disabled={!messageId || !agentId || isSubmitting || branchMessage.isLoading}
|
||||
className={cn(
|
||||
'flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-md',
|
||||
'text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-primary',
|
||||
'focus:outline-none focus:ring-2 focus:ring-border-medium focus:ring-offset-1',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
(!messageId || !agentId || isSubmitting) && 'invisible',
|
||||
)}
|
||||
aria-label={localize('com_ui_branch_message')}
|
||||
title={localize('com_ui_branch_message')}
|
||||
>
|
||||
<GitBranchPlus className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ export default function Conversation({
|
|||
conversationId,
|
||||
isPopoverActive,
|
||||
setIsPopoverActive,
|
||||
isShiftHeld: isActiveConvo ? isShiftHeld : false,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -236,7 +237,7 @@ export default function Conversation({
|
|||
isPopoverActive || isActiveConvo
|
||||
? 'pointer-events-auto 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',
|
||||
(isPopoverActive || isActiveConvo) && (isShiftHeld ? 'max-w-[60px]' : 'max-w-[28px]'),
|
||||
!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
|
||||
// but not sure what its original purpose was, so leaving the property commented out until it can be cleared safe to delete.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
useGetStartupConfig,
|
||||
useArchiveConvoMutation,
|
||||
} from '~/data-provider';
|
||||
import { useLocalize, useNavigateToConvo, useNewConvo, useShiftKey } from '~/hooks';
|
||||
import { useLocalize, useNavigateToConvo, useNewConvo } from '~/hooks';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import DeleteButton from './DeleteButton';
|
||||
|
|
@ -28,6 +28,7 @@ function ConvoOptions({
|
|||
isPopoverActive,
|
||||
setIsPopoverActive,
|
||||
isActiveConvo,
|
||||
isShiftHeld = false,
|
||||
}: {
|
||||
conversationId: string | null;
|
||||
title: string | null;
|
||||
|
|
@ -36,10 +37,10 @@ function ConvoOptions({
|
|||
isPopoverActive: boolean;
|
||||
setIsPopoverActive: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isActiveConvo: boolean;
|
||||
isShiftHeld?: boolean;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const queryClient = useQueryClient();
|
||||
const isShiftHeld = useShiftKey();
|
||||
const { index } = useChatContext();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { navigateToConvo } = useNavigateToConvo(index);
|
||||
|
|
@ -253,7 +254,7 @@ function ConvoOptions({
|
|||
: 'opacity-0 focus:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100 data-[open]:opacity-100',
|
||||
);
|
||||
|
||||
if (isShiftHeld) {
|
||||
if (isShiftHeld && isActiveConvo && !isPopoverActive && !showShareDialog && !showDeleteDialog) {
|
||||
return (
|
||||
<div className="flex items-center gap-0.5">
|
||||
<button
|
||||
|
|
@ -350,6 +351,7 @@ export default memo(ConvoOptions, (prevProps, nextProps) => {
|
|||
prevProps.conversationId === nextProps.conversationId &&
|
||||
prevProps.title === nextProps.title &&
|
||||
prevProps.isPopoverActive === nextProps.isPopoverActive &&
|
||||
prevProps.isActiveConvo === nextProps.isActiveConvo
|
||||
prevProps.isActiveConvo === nextProps.isActiveConvo &&
|
||||
prevProps.isShiftHeld === nextProps.isShiftHeld
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ const FloatingCodeBar: React.FC<FloatingCodeBarProps> = React.memo(
|
|||
) : (
|
||||
<>
|
||||
{allowExecution === true && (
|
||||
<RunCode lang={lang} codeRef={codeRef} blockIndex={blockIndex} />
|
||||
<RunCode lang={lang} codeRef={codeRef} blockIndex={blockIndex} iconOnly />
|
||||
)}
|
||||
<TooltipAnchor
|
||||
description={isCopied ? localize('com_ui_copied') : localize('com_ui_copy_code')}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useMemo, useCallback, useEffect } from 'react';
|
|||
import debounce from 'lodash/debounce';
|
||||
import { TerminalSquareIcon } from 'lucide-react';
|
||||
import { Tools, AuthType } from 'librechat-data-provider';
|
||||
import { Spinner, useToastContext } from '@librechat/client';
|
||||
import { Spinner, TooltipAnchor, useToastContext } from '@librechat/client';
|
||||
import type { CodeBarProps } from '~/common';
|
||||
import { useVerifyAgentToolAuth, useToolCallMutation } from '~/data-provider';
|
||||
import ApiKeyDialog from '~/components/SidePanel/Agents/Code/ApiKeyDialog';
|
||||
|
|
@ -10,107 +10,125 @@ import { useLocalize, useCodeApiKeyForm } from '~/hooks';
|
|||
import { useMessageContext } from '~/Providers';
|
||||
import { cn, normalizeLanguage } from '~/utils';
|
||||
|
||||
const RunCode: React.FC<CodeBarProps> = React.memo(({ lang, codeRef, blockIndex }) => {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const execute = useToolCallMutation(Tools.execute_code, {
|
||||
onError: () => {
|
||||
showToast({ message: localize('com_ui_run_code_error'), status: 'error' });
|
||||
},
|
||||
});
|
||||
const RunCode: React.FC<CodeBarProps & { iconOnly?: boolean }> = React.memo(
|
||||
({ lang, codeRef, blockIndex, iconOnly = false }) => {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const execute = useToolCallMutation(Tools.execute_code, {
|
||||
onError: () => {
|
||||
showToast({ message: localize('com_ui_run_code_error'), status: 'error' });
|
||||
},
|
||||
});
|
||||
|
||||
const { messageId, conversationId, partIndex } = useMessageContext();
|
||||
const normalizedLang = useMemo(() => normalizeLanguage(lang), [lang]);
|
||||
const { data } = useVerifyAgentToolAuth(
|
||||
{ toolId: Tools.execute_code },
|
||||
{
|
||||
retry: 1,
|
||||
},
|
||||
);
|
||||
const authType = useMemo(() => data?.message ?? false, [data?.message]);
|
||||
const isAuthenticated = useMemo(() => data?.authenticated ?? false, [data?.authenticated]);
|
||||
const { methods, onSubmit, isDialogOpen, setIsDialogOpen, handleRevokeApiKey } =
|
||||
useCodeApiKeyForm({});
|
||||
const { messageId, conversationId, partIndex } = useMessageContext();
|
||||
const normalizedLang = useMemo(() => normalizeLanguage(lang), [lang]);
|
||||
const { data } = useVerifyAgentToolAuth(
|
||||
{ toolId: Tools.execute_code },
|
||||
{
|
||||
retry: 1,
|
||||
},
|
||||
);
|
||||
const authType = useMemo(() => data?.message ?? false, [data?.message]);
|
||||
const isAuthenticated = useMemo(() => data?.authenticated ?? false, [data?.authenticated]);
|
||||
const { methods, onSubmit, isDialogOpen, setIsDialogOpen, handleRevokeApiKey } =
|
||||
useCodeApiKeyForm({});
|
||||
|
||||
const handleExecute = useCallback(async () => {
|
||||
if (!isAuthenticated) {
|
||||
setIsDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
const codeString: string = codeRef.current?.textContent ?? '';
|
||||
if (
|
||||
typeof codeString !== 'string' ||
|
||||
codeString.length === 0 ||
|
||||
typeof normalizedLang !== 'string' ||
|
||||
normalizedLang.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const handleExecute = useCallback(async () => {
|
||||
if (!isAuthenticated) {
|
||||
setIsDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
const codeString: string = codeRef.current?.textContent ?? '';
|
||||
if (
|
||||
typeof codeString !== 'string' ||
|
||||
codeString.length === 0 ||
|
||||
typeof normalizedLang !== 'string' ||
|
||||
normalizedLang.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
execute.mutate({
|
||||
execute.mutate({
|
||||
partIndex,
|
||||
messageId,
|
||||
blockIndex,
|
||||
conversationId: conversationId ?? '',
|
||||
lang: normalizedLang,
|
||||
code: codeString,
|
||||
});
|
||||
}, [
|
||||
codeRef,
|
||||
execute,
|
||||
partIndex,
|
||||
messageId,
|
||||
blockIndex,
|
||||
conversationId: conversationId ?? '',
|
||||
lang: normalizedLang,
|
||||
code: codeString,
|
||||
});
|
||||
}, [
|
||||
codeRef,
|
||||
execute,
|
||||
partIndex,
|
||||
messageId,
|
||||
blockIndex,
|
||||
conversationId,
|
||||
normalizedLang,
|
||||
setIsDialogOpen,
|
||||
isAuthenticated,
|
||||
]);
|
||||
conversationId,
|
||||
normalizedLang,
|
||||
setIsDialogOpen,
|
||||
isAuthenticated,
|
||||
]);
|
||||
|
||||
const debouncedExecute = useMemo(
|
||||
() => debounce(handleExecute, 1000, { leading: true }),
|
||||
[handleExecute],
|
||||
);
|
||||
const debouncedExecute = useMemo(
|
||||
() => debounce(handleExecute, 1000, { leading: true }),
|
||||
[handleExecute],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedExecute.cancel();
|
||||
};
|
||||
}, [debouncedExecute]);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedExecute.cancel();
|
||||
};
|
||||
}, [debouncedExecute]);
|
||||
|
||||
if (typeof normalizedLang !== 'string' || normalizedLang.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (typeof normalizedLang !== 'string' || normalizedLang.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'ml-auto flex gap-2 rounded-sm px-2 py-1 hover:bg-gray-700 focus:bg-gray-700 focus:outline focus:outline-white',
|
||||
)}
|
||||
onClick={debouncedExecute}
|
||||
disabled={execute.isLoading}
|
||||
>
|
||||
const buttonContent = (
|
||||
<>
|
||||
{execute.isLoading ? (
|
||||
<Spinner className="animate-spin" size={18} />
|
||||
) : (
|
||||
<TerminalSquareIcon size={18} aria-hidden="true" />
|
||||
)}
|
||||
{localize('com_ui_run_code')}
|
||||
{!iconOnly && localize('com_ui_run_code')}
|
||||
</>
|
||||
);
|
||||
|
||||
const button = (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex items-center justify-center rounded-sm hover:bg-gray-700 focus:bg-gray-700 focus:outline focus:outline-white',
|
||||
iconOnly ? 'p-1.5' : 'ml-auto gap-2 px-2 py-1',
|
||||
)}
|
||||
onClick={debouncedExecute}
|
||||
disabled={execute.isLoading}
|
||||
aria-label={localize('com_ui_run_code')}
|
||||
>
|
||||
{buttonContent}
|
||||
</button>
|
||||
<ApiKeyDialog
|
||||
onSubmit={onSubmit}
|
||||
isOpen={isDialogOpen}
|
||||
register={methods.register}
|
||||
onRevoke={handleRevokeApiKey}
|
||||
onOpenChange={setIsDialogOpen}
|
||||
handleSubmit={methods.handleSubmit}
|
||||
isToolAuthenticated={isAuthenticated}
|
||||
isUserProvided={authType === AuthType.USER_PROVIDED}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{iconOnly ? (
|
||||
<TooltipAnchor description={localize('com_ui_run_code')} render={button} />
|
||||
) : (
|
||||
button
|
||||
)}
|
||||
<ApiKeyDialog
|
||||
onSubmit={onSubmit}
|
||||
isOpen={isDialogOpen}
|
||||
register={methods.register}
|
||||
onRevoke={handleRevokeApiKey}
|
||||
onOpenChange={setIsDialogOpen}
|
||||
handleSubmit={methods.handleSubmit}
|
||||
isToolAuthenticated={isAuthenticated}
|
||||
isUserProvided={authType === AuthType.USER_PROVIDED}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default RunCode;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import filenamify from 'filenamify';
|
||||
import { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
OGDialogTemplate,
|
||||
OGDialog,
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
Button,
|
||||
OGDialog,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
OGDialogTemplate,
|
||||
} from '@librechat/client';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { useLocalize, useExportConversation } from '~/hooks';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ import { usePromptGroupsContext } from '~/Providers';
|
|||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
export default function FilterPrompts({ className = '' }: { className?: string }) {
|
||||
export default function FilterPrompts({
|
||||
className = '',
|
||||
dropdownClassName = '',
|
||||
}: {
|
||||
className?: string;
|
||||
dropdownClassName?: string;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { name, setName, hasAccess, promptGroups } = usePromptGroupsContext();
|
||||
const { categories } = useCategories({ className: 'h-4 w-4', hasAccess });
|
||||
|
|
@ -94,7 +100,7 @@ export default function FilterPrompts({ className = '' }: { className?: string }
|
|||
value={categoryFilter || SystemCategories.ALL}
|
||||
onChange={onSelect}
|
||||
options={filterOptions}
|
||||
className="rounded-lg bg-transparent"
|
||||
className={cn('rounded-lg bg-transparent', dropdownClassName)}
|
||||
icon={<ListFilter className="h-4 w-4" />}
|
||||
label="Filter: "
|
||||
ariaLabel={localize('com_ui_filter_prompts')}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export default function PromptsView() {
|
|||
onClose={isSmallerScreen && isDetailView ? togglePanel : undefined}
|
||||
>
|
||||
<div className="mt-1 flex flex-row items-center justify-between px-2 md:px-2">
|
||||
<FilterPrompts />
|
||||
<FilterPrompts dropdownClassName="z-[100]" />
|
||||
</div>
|
||||
</GroupSidePanel>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
Tools,
|
||||
QueryKeys,
|
||||
Constants,
|
||||
inferMimeType,
|
||||
EToolResources,
|
||||
EModelEndpoint,
|
||||
mergeFileConfig,
|
||||
|
|
@ -118,7 +119,9 @@ export default function useDragHelpers() {
|
|||
}
|
||||
|
||||
/** Determine if dragged files are all images (enables the base image option) */
|
||||
const allImages = item.files.every((f) => f.type?.startsWith('image/'));
|
||||
const allImages = item.files.every((f) =>
|
||||
inferMimeType(f.name, f.type)?.startsWith('image/'),
|
||||
);
|
||||
|
||||
const shouldShowModal =
|
||||
allImages ||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,20 @@
|
|||
import { useMemo, useCallback } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { Sidebar } from '@librechat/client';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SystemRoles } from 'librechat-data-provider';
|
||||
import { ArrowLeft, MessageSquareQuote } from 'lucide-react';
|
||||
import { Sidebar } from '@librechat/client';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbSeparator,
|
||||
// BreadcrumbEllipsis,
|
||||
// DropdownMenu,
|
||||
// DropdownMenuItem,
|
||||
// DropdownMenuContent,
|
||||
// DropdownMenuTrigger,
|
||||
} from '@librechat/client';
|
||||
import { useLocalize, useCustomLink, useAuthContext } from '~/hooks';
|
||||
import AdvancedSwitch from '~/components/Prompts/AdvancedSwitch';
|
||||
// import { RightPanel } from '../../components/Prompts/RightPanel';
|
||||
import AdminSettings from '~/components/Prompts/AdminSettings';
|
||||
import { useDashboardContext } from '~/Providers';
|
||||
// import { PromptsEditorMode } from '~/common';
|
||||
import store from '~/store';
|
||||
|
||||
const promptsPathPattern = /prompts\/(?!new(?:\/|$)).*$/;
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export default function ComboboxComponent({
|
|||
aria-label={ariaLabel + 's'}
|
||||
position="popper"
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground relative z-50 max-h-[52vh] min-w-[8rem] overflow-hidden rounded-md border border-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-600',
|
||||
'bg-popover text-popover-foreground relative z-40 max-h-[52vh] min-w-[8rem] overflow-hidden rounded-md border border-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-600',
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
'bg-white dark:bg-gray-700',
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ function ControlCombobox({
|
|||
gutter={4}
|
||||
portal
|
||||
className={cn(
|
||||
'animate-popover z-50 overflow-hidden rounded-xl border border-border-light bg-surface-secondary shadow-lg',
|
||||
'animate-popover z-40 overflow-hidden rounded-xl border border-border-light bg-surface-secondary shadow-lg',
|
||||
)}
|
||||
style={{ width: isCollapsed ? '300px' : (buttonWidth ?? '300px') }}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
translate: 0 -0.5rem;
|
||||
margin-top: 4px;
|
||||
margin-right: -2px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.popover-animate {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ const Dropdown: React.FC<DropdownProps> = ({
|
|||
portal={portal}
|
||||
store={selectProps}
|
||||
className={cn(
|
||||
'popover-ui',
|
||||
'popover-ui z-40',
|
||||
sizeClasses,
|
||||
className,
|
||||
'max-h-[80vh] overflow-y-auto',
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ function DropdownMenuContent({
|
|||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'text-popover-foreground max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-border-light bg-surface-secondary p-1 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'text-popover-foreground max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) z-40 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-border-light bg-surface-secondary p-1 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -198,7 +198,7 @@ function DropdownMenuSubContent({
|
|||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
'text-popover-foreground origin-(--radix-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border border-border-medium bg-surface-secondary p-1 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'text-popover-foreground origin-(--radix-dropdown-menu-content-transform-origin) z-40 min-w-[8rem] overflow-hidden rounded-md border border-border-medium bg-surface-secondary p-1 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ const Menu: React.FC<MenuProps> = ({
|
|||
finalFocus={finalFocus}
|
||||
unmountOnHide={unmountOnHide}
|
||||
preserveTabOrder={preserveTabOrder}
|
||||
className={cn('popover-ui z-50', className)}
|
||||
className={cn('popover-ui z-40', className)}
|
||||
{...props}
|
||||
>
|
||||
{items
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ export default function MultiSelect<T extends string>({
|
|||
unmountOnHide
|
||||
finalFocus={selectRef}
|
||||
className={cn(
|
||||
'animate-popover z-50 flex max-h-[300px]',
|
||||
'animate-popover z-40 flex max-h-[300px]',
|
||||
'flex-col overflow-auto overscroll-contain rounded-xl',
|
||||
'bg-surface-secondary px-1.5 py-1 text-text-primary shadow-lg',
|
||||
'border border-border-light',
|
||||
|
|
|
|||
|
|
@ -96,33 +96,34 @@ const DialogContent = React.forwardRef<
|
|||
const depth = React.useContext(DialogDepthContext);
|
||||
const contentZIndex = 100 + (depth - 1) * 60;
|
||||
|
||||
/* Handle Escape key to prevent closing dialog if a tooltip or dropdown is open
|
||||
/* Handle Escape key to prevent closing dialog if a tooltip or dropdown has focus
|
||||
(this is a workaround in order to achieve WCAG compliance which requires
|
||||
that our tooltips be dismissable with Escape key) */
|
||||
const handleEscapeKeyDown = React.useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
const tooltips = document.querySelectorAll('.tooltip');
|
||||
const dropdownMenus = document.querySelectorAll('[role="menu"]');
|
||||
const activeElement = document.activeElement;
|
||||
|
||||
for (const tooltip of tooltips) {
|
||||
const computedStyle = window.getComputedStyle(tooltip);
|
||||
if (
|
||||
computedStyle.display !== 'none' &&
|
||||
computedStyle.visibility !== 'hidden' &&
|
||||
parseFloat(computedStyle.opacity) > 0
|
||||
) {
|
||||
// Check if active element is a trigger with an open popover (aria-expanded="true")
|
||||
if (activeElement?.getAttribute('aria-expanded') === 'true') {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a dropdown menu, listbox, or combobox has focus (focus is within it)
|
||||
const popoverElements = document.querySelectorAll(
|
||||
'[role="menu"], [role="listbox"], [role="combobox"]',
|
||||
);
|
||||
for (const popover of popoverElements) {
|
||||
if (popover.contains(activeElement)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const dropdownMenu of dropdownMenus) {
|
||||
const computedStyle = window.getComputedStyle(dropdownMenu);
|
||||
if (
|
||||
computedStyle.display !== 'none' &&
|
||||
computedStyle.visibility !== 'hidden' &&
|
||||
parseFloat(computedStyle.opacity) > 0
|
||||
) {
|
||||
// Check if a tooltip has focus (focus is within it)
|
||||
const tooltips = document.querySelectorAll('.tooltip');
|
||||
for (const tooltip of tooltips) {
|
||||
if (tooltip.contains(activeElement)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ const SelectContent = React.forwardRef<
|
|||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-600',
|
||||
'bg-popover text-popover-foreground relative z-40 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-600',
|
||||
position === 'popper'
|
||||
? 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1'
|
||||
: '',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue