add delete chat area

This commit is contained in:
Nate Kelley 2025-02-06 15:06:37 -07:00
parent 7d95f1f008
commit 98d62419b9
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
14 changed files with 221 additions and 113 deletions

View File

@ -40,8 +40,11 @@ export interface BusterDashboard
deleted_at: string | null;
description: string | null;
id: string;
name: string;
title: string;
updated_at: string | null;
updated_by: string;
status: VerificationStatus;
version_number: number;
file: string; //yaml file
file_name: string;
}

View File

@ -25,7 +25,7 @@ export const DashboardViewDashboardController: React.FC<DashboardViewProps> = ({
});
return (
<div className="flex flex-col space-y-3 overflow-y-auto p-5">
<div className="flex flex-col space-y-3 overflow-y-auto p-10">
<DashboardEditTitles
onUpdateDashboard={onUpdateDashboard}
allowEdit={allowEdit}

View File

@ -1,18 +1,24 @@
import { Dropdown, MenuProps } from 'antd';
import React, { useMemo } from 'react';
import { HeaderOptionsRecord } from './config';
import { useChatContextSelector } from '../../../ChatContext';
import { AppMaterialIcons } from '@/components/icons';
export const ChatContainerHeaderDropdown: React.FC<{
children: React.ReactNode;
}> = React.memo(({ children }) => {
const selectedFileType = useChatContextSelector((state) => state.selectedFileType);
const hasChat = useChatContextSelector((state) => state.hasChat);
const onDeleteChat = useChatContextSelector((state) => state.onDeleteChat);
const menuItem: MenuProps['items'] = useMemo(() => {
if (!selectedFileType || !(selectedFileType in HeaderOptionsRecord))
return [] as MenuProps['items'];
return HeaderOptionsRecord[selectedFileType]();
}, [selectedFileType]);
return [
{
label: 'Delete chat',
key: 'delete',
icon: <AppMaterialIcons icon="delete" />,
onClick: () => onDeleteChat()
}
];
}, []);
const menu = useMemo(() => {
return {
@ -23,7 +29,7 @@ export const ChatContainerHeaderDropdown: React.FC<{
return (
<div>
<Dropdown menu={menu} trigger={['click']}>
{children}
{hasChat ? children : null}
</Dropdown>
</div>
);

View File

@ -1,36 +0,0 @@
import type { FileType } from '@/api/asset_interfaces';
import { AppMaterialIcons } from '@/components/icons';
import type { MenuProps } from 'antd';
export const HeaderOptionsRecord: Record<FileType, () => MenuProps['items']> = {
dataset: () => [
{
label: 'Delete',
key: 'delete',
icon: <AppMaterialIcons icon="delete" />
}
],
collection: () => [
{
label: 'Delete',
key: 'delete',
icon: <AppMaterialIcons icon="delete" />
}
],
metric: () => [
{
label: 'Delete',
key: 'delete',
icon: <AppMaterialIcons icon="delete" />
}
],
dashboard: () => [
{
label: 'Delete',
key: 'delete',
icon: <AppMaterialIcons icon="delete" />
}
],
term: () => [],
value: () => []
};

View File

@ -6,6 +6,7 @@ import {
} from '@fluentui/react-context-selector';
import { useBusterChatIndividual } from '@/context/Chats';
import type { SelectedFile } from '../interfaces';
import { useChatAssociation } from './useChatAssosciation';
export const useChatContext = ({
chatId,
@ -16,12 +17,11 @@ export const useChatContext = ({
}) => {
const selectedFileId = defaultSelectedFile?.id;
const selectedFileType = defaultSelectedFile?.type;
const metricId = defaultSelectedFile?.type === 'metric' ? defaultSelectedFile?.id : undefined;
//CHAT
const { chat } = useBusterChatIndividual({
chatId,
metricId
defaultSelectedFile
});
const hasChat = !!chatId && !!chat;
const chatTitle = chat?.title;
@ -33,7 +33,11 @@ export const useChatContext = ({
//MESSAGES
const currentMessageId = chatMessages[chatMessages.length - 1]?.id;
//ASSOCIATION
const association = useChatAssociation({ chatId });
return {
...association,
hasChat,
hasFile,
selectedFileId,

View File

@ -0,0 +1,9 @@
import { useMemoizedFn } from 'ahooks';
export const useChatAssociation = ({ chatId }: { chatId?: string }) => {
const onDeleteChat = useMemoizedFn(async (chatIdProp?: string) => {
console.log('delete chat', chatIdProp);
});
return { onDeleteChat };
};

View File

@ -22,6 +22,7 @@ export const CreateChatButton = React.memo(() => {
color="default"
variant="solid"
type="primary"
className="ml-1.5"
icon={<AppMaterialIcons icon="stars" />}>
Edit
</Button>

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { FileContainerButtonsProps } from './interfaces';
import { FileButtonContainer } from './FileButtonContainer';
import { useChatContextSelector } from '../../ChatContext';
@ -7,8 +7,13 @@ import { HideButtonContainer } from './HideButtonContainer';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { CreateChatButton } from './CreateChatButtont';
import { ShareDashboardButton } from '@appComponents/Buttons/ShareDashboardButton';
import { Button } from 'antd';
import { Button, Dropdown } from 'antd';
import { AppMaterialIcons } from '@/components/icons';
import { MenuProps } from 'antd/lib';
import { useDashboardContextSelector } from '@/context/Dashboards';
import { useRouter } from 'next/router';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { BusterRoutes } from '@/routes';
export const DashboardContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(
() => {
@ -19,6 +24,7 @@ export const DashboardContainerHeaderButtons: React.FC<FileContainerButtonsProps
<FileButtonContainer>
<SaveToCollectionButton />
<ShareDashboardButton dashboardId={selectedFileId} /> <AddContentToDashboardButton />
<ThreeDotMenu dashboardId={selectedFileId} />
<HideButtonContainer show={isPureFile}>
<CreateChatButton />
</HideButtonContainer>
@ -43,3 +49,31 @@ const AddContentToDashboardButton = React.memo(() => {
);
});
AddContentToDashboardButton.displayName = 'AddContentToDashboardButton';
const ThreeDotMenu = React.memo(({ dashboardId }: { dashboardId: string }) => {
const onDeleteDashboard = useDashboardContextSelector((x) => x.onDeleteDashboard);
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
const menu: MenuProps = useMemo(() => {
return {
items: [
{
label: 'Delete',
key: 'delete',
icon: <AppMaterialIcons icon="delete" />,
onClick: async () => {
await onDeleteDashboard(dashboardId);
onChangePage({ route: BusterRoutes.APP_DASHBOARDS });
}
}
]
};
}, [dashboardId, onDeleteDashboard, onChangePage]);
return (
<Dropdown menu={menu}>
<Button type="text" icon={<AppMaterialIcons icon="more_horiz" />} />
</Dropdown>
);
});
ThreeDotMenu.displayName = 'ThreeDotMenu';

View File

@ -11,8 +11,8 @@ import { createMockResponseMessageThought, MOCK_CHAT } from './MOCK_CHAT';
import { IBusterChat } from './interfaces';
import { chatUpgrader } from './helpers';
import { useHotkeys } from 'react-hotkeys-hook';
import { useBusterMetricsContextSelector } from '../Metrics';
import { fallbackToMetricChat } from './helpers/fallbackToMetricChat';
import { useFileFallback } from './helpers/useFileFallback';
import { SelectedFile } from '@/app/app/_layouts/ChatLayout';
export const useBusterChat = () => {
const busterSocket = useBusterWebSocket();
@ -104,24 +104,19 @@ export const useBusterChatContextSelector = <T,>(
export const useBusterChatIndividual = ({
chatId: chatIdProp,
metricId
defaultSelectedFile
}: {
chatId?: string;
//if metricId is provided (without a chatId), we can assume that the chat is a new chat starting from a metric
metricId?: string;
defaultSelectedFile?: SelectedFile;
}) => {
const chatId = chatIdProp || '';
const chat: IBusterChat | undefined = useBusterChatContextSelector((x) => x.chats[chatId]);
const subscribeToChat = useBusterChatContextSelector((x) => x.subscribeToChat);
const unsubscribeFromChat = useBusterChatContextSelector((x) => x.unsubscribeFromChat);
const metricTitle = useBusterMetricsContextSelector((x) => x.metrics[metricId || '']?.title);
const metricVersionNumber = useBusterMetricsContextSelector(
(x) => x.metrics[metricId || '']?.version_number
);
const memoizedFallbackToMetricChat = useMemo(() => {
return fallbackToMetricChat({ id: metricId || '', title: metricTitle, metricVersionNumber });
}, [metricId, metricVersionNumber, metricTitle]);
const memoizedFallbackToMetricChat = useFileFallback({
defaultSelectedFile
});
useEffect(() => {
if (chatId) subscribeToChat({ chatId });

View File

@ -1,50 +0,0 @@
import { IBusterChat } from '../interfaces';
export const fallbackToMetricChat = (metric: {
id: string;
title: string | undefined;
metricVersionNumber: number;
}): IBusterChat => {
return {
id: metric.id,
isNewChat: true,
isFollowupMessage: false,
messages: [
{
request_message: null,
response_messages: [
{
id: 'init',
type: 'text',
message: `Ive pulled in your metric. How can I help? Is there anything you'd like to modify?`
},
{
id: metric.id,
type: 'file',
file_type: 'metric',
file_name: metric.title || 'New Metric',
version_number: metric.metricVersionNumber,
version_id: metric.id,
metadata: [
{
status: 'completed',
message: 'Retrieved metric'
}
]
}
],
created_at: '',
id: 'init-message',
isCompletedStream: false
}
],
title: metric.title || 'New Chat',
is_favorited: false,
updated_at: '',
created_at: '',
created_by: '',
created_by_id: '',
created_by_name: '',
created_by_avatar: ''
};
};

View File

@ -0,0 +1,115 @@
import { SelectedFile } from '@/app/app/_layouts/ChatLayout';
import { useDashboardContextSelector } from '@/context/Dashboards';
import { useBusterMetricsContextSelector } from '@/context/Metrics';
import { useMemo } from 'react';
import { IBusterChat } from '../interfaces';
export const useFileFallback = ({
defaultSelectedFile
}: {
//if metricId is provided (without a chatId), we can assume that the chat is a new chat starting from a metric
defaultSelectedFile?: SelectedFile;
}) => {
const fileId = defaultSelectedFile?.id || '';
const metricTitle = useBusterMetricsContextSelector((x) => x.metrics[fileId]?.title);
const metricVersionNumber = useBusterMetricsContextSelector(
(x) => x.metrics[fileId]?.version_number
);
const dashboardTitle = useDashboardContextSelector((x) => x.dashboards[fileId]?.dashboard?.title);
const dashboardVersionNumber = useDashboardContextSelector(
(x) => x.dashboards[fileId]?.dashboard?.version_number
);
const fileType: 'metric' | 'dashboard' = useMemo(() => {
if (defaultSelectedFile?.type === 'metric') {
return 'metric';
} else {
return 'dashboard';
}
}, [defaultSelectedFile]);
const title = useMemo(() => {
if (fileType === 'metric') {
return metricTitle;
} else if (fileType === 'dashboard') {
return dashboardTitle;
}
}, [fileType, metricTitle, dashboardTitle]);
const versionNumber = useMemo(() => {
if (fileType === 'metric') {
return metricVersionNumber || 1;
} else if (fileType === 'dashboard') {
return dashboardVersionNumber || 1;
}
return 1;
}, [fileType, metricVersionNumber, dashboardVersionNumber]);
const memoizedFallbackToMetricChat = useMemo(() => {
console.log(fileType, defaultSelectedFile);
return fallbackToFileChat({
id: fileId,
title: title,
versionNumber: versionNumber,
type: fileType
});
}, [fileId, title, versionNumber, fileType]);
return memoizedFallbackToMetricChat;
};
const fallbackToFileChat = ({
id,
title,
versionNumber,
type = 'metric'
}: {
id: string;
title: string | undefined;
versionNumber: number;
type?: 'metric' | 'dashboard';
}): IBusterChat => {
console.log(type);
return {
id,
isNewChat: true,
isFollowupMessage: false,
messages: [
{
request_message: null,
response_messages: [
{
id: 'init',
type: 'text',
message: `Ive pulled in your ${type}. How can I help? Is there anything you'd like to modify?`
},
{
id,
type: 'file',
file_type: type,
file_name: title || `New ${type}`,
version_number: versionNumber,
version_id: id,
metadata: [
{
status: 'completed',
message: `Retrieved ${type}`
}
]
}
],
created_at: '',
id: 'init-message',
isCompletedStream: false
}
],
title: title || 'New Chat',
is_favorited: false,
updated_at: '',
created_at: '',
created_by: '',
created_by_id: '',
created_by_name: '',
created_by_avatar: ''
};
};

View File

@ -39,6 +39,7 @@ export const useDashboards = () => {
//DASHBOARD INDIVIDUAL
const {
dashboards,
getDashboardMemoized,
removeItemFromIndividualDashboard,
refreshDashboard,
onVerifiedDashboard,
@ -99,6 +100,7 @@ export const useDashboards = () => {
unsubscribeFromDashboardsList,
onShareDashboard,
openAddContentModal,
getDashboardMemoized,
setOpenAddContentModal
};
};

View File

@ -72,7 +72,26 @@ export const generateMockDashboard = (numMetrics: number): DashboardMockResponse
const dashboard: BusterDashboard = {
id: '123',
name: 'Mock Dashboard',
title: 'Mock Dashboard',
file: `title: Mock Dashboard
description: A sample dashboard configuration
version: 1.0
layout:
rows:
- id: row1
columns:
- width: 6
metric_id: metric-1
- width: 6
metric_id: metric-2
- id: row2
columns:
- width: 12
metric_id: metric-3
theme: light
refresh_interval: 300`,
file_name: 'mock-file.yaml',
version_number: 1,
description: null,
created_at: new Date().toISOString(),
created_by: 'user-123',
@ -113,4 +132,4 @@ export const generateMockDashboard = (numMetrics: number): DashboardMockResponse
// Example usage:
// const { dashboard, response } = generateMockDashboard(12);
export const MOCK_DASHBOARD_RESPONSE = generateMockDashboard(15).response;
export const MOCK_DASHBOARD_RESPONSE = generateMockDashboard(3).response;

View File

@ -7,6 +7,7 @@ import { useDashboardUpdateConfig } from './useDashboardUpdateConfig';
import { useDashboardSubscribe } from './useDashboardSubscribe';
import { useDashboardCreate } from './useDashboardCreate';
import { useShareDashboard } from './useShareDashboard';
import { useMemoizedFn } from 'ahooks';
export const useDashboardIndividual = ({
refreshDashboardsList,
@ -53,6 +54,10 @@ export const useDashboardIndividual = ({
onUpdateDashboard
});
const getDashboardMemoized = useMemoizedFn((id: string) => {
return dashboards[id];
});
return {
dashboards,
onRemoveFromCollection,
@ -63,6 +68,7 @@ export const useDashboardIndividual = ({
onVerifiedDashboard,
openedDashboardId,
creatingDashboard,
getDashboardMemoized,
onCreateNewDashboard,
onDeleteDashboard,
subscribeToDashboard,