From 79336d5d9d35bb090a5dbb505b5c359cc0b9839f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 19 Jul 2025 03:47:42 +0000 Subject: [PATCH 1/3] feat(BUS-1447): Add useTrackAndUpdateNewMessages hook to track external message insertions - Create new hook to detect message insertions from external sources like Slack - Configure useShapeStream with 'insert' operations only - Update chat message_ids array when new messages are detected - Integrate hook into useChatStreaming alongside existing tracking hooks - Handle message ordering and deduplication with uniq - Fix TypeScript errors by ensuring id property is included in onUpdateChat calls Co-Authored-By: nate@buster.so --- .../src/api/buster-electric/messages/hooks.ts | 36 +++++++++++++++++++ .../ChatContext/useChatStreaming.tsx | 3 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/apps/web/src/api/buster-electric/messages/hooks.ts b/apps/web/src/api/buster-electric/messages/hooks.ts index 935ac8f83..174bfae4c 100644 --- a/apps/web/src/api/buster-electric/messages/hooks.ts +++ b/apps/web/src/api/buster-electric/messages/hooks.ts @@ -25,6 +25,7 @@ export const useGetMessages = ({ chatId }: { chatId: string }) => { }; const updateOperations: Array<'insert' | 'update' | 'delete'> = ['update']; +const insertOperations: Array<'insert' | 'update' | 'delete'> = ['insert']; export const useTrackAndUpdateMessageChanges = ( { @@ -64,6 +65,7 @@ export const useTrackAndUpdateMessageChanges = ( if (currentMessageIds.length !== allMessageIds.length) { onUpdateChat({ ...chat, + id: chatId, message_ids: allMessageIds }); } @@ -129,3 +131,37 @@ const useCheckIfWeHaveAFollowupDashboard = (messageId: string) => { return useMemoizedFn(method); }; + +export const useTrackAndUpdateNewMessages = ({ chatId }: { chatId: string | undefined }) => { + const { onUpdateChat } = useChatUpdate(); + const getChatMemoized = useGetChatMemoized(); + + const subscribe = !!chatId; + + const shape = useMemo(() => messagesShape({ chatId: chatId || '' }), [chatId]); + + return useShapeStream( + shape, + insertOperations, + useMemoizedFn((message) => { + if (message && message.value && chatId) { + const messageId = message.value.id; + const chat = getChatMemoized(chatId); + + if (chat && messageId) { + const currentMessageIds = chat.message_ids; + const allMessageIds = uniq([...currentMessageIds, messageId]); + + if (currentMessageIds.length !== allMessageIds.length) { + onUpdateChat({ + ...chat, + id: chatId, + message_ids: allMessageIds + }); + } + } + } + }), + subscribe + ); +}; diff --git a/apps/web/src/layouts/ChatLayout/ChatContext/useChatStreaming.tsx b/apps/web/src/layouts/ChatLayout/ChatContext/useChatStreaming.tsx index f326ff384..8326d5a0f 100644 --- a/apps/web/src/layouts/ChatLayout/ChatContext/useChatStreaming.tsx +++ b/apps/web/src/layouts/ChatLayout/ChatContext/useChatStreaming.tsx @@ -5,7 +5,7 @@ import { updateChatToIChat } from '@/lib/chat'; import { useQueryClient } from '@tanstack/react-query'; import { prefetchGetMetricDataClient } from '@/api/buster_rest/metrics'; import { queryKeys } from '@/api/query_keys'; -import { useTrackAndUpdateMessageChanges } from '@/api/buster-electric/messages'; +import { useTrackAndUpdateMessageChanges, useTrackAndUpdateNewMessages } from '@/api/buster-electric/messages'; import { useTrackAndUpdateChatChanges } from '@/api/buster-electric/chats'; import { useEffect } from 'react'; import { useGetChatMessageMemoized } from '@/api/buster_rest/chats'; @@ -90,6 +90,7 @@ export const useChatStreaming = ({ //HOOKS FOR TRACKING CHAT AND MESSAGE CHANGES useTrackAndUpdateChatChanges({ chatId, isStreamingMessage }); + useTrackAndUpdateNewMessages({ chatId }); useTrackAndUpdateMessageChanges({ chatId, messageId, isStreamingMessage }, (c) => { const { reasoning_messages, From f4b24500e15b6ae0759fcc938644762e0f958ea8 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Mon, 21 Jul 2025 15:39:14 -0600 Subject: [PATCH 2/3] check for new chats update --- .../src/api/buster-electric/messages/hooks.ts | 23 +++++++++++++++---- .../api/buster-electric/messages/shapes.ts | 14 +++++++---- .../api/buster_rest/chats/queryRequests.ts | 16 ++++++++++++- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/apps/web/src/api/buster-electric/messages/hooks.ts b/apps/web/src/api/buster-electric/messages/hooks.ts index 174bfae4c..59191d74a 100644 --- a/apps/web/src/api/buster-electric/messages/hooks.ts +++ b/apps/web/src/api/buster-electric/messages/hooks.ts @@ -4,15 +4,21 @@ import { useShape, useShapeStream } from '../instances'; import { useChatUpdate } from '@/context/Chats/useChatUpdate'; import { updateMessageShapeToIChatMessage } from './helpers'; import { useMemoizedFn } from '@/hooks'; -import { prefetchGetListChats, useGetChatMemoized } from '@/api/buster_rest/chats'; +import { + prefetchGetListChats, + prefetchGetChat, + useGetChatMemoized, + useGetChatMessageMemoized, + useGetChat +} from '@/api/buster_rest/chats'; import uniq from 'lodash/uniq'; import type { ChatMessageResponseMessage_File } from '@buster/server-shared/chats'; import type { BusterChatMessage } from '../../asset_interfaces/chat'; import { useQueryClient } from '@tanstack/react-query'; import { dashboardQueryKeys } from '../../query_keys/dashboard'; -import last from 'lodash/last'; import isEmpty from 'lodash/isEmpty'; import { metricsQueryKeys } from '../../query_keys/metric'; +import { chatQueryKeys } from '../../query_keys/chat'; export const useGetMessage = ({ chatId, messageId }: { chatId: string; messageId: string }) => { const shape = useMemo(() => messageShape({ chatId, messageId }), [chatId, messageId]); @@ -135,10 +141,12 @@ const useCheckIfWeHaveAFollowupDashboard = (messageId: string) => { export const useTrackAndUpdateNewMessages = ({ chatId }: { chatId: string | undefined }) => { const { onUpdateChat } = useChatUpdate(); const getChatMemoized = useGetChatMemoized(); + const getChatMessageMemoized = useGetChatMessageMemoized(); + const queryClient = useQueryClient(); const subscribe = !!chatId; - const shape = useMemo(() => messagesShape({ chatId: chatId || '' }), [chatId]); + const shape = useMemo(() => messagesShape({ chatId: chatId || '', columns: ['id'] }), [chatId]); return useShapeStream( shape, @@ -151,13 +159,20 @@ export const useTrackAndUpdateNewMessages = ({ chatId }: { chatId: string | unde if (chat && messageId) { const currentMessageIds = chat.message_ids; const allMessageIds = uniq([...currentMessageIds, messageId]); - + if (currentMessageIds.length !== allMessageIds.length) { onUpdateChat({ ...chat, id: chatId, message_ids: allMessageIds }); + + const messageIsStored = getChatMessageMemoized(messageId); + if (!messageIsStored) { + queryClient.invalidateQueries({ + queryKey: chatQueryKeys.chatsGetChat(chatId).queryKey + }); + } } } } diff --git a/apps/web/src/api/buster-electric/messages/shapes.ts b/apps/web/src/api/buster-electric/messages/shapes.ts index 28d7197a0..5bc687886 100644 --- a/apps/web/src/api/buster-electric/messages/shapes.ts +++ b/apps/web/src/api/buster-electric/messages/shapes.ts @@ -15,7 +15,7 @@ export type BusterChatMessageShape = { is_completed: boolean; }; -const columns: (keyof BusterChatMessageShape)[] = [ +const MESSAGE_DEFAULT_COLUMNS: (keyof BusterChatMessageShape)[] = [ 'id', 'response_messages', 'reasoning', @@ -36,18 +36,24 @@ export const messageShape = ({ params: { table: 'messages', where: `chat_id='${chatId}' AND id='${messageId}'`, - columns, + columns: MESSAGE_DEFAULT_COLUMNS, replica: 'default' } }; }; export const messagesShape = ({ - chatId + chatId, + columns = MESSAGE_DEFAULT_COLUMNS }: { chatId: string; + columns?: (keyof BusterChatMessageShape)[]; }): ElectricShapeOptions => { return { - params: { table: 'messages', where: `chat_id='${chatId}'`, columns } + params: { + table: 'messages', + where: `chat_id='${chatId}'`, + columns: columns || MESSAGE_DEFAULT_COLUMNS + } }; }; diff --git a/apps/web/src/api/buster_rest/chats/queryRequests.ts b/apps/web/src/api/buster_rest/chats/queryRequests.ts index f4d560daf..24ceae339 100644 --- a/apps/web/src/api/buster_rest/chats/queryRequests.ts +++ b/apps/web/src/api/buster_rest/chats/queryRequests.ts @@ -152,7 +152,7 @@ export const useStartChatFromAsset = () => { }); }; -export const prefetchGetChat = async ( +export const prefetchGetChatServer = async ( params: Parameters[0], queryClientProp?: QueryClient ) => { @@ -170,6 +170,20 @@ export const prefetchGetChat = async ( return queryClient; }; +export const prefetchGetChat = async ( + params: Parameters[0], + queryClientProp?: QueryClient +) => { + const queryClient = queryClientProp || new QueryClient(); + + await queryClient.prefetchQuery({ + ...chatQueryKeys.chatsGetChat(params.id), + queryFn: () => getChat(params) + }); + + return queryClient; +}; + export const useUpdateChat = (params?: { updateToServer?: boolean }) => { const queryClient = useQueryClient(); const { updateToServer = true } = params || {}; From bebdcdc347befe30a84eaa0c20f1ccf92a423150 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Mon, 21 Jul 2025 15:43:43 -0600 Subject: [PATCH 3/3] Update list invlidation logic --- apps/web/src/api/buster-electric/messages/hooks.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/web/src/api/buster-electric/messages/hooks.ts b/apps/web/src/api/buster-electric/messages/hooks.ts index 323b2a7f3..6837f566d 100644 --- a/apps/web/src/api/buster-electric/messages/hooks.ts +++ b/apps/web/src/api/buster-electric/messages/hooks.ts @@ -42,6 +42,7 @@ export const useTrackAndUpdateMessageChanges = ( const { onUpdateChatMessage, onUpdateChat } = useChatUpdate(); const checkIfWeHaveAFollowupDashboard = useCheckIfWeHaveAFollowupDashboard(messageId); const getChatMemoized = useGetChatMemoized(); + const queryClient = useQueryClient(); const subscribe = !!chatId && !!messageId && messageId !== 'undefined'; @@ -78,16 +79,20 @@ export const useTrackAndUpdateMessageChanges = ( (reasoningMessage as ChatMessageResponseMessage_File)?.file_type === 'dashboard' ); }); - if (hasFiles) { - prefetchGetChatsList(); - } if (!isEmpty(iChatMessage.response_message_ids)) { checkIfWeHaveAFollowupDashboard(iChatMessage); } if (iChatMessage.is_completed) { - prefetchGetChatsList(); + queryClient.invalidateQueries({ + queryKey: chatQueryKeys.chatsGetList().queryKey + }); + if (hasFiles) { + queryClient.invalidateQueries({ + queryKey: metricsQueryKeys.metricsGetList().queryKey + }); + } } } callback?.(iChatMessage);