mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 04:07:05 +00:00
🦥 perf: Lazy-Load Agent Version History in Editor (#13977)
Opening the agent editor fetched the full `versions` array (each a complete config snapshot) alongside the agent, so agents with large histories were slow to open. Version history is now loaded only when the user opens it. - Add `getAgentWithVersionCount` (aggregation: version count, no versions array) and `getAgentVersions` data-schemas methods. - `getAgentHandler` returns the version count without the heavy array; add `GET /agents/:id/versions` (EDIT-gated) for lazy retrieval. - Add `useGetAgentVersionsQuery`; VersionPanel reads current config from the cached expanded query and fetches versions on open. Revert keeps the expanded cache and versions query in sync.
This commit is contained in:
parent
b15d40e3e4
commit
12fea693bb
10 changed files with 301 additions and 45 deletions
|
|
@ -1,8 +1,12 @@
|
|||
import { ChevronLeft } from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { useToastContext } from '@librechat/client';
|
||||
import { useGetAgentByIdQuery, useRevertAgentVersionMutation } from '~/data-provider';
|
||||
import type { AgentWithVersions, VersionContext } from './types';
|
||||
import type { AgentWithVersions, VersionContext, VersionRecord } from './types';
|
||||
import {
|
||||
useGetAgentVersionsQuery,
|
||||
useRevertAgentVersionMutation,
|
||||
useGetExpandedAgentByIdQuery,
|
||||
} from '~/data-provider';
|
||||
import { isActiveVersion } from './isActiveVersion';
|
||||
import { useAgentPanelContext } from '~/Providers';
|
||||
import VersionContent from './VersionContent';
|
||||
|
|
@ -16,7 +20,15 @@ export default function VersionPanel() {
|
|||
|
||||
const selectedAgentId = agent_id ?? '';
|
||||
|
||||
const { data: agent, isLoading, error, refetch } = useGetAgentByIdQuery(selectedAgentId);
|
||||
const { data: agent } = useGetExpandedAgentByIdQuery(selectedAgentId, {
|
||||
enabled: !!selectedAgentId,
|
||||
});
|
||||
const {
|
||||
data: versionsData,
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
} = useGetAgentVersionsQuery(selectedAgentId);
|
||||
|
||||
const revertAgentVersion = useRevertAgentVersionMutation({
|
||||
onSuccess: () => {
|
||||
|
|
@ -34,7 +46,7 @@ export default function VersionPanel() {
|
|||
},
|
||||
});
|
||||
|
||||
const agentWithVersions = agent as AgentWithVersions;
|
||||
const agentWithVersions = agent as AgentWithVersions | undefined;
|
||||
|
||||
const currentAgent = useMemo(() => {
|
||||
if (!agentWithVersions) return null;
|
||||
|
|
@ -48,14 +60,15 @@ export default function VersionPanel() {
|
|||
};
|
||||
}, [agentWithVersions]);
|
||||
|
||||
const versionRecords = useMemo<VersionRecord[]>(() => versionsData ?? [], [versionsData]);
|
||||
|
||||
const versions = useMemo(() => {
|
||||
const versionsCopy = [...(agentWithVersions?.versions || [])];
|
||||
return versionsCopy.sort((a, b) => {
|
||||
return [...versionRecords].sort((a, b) => {
|
||||
const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
||||
const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
}, [agentWithVersions?.versions]);
|
||||
}, [versionRecords]);
|
||||
|
||||
const activeVersion = useMemo(() => {
|
||||
return versions.length > 0
|
||||
|
|
@ -73,7 +86,7 @@ export default function VersionPanel() {
|
|||
|
||||
return versions.map((version, displayIndex) => {
|
||||
const originalIndex =
|
||||
agentWithVersions?.versions?.findIndex(
|
||||
versionRecords.findIndex(
|
||||
(v) =>
|
||||
v.updatedAt === version.updatedAt &&
|
||||
v.createdAt === version.createdAt &&
|
||||
|
|
@ -87,7 +100,7 @@ export default function VersionPanel() {
|
|||
isActive: displayIndex === activeVersionId,
|
||||
};
|
||||
});
|
||||
}, [versions, currentAgent, agentWithVersions?.versions]);
|
||||
}, [versions, currentAgent, versionRecords]);
|
||||
|
||||
const versionContext: VersionContext = useMemo(
|
||||
() => ({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { Panel } from '~/common/types';
|
||||
import VersionContent from '../VersionContent';
|
||||
import VersionPanel from '../VersionPanel';
|
||||
import { Panel } from '~/common/types';
|
||||
|
||||
const mockAgentData = {
|
||||
name: 'Test Agent',
|
||||
|
|
@ -10,35 +10,42 @@ const mockAgentData = {
|
|||
instructions: 'Test Instructions',
|
||||
tools: ['tool1', 'tool2'],
|
||||
capabilities: ['capability1', 'capability2'],
|
||||
versions: [
|
||||
{
|
||||
name: 'Version 1',
|
||||
description: 'Description 1',
|
||||
instructions: 'Instructions 1',
|
||||
tools: ['tool1'],
|
||||
capabilities: ['capability1'],
|
||||
createdAt: '2023-01-01T00:00:00Z',
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'Version 2',
|
||||
description: 'Description 2',
|
||||
instructions: 'Instructions 2',
|
||||
tools: ['tool1', 'tool2'],
|
||||
capabilities: ['capability1', 'capability2'],
|
||||
createdAt: '2023-01-02T00:00:00Z',
|
||||
updatedAt: '2023-01-02T00:00:00Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockVersions = [
|
||||
{
|
||||
name: 'Version 1',
|
||||
description: 'Description 1',
|
||||
instructions: 'Instructions 1',
|
||||
tools: ['tool1'],
|
||||
capabilities: ['capability1'],
|
||||
createdAt: '2023-01-01T00:00:00Z',
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'Version 2',
|
||||
description: 'Description 2',
|
||||
instructions: 'Instructions 2',
|
||||
tools: ['tool1', 'tool2'],
|
||||
capabilities: ['capability1', 'capability2'],
|
||||
createdAt: '2023-01-02T00:00:00Z',
|
||||
updatedAt: '2023-01-02T00:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('~/data-provider', () => ({
|
||||
useGetAgentByIdQuery: jest.fn(() => ({
|
||||
useGetExpandedAgentByIdQuery: jest.fn(() => ({
|
||||
data: mockAgentData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
})),
|
||||
useGetAgentVersionsQuery: jest.fn(() => ({
|
||||
data: mockVersions,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
})),
|
||||
useRevertAgentVersionMutation: jest.fn(() => ({
|
||||
mutate: jest.fn(),
|
||||
isLoading: false,
|
||||
|
|
@ -67,16 +74,24 @@ describe('VersionPanel', () => {
|
|||
'~/Providers/AgentPanelContext',
|
||||
).useAgentPanelContext;
|
||||
|
||||
const mockUseGetAgentByIdQuery = jest.requireMock('~/data-provider').useGetAgentByIdQuery;
|
||||
const mockUseGetExpandedAgentByIdQuery =
|
||||
jest.requireMock('~/data-provider').useGetExpandedAgentByIdQuery;
|
||||
const mockUseGetAgentVersionsQuery = jest.requireMock('~/data-provider').useGetAgentVersionsQuery;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseGetAgentByIdQuery.mockReturnValue({
|
||||
mockUseGetExpandedAgentByIdQuery.mockReturnValue({
|
||||
data: mockAgentData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
mockUseGetAgentVersionsQuery.mockReturnValue({
|
||||
data: mockVersions,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
|
||||
// Set up the default context mock
|
||||
mockUseAgentPanelContext.mockReturnValue({
|
||||
|
|
@ -126,7 +141,13 @@ describe('VersionPanel', () => {
|
|||
);
|
||||
|
||||
// Test with null data
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
mockUseGetExpandedAgentByIdQuery.mockReturnValueOnce({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
|
@ -150,8 +171,8 @@ describe('VersionPanel', () => {
|
|||
);
|
||||
|
||||
// 3. versions is undefined
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
data: { ...mockAgentData, versions: undefined },
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
|
|
@ -165,7 +186,7 @@ describe('VersionPanel', () => {
|
|||
);
|
||||
|
||||
// 4. loading state
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: null,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
|
|
@ -179,7 +200,7 @@ describe('VersionPanel', () => {
|
|||
|
||||
// 5. error state
|
||||
const testError = new Error('Test error');
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
error: testError,
|
||||
|
|
@ -193,12 +214,18 @@ describe('VersionPanel', () => {
|
|||
});
|
||||
|
||||
test('memoizes agent data correctly', () => {
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
mockUseGetExpandedAgentByIdQuery.mockReturnValueOnce({
|
||||
data: mockAgentData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: mockVersions,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
|
||||
render(<VersionPanel />);
|
||||
expect(VersionContent).toHaveBeenCalledWith(
|
||||
|
|
|
|||
|
|
@ -379,6 +379,11 @@ export const useRevertAgentVersionMutation = (
|
|||
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
||||
onSuccess: (revertedAgent, variables, context) => {
|
||||
queryClient.setQueryData<t.Agent>([QueryKeys.agent, variables.agent_id], revertedAgent);
|
||||
queryClient.setQueryData<t.Agent>(
|
||||
[QueryKeys.agent, variables.agent_id, 'expanded'],
|
||||
revertedAgent,
|
||||
);
|
||||
queryClient.invalidateQueries([QueryKeys.agent, variables.agent_id, 'versions']);
|
||||
|
||||
((keys: t.AgentListParams[]) => {
|
||||
keys.forEach((key) => {
|
||||
|
|
|
|||
|
|
@ -133,6 +133,33 @@ export const useGetExpandedAgentByIdQuery = (
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for lazily retrieving an agent's version history (EDIT permission).
|
||||
* Only fetched when the user opens version history, so editors with large
|
||||
* histories don't pay the cost on every open.
|
||||
*/
|
||||
export const useGetAgentVersionsQuery = (
|
||||
agent_id: string | null | undefined,
|
||||
config?: UseQueryOptions<t.Agent[]>,
|
||||
): QueryObserverResult<t.Agent[]> => {
|
||||
const isValidAgentId = !!agent_id && !isEphemeralAgent(agent_id);
|
||||
|
||||
return useQuery<t.Agent[]>(
|
||||
[QueryKeys.agent, agent_id, 'versions'],
|
||||
() =>
|
||||
dataService.getAgentVersions({
|
||||
agent_id: agent_id as string,
|
||||
}),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
retry: false,
|
||||
...config,
|
||||
enabled: isValidAgentId && (config?.enabled ?? true),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* MARKETPLACE
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue