add additional context provider for chats

This commit is contained in:
Nate Kelley 2025-01-27 16:23:08 -07:00
parent be050b3521
commit 90d30c93af
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
24 changed files with 107 additions and 145 deletions

View File

@ -0,0 +1,5 @@
import React from 'react';
export default function Layout({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}

View File

@ -1,15 +1,15 @@
import { appContentHeaderHeight } from '@/components/layout/AppContentHeader';
import { createStyles } from 'antd-style';
import React from 'react';
import { useChatSplitterContextSelector } from '../../ChatLayoutContext';
import { ChatHeaderOptions } from './ChatHeaderOptions';
import { ChatHeaderTitle } from './ChatHeaderTitle';
import { useChatContextSelector } from '../../ChatContext';
export const ChatHeader: React.FC<{
showScrollOverflow: boolean;
}> = React.memo(({ showScrollOverflow }) => {
const { cx, styles } = useStyles();
const hasFile = useChatSplitterContextSelector((state) => state.hasFile);
const hasFile = useChatContextSelector((state) => state.hasFile);
return (
<div

View File

@ -1,12 +1,12 @@
import { Dropdown, MenuProps } from 'antd';
import React, { useMemo } from 'react';
import { useChatSplitterContextSelector } from '../../../ChatLayoutContext';
import { HeaderOptionsRecord } from './config';
import { useChatContextSelector } from '../../../ChatContext';
export const ChatContainerHeaderDropdown: React.FC<{
children: React.ReactNode;
}> = React.memo(({ children }) => {
const selectedFileType = useChatSplitterContextSelector((state) => state.selectedFileType);
const selectedFileType = useChatContextSelector((state) => state.selectedFileType);
const menuItem: MenuProps['items'] = useMemo(() => {
if (!selectedFileType || !(selectedFileType in HeaderOptionsRecord))

View File

@ -1,8 +1,8 @@
import type { FileType } from '@/api/buster_socket/chats';
import { AppMaterialIcons } from '@/components/icons';
import type { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer';
import { MenuProps } from 'antd';
import type { MenuProps } from 'antd';
export const HeaderOptionsRecord: Record<AppChatMessageFileType, () => MenuProps['items']> = {
export const HeaderOptionsRecord: Record<FileType, () => MenuProps['items']> = {
dataset: () => [
{
label: 'Delete',
@ -30,5 +30,7 @@ export const HeaderOptionsRecord: Record<AppChatMessageFileType, () => MenuProps
key: 'delete',
icon: <AppMaterialIcons icon="delete" />
}
]
],
term: () => [],
value: () => []
};

View File

@ -1,7 +1,7 @@
import { Text } from '@/components/text';
import React from 'react';
import { useChatSplitterContextSelector } from '../../ChatLayoutContext';
import { AnimatePresence, motion } from 'framer-motion';
import { useChatContextSelector } from '../../ChatContext';
const animation = {
initial: { opacity: 0 },
@ -11,12 +11,12 @@ const animation = {
};
export const ChatHeaderTitle: React.FC<{}> = React.memo(() => {
const selectedFileTitle = useChatSplitterContextSelector((state) => state.selectedFileTitle);
const chatTitle = useChatContextSelector((state) => state.chatTitle);
return (
<AnimatePresence mode="wait" initial={false}>
<motion.div {...animation} key={selectedFileTitle} className="flex items-center">
<Text>{selectedFileTitle}</Text>
<motion.div {...animation} key={chatTitle} className="flex items-center">
<Text>{chatTitle}</Text>
</motion.div>
</AnimatePresence>
);

View File

@ -0,0 +1,51 @@
import React, { PropsWithChildren } from 'react';
import {
ContextSelector,
createContext,
useContextSelector
} from '@fluentui/react-context-selector';
import { useBusterChatIndividual } from '@/context/Chats';
import type { SelectedFile } from '../interfaces';
export const useChatContext = ({
chatId,
defaultSelectedFile
}: {
chatId?: string;
defaultSelectedFile?: SelectedFile;
}) => {
const selectedFileId = defaultSelectedFile?.id;
const selectedFileType = defaultSelectedFile?.type;
//CHAT
const { chat } = useBusterChatIndividual({
chatId
});
const chatTitle = chat?.title;
//FILE
const hasFile = !!defaultSelectedFile?.id;
return {
hasFile,
selectedFileId,
chatTitle,
selectedFileType
};
};
export const ChatContext = createContext<ReturnType<typeof useChatContext>>(
{} as ReturnType<typeof useChatContext>
);
export const ChatContextProvider = React.memo(
({ value, children }: PropsWithChildren<{ value: ReturnType<typeof useChatContext> }>) => {
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
}
);
ChatContextProvider.displayName = 'ChatContextProvider';
export const useChatContextSelector = <T,>(
selector: ContextSelector<ReturnType<typeof useChatContext>, T>
) => useContextSelector(ChatContext, selector);

View File

@ -0,0 +1 @@
export * from './ChatContext';

View File

@ -7,7 +7,8 @@ import { FileContainer } from './FileContainer';
import { ChatSplitterContextProvider } from './ChatLayoutContext';
import { useChatLayout } from './ChatLayoutContext';
import { SelectedFile } from './interfaces';
import { useAutoSetLayout, useDefaultSplitterLayout } from './hooks';
import { useDefaultSplitterLayout } from './hooks';
import { ChatContextProvider, useChatContext } from './ChatContext/ChatContext';
export interface ChatSplitterProps {
showChatCollapse?: boolean;
@ -30,20 +31,25 @@ export const ChatLayout: React.FC<ChatSplitterProps> = React.memo(
chatId
});
const { hasFile, isPureChat, isPureFile } = useChatSplitterProps;
const useChatContextValue = useChatContext({ chatId, defaultSelectedFile });
const { isPureChat, isPureFile } = useChatSplitterProps;
const { hasFile } = useChatContextValue;
return (
<ChatSplitterContextProvider useChatSplitterProps={useChatSplitterProps}>
<AppSplitter
ref={appSplitterRef}
leftChildren={isPureFile ? null : <ChatContainer />}
rightChildren={<FileContainer children={children} />}
autoSaveId="chat-splitter"
defaultLayout={defaultSplitterLayout}
rightHidden={isPureChat}
preserveSide="left"
leftPanelMinSize={hasFile ? 225 : undefined}
/>
<ChatContextProvider value={useChatContextValue}>
<AppSplitter
ref={appSplitterRef}
leftChildren={isPureFile ? null : <ChatContainer />}
rightChildren={<FileContainer children={children} />}
autoSaveId="chat-splitter"
defaultLayout={defaultSplitterLayout}
rightHidden={isPureChat}
preserveSide="left"
leftPanelMinSize={hasFile ? 225 : undefined}
/>
</ChatContextProvider>
</ChatSplitterContextProvider>
);
}

View File

@ -3,7 +3,7 @@ import {
createContext,
useContextSelector
} from '@fluentui/react-context-selector';
import React, { PropsWithChildren, useMemo, useState, useTransition } from 'react';
import React, { PropsWithChildren, useMemo, useTransition } from 'react';
import type { SelectedFile } from '../interfaces';
import type { ChatSplitterProps } from '../ChatLayout';
import { useMemoizedFn } from 'ahooks';
@ -29,14 +29,6 @@ export const useChatLayout = ({
const [isPending, startTransition] = useTransition();
const onChangePage = useAppLayoutContextSelector((state) => state.onChangePage);
const selectedLayout = defaultSelectedLayout;
const selectedFileId = defaultSelectedFile?.id;
const selectedFileType = defaultSelectedFile?.type;
const hasFile = !!selectedFileId;
const selectedFileTitle: string = useMemo(() => {
if (!selectedFileId) return '';
return 'test';
}, [selectedFileId]);
const animateOpenSplitter = useMemoizedFn((side: 'left' | 'right' | 'both') => {
if (appSplitterRef.current) {
@ -84,11 +76,7 @@ export const useChatLayout = ({
});
return {
selectedFileTitle,
selectedFileType,
selectedLayout,
selectedFileId,
hasFile,
isPureFile,
isPureChat,
onSetSelectedFile,

View File

@ -6,7 +6,6 @@ import { CollapseFileButton } from './CollapseFileButton';
export const FileContainerHeader: React.FC = React.memo(() => {
const { styles, cx } = useStyles();
const selectedFileType = useChatSplitterContextSelector((state) => state.selectedFileType);
const selectedLayout = useChatSplitterContextSelector((state) => state.selectedLayout);
const showCollapseButton = true;

View File

@ -3,8 +3,8 @@
import { useMemo } from 'react';
import { SelectedFile } from '../interfaces';
import { useParams } from 'next/navigation';
import { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer';
import { ChatSplitterProps } from '../ChatLayout';
import { FileType } from '@/api/buster_socket/chats';
export const useSelectedFileByParams = () => {
const { metricId, collectionId, datasetId, dashboardId, chatId } = useParams() as {
@ -16,10 +16,10 @@ export const useSelectedFileByParams = () => {
};
const selectedFile: SelectedFile | undefined = useMemo(() => {
if (metricId) return { id: metricId, type: AppChatMessageFileType.Metric };
if (collectionId) return { id: collectionId, type: AppChatMessageFileType.Collection };
if (datasetId) return { id: datasetId, type: AppChatMessageFileType.Dataset };
if (dashboardId) return { id: dashboardId, type: AppChatMessageFileType.Dashboard };
if (metricId) return { id: metricId, type: FileType.METRIC };
if (collectionId) return { id: collectionId, type: FileType.COLLECTION };
if (datasetId) return { id: datasetId, type: FileType.DATASET };
if (dashboardId) return { id: dashboardId, type: FileType.DASHBOARD };
}, [metricId, collectionId, datasetId, dashboardId, chatId]);
const selectedLayout: ChatSplitterProps['defaultSelectedLayout'] = useMemo(() => {

View File

@ -1,3 +0,0 @@
export default function Page() {
return <></>;
}

View File

@ -1,3 +0,0 @@
export default function Page() {
return <div className="h-full w-full bg-orange-500">Dashboard swag</div>;
}

View File

@ -1,3 +0,0 @@
export default function Page() {
return <></>;
}

View File

@ -1,3 +0,0 @@
export default function Page() {
return <></>;
}

View File

@ -1,3 +0,0 @@
export default function Page() {
return <></>;
}

View File

@ -1,3 +0,0 @@
export default function Page() {
return <></>;
}

View File

@ -1,3 +0,0 @@
export default function Page() {
return <div className="h-full w-full bg-red-500">Dashboard swag</div>;
}

View File

@ -1,3 +0,0 @@
export default function Page() {
return <></>;
}

View File

@ -1,43 +0,0 @@
'use client';
import React from 'react';
import { ChatLayout, useSelectedFileByParams } from '@chatLayout/index';
import { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer';
import { useRouter } from 'next/navigation';
import { useHotkeys } from 'react-hotkeys-hook';
export default function Layout({ children }: { children: React.ReactNode }) {
const { selectedFile, selectedLayout, chatId } = useSelectedFileByParams();
const router = useRouter();
useHotkeys('m', () => {
const randomType: AppChatMessageFileType = (
['dataset', 'collection', 'metric', 'dashboard'] as AppChatMessageFileType[]
)[Math.floor(Math.random() * 4)];
const isPureChat = Math.random() < 0.15;
const isChat = Math.random() < 0.55;
const randomChatId = Math.floor(Math.random() * 1000);
const randomId = Math.floor(Math.random() * 1000000);
if (isPureChat) {
router.push(`/test/splitter/chat/${randomChatId}`);
return;
}
const route = isChat
? `/test/splitter/chat/${randomChatId}/${randomType}/${randomId}`
: `/test/splitter/${randomType}/${randomId}`;
router.push(route);
});
return (
<div className="h-screen w-screen">
<ChatLayout
chatId={chatId}
defaultSelectedLayout={selectedLayout}
defaultSelectedFile={selectedFile}>
{children}
</ChatLayout>
</div>
);
}

View File

@ -1,3 +0,0 @@
export default function Page() {
return <></>;
}

View File

@ -1,5 +0,0 @@
'use client';
export default function Page() {
return <></>;
}

View File

@ -1,4 +1,4 @@
import React, { useCallback, useRef, useState, useTransition } from 'react';
import React, { useEffect, useRef, useTransition } from 'react';
import {
createContext,
ContextSelector,
@ -6,30 +6,18 @@ import {
} from '@fluentui/react-context-selector';
import { useBusterWebSocket } from '../BusterWebSocket';
import type { BusterChatAsset, IBusterChat } from '@/api/buster_socket/chats';
import { useMemoizedFn, useMount, useUnmount } from 'ahooks';
import { useMemoizedFn, useUnmount } from 'ahooks';
import type { FileType } from '@/api/buster_socket/chats';
export const useBusterChat = () => {
const busterSocket = useBusterWebSocket();
const [isPending, startTransition] = useTransition();
const chatsRef = useRef<Record<string, IBusterChat>>({});
const [seletedAssetId, setSeletedAssetId] = useState<Record<string, string | null>>({});
// GETTERS
const getSelectedAssetId = useCallback(
(chatId: string) => {
return seletedAssetId[chatId] || null;
},
[seletedAssetId]
);
// SETTERS
const onSetSelectedAssetId = useMemoizedFn((chatId: string, assetId: string | null) => {
setSeletedAssetId((prev) => ({ ...prev, [chatId]: assetId }));
});
// LISTENERS
const _onGetChat = useMemoizedFn((chat: IBusterChat) => {
@ -103,12 +91,10 @@ export const useBusterChat = () => {
);
return {
getSelectedAssetId,
chats: chatsRef.current,
unsubscribeFromChat,
subscribeToChat,
getChatAsset,
onSetSelectedAssetId
getChatAsset
};
};
@ -128,25 +114,22 @@ export const useBusterChatContextSelector = <T,>(
selector: ContextSelector<ReturnType<typeof useBusterChat>, T>
) => useContextSelector(BusterChat, selector);
export const useBusterChatIndividual = ({ chatId }: { chatId: string }) => {
export const useBusterChatIndividual = ({ chatId: chatIdProp }: { chatId?: string }) => {
const chatId = chatIdProp || '';
const chat = useBusterChatContextSelector((x) => x.chats[chatId]);
const subscribeToChat = useBusterChatContextSelector((x) => x.subscribeToChat);
const unsubscribeFromChat = useBusterChatContextSelector((x) => x.unsubscribeFromChat);
const selectedAssetId = useBusterChatContextSelector((x) => x.getSelectedAssetId(chatId));
const onSetSelectedAssetId = useBusterChatContextSelector((x) => x.onSetSelectedAssetId);
useMount(() => {
subscribeToChat({ chatId });
});
useEffect(() => {
if (chatId) subscribeToChat({ chatId });
}, [chatId]);
useUnmount(() => {
unsubscribeFromChat({ chatId });
if (chatId) unsubscribeFromChat({ chatId });
});
return {
chat,
selectedAssetId,
onSetSelectedAssetId
chat
};
};

View File

@ -237,4 +237,6 @@ export type BusterAppRoutesWithArgs = {
chatId: string;
valueId: string;
};
[BusterAppRoutes.APP_METRIC_ID]: { route: BusterAppRoutes.APP_METRIC_ID; metricId: string };
[BusterAppRoutes.APP_VALUE_ID]: { route: BusterAppRoutes.APP_VALUE_ID; valueId: string };
};