fix: Handle project search and stale ids

This commit is contained in:
Danny Avila 2026-06-02 17:44:38 -04:00
parent 3d50d2619b
commit b1addcad5d
2 changed files with 67 additions and 10 deletions

View file

@ -151,6 +151,7 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
const search = useRecoilValue(store.search);
const isSmallScreen = useMediaQuery('(max-width: 768px)');
const [expandedSectionId, setExpandedSectionId] = useState<string | null>(null);
const isSearching = Boolean(search.debouncedQuery);
const projectSortBy = mode === 'recentProjects' ? 'lastConversationAt' : 'name';
const projectSortDirection = mode === 'recentProjects' ? 'desc' : 'asc';
@ -179,14 +180,14 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
isLoading: isConversationsLoading,
} = useConversationsInfiniteQuery(
{
projectId: expandedSectionId ?? undefined,
projectId: isSearching ? undefined : (expandedSectionId ?? undefined),
tags: tags.length === 0 ? undefined : tags,
sortBy: chatSortBy,
sortDirection: 'desc',
search: search.debouncedQuery || undefined,
},
{
enabled: isAuthenticated && isChatsExpanded && expandedSectionId != null,
enabled: isAuthenticated && isChatsExpanded && (isSearching || expandedSectionId != null),
staleTime: 30000,
cacheTime: 300000,
},
@ -236,6 +237,29 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
return items;
}
if (isSearching) {
if (isConversationsLoading) {
items.push({ type: 'loading', key: 'loading-search-conversations' });
return items;
}
if (conversations.length === 0) {
items.push({ type: 'empty', title: localize('com_ui_no_results_found') });
return items;
}
groupedConversations.forEach(([groupName, convos]) => {
items.push({ type: 'date', groupName });
convos.forEach((convo) => items.push({ type: 'convo', convo }));
});
if (isFetchingNextConversationPage) {
items.push({ type: 'loading', key: 'loading-more-search-conversations' });
}
return items;
}
sections.forEach((section) => {
const isExpanded = expandedSectionId === section.id;
items.push({ type: 'section', section, isExpanded });
@ -278,6 +302,7 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
}, [
sections,
isChatsExpanded,
isSearching,
expandedSectionId,
isConversationsLoading,
conversations.length,
@ -309,18 +334,18 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
return 'project-chats-header';
}
if (item.type === 'date') {
return `project-date-${expandedSectionId}-${item.groupName}`;
return `project-date-${isSearching ? 'search' : expandedSectionId}-${item.groupName}`;
}
if (item.type === 'convo') {
return `project-convo-${item.convo.conversationId}`;
}
if (item.type === 'empty') {
return `project-empty-${expandedSectionId}`;
return `project-empty-${isSearching ? 'search' : expandedSectionId}`;
}
return item.key;
},
}),
[expandedSectionId, rowHeight],
[expandedSectionId, isSearching, rowHeight],
);
useEffect(() => {
@ -372,7 +397,11 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
if (!isChatsExpanded) {
return;
}
if (expandedSectionId && conversationHasNextPage && !isFetchingNextConversationPage) {
if (
(isSearching || expandedSectionId) &&
conversationHasNextPage &&
!isFetchingNextConversationPage
) {
fetchNextConversationPage();
return;
}
@ -381,6 +410,7 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
}
}, [
expandedSectionId,
isSearching,
conversationHasNextPage,
isFetchingNextConversationPage,
fetchNextConversationPage,

View file

@ -1,10 +1,11 @@
import type { FilterQuery, Model, SortOrder } from 'mongoose';
import { RetentionMode } from 'librechat-data-provider';
import { isValidObjectIdString } from '~/utils/objectId';
import { createTempChatExpirationDate } from '~/utils/tempChatRetention';
import { buildRetentionVisibilityFilter, createFallbackRetentionDate } from '~/utils/retention';
import { tenantSafeBulkWrite } from '~/utils/tenantBulkWrite';
import logger from '~/config/winston';
import type { AppConfig, IConversation } from '~/types';
import type { AppConfig, IChatProjectDocument, IConversation } from '~/types';
import {
refreshChatProjectStatsForUser,
updateChatProjectLastConversationForUser,
@ -213,6 +214,26 @@ export function createConversationMethods(
const messages = await getMessages({ conversationId, user: userId }, '_id');
const update: Record<string, unknown> = { ...convo, messages, user: userId };
const unsetFields: Record<string, number> = { ...(metadata?.unsetFields ?? {}) };
if (Object.prototype.hasOwnProperty.call(update, 'chatProjectId') && update.chatProjectId) {
const chatProjectId = typeof update.chatProjectId === 'string' ? update.chatProjectId : '';
let isValidChatProject = isValidObjectIdString(chatProjectId);
if (isValidChatProject) {
const ChatProject = mongoose.models.ChatProject as Model<IChatProjectDocument>;
const project = await ChatProject.exists({
_id: new mongoose.Types.ObjectId(chatProjectId),
user: userId,
});
isValidChatProject = project != null;
}
if (!isValidChatProject) {
delete update.chatProjectId;
unsetFields.chatProjectId = 1;
}
}
if (newConversationId) {
update.conversationId = newConversationId;
@ -253,8 +274,8 @@ export function createConversationMethods(
}
const updateOperation: Record<string, unknown> = { $set: update };
if (metadata?.unsetFields && Object.keys(metadata.unsetFields).length > 0) {
updateOperation.$unset = metadata.unsetFields;
if (Object.keys(unsetFields).length > 0) {
updateOperation.$unset = unsetFields;
}
if (createdAtOnInsert) {
updateOperation.$setOnInsert = { createdAt: createdAtOnInsert };
@ -302,9 +323,15 @@ export function createConversationMethods(
}
if (conversation.chatProjectId) {
const isRetentionVisibilityUpdate =
typeof update.isTemporary === 'boolean' ||
Object.prototype.hasOwnProperty.call(convo, 'expiredAt') ||
Object.prototype.hasOwnProperty.call(unsetFields, 'isTemporary') ||
Object.prototype.hasOwnProperty.call(unsetFields, 'expiredAt');
const shouldRefreshProjectStats =
typeof update.isArchived === 'boolean' ||
Object.prototype.hasOwnProperty.call(metadata?.unsetFields ?? {}, 'isArchived');
Object.prototype.hasOwnProperty.call(unsetFields, 'isArchived') ||
isRetentionVisibilityUpdate;
if (shouldRefreshProjectStats) {
await refreshChatProjectStatsForUser(mongoose, userId, conversation.chatProjectId);