mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 11:50:14 +01:00
✨ 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:
parent
d3d7d11ea8
commit
e14df5956a
12 changed files with 4460 additions and 1477 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue