Skip to content

Commit

Permalink
Merge upstream into jm-production (#19)
Browse files Browse the repository at this point in the history
* 🙌 a11y: Accessibility Improvements (danny-avila#4978)

* 🔃 fix: Safeguard against null token in SSE refresh token handling

* 🔃 fix: Update import path for AnnounceOptions in LiveAnnouncer component

* 🔃 a11y: Add aria-live attribute for accessibility in error messages

* fix: prevent double screen reader notification for toast

* 🔃 a11y: Enhance accessibility for main menus and buttons with ARIA roles and labels

* refactor: better alt text for logo on login page danny-avila#4095

* refactor: remove unused import for DropdownNoState in Voices component

* fix: Focus management issue in the Export Options Modal danny-avila#4100

* 🐛 fix: Enforced Model Spec Icons/Labels and Agent Descriptions (danny-avila#4979)

* fix: Previous convos missing model spec info when enforce is set to `true` danny-avila#4749

* refactor: Include description field in agent list response

* 🔧 fix: Add modelLabel to OpenAIClient and PluginsClient options (danny-avila#4995)

---------

Co-authored-by: Danny Avila <[email protected]>
  • Loading branch information
jmaddington and danny-avila authored Dec 15, 2024
1 parent ec27ff7 commit dd379a2
Show file tree
Hide file tree
Showing 23 changed files with 110 additions and 26 deletions.
1 change: 1 addition & 0 deletions api/app/clients/OpenAIClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ class OpenAIClient extends BaseClient {
promptPrefix: this.options.promptPrefix,
resendFiles: this.options.resendFiles,
imageDetail: this.options.imageDetail,
modelLabel: this.options.modelLabel,
iconURL: this.options.iconURL,
greeting: this.options.greeting,
spec: this.options.spec,
Expand Down
1 change: 1 addition & 0 deletions api/app/clients/PluginsClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class PluginsClient extends OpenAIClient {
return {
artifacts: this.options.artifacts,
chatGptLabel: this.options.chatGptLabel,
modelLabel: this.options.modelLabel,
promptPrefix: this.options.promptPrefix,
tools: this.options.tools,
...this.modelOptions,
Expand Down
1 change: 1 addition & 0 deletions api/app/clients/specs/OpenAIClient.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ describe('OpenAIClient', () => {
it('should return the correct save options', () => {
const options = client.getSaveOptions();
expect(options).toHaveProperty('chatGptLabel');
expect(options).toHaveProperty('modelLabel');
expect(options).toHaveProperty('promptPrefix');
});
});
Expand Down
1 change: 1 addition & 0 deletions api/models/Agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ const getListAgents = async (searchParameter) => {
avatar: 1,
author: 1,
projectIds: 1,
description: 1,
isCollaborative: 1,
}).lean()
).map((agent) => {
Expand Down
4 changes: 4 additions & 0 deletions api/server/middleware/buildEndpointOption.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ async function buildEndpointOption(req, res, next) {
}

try {
currentModelSpec.preset.spec = spec;
if (currentModelSpec.iconURL != null && currentModelSpec.iconURL !== '') {
currentModelSpec.preset.iconURL = currentModelSpec.iconURL;
}
parsedBody = parseCompactConvo({
endpoint,
endpointType,
Expand Down
2 changes: 1 addition & 1 deletion client/src/a11y/LiveAnnouncer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// client/src/a11y/LiveAnnouncer.tsx
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import type { AnnounceOptions } from '~/Providers/AnnouncerContext';
import type { AnnounceOptions } from '~/common';
import AnnouncerContext from '~/Providers/AnnouncerContext';
import useLocalize from '~/hooks/useLocalize';
import Announcer from './Announcer';
Expand Down
1 change: 0 additions & 1 deletion client/src/components/Audio/Voices.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { useRecoilState } from 'recoil';
import type { Option } from '~/common';
import DropdownNoState from '~/components/ui/DropdownNoState';
import { useLocalize, useTTSBrowser, useTTSEdge, useTTSExternal } from '~/hooks';
import { Dropdown } from '~/components/ui';
import { logger } from '~/utils';
Expand Down
16 changes: 11 additions & 5 deletions client/src/components/Auth/AuthLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import Footer from './Footer';
const ErrorRender = ({ children }: { children: React.ReactNode }) => (
<div className="mt-16 flex justify-center">
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
aria-live="assertive"
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
>
{children}
</div>
Expand All @@ -36,8 +37,9 @@ function AuthLayout({
}) {
const localize = useLocalize();

const hasStartupConfigError = startupConfigError !== null && startupConfigError !== undefined;
const DisplayError = () => {
if (startupConfigError !== null && startupConfigError !== undefined) {
if (hasStartupConfigError) {
return <ErrorRender>{localize('com_auth_error_login_server')}</ErrorRender>;
} else if (error === 'com_auth_error_invalid_reset_token') {
return (
Expand All @@ -49,7 +51,7 @@ function AuthLayout({
{localize('com_auth_to_try_again')}
</ErrorRender>
);
} else if (error) {
} else if (error != null && error) {
return <ErrorRender>{localize(error)}</ErrorRender>;
}
return null;
Expand All @@ -60,7 +62,11 @@ function AuthLayout({
<Banner />
<BlinkAnimation active={isFetching}>
<div className="mt-6 h-10 w-full bg-cover">
<img src="/assets/logo.svg" className="h-full w-full object-contain" alt="Logo" />
<img
src="/assets/logo.svg"
className="h-full w-full object-contain"
alt={localize('com_ui_logo', startupConfig?.appTitle ?? 'LibreChat')}
/>
</div>
</BlinkAnimation>
<DisplayError />
Expand All @@ -70,7 +76,7 @@ function AuthLayout({

<div className="flex flex-grow items-center justify-center">
<div className="w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
{!startupConfigError && !isFetching && (
{!hasStartupConfigError && !isFetching && (
<h1
className="mb-4 text-center text-3xl font-semibold text-black dark:text-white"
style={{ userSelect: 'none' }}
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Auth/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export const ErrorMessage = ({ children }: { children: React.ReactNode }) => (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
aria-live="assertive"
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
>
{children}
</div>
Expand Down
9 changes: 6 additions & 3 deletions client/src/components/Chat/ExportAndShareMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useState, useId } from 'react';
import { useState, useId, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import * as Ariakit from '@ariakit/react';
import { Upload, Share2 } from 'lucide-react';
import { ShareButton } from '~/components/Conversations/ConvoOptions';
import { useMediaQuery, useLocalize } from '~/hooks';
import ExportModal from '~/components/Nav/ExportConversation/ExportModal';
import { DropdownPopup } from '~/components/ui';
import { ExportModal } from '../Nav';
import store from '~/store';

export default function ExportAndShareMenu({
Expand All @@ -19,6 +19,7 @@ export default function ExportAndShareMenu({
const [showShareDialog, setShowShareDialog] = useState(false);

const menuId = useId();
const exportButtonRef = useRef<HTMLButtonElement>(null);
const isSmallScreen = useMediaQuery('(max-width: 768px)');
const conversation = useRecoilValue(store.conversationByIndex(0));

Expand Down Expand Up @@ -68,6 +69,7 @@ export default function ExportAndShareMenu({
setIsOpen={setIsPopoverActive}
trigger={
<Ariakit.MenuButton
ref={exportButtonRef}
id="export-menu-button"
aria-label="Export options"
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"
Expand All @@ -91,7 +93,8 @@ export default function ExportAndShareMenu({
open={showExports}
onOpenChange={onOpenChange}
conversation={conversation}
aria-label="Export conversation modal"
triggerRef={exportButtonRef}
aria-label={localize('com_ui_export_convo_modal')}
/>
)}
</>
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/Chat/Menus/EndpointsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import type { FC } from 'react';
import { useChatContext, useAgentsMapContext, useAssistantsMapContext } from '~/Providers';
import { mapEndpoints, getEntity } from '~/utils';
import EndpointItems from './Endpoints/MenuItems';
import useLocalize from '~/hooks/useLocalize';
import TitleButton from './UI/TitleButton';

const EndpointsMenu: FC = () => {
const { data: endpoints = [] } = useGetEndpointsQuery({
select: mapEndpoints,
});

const localize = useLocalize();
const agentsMap = useAgentsMapContext();
const assistantMap = useAssistantsMapContext();
const { conversation } = useChatContext();
Expand Down Expand Up @@ -51,6 +53,9 @@ const EndpointsMenu: FC = () => {
<Content
side="bottom"
align="start"
role="listbox"
id="llm-endpoint-menu"
aria-label={localize('com_ui_endpoints_available')}
className="mt-2 max-h-[65vh] min-w-[340px] overflow-y-auto rounded-lg border border-border-light bg-header-primary text-text-primary shadow-lg lg:max-h-[75vh]"
>
<EndpointItems endpoints={endpoints} selected={endpoint} />
Expand Down
11 changes: 10 additions & 1 deletion client/src/components/Chat/Menus/Models/MenuButton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import { Trigger } from '@radix-ui/react-popover';
import type { TModelSpec, TEndpointsConfig } from 'librechat-data-provider';
import { useLocalize } from '~/hooks';
Expand All @@ -20,6 +21,8 @@ export default function MenuButton({
endpointsConfig: TEndpointsConfig;
}) {
const localize = useLocalize();
const [isExpanded, setIsExpanded] = useState(false);

return (
<Trigger asChild>
<button
Expand All @@ -28,7 +31,13 @@ export default function MenuButton({
className,
)}
type="button"
aria-label={`Select ${primaryText}`}
aria-label={localize('com_ui_llm_menu')}
role="combobox"
aria-haspopup="listbox"
aria-expanded={isExpanded}
aria-controls="llm-menu"
aria-activedescendant={isExpanded ? 'selected-llm' : undefined}
onClick={() => setIsExpanded(!isExpanded)}
>
{selected && selected.showIconInHeader === true && (
<SpecIcon currentSpec={selected} endpointsConfig={endpointsConfig} />
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/Chat/Menus/Models/ModelSpec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ const MenuItem: FC<MenuItemProps> = ({
return (
<>
<div
role="menuitem"
id={selected ? 'selected-llm' : undefined}
role="option"
aria-selected={selected}
className="group m-1.5 flex cursor-pointer gap-2 rounded px-1 py-2.5 !pr-3 text-sm !opacity-100 hover:bg-black/5 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-white/5"
tabIndex={0}
{...rest}
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Chat/Menus/Models/ModelSpecs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import MenuSeparator from '~/components/Chat/Menus/UI/MenuSeparator';
import ModelSpec from './ModelSpec';

const ModelSpecs: FC<{
specs?: TModelSpec[];
specs?: Array<TModelSpec | undefined>;
selected?: TModelSpec;
setSelected?: (spec: TModelSpec) => void;
endpointsConfig: TEndpointsConfig;
}> = ({ specs = [], selected, setSelected = () => ({}), endpointsConfig }) => {
return (
<>
{specs &&
{specs.length &&
specs.map((spec, i) => {
if (!spec) {
return null;
Expand Down
6 changes: 5 additions & 1 deletion client/src/components/Chat/Menus/Models/ModelSpecsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type { TModelSpec, TConversation, TEndpointsConfig } from 'librechat-data-provider';
import { useChatContext, useAssistantsMapContext } from '~/Providers';
import { useDefaultConvo, useNewConvo, useLocalize } from '~/hooks';
import { getConvoSwitchLogic, getModelSpecIconURL } from '~/utils';
import { useDefaultConvo, useNewConvo } from '~/hooks';
import MenuButton from './MenuButton';
import ModelSpecs from './ModelSpecs';
import store from '~/store';
Expand All @@ -15,6 +15,7 @@ export default function ModelSpecsMenu({ modelSpecs }: { modelSpecs?: TModelSpec
const { conversation } = useChatContext();
const { newConversation } = useNewConvo();

const localize = useLocalize();
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
const modularChat = useRecoilValue(store.modularChat);
const getDefaultConversation = useDefaultConvo();
Expand Down Expand Up @@ -111,6 +112,9 @@ export default function ModelSpecsMenu({ modelSpecs }: { modelSpecs?: TModelSpec
<Content
side="bottom"
align="start"
id="llm-menu"
role="listbox"
aria-label={localize('com_ui_llms_available')}
className="models-scrollbar mt-2 max-h-[65vh] min-w-[340px] max-w-xs overflow-y-auto rounded-lg border border-gray-100 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white lg:max-h-[75vh]"
>
<ModelSpecs
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/Chat/Menus/UI/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ const MenuItem: FC<MenuItemProps> = ({
}) => {
return (
<div
role="menuitem"
id={selected ? 'selected-endpoint' : undefined}
role="option"
aria-selected={selected}
aria-label={title}
data-testid="chat-menu-item"
className={cn(
Expand Down
10 changes: 7 additions & 3 deletions client/src/components/Chat/Menus/UI/TitleButton.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { useState } from 'react';
import { ChevronDown } from 'lucide-react';
import { Trigger } from '@radix-ui/react-popover';
import useLocalize from '~/hooks/useLocalize';

export default function TitleButton({ primaryText = '', secondaryText = '' }) {
const localize = useLocalize();
const [isExpanded, setIsExpanded] = useState(false);

return (
<Trigger asChild>
<button
className="group flex cursor-pointer items-center gap-2 rounded-lg px-3 py-1.5 text-lg font-medium transition-colors duration-200 hover:bg-surface-hover radix-state-open:bg-surface-hover"
aria-label={`Select ${primaryText}`}
aria-haspopup="dialog"
aria-label={localize('com_ui_endpoint_menu')}
aria-expanded={isExpanded}
aria-controls="radix-:r6:"
role="combobox"
aria-haspopup="listbox"
aria-controls="llm-endpoint-menu"
aria-activedescendant={isExpanded ? 'selected-endpoint' : undefined}
onClick={() => setIsExpanded(!isExpanded)}
>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export const ErrorMessage = ({
return (
<Container message={message}>
<div
role="alert"
aria-live="assertive"
className={cn(
'rounded-xl border border-red-500/20 bg-red-500/5 px-3 py-2 text-sm text-gray-600 dark:text-gray-200',
className,
Expand Down
12 changes: 10 additions & 2 deletions client/src/components/Nav/ExportConversation/ExportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ export default function ExportModal({
open,
onOpenChange,
conversation,
triggerRef,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
conversation: TConversation | null;
onOpenChange: (open: boolean) => void;
triggerRef: React.RefObject<HTMLButtonElement>;
}) {
const localize = useLocalize();

Expand All @@ -31,6 +33,12 @@ export default function ExportModal({
{ value: 'csv', label: 'csv (.csv)' },
];

useEffect(() => {
if (!open && triggerRef.current) {
triggerRef.current.focus();
}
}, [open, triggerRef]);

useEffect(() => {
setFileName(filenamify(String(conversation?.title ?? 'file')));
setType('screenshot');
Expand Down Expand Up @@ -61,7 +69,7 @@ export default function ExportModal({
});

return (
<OGDialog open={open} onOpenChange={onOpenChange}>
<OGDialog open={open} onOpenChange={onOpenChange} triggerRef={triggerRef}>
<OGDialogTemplate
title={localize('com_nav_export_conversation')}
className="max-w-full sm:max-w-2xl"
Expand Down
23 changes: 22 additions & 1 deletion client/src/components/ui/OriginalDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,28 @@ import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import { cn } from '~/utils';

const Dialog = DialogPrimitive.Root;
interface OGDialogProps extends DialogPrimitive.DialogProps {
triggerRef?: React.RefObject<HTMLButtonElement>;
}

const Dialog = React.forwardRef<HTMLDivElement, OGDialogProps>(
({ children, triggerRef, onOpenChange, ...props }) => {
const handleOpenChange = (open: boolean) => {
if (!open && triggerRef?.current) {
setTimeout(() => {
triggerRef.current?.focus();
}, 0);
}
onOpenChange?.(open);
};

return (
<DialogPrimitive.Root {...props} onOpenChange={handleOpenChange}>
{children}
</DialogPrimitive.Root>
);
},
);

const DialogTrigger = DialogPrimitive.Trigger;

Expand Down
1 change: 0 additions & 1 deletion client/src/components/ui/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export default function Toast() {
className={`alert-root pointer-events-auto inline-flex flex-row gap-2 rounded-md border px-3 py-2 text-white ${
severityClassName[toast.severity]
}`}
role="alert"
>
{toast.showIcon && (
<div className="mt-1 flex-shrink-0 flex-grow-0">
Expand Down
Loading

0 comments on commit dd379a2

Please sign in to comment.