mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-10 04:28:50 +01:00
* 🔧 refactor: Improve accessibility and styling in ChatGroupItem and FilterPrompts components * 🔧 fix: Add button type and keyboard accessibility to dropdown menu trigger in ChatGroupItem * 🔧 fix(757): Enhance accessibility by updating aria-labels and adding localization for prompt groups * 🔧 fix(618): Update version to 0.3.1 and enhance accessibility in InfoHoverCard component * 🔧 fix(618): Update aria-label in InfoHoverCard to use dynamic text prop for improved accessibility * 🔧 fix: Enhance accessibility by updating aria-labels and roles in Conversations components * 🔧 fix(620): Enhance accessibility by adding tabIndex to Tabs.Content components in ArtifactTabs, Settings, and Speech components * refactor: remove RevokeKeysButton component and update related components for accessibility - Deleted RevokeKeysButton component. - Updated SharedLinks and General components to use Label for accessibility. - Enhanced Personalization component with aria-labelledby and aria-describedby attributes. - Refactored ConversationModeSwitch to use ToggleSwitch for better state management. - Improved AutoSendTextSelector with local state management and accessibility attributes. - Replaced Switch components with ToggleSwitch in various Speech and TTS components for consistency. - Added aria-labelledby attributes to Dropdown components for better accessibility. - Updated translation.json to include new localization keys and improved existing ones. - Enhanced Slider component to support aria attributes for better accessibility. * 🔧 fix: Enhance user feedback for API key operations with success and error messages * 🔧 fix: Update aria-labels in Avatar component for improved localization and accessibility * 🔧 fix: Refactor handleFile and handleDrop functions for improved readability and maintainability
139 lines
5.3 KiB
TypeScript
139 lines
5.3 KiB
TypeScript
import { useState, useMemo, memo } from 'react';
|
|
import { Menu as MenuIcon, Edit as EditIcon, EarthIcon, TextSearch } from 'lucide-react';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuItem,
|
|
DropdownMenuGroup,
|
|
DropdownMenuContent,
|
|
DropdownMenuTrigger,
|
|
} from '@librechat/client';
|
|
import { PermissionBits } from 'librechat-data-provider';
|
|
import type { TPromptGroup } from 'librechat-data-provider';
|
|
import { useLocalize, useSubmitMessage, useCustomLink, useResourcePermissions } from '~/hooks';
|
|
import VariableDialog from '~/components/Prompts/Groups/VariableDialog';
|
|
import PreviewPrompt from '~/components/Prompts/PreviewPrompt';
|
|
import ListCard from '~/components/Prompts/Groups/ListCard';
|
|
import { detectVariables } from '~/utils';
|
|
|
|
function ChatGroupItem({
|
|
group,
|
|
instanceProjectId,
|
|
}: {
|
|
group: TPromptGroup;
|
|
instanceProjectId?: string;
|
|
}) {
|
|
const localize = useLocalize();
|
|
const { submitPrompt } = useSubmitMessage();
|
|
const [isPreviewDialogOpen, setPreviewDialogOpen] = useState(false);
|
|
const [isVariableDialogOpen, setVariableDialogOpen] = useState(false);
|
|
const onEditClick = useCustomLink<HTMLDivElement>(`/d/prompts/${group._id}`);
|
|
|
|
const groupIsGlobal = useMemo(
|
|
() => instanceProjectId != null && group.projectIds?.includes(instanceProjectId),
|
|
[group, instanceProjectId],
|
|
);
|
|
|
|
// Check permissions for the promptGroup
|
|
const { hasPermission } = useResourcePermissions('promptGroup', group._id || '');
|
|
const canEdit = hasPermission(PermissionBits.EDIT);
|
|
|
|
const onCardClick: React.MouseEventHandler<HTMLButtonElement> = () => {
|
|
const text = group.productionPrompt?.prompt;
|
|
if (!text?.trim()) {
|
|
return;
|
|
}
|
|
|
|
if (detectVariables(text)) {
|
|
setVariableDialogOpen(true);
|
|
return;
|
|
}
|
|
|
|
submitPrompt(text);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<ListCard
|
|
name={group.name}
|
|
category={group.category ?? ''}
|
|
onClick={onCardClick}
|
|
snippet={
|
|
typeof group.oneliner === 'string' && group.oneliner.length > 0
|
|
? group.oneliner
|
|
: (group.productionPrompt?.prompt ?? '')
|
|
}
|
|
>
|
|
<div className="flex flex-row items-center gap-2">
|
|
{groupIsGlobal === true && (
|
|
<EarthIcon className="icon-md text-green-400" aria-label="Global prompt group" />
|
|
)}
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger asChild>
|
|
<button
|
|
id={`prompt-actions-${group._id}`}
|
|
type="button"
|
|
aria-label={
|
|
localize('com_ui_sr_actions_menu', { 0: group.name }) +
|
|
' ' +
|
|
localize('com_ui_prompt')
|
|
}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
}}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.stopPropagation();
|
|
}
|
|
}}
|
|
className="z-50 inline-flex h-8 w-8 items-center justify-center rounded-lg border border-border-medium bg-transparent p-0 text-sm font-medium transition-all duration-300 ease-in-out hover:border-border-heavy hover:bg-surface-hover focus:border-border-heavy focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
|
>
|
|
<MenuIcon className="icon-md text-text-secondary" aria-hidden="true" />
|
|
</button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
id={`prompt-menu-${group._id}`}
|
|
aria-label={`Available actions for ${group.name}`}
|
|
className="z-50 w-fit rounded-xl"
|
|
collisionPadding={2}
|
|
align="start"
|
|
>
|
|
<DropdownMenuItem
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setPreviewDialogOpen(true);
|
|
}}
|
|
className="w-full cursor-pointer rounded-lg text-text-primary hover:bg-surface-hover focus:bg-surface-hover disabled:cursor-not-allowed"
|
|
>
|
|
<TextSearch className="mr-2 h-4 w-4 text-text-primary" aria-hidden="true" />
|
|
<span>{localize('com_ui_preview')}</span>
|
|
</DropdownMenuItem>
|
|
{canEdit && (
|
|
<DropdownMenuGroup>
|
|
<DropdownMenuItem
|
|
disabled={!canEdit}
|
|
className="cursor-pointer rounded-lg text-text-primary hover:bg-surface-hover focus:bg-surface-hover disabled:cursor-not-allowed"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onEditClick(e);
|
|
}}
|
|
>
|
|
<EditIcon className="mr-2 h-4 w-4 text-text-primary" aria-hidden="true" />
|
|
<span>{localize('com_ui_edit')}</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuGroup>
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</ListCard>
|
|
<PreviewPrompt group={group} open={isPreviewDialogOpen} onOpenChange={setPreviewDialogOpen} />
|
|
<VariableDialog
|
|
open={isVariableDialogOpen}
|
|
onClose={() => setVariableDialogOpen(false)}
|
|
group={group}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default memo(ChatGroupItem);
|