start chat from asset 🦚

This commit is contained in:
Nate Kelley 2025-04-09 17:09:06 -06:00
parent fec77bda4b
commit 6c994bb9e8
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
15 changed files with 127 additions and 48 deletions

View File

@ -8,7 +8,8 @@ import {
updateChat, updateChat,
deleteChat, deleteChat,
getListLogs, getListLogs,
duplicateChat duplicateChat,
startChatFromAsset
} from './requests'; } from './requests';
import type { IBusterChat, IBusterChatMessage } from '@/api/asset_interfaces/chat'; import type { IBusterChat, IBusterChatMessage } from '@/api/asset_interfaces/chat';
import { queryKeys } from '@/api/query_keys'; import { queryKeys } from '@/api/query_keys';
@ -78,7 +79,6 @@ export const useGetChat = <TData = IBusterChat>(
const queryFn = useMemoizedFn(() => { const queryFn = useMemoizedFn(() => {
return getChat(params).then((chat) => { return getChat(params).then((chat) => {
const { iChat, iChatMessages } = updateChatToIChat(chat, false); const { iChat, iChatMessages } = updateChatToIChat(chat, false);
const lastMessageId = last(iChat.message_ids); const lastMessageId = last(iChat.message_ids);
const lastMessage = iChatMessages[lastMessageId!]; const lastMessage = iChatMessages[lastMessageId!];
if (lastMessage) { if (lastMessage) {
@ -108,6 +108,32 @@ export const useGetChat = <TData = IBusterChat>(
}); });
}; };
export const useStartChatFromAsset = () => {
const queryClient = useQueryClient();
const mutationFn = useMemoizedFn(async (params: Parameters<typeof startChatFromAsset>[0]) => {
const chat = await startChatFromAsset(params);
const { iChat, iChatMessages } = updateChatToIChat(chat, false);
iChat.message_ids.forEach((messageId) => {
queryClient.setQueryData(
queryKeys.chatsMessages(messageId).queryKey,
iChatMessages[messageId]
);
});
queryClient.setQueryData(queryKeys.chatsGetChat(chat.id).queryKey, iChat);
return iChat;
});
return useMutation({
mutationFn,
onSuccess: (chat) => {
queryClient.invalidateQueries({
queryKey: queryKeys.chatsGetList().queryKey
});
}
});
};
export const prefetchGetChat = async ( export const prefetchGetChat = async (
params: Parameters<typeof getChat>[0], params: Parameters<typeof getChat>[0],
queryClientProp?: QueryClient queryClientProp?: QueryClient

View File

@ -75,3 +75,18 @@ export const duplicateChat = async ({
}): Promise<BusterChat> => { }): Promise<BusterChat> => {
return mainApi.post(`${CHATS_BASE}/duplicate`, { id, message_id }).then((res) => res.data); return mainApi.post(`${CHATS_BASE}/duplicate`, { id, message_id }).then((res) => res.data);
}; };
export const startChatFromAsset = async ({
asset_id,
asset_type
}: {
asset_id: string;
asset_type: 'metric' | 'dashboard';
}): Promise<BusterChat> => {
return mainApi
.post(`${CHATS_BASE}`, {
asset_id,
asset_type
})
.then((res) => res.data);
};

View File

@ -171,7 +171,7 @@ export const useUpdateDashboardConfig = (params?: {
saveToServer?: boolean; saveToServer?: boolean;
updateVersion?: boolean; updateVersion?: boolean;
}) => { }) => {
const { saveToServer = false, updateVersion = true } = params || {}; const { saveToServer = false, updateVersion = false } = params || {};
const { mutateAsync } = useUpdateDashboard({ const { mutateAsync } = useUpdateDashboard({
saveToServer, saveToServer,
updateVersion updateVersion

View File

@ -296,7 +296,11 @@ export const DropdownContent = <T,>({
)} )}
</div> </div>
{footerContent && <div className={cn('border-t p-1', footerClassName)}>{footerContent}</div>} {footerContent && (
<div className={cn(hasShownItem && 'border-t', 'p-1', footerClassName)}>
{footerContent}
</div>
)}
</> </>
); );
}; };

View File

@ -23,7 +23,6 @@ export const useUpdateMetricChart = (props?: { metricId?: string }) => {
saveToServer: false saveToServer: false
}); });
const { mutateAsync: saveMetricToServer } = useUpdateMetric({ const { mutateAsync: saveMetricToServer } = useUpdateMetric({
updateVersion: true,
updateOnSave: true, updateOnSave: true,
saveToServer: true saveToServer: true
}); });

View File

@ -52,9 +52,7 @@ export const MetricViewChart: React.FC<{
} = useGetMetricData({ id: metricId }); } = useGetMetricData({ id: metricId });
const { mutate: updateMetric } = useUpdateMetric({ const { mutate: updateMetric } = useUpdateMetric({
updateOnSave: false, saveToServer: false
updateVersion: true,
saveToServer: true
}); });
const { name, description, time_frame, evaluation_score, evaluation_summary } = metric || {}; const { name, description, time_frame, evaluation_score, evaluation_summary } = metric || {};

View File

@ -25,8 +25,7 @@ export const MetricViewFile: React.FC<{ metricId: string }> = React.memo(({ metr
} = useUpdateMetric({ } = useUpdateMetric({
updateOnSave: true, updateOnSave: true,
saveToServer: true, saveToServer: true,
updateVersion: true, updateVersion: false
wait: 0
}); });
const { isReadOnly } = useIsMetricReadOnly({ const { isReadOnly } = useIsMetricReadOnly({

View File

@ -16,8 +16,7 @@ export const useMetricRunSQL = () => {
const { mutateAsync: stageMetric } = useUpdateMetric({ const { mutateAsync: stageMetric } = useUpdateMetric({
updateVersion: false, updateVersion: false,
saveToServer: false, saveToServer: false,
updateOnSave: false, updateOnSave: false
wait: 0
}); });
const { const {
mutateAsync: saveMetric, mutateAsync: saveMetric,
@ -26,8 +25,7 @@ export const useMetricRunSQL = () => {
} = useUpdateMetric({ } = useUpdateMetric({
updateOnSave: true, updateOnSave: true,
updateVersion: true, updateVersion: true,
saveToServer: true, saveToServer: true
wait: 0
}); });
const { const {
mutateAsync: runSQLMutation, mutateAsync: runSQLMutation,

View File

@ -19,16 +19,19 @@ export const ChatResponseMessages: React.FC<ChatResponseMessagesProps> = React.m
(x) => x?.reasoning_message_ids?.[x.reasoning_message_ids.length - 1] (x) => x?.reasoning_message_ids?.[x.reasoning_message_ids.length - 1]
); );
const finalReasoningMessage = useGetChatMessage(messageId, (x) => x?.final_reasoning_message); const finalReasoningMessage = useGetChatMessage(messageId, (x) => x?.final_reasoning_message);
const showReasoningMessage = !!lastReasoningMessageId || !isCompletedStream;
return ( return (
<MessageContainer className="flex w-full flex-col space-y-3 overflow-hidden"> <MessageContainer className="flex w-full flex-col space-y-3 overflow-hidden">
<ChatResponseReasoning {showReasoningMessage && (
reasoningMessageId={lastReasoningMessageId} <ChatResponseReasoning
finalReasoningMessage={finalReasoningMessage} reasoningMessageId={lastReasoningMessageId}
isCompletedStream={isCompletedStream} finalReasoningMessage={finalReasoningMessage}
messageId={messageId} isCompletedStream={isCompletedStream}
chatId={chatId} messageId={messageId}
/> chatId={chatId}
/>
)}
{responseMessageIds.map((responseMessageId, index) => ( {responseMessageIds.map((responseMessageId, index) => (
<React.Fragment key={responseMessageId}> <React.Fragment key={responseMessageId}>

View File

@ -163,7 +163,7 @@ export const useLayoutConfig = ({
return 'chat'; return 'chat';
} }
return 'file'; return 'file';
}, [selectedFileId]); }, [selectedFileId, chatId]);
useEffect(() => { useEffect(() => {
if ( if (

View File

@ -1,29 +1,67 @@
import { Stars } from '@/components/ui/icons'; import { Stars } from '@/components/ui/icons';
import { Button } from '@/components/ui/buttons'; import { Button } from '@/components/ui/buttons';
import React from 'react'; import React, { useState } from 'react';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { AppTooltip } from '@/components/ui/tooltip'; import { AppTooltip } from '@/components/ui/tooltip';
import { useStartChatFromAsset } from '@/api/buster_rest/chats/queryRequests';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { BusterRoutes } from '@/routes';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { timeout } from '@/lib';
export const CreateChatButton = React.memo(() => { export const CreateChatButton = React.memo(
const onCollapseFileClickPreflight = useMemoizedFn(() => { ({ assetId, assetType }: { assetId: string; assetType: 'metric' | 'dashboard' }) => {
// onCollapseFileClick(false); const [loading, setLoading] = useState(false);
alert('TODO'); const { mutateAsync: startChatFromAsset, isPending } = useStartChatFromAsset();
}); const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
useHotkeys('e', onCollapseFileClickPreflight, { preventDefault: true }); const onCreateFileClick = useMemoizedFn(async () => {
setLoading(true);
try {
const result = await startChatFromAsset({ asset_id: assetId, asset_type: assetType });
return ( if (assetType === 'metric') {
<AppTooltip title={'Start chat'} shortcuts={['e']}> await onChangePage({
<Button route: BusterRoutes.APP_CHAT_ID_METRIC_ID_CHART,
onClick={onCollapseFileClickPreflight} metricId: assetId,
variant="black" chatId: result.id
className="ml-1.5" });
prefix={<Stars />}> } else if (assetType === 'dashboard') {
Start chat await onChangePage({
</Button> route: BusterRoutes.APP_CHAT_ID_DASHBOARD_ID,
</AppTooltip> dashboardId: assetId,
); chatId: result.id
}); });
}
await timeout(250); //wait for the chat to load and the file to be selected
onSetFileView({
fileId: assetId,
fileView: 'chart'
});
} catch (error) {
//
} finally {
setLoading(false);
}
});
useHotkeys('e', onCreateFileClick, { preventDefault: true });
return (
<AppTooltip title={'Start chat'} shortcuts={['e']} delayDuration={650}>
<Button
loading={isPending || loading}
onClick={onCreateFileClick}
variant="black"
className="ml-1.5"
prefix={<Stars />}>
Start chat
</Button>
</AppTooltip>
);
}
);
CreateChatButton.displayName = 'CreateChatButton'; CreateChatButton.displayName = 'CreateChatButton';

View File

@ -31,7 +31,7 @@ export const DashboardHeaderButtons: React.FC<{
{isEditor && <AddContentToDashboardButton />} {isEditor && <AddContentToDashboardButton />}
<DashboardThreeDotMenu dashboardId={dashboardId} /> <DashboardThreeDotMenu dashboardId={dashboardId} />
<HideButtonContainer show> <HideButtonContainer show>
<CreateChatButton /> <CreateChatButton assetId={dashboardId} assetType="dashboard" />
</HideButtonContainer> </HideButtonContainer>
</FileButtonContainer> </FileButtonContainer>
); );

View File

@ -50,7 +50,7 @@ export const DashboardThreeDotMenu = React.memo(({ dashboardId }: { dashboardId:
{ id: dashboardId }, { id: dashboardId },
{ select: (x) => x.permission } { select: (x) => x.permission }
); );
const isOwner = getIsEffectiveOwner(permission); const isEffectiveOwner = getIsEffectiveOwner(permission);
const isFilter = canFilter(permission); const isFilter = canFilter(permission);
const isEditor = canEdit(permission); const isEditor = canEdit(permission);
@ -60,13 +60,13 @@ export const DashboardThreeDotMenu = React.memo(({ dashboardId }: { dashboardId:
isFilter && filterDashboardMenu, isFilter && filterDashboardMenu,
isEditor && addContentToDashboardMenu, isEditor && addContentToDashboardMenu,
{ type: 'divider' }, { type: 'divider' },
isOwner && shareMenu, isEffectiveOwner && shareMenu,
collectionSelectMenu, collectionSelectMenu,
favoriteDashboard, favoriteDashboard,
versionHistoryItems, versionHistoryItems,
{ type: 'divider' }, { type: 'divider' },
isEditor && renameDashboardMenu, isEditor && renameDashboardMenu,
isOwner && deleteDashboardMenu isEffectiveOwner && deleteDashboardMenu
].filter(Boolean) as DropdownItems, ].filter(Boolean) as DropdownItems,
[ [
filterDashboardMenu, filterDashboardMenu,

View File

@ -41,7 +41,7 @@ export const MetricContainerHeaderButtons: React.FC<FileContainerButtonsProps> =
{isEffectiveOwner && <ShareMetricButton metricId={metricId} />} {isEffectiveOwner && <ShareMetricButton metricId={metricId} />}
<ThreeDotMenuButton metricId={metricId} /> <ThreeDotMenuButton metricId={metricId} />
<HideButtonContainer show={selectedLayout === 'file'}> <HideButtonContainer show={selectedLayout === 'file'}>
<CreateChatButton /> <CreateChatButton assetId={metricId} assetType="metric" />
</HideButtonContainer> </HideButtonContainer>
</FileButtonContainer> </FileButtonContainer>
); );

View File

@ -104,7 +104,6 @@ export const ThreeDotMenuButton = React.memo(({ metricId }: { metricId: string }
deleteMetricMenu, deleteMetricMenu,
downloadCSVMenu, downloadCSVMenu,
downloadPNGMenu, downloadPNGMenu,
metricId,
openSuccessMessage, openSuccessMessage,
onSetSelectedFile, onSetSelectedFile,
versionHistoryItems, versionHistoryItems,