mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
fix: read project scope from router params; align sidebar header icons
- useSelectMention now reads the active project from React Router's search params instead of window.location, which can drift out of sync because new-chat params are written to the URL via raw history.pushState; the Model Selector and @ command now reliably keep the project on switch - Move the Chats section header out of the virtualized list so it renders in the same context as the Projects header and isn't shifted by the list scrollbar - Inset header action icons (pr-2) so Projects/Chats header icons line up with the project-row and conversation-row trailing actions - Extract getRouteChatProjectId into utils for the submit path
This commit is contained in:
parent
e1e1ebb825
commit
e185e2ce0a
5 changed files with 49 additions and 48 deletions
|
|
@ -102,7 +102,7 @@ const ChatsHeader: FC<ChatsHeaderProps> = memo(({ isExpanded, onToggle }) => {
|
|||
}, [conversation?.conversationId, newConversation, queryClient]);
|
||||
|
||||
return (
|
||||
<div className="flex h-8 w-full items-center gap-0.5">
|
||||
<div className="flex h-8 w-full items-center gap-0.5 pr-2">
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className="group flex min-w-0 flex-1 items-center gap-1 rounded-lg px-1 py-2 text-xs font-bold text-text-secondary outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-black dark:focus-visible:ring-white"
|
||||
|
|
@ -156,7 +156,6 @@ DateLabel.displayName = 'DateLabel';
|
|||
|
||||
type FlattenedItem =
|
||||
| { type: 'favorites' }
|
||||
| { type: 'chats-header' }
|
||||
| { type: 'header'; groupName: string }
|
||||
| { type: 'convo'; convo: TConversation }
|
||||
| { type: 'loading' };
|
||||
|
|
@ -244,7 +243,6 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
if (shouldShowFavorites) {
|
||||
items.push({ type: 'favorites' });
|
||||
}
|
||||
items.push({ type: 'chats-header' });
|
||||
|
||||
if (isChatsExpanded) {
|
||||
groupedConversations.forEach(([groupName, convos]) => {
|
||||
|
|
@ -277,9 +275,6 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
if (item.type === 'favorites') {
|
||||
return `favorites-${favoritesContentKeyRef.current}`;
|
||||
}
|
||||
if (item.type === 'chats-header') {
|
||||
return 'chats-header';
|
||||
}
|
||||
if (item.type === 'header') {
|
||||
return `header-${item.groupName}`;
|
||||
}
|
||||
|
|
@ -342,22 +337,9 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (item.type === 'chats-header') {
|
||||
return (
|
||||
<MeasuredRow key={key} {...rowProps}>
|
||||
<ChatsHeader
|
||||
isExpanded={isChatsExpanded}
|
||||
onToggle={() => setIsChatsExpanded(!isChatsExpanded)}
|
||||
/>
|
||||
</MeasuredRow>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === 'header') {
|
||||
// First date header index depends on whether favorites row is included
|
||||
// With favorites: [favorites, chats-header, first-header] → index 2
|
||||
// Without favorites: [chats-header, first-header] → index 1
|
||||
const firstHeaderIndex = shouldShowFavorites ? 2 : 1;
|
||||
// First date header index depends on whether the favorites row is included
|
||||
const firstHeaderIndex = shouldShowFavorites ? 1 : 0;
|
||||
return (
|
||||
<MeasuredRow key={key} {...rowProps}>
|
||||
<DateLabel groupName={item.groupName} isFirst={index === firstHeaderIndex} />
|
||||
|
|
@ -381,17 +363,7 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
|
||||
return null;
|
||||
},
|
||||
[
|
||||
cache,
|
||||
flattenedItems,
|
||||
moveToTop,
|
||||
toggleNav,
|
||||
isSmallScreen,
|
||||
isChatsExpanded,
|
||||
setIsChatsExpanded,
|
||||
shouldShowFavorites,
|
||||
activeJobIds,
|
||||
],
|
||||
[cache, flattenedItems, moveToTop, toggleNav, isSmallScreen, shouldShowFavorites, activeJobIds],
|
||||
);
|
||||
|
||||
const getRowHeight = useCallback(
|
||||
|
|
@ -415,6 +387,12 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
|
||||
return (
|
||||
<div className="relative flex h-full min-h-0 flex-col pb-2 text-sm text-text-primary">
|
||||
<div className="px-3">
|
||||
<ChatsHeader
|
||||
isExpanded={isChatsExpanded}
|
||||
onToggle={() => setIsChatsExpanded(!isChatsExpanded)}
|
||||
/>
|
||||
</div>
|
||||
{isSearchLoading ? (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<Spinner className="text-text-primary" />
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ const ProjectsSection = ({ toggleNav, isAuthenticated }: ProjectsSectionProps) =
|
|||
|
||||
return (
|
||||
<div className="flex flex-col px-3 text-sm">
|
||||
<div className="flex h-8 w-full items-center gap-0.5">
|
||||
<div className="flex h-8 w-full items-center gap-0.5 pr-2">
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="group flex min-w-0 flex-1 items-center gap-1 rounded-lg px-1 py-2 text-xs font-bold text-text-secondary outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-black dark:focus-visible:ring-white"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import type { SetterOrUpdater } from 'recoil';
|
|||
import type { TAskFunction, ExtendedFile } from '~/common';
|
||||
import useSetFilesToDelete from '~/hooks/Files/useSetFilesToDelete';
|
||||
import useGetSender from '~/hooks/Conversations/useGetSender';
|
||||
import { logger, createDualMessageContent } from '~/utils';
|
||||
import { logger, createDualMessageContent, getRouteChatProjectId } from '~/utils';
|
||||
import store, { useGetEphemeralAgent } from '~/store';
|
||||
import { startupConfigKey } from '~/data-provider';
|
||||
import useUserKey from '~/hooks/Input/useUserKey';
|
||||
|
|
@ -40,15 +40,6 @@ const logChatRequest = (request: Record<string, unknown>) => {
|
|||
logger.log('=====================================');
|
||||
};
|
||||
|
||||
const getRouteChatProjectId = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const projectId = new URLSearchParams(window.location.search).get('projectId');
|
||||
return projectId != null && /^[a-f\d]{24}$/i.test(projectId) ? projectId : null;
|
||||
};
|
||||
|
||||
export default function useChatFunctions({
|
||||
index = 0,
|
||||
files,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { EModelEndpoint, isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type {
|
||||
TPreset,
|
||||
|
|
@ -39,6 +40,16 @@ export default function useSelectMention({
|
|||
const getDefaultConversation = useDefaultConvo();
|
||||
const modularChat = useRecoilValue(store.modularChat);
|
||||
const availableTools = useRecoilValue(store.availableTools);
|
||||
const [searchParams] = useSearchParams();
|
||||
/**
|
||||
* Project scope is read from React Router's search params (the same source the
|
||||
* project chip reads), not window.location — new-chat params are written to the
|
||||
* URL via raw history.pushState, so window.location can drift out of sync.
|
||||
*/
|
||||
const routeChatProjectId = useMemo(() => {
|
||||
const projectId = searchParams.get('projectId');
|
||||
return projectId != null && /^[a-f\d]{24}$/i.test(projectId) ? projectId : null;
|
||||
}, [searchParams]);
|
||||
|
||||
const onSelectSpec = useCallback(
|
||||
(spec?: TModelSpec) => {
|
||||
|
|
@ -109,7 +120,7 @@ export default function useSelectMention({
|
|||
newConversation({
|
||||
template: {
|
||||
...(template as Partial<TConversation>),
|
||||
chatProjectId: conversation?.chatProjectId ?? null,
|
||||
chatProjectId: routeChatProjectId,
|
||||
},
|
||||
preset,
|
||||
keepAddedConvos: isModular,
|
||||
|
|
@ -122,6 +133,7 @@ export default function useSelectMention({
|
|||
newConversation,
|
||||
endpointsConfig,
|
||||
assistantsMap,
|
||||
routeChatProjectId,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
@ -206,13 +218,20 @@ export default function useSelectMention({
|
|||
newConversation({
|
||||
template: {
|
||||
...(template as Partial<TConversation>),
|
||||
chatProjectId: conversation?.chatProjectId ?? null,
|
||||
chatProjectId: routeChatProjectId,
|
||||
},
|
||||
preset: { ...kwargs, spec: null, iconURL: null, modelLabel: null, endpoint: newEndpoint },
|
||||
keepAddedConvos: isNewModular,
|
||||
});
|
||||
},
|
||||
[getConversation, getDefaultConversation, modularChat, newConversation, endpointsConfig],
|
||||
[
|
||||
getConversation,
|
||||
getDefaultConversation,
|
||||
modularChat,
|
||||
newConversation,
|
||||
endpointsConfig,
|
||||
routeChatProjectId,
|
||||
],
|
||||
);
|
||||
|
||||
const onSelectPreset = useCallback(
|
||||
|
|
@ -270,7 +289,7 @@ export default function useSelectMention({
|
|||
|
||||
logger.info('conversation', 'Switching conversation to new preset', template);
|
||||
newConversation({
|
||||
template: { chatProjectId: conversation?.chatProjectId ?? null },
|
||||
template: { chatProjectId: routeChatProjectId },
|
||||
preset: newPreset,
|
||||
keepAddedConvos: isModular,
|
||||
disableParams,
|
||||
|
|
@ -283,6 +302,7 @@ export default function useSelectMention({
|
|||
newConversation,
|
||||
endpointsConfig,
|
||||
getDefaultConversation,
|
||||
routeChatProjectId,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,18 @@ function conversationMatchesProjectQuery(
|
|||
return conversation.chatProjectId === projectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the project id from the current URL's `?projectId` param — the source of
|
||||
* truth for a new chat's project scope (the conversation atom can lag behind it).
|
||||
*/
|
||||
export function getRouteChatProjectId(): string | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
const projectId = new URLSearchParams(window.location.search).get('projectId');
|
||||
return projectId != null && /^[a-f\d]{24}$/i.test(projectId) ? projectId : null;
|
||||
}
|
||||
|
||||
// === InfiniteData helpers for cursor-based convo queries ===
|
||||
|
||||
export function findConversationInInfinite(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue