fix: resolve agent selection race condition in marketplace HandleStartChat

- Set agent in localStorage before newConversation to prevent useSelectorEffects from auto-selecting previous agent
This commit is contained in:
Atef Bellaaj 2025-07-22 10:36:56 +02:00 committed by Danny Avila
parent e049fb8821
commit 60db466298
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
3 changed files with 49 additions and 43 deletions

View file

@ -1,8 +1,20 @@
import React, { useRef, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import type t from 'librechat-data-provider';
import { AgentListResponse, PERMISSION_BITS, QueryKeys } from 'librechat-data-provider';
import {
AgentListResponse,
PERMISSION_BITS,
QueryKeys,
Constants,
EModelEndpoint,
LocalStorageKeys,
} from 'librechat-data-provider';
import { useChatContext } from '~/Providers';
import { Dialog, DialogContent, Button } from '~/components/ui';
import { renderAgentAvatar } from '~/utils/agents';
import { DotsIcon } from '~/components/svg';
import { useToast, useLocalize } from '~/hooks';
interface SupportContact {
name?: string;
email?: string;
@ -11,14 +23,6 @@ interface SupportContact {
interface AgentWithSupport extends t.Agent {
support_contact?: SupportContact;
}
import { useQueryClient } from '@tanstack/react-query';
import { Dialog, DialogContent, Button } from '~/components/ui';
import { renderAgentAvatar } from '~/utils/agents';
import useLocalize from '~/hooks/useLocalize';
import { DotsIcon } from '~/components/svg';
import { useToast } from '~/hooks';
interface AgentDetailProps {
agent: AgentWithSupport; // The agent data to display
isOpen: boolean; // Whether the detail dialog is open
@ -30,7 +34,8 @@ interface AgentDetailProps {
*/
const AgentDetail: React.FC<AgentDetailProps> = ({ agent, isOpen, onClose }) => {
const localize = useLocalize();
const navigate = useNavigate();
// const navigate = useNavigate();
const { conversation, newConversation } = useChatContext();
const { showToast } = useToast();
const dialogRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
@ -67,7 +72,23 @@ const AgentDetail: React.FC<AgentDetailProps> = ({ agent, isOpen, onClose }) =>
queryClient.setQueryData<AgentListResponse>(keys, { ...listResp, data: currentAgents });
}
}
navigate(`/c/new?agent_id=${agent.id}`);
localStorage.setItem(`${LocalStorageKeys.AGENT_ID_PREFIX}0`, agent.id);
queryClient.setQueryData<t.TMessage[]>(
[QueryKeys.messages, conversation?.conversationId ?? Constants.NEW_CONVO],
[],
);
queryClient.invalidateQueries([QueryKeys.messages]);
newConversation({
template: {
conversationId: Constants.NEW_CONVO as string,
endpoint: EModelEndpoint.agents,
agent_id: agent.id,
title: `Chat with ${agent.name || 'Agent'}`,
},
});
}
};

View file

@ -1,7 +1,8 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useOutletContext } from 'react-router-dom';
import { useSetRecoilState, useRecoilValue } from 'recoil';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import { useQueryClient } from '@tanstack/react-query';
import { PermissionTypes, Permissions, QueryKeys, Constants } from 'librechat-data-provider';
import { useSearchParams, useParams, useNavigate } from 'react-router-dom';
import type t from 'librechat-data-provider';
import type { ContextType } from '~/common';
@ -11,7 +12,7 @@ import { useDocumentTitle, useHasAccess } from '~/hooks';
import { TooltipAnchor, Button } from '~/components/ui';
import { SidePanelGroup } from '~/components/SidePanel';
import { OpenSidebar } from '~/components/Chat/Menus';
import { SidePanelProvider } from '~/Providers';
import { SidePanelProvider, useChatContext } from '~/Providers';
import { NewChatIcon } from '~/components/svg';
import useLocalize from '~/hooks/useLocalize';
import CategoryTabs from './CategoryTabs';
@ -34,6 +35,8 @@ interface AgentMarketplaceProps {
const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) => {
const localize = useLocalize();
const navigate = useNavigate();
const queryClient = useQueryClient();
const { conversation, newConversation } = useChatContext();
const [searchParams, setSearchParams] = useSearchParams();
const { category } = useParams();
const setHideSidePanel = useSetRecoilState(store.hideSidePanel);
@ -142,12 +145,18 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
/**
* Handle new chat button click
*/
const handleNewChat = (e: React.MouseEvent<HTMLButtonElement>) => {
if (e.button === 0 && (e.ctrlKey || e.metaKey)) {
window.open('/c/new', '_blank');
return;
}
navigate('/c/new');
queryClient.setQueryData<t.TMessage[]>(
[QueryKeys.messages, conversation?.conversationId ?? Constants.NEW_CONVO],
[],
);
queryClient.invalidateQueries([QueryKeys.messages]);
newConversation();
};
// Check if a detail view should be open based on URL

View file

@ -3,6 +3,7 @@ import React, { useMemo } from 'react';
import { EModelEndpoint } from 'librechat-data-provider';
import { ChatContext } from '~/Providers';
import { useChatHelpers } from '~/hooks';
/**
* Minimal marketplace provider that provides only what SidePanel actually needs
@ -13,32 +14,7 @@ interface MarketplaceProviderProps {
}
export const MarketplaceProvider: React.FC<MarketplaceProviderProps> = ({ children }) => {
// Create more complete context to prevent FileRow and other component errors
// when agents with files are opened in the marketplace
const marketplaceContext = useMemo(
() => ({
conversation: {
endpoint: EModelEndpoint.agents,
conversationId: 'marketplace',
title: 'Agent Marketplace',
},
// File-related context properties to prevent FileRow errors
files: new Map(),
setFiles: () => {},
setFilesLoading: () => {},
// Other commonly used context properties to prevent undefined errors
isSubmitting: false,
setIsSubmitting: () => {},
latestMessage: null,
setLatestMessage: () => {},
// Minimal functions to prevent errors when components try to use them
ask: () => {},
regenerate: () => {},
stopGenerating: () => {},
submitMessage: () => {},
}),
[],
);
const chatHelpers = useChatHelpers(0, 'new');
return <ChatContext.Provider value={marketplaceContext as any}>{children}</ChatContext.Provider>;
return <ChatContext.Provider value={chatHelpers as any}>{children}</ChatContext.Provider>;
};