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,
deleteChat,
getListLogs,
duplicateChat
duplicateChat,
startChatFromAsset
} from './requests';
import type { IBusterChat, IBusterChatMessage } from '@/api/asset_interfaces/chat';
import { queryKeys } from '@/api/query_keys';
@ -78,7 +79,6 @@ export const useGetChat = <TData = IBusterChat>(
const queryFn = useMemoizedFn(() => {
return getChat(params).then((chat) => {
const { iChat, iChatMessages } = updateChatToIChat(chat, false);
const lastMessageId = last(iChat.message_ids);
const lastMessage = iChatMessages[lastMessageId!];
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 (
params: Parameters<typeof getChat>[0],
queryClientProp?: QueryClient

View File

@ -75,3 +75,18 @@ export const duplicateChat = async ({
}): Promise<BusterChat> => {
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;
updateVersion?: boolean;
}) => {
const { saveToServer = false, updateVersion = true } = params || {};
const { saveToServer = false, updateVersion = false } = params || {};
const { mutateAsync } = useUpdateDashboard({
saveToServer,
updateVersion

View File

@ -296,7 +296,11 @@ export const DropdownContent = <T,>({
)}
</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
});
const { mutateAsync: saveMetricToServer } = useUpdateMetric({
updateVersion: true,
updateOnSave: true,
saveToServer: true
});

View File

@ -52,9 +52,7 @@ export const MetricViewChart: React.FC<{
} = useGetMetricData({ id: metricId });
const { mutate: updateMetric } = useUpdateMetric({
updateOnSave: false,
updateVersion: true,
saveToServer: true
saveToServer: false
});
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({
updateOnSave: true,
saveToServer: true,
updateVersion: true,
wait: 0
updateVersion: false
});
const { isReadOnly } = useIsMetricReadOnly({

View File

@ -16,8 +16,7 @@ export const useMetricRunSQL = () => {
const { mutateAsync: stageMetric } = useUpdateMetric({
updateVersion: false,
saveToServer: false,
updateOnSave: false,
wait: 0
updateOnSave: false
});
const {
mutateAsync: saveMetric,
@ -26,8 +25,7 @@ export const useMetricRunSQL = () => {
} = useUpdateMetric({
updateOnSave: true,
updateVersion: true,
saveToServer: true,
wait: 0
saveToServer: true
});
const {
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]
);
const finalReasoningMessage = useGetChatMessage(messageId, (x) => x?.final_reasoning_message);
const showReasoningMessage = !!lastReasoningMessageId || !isCompletedStream;
return (
<MessageContainer className="flex w-full flex-col space-y-3 overflow-hidden">
<ChatResponseReasoning
reasoningMessageId={lastReasoningMessageId}
finalReasoningMessage={finalReasoningMessage}
isCompletedStream={isCompletedStream}
messageId={messageId}
chatId={chatId}
/>
{showReasoningMessage && (
<ChatResponseReasoning
reasoningMessageId={lastReasoningMessageId}
finalReasoningMessage={finalReasoningMessage}
isCompletedStream={isCompletedStream}
messageId={messageId}
chatId={chatId}
/>
)}
{responseMessageIds.map((responseMessageId, index) => (
<React.Fragment key={responseMessageId}>

View File

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

View File

@ -1,29 +1,67 @@
import { Stars } from '@/components/ui/icons';
import { Button } from '@/components/ui/buttons';
import React from 'react';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import React, { useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useMemoizedFn } from '@/hooks';
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(() => {
const onCollapseFileClickPreflight = useMemoizedFn(() => {
// onCollapseFileClick(false);
alert('TODO');
});
export const CreateChatButton = React.memo(
({ assetId, assetType }: { assetId: string; assetType: 'metric' | 'dashboard' }) => {
const [loading, setLoading] = useState(false);
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 (
<AppTooltip title={'Start chat'} shortcuts={['e']}>
<Button
onClick={onCollapseFileClickPreflight}
variant="black"
className="ml-1.5"
prefix={<Stars />}>
Start chat
</Button>
</AppTooltip>
);
});
if (assetType === 'metric') {
await onChangePage({
route: BusterRoutes.APP_CHAT_ID_METRIC_ID_CHART,
metricId: assetId,
chatId: result.id
});
} else if (assetType === 'dashboard') {
await onChangePage({
route: BusterRoutes.APP_CHAT_ID_DASHBOARD_ID,
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';

View File

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

View File

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

View File

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

View File

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