feat: Anthropic Agents Prompt Caching & UI Accessibility Enhancements (#6045)

* chore: remove auto-focus for now

* refactor: move react-hook-form Controller Logic to AgentSelect from AgentPanel

* fix: a11y focus issue with AgentSelect by never replacing it in its component tree

* fix: maintain ComboBox focus and force re-render on agent ID change in AgentPanel

* chore: `gemini-2.0-flash-lite-preview-02-05` (deprecated)

* refactor: extract cache control logic and headers configuration to helper functions in AnthropicClient

* feat: anthropic agents prompt caching

* chore: bump @librechat/agents and related dependencies

* fix: typo
This commit is contained in:
Danny Avila 2025-02-25 22:14:58 -05:00 committed by GitHub
parent d3d7d11ea8
commit e14df5956a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 4460 additions and 1477 deletions

View file

@ -1,7 +1,7 @@
import { Plus } from 'lucide-react';
import React, { useMemo, useCallback } from 'react';
import { useWatch, useForm, FormProvider } from 'react-hook-form';
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
import { Controller, useWatch, useForm, FormProvider } from 'react-hook-form';
import {
Tools,
SystemRoles,
@ -201,10 +201,6 @@ export default function AgentPanel({
user?.role,
]);
if (agentQuery.isInitialLoading) {
return <AgentPanelSkeleton />;
}
return (
<FormProvider {...methods}>
<form
@ -214,19 +210,13 @@ export default function AgentPanel({
>
<div className="mx-1 mt-2 flex w-full flex-wrap gap-2">
<div className="w-full">
<Controller
name="agent"
control={control}
render={({ field }) => (
<AgentSelect
reset={reset}
value={field.value}
agentQuery={agentQuery}
setCurrentAgentId={setCurrentAgentId}
selectedAgentId={current_agent_id ?? null}
createMutation={create}
/>
)}
<AgentSelect
createMutation={create}
agentQuery={agentQuery}
setCurrentAgentId={setCurrentAgentId}
// The following is required to force re-render the component when the form's agent ID changes
// Also maintains ComboBox Focus for Accessibility
selectedAgentId={agentQuery.isInitialLoading ? null : (current_agent_id ?? null)}
/>
</div>
{/* Create + Select Button */}
@ -240,6 +230,7 @@ export default function AgentPanel({
reset(defaultAgentFormValues);
setCurrentAgentId(undefined);
}}
disabled={agentQuery.isInitialLoading}
>
<Plus className="mr-1 h-4 w-4" />
{localize('com_ui_create') +
@ -250,7 +241,7 @@ export default function AgentPanel({
</Button>
<Button
variant="submit"
disabled={!agent_id}
disabled={!agent_id || agentQuery.isInitialLoading}
onClick={(e) => {
e.preventDefault();
handleSelectAgent();
@ -262,7 +253,8 @@ export default function AgentPanel({
</div>
)}
</div>
{!canEditAgent && (
{agentQuery.isInitialLoading && <AgentPanelSkeleton />}
{!canEditAgent && !agentQuery.isInitialLoading && (
<div className="flex h-[30vh] w-full items-center justify-center">
<div className="text-center">
<h2 className="text-token-text-primary m-2 text-xl font-semibold">
@ -272,7 +264,7 @@ export default function AgentPanel({
</div>
</div>
)}
{canEditAgent && activePanel === Panel.model && (
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.model && (
<ModelPanel
setActivePanel={setActivePanel}
agent_id={agent_id}
@ -280,7 +272,7 @@ export default function AgentPanel({
models={models}
/>
)}
{canEditAgent && activePanel === Panel.builder && (
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && (
<AgentConfig
actions={actions}
setAction={setAction}

View file

@ -3,81 +3,67 @@ import { Skeleton } from '~/components/ui';
export default function AgentPanelSkeleton() {
return (
<div className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden">
<div className="mx-1 mt-2 flex w-full flex-wrap gap-2">
{/* Agent Select Dropdown */}
<div className="w-full">
<Skeleton className="h-[40px] w-full rounded-md" />
<div className="h-auto bg-white px-4 pb-8 pt-3 dark:bg-transparent">
{/* Avatar */}
<div className="mb-4">
<div className="flex w-full items-center justify-center gap-4">
<Skeleton className="relative h-20 w-20 rounded-full" />
</div>
{/* Create and Select Buttons */}
<div className="flex w-full gap-2">
<Skeleton className="h-[40px] w-3/4 rounded-md" /> {/* Create Button */}
<Skeleton className="h-[40px] w-1/4 rounded-md" /> {/* Select Button */}
{/* Name */}
<Skeleton className="mb-2 h-5 w-1/5 rounded-lg" />
<Skeleton className="mb-1 h-[40px] w-full rounded-lg" />
<Skeleton className="h-3 w-1/4 rounded-lg" />
</div>
{/* Description */}
<div className="mb-4">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="h-[40px] w-full rounded-lg" />
</div>
{/* Instructions */}
<div className="mb-6">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="h-[100px] w-full rounded-lg" />
</div>
{/* Model and Provider */}
<div className="mb-6">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="h-[40px] w-full rounded-lg" />
</div>
{/* Capabilities */}
<div className="mb-6">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="mb-2 h-5 w-36 rounded-lg" />
<Skeleton className="mb-4 h-[35px] w-full rounded-lg" />
<Skeleton className="mb-2 h-5 w-24 rounded-lg" />
<Skeleton className="h-[35px] w-full rounded-lg" />
</div>
{/* Tools & Actions */}
<div className="mb-6">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
<div className="flex space-x-2">
<Skeleton className="h-8 w-1/2 rounded-lg" />
<Skeleton className="h-8 w-1/2 rounded-lg" />
</div>
</div>
<div className="h-auto bg-white px-4 pb-8 pt-3 dark:bg-transparent">
{/* Avatar */}
<div className="mb-4">
<div className="flex w-full items-center justify-center gap-4">
<Skeleton className="relative h-20 w-20 rounded-full" />
</div>
{/* Name */}
<Skeleton className="mb-2 h-5 w-1/5 rounded-lg" />
<Skeleton className="mb-1 h-[40px] w-full rounded-lg" />
<Skeleton className="h-3 w-1/4 rounded-lg" />
</div>
{/* Admin Settings */}
<div className="mb-6">
<Skeleton className="h-[35px] w-full rounded-lg" />
</div>
{/* Description */}
<div className="mb-4">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="h-[40px] w-full rounded-lg" />
</div>
{/* Instructions */}
<div className="mb-6">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="h-[100px] w-full rounded-lg" />
</div>
{/* Model and Provider */}
<div className="mb-6">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="h-[40px] w-full rounded-lg" />
</div>
{/* Capabilities */}
<div className="mb-6">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="mb-2 h-5 w-36 rounded-lg" />
<Skeleton className="mb-4 h-[35px] w-full rounded-lg" />
<Skeleton className="mb-2 h-5 w-24 rounded-lg" />
<Skeleton className="h-[35px] w-full rounded-lg" />
</div>
{/* Tools & Actions */}
<div className="mb-6">
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
<div className="flex space-x-2">
<Skeleton className="h-8 w-1/2 rounded-lg" />
<Skeleton className="h-8 w-1/2 rounded-lg" />
</div>
</div>
{/* Admin Settings */}
<div className="mb-6">
<Skeleton className="h-[35px] w-full rounded-lg" />
</div>
{/* Bottom Buttons */}
<div className="flex items-center justify-end gap-2">
<Skeleton className="h-[35px] w-16 rounded-lg" />
<Skeleton className="h-[35px] w-16 rounded-lg" />
<Skeleton className="h-[35px] w-16 rounded-lg" />
<Skeleton className="h-[35px] w-full rounded-lg" />
</div>
{/* Bottom Buttons */}
<div className="flex items-center justify-end gap-2">
<Skeleton className="h-[35px] w-16 rounded-lg" />
<Skeleton className="h-[35px] w-16 rounded-lg" />
<Skeleton className="h-[35px] w-16 rounded-lg" />
<Skeleton className="h-[35px] w-full rounded-lg" />
</div>
</div>
);

View file

@ -1,28 +1,23 @@
import { EarthIcon } from 'lucide-react';
import { useCallback, useEffect, useRef } from 'react';
import { useFormContext, Controller } from 'react-hook-form';
import { AgentCapabilities, defaultAgentFormValues } from 'librechat-data-provider';
import type { UseMutationResult, QueryObserverResult } from '@tanstack/react-query';
import type { Agent, AgentCreateParams } from 'librechat-data-provider';
import type { UseFormReset } from 'react-hook-form';
import type { TAgentCapabilities, AgentForm, TAgentOption } from '~/common';
import type { TAgentCapabilities, AgentForm } from '~/common';
import { useListAgentsQuery, useGetStartupConfig } from '~/data-provider';
import { cn, createProviderOption, processAgentOption } from '~/utils';
import ControlCombobox from '~/components/ui/ControlCombobox';
import { useLocalize } from '~/hooks';
const keys = new Set(Object.keys(defaultAgentFormValues));
const SELECT_ID = 'agent-builder-combobox';
export default function AgentSelect({
reset,
agentQuery,
value: currentAgentValue,
selectedAgentId = null,
setCurrentAgentId,
createMutation,
}: {
reset: UseFormReset<AgentForm>;
value?: TAgentOption;
selectedAgentId: string | null;
agentQuery: QueryObserverResult<Agent>;
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
@ -30,6 +25,7 @@ export default function AgentSelect({
}) {
const localize = useLocalize();
const lastSelectedAgent = useRef<string | null>(null);
const { control, reset } = useFormContext();
const { data: startupConfig } = useGetStartupConfig();
const { data: agents = null } = useListAgentsQuery(undefined, {
@ -121,9 +117,6 @@ export default function AgentSelect({
}
resetAgentForm(agent);
setTimeout(() => {
document.getElementById(SELECT_ID)?.focus();
}, 5);
},
[agents, createMutation, setCurrentAgentId, agentQuery.data, resetAgentForm, reset],
);
@ -158,34 +151,39 @@ export default function AgentSelect({
const createAgent = localize('com_ui_create') + ' ' + localize('com_ui_agent');
return (
<ControlCombobox
selectId={SELECT_ID}
containerClassName="px-0"
selectedValue={(currentAgentValue?.value ?? '') + ''}
displayValue={currentAgentValue?.label ?? ''}
selectPlaceholder={createAgent}
iconSide="right"
searchPlaceholder={localize('com_agents_search_name')}
SelectIcon={currentAgentValue?.icon}
setValue={onSelect}
items={
agents?.map((agent) => ({
label: agent.name ?? '',
value: agent.id ?? '',
icon: agent.icon,
})) ?? [
{
label: 'Loading...',
value: '',
},
]
}
className={cn(
'z-50 flex h-[40px] w-full flex-none items-center justify-center truncate rounded-md bg-transparent font-bold',
<Controller
name="agent"
control={control}
render={({ field }) => (
<ControlCombobox
containerClassName="px-0"
selectedValue={(field?.value?.value ?? '') + ''}
displayValue={field?.value?.label ?? ''}
selectPlaceholder={createAgent}
iconSide="right"
searchPlaceholder={localize('com_agents_search_name')}
SelectIcon={field?.value?.icon}
setValue={onSelect}
items={
agents?.map((agent) => ({
label: agent.name ?? '',
value: agent.id ?? '',
icon: agent.icon,
})) ?? [
{
label: 'Loading...',
value: '',
},
]
}
className={cn(
'z-50 flex h-[40px] w-full flex-none items-center justify-center truncate rounded-md bg-transparent font-bold',
)}
ariaLabel={localize('com_ui_agent')}
isCollapsed={false}
showCarat={true}
/>
)}
ariaLabel={localize('com_ui_agent')}
isCollapsed={false}
showCarat={true}
/>
);
}