add provider for chats

This commit is contained in:
Nate Kelley 2025-01-27 14:20:26 -07:00
parent 604bfbdd6c
commit d5ee691de4
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
17 changed files with 197 additions and 42 deletions

Binary file not shown.

View File

@ -1,3 +1,6 @@
import type { FileType } from '../config';
export type BusterCollectionAsset = { export type BusterCollectionAsset = {
id: string; id: string;
type: FileType.COLLECTION;
}; };

View File

@ -1,8 +1,10 @@
import type { BusterDashboardMetric } from '../../../buster_rest/dashboards'; import type { BusterDashboardMetric } from '../../../buster_rest/dashboards';
import type { DashboardConfig } from '../../dashboards'; import type { DashboardConfig } from '../../dashboards';
import type { FileType } from '../config';
export type BusterDashboardAsset = { export type BusterDashboardAsset = {
id: string; id: string;
type: FileType.DASHBOARD;
metrics: BusterDashboardMetric[]; metrics: BusterDashboardMetric[];
config: DashboardConfig; config: DashboardConfig;
created_at: string; created_at: string;

View File

@ -1,3 +1,6 @@
import type { FileType } from '../config';
export type BusterDatasetAsset = { export type BusterDatasetAsset = {
id: string; id: string;
type: FileType.DATASET;
}; };

View File

@ -1,7 +1,9 @@
import type { BusterChartConfigProps } from '@/components/charts'; import type { BusterChartConfigProps } from '@/components/charts';
import type { FileType } from '../config';
export type BusterMetricAsset = { export type BusterMetricAsset = {
id: string; id: string;
type: FileType.METRIC;
title: string; title: string;
description: string | null; description: string | null;
time_frame: string; time_frame: string;

View File

@ -1,3 +1,6 @@
import type { FileType } from '../config';
export type BusterTermAsset = { export type BusterTermAsset = {
id: string; id: string;
type: FileType.TERM;
}; };

View File

@ -1,3 +1,6 @@
import type { FileType } from '../config';
export type BusterValueAsset = { export type BusterValueAsset = {
id: string; id: string;
type: FileType.VALUE;
}; };

View File

@ -1,6 +1,6 @@
import type { BusterChatMessage } from './chatMessageInterfaces'; import type { BusterChatMessage } from './chatMessageInterfaces';
export interface BusterChat { export interface IBusterChat {
id: string; id: string;
title: string; title: string;
is_favorited: boolean; is_favorited: boolean;

View File

@ -19,7 +19,7 @@ export type ChatUnsubscribeFromChat = BusterSocketRequestBase<'/chats/unsubscrib
export type ChatGetChatAsset = BusterSocketRequestBase< export type ChatGetChatAsset = BusterSocketRequestBase<
'/chats/get/asset', '/chats/get/asset',
{ id: string; type: FileType; version_id?: string } { chat_id?: string; asset_id: string; type: FileType; version_id?: string }
>; >;
export type ChatListEmitPayload = BusterSocketRequestBase< export type ChatListEmitPayload = BusterSocketRequestBase<

View File

@ -1,6 +1,6 @@
import type { RustApiError } from '../../buster_rest/errors'; import type { RustApiError } from '../../buster_rest/errors';
import type { BusterChatAsset } from './chatAssetInterfaces'; import type { BusterChatAsset } from './chatAssetInterfaces';
import type { BusterChat, BusterChatListItem } from './chatInterfaces'; import type { IBusterChat, BusterChatListItem } from './chatInterfaces';
export enum ChatsResponses { export enum ChatsResponses {
'/chats/list:getChatsList' = '/chats/list:getChatsList', '/chats/list:getChatsList' = '/chats/list:getChatsList',
@ -25,7 +25,7 @@ export type Chat_unsubscribed = {
export type Chat_getChat = { export type Chat_getChat = {
route: '/chats/get:getChat'; route: '/chats/get:getChat';
callback: (d: BusterChat) => void; callback: (d: IBusterChat) => void;
onError?: (d: unknown | RustApiError) => void; onError?: (d: unknown | RustApiError) => void;
}; };
@ -39,13 +39,13 @@ export type Chat_getChatAsset = {
export type ChatPost_initializeChat = { export type ChatPost_initializeChat = {
route: '/chats/post:initializeChat'; route: '/chats/post:initializeChat';
callback: (d: BusterChat) => void; callback: (d: IBusterChat) => void;
onError?: (d: unknown | RustApiError) => void; onError?: (d: unknown | RustApiError) => void;
}; };
export type ChatPost_generatingTitle = { export type ChatPost_generatingTitle = {
route: '/chats/post:generatingTitle'; route: '/chats/post:generatingTitle';
callback: (d: BusterChat) => void; callback: (d: IBusterChat) => void;
onError?: (d: unknown | RustApiError) => void; onError?: (d: unknown | RustApiError) => void;
}; };

View File

@ -1,3 +1,5 @@
export * from './chatAssetInterfaces'; export * from './chatAssetInterfaces';
export * from './chatInterfaces';
export * from './chatRequests'; export * from './chatRequests';
export * from './chatResponses'; export * from './chatResponses';
export * from './config';

View File

@ -18,6 +18,7 @@ import {
OrganizationResponsesTypes, OrganizationResponsesTypes,
OrganizationsEmits OrganizationsEmits
} from './organizations'; } from './organizations';
import { ChatEmits, ChatResponseTypes, ChatsResponses } from './chats';
export type BusterSocketRequest = export type BusterSocketRequest =
| ThreadEmits | ThreadEmits
@ -31,7 +32,8 @@ export type BusterSocketRequest =
| TermsEmits | TermsEmits
| PermissionsEmits | PermissionsEmits
| BusterSearchEmits | BusterSearchEmits
| OrganizationsEmits; | OrganizationsEmits
| ChatEmits;
export type BusterSocketResponse = export type BusterSocketResponse =
| ThreadResponseTypes | ThreadResponseTypes
@ -45,7 +47,8 @@ export type BusterSocketResponse =
| TermsResponseTypes | TermsResponseTypes
| PermissionsResponseTypes | PermissionsResponseTypes
| SearchResponseTypes | SearchResponseTypes
| OrganizationResponsesTypes; | OrganizationResponsesTypes
| ChatResponseTypes;
export type BusterSocketResponseRoute = export type BusterSocketResponseRoute =
| keyof typeof ThreadResponses | keyof typeof ThreadResponses
@ -59,4 +62,5 @@ export type BusterSocketResponseRoute =
| keyof typeof TermsResponses | keyof typeof TermsResponses
| keyof typeof PermissionsResponses | keyof typeof PermissionsResponses
| keyof typeof SearchResponses | keyof typeof SearchResponses
| keyof typeof OrganizationResponses; | keyof typeof OrganizationResponses
| keyof typeof ChatsResponses;

View File

@ -24,6 +24,7 @@ import { BusterAssetsProvider } from './Assets/BusterAssetsProvider';
import { BusterUserResponse } from '@/api/buster_rest/users'; import { BusterUserResponse } from '@/api/buster_rest/users';
import { BusterPosthogProvider } from './Posthog/usePosthog'; import { BusterPosthogProvider } from './Posthog/usePosthog';
import { BusterNotificationsProvider } from './BusterNotifications'; import { BusterNotificationsProvider } from './BusterNotifications';
import { BusterChatProvider } from './Chats';
import { RoutePrefetcher } from './RoutePrefetcher'; import { RoutePrefetcher } from './RoutePrefetcher';
import { BusterMessageDataProvider } from './MessageData'; import { BusterMessageDataProvider } from './MessageData';
@ -66,16 +67,19 @@ export const AppProviders: React.FC<
<BusterCollectionsProvider> <BusterCollectionsProvider>
<BusterMessageDataProvider> <BusterMessageDataProvider>
<BusterDashboardProvider> <BusterDashboardProvider>
{/* TODO: remove when we are ready to use chats */}
<BusterThreadsProvider> <BusterThreadsProvider>
<BusterSQLProvider> <BusterSQLProvider>
<BusterTermsProvider> <BusterTermsProvider>
<BusterPermissionsProvider> <BusterPermissionsProvider>
<BusterChatProvider>
<AppHotKeysProvider> <AppHotKeysProvider>
<BusterPosthogProvider> <BusterPosthogProvider>
{children} {children}
<RoutePrefetcher /> <RoutePrefetcher />
</BusterPosthogProvider> </BusterPosthogProvider>
</AppHotKeysProvider> </AppHotKeysProvider>
</BusterChatProvider>
</BusterPermissionsProvider> </BusterPermissionsProvider>
</BusterTermsProvider> </BusterTermsProvider>
</BusterSQLProvider> </BusterSQLProvider>

View File

@ -0,0 +1,151 @@
import React, { useCallback, useRef, useState, useTransition } from 'react';
import {
createContext,
ContextSelector,
useContextSelector
} 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 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) => {
chatsRef.current[chat.id] = chat;
startTransition(() => {
//just used to trigger UI update
});
return chat;
});
const _onGetChatAsset = useMemoizedFn((asset: BusterChatAsset) => {
const { id, type } = asset;
console.log('TODO: handle this. Put the asset in their respective chat');
return asset;
});
// EMITTERS
const unsubscribeFromChat = useMemoizedFn(({ chatId }: { chatId: string }) => {
return busterSocket.emit({
route: '/chats/unsubscribe',
payload: {
id: chatId
}
});
});
const subscribeToChat = useMemoizedFn(({ chatId }: { chatId: string }) => {
return busterSocket.emitAndOnce({
emitEvent: {
route: '/chats/get',
payload: {
id: chatId
}
},
responseEvent: {
route: '/chats/get:getChat',
callback: _onGetChat
}
});
});
const getChatAsset = useMemoizedFn(
({
chatId: chat_id,
assetId: asset_id,
type,
versionId: version_id
}: {
chatId?: string;
assetId: string;
type: FileType;
versionId?: string;
}) => {
return busterSocket.emitAndOnce({
emitEvent: {
route: '/chats/get/asset',
payload: {
type,
chat_id,
asset_id,
version_id
}
},
responseEvent: {
route: '/chats/get:getChatAsset',
callback: _onGetChatAsset
}
});
}
);
return {
getSelectedAssetId,
chats: chatsRef.current,
unsubscribeFromChat,
subscribeToChat,
getChatAsset,
onSetSelectedAssetId
};
};
const BusterChat = createContext<ReturnType<typeof useBusterChat>>(
{} as ReturnType<typeof useBusterChat>
);
export const BusterChatProvider: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const value = useBusterChat();
return <BusterChat.Provider value={value}>{children}</BusterChat.Provider>;
};
export const useBusterChatContextSelector = <T,>(
selector: ContextSelector<ReturnType<typeof useBusterChat>, T>
) => useContextSelector(BusterChat, selector);
export const useBusterChatIndividual = ({ chatId }: { chatId: string }) => {
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 });
});
useUnmount(() => {
unsubscribeFromChat({ chatId });
});
return {
chat,
selectedAssetId,
onSetSelectedAssetId
};
};

View File

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

View File

@ -1,35 +1,12 @@
import { import { useParams } from 'next/navigation';
BusterDashboard, import React, { PropsWithChildren, useEffect, useLayoutEffect, useState } from 'react';
BusterDashboardListItem,
BusterDashboardResponse,
BusterMetricDataResponse,
BusterVerificationStatus,
IBusterDashboardMetric
} from '@/api/buster_rest';
import { useParams, useRouter } from 'next/navigation';
import React, {
PropsWithChildren,
useContext,
useEffect,
useLayoutEffect,
useRef,
useState
} from 'react';
import { useBusterWebSocket } from '../BusterWebSocket';
import { useAppLayoutContextSelector } from '../BusterAppLayout'; import { useAppLayoutContextSelector } from '../BusterAppLayout';
import { BusterRoutes, createBusterRoute } from '@/routes'; import { useUnmount } from 'ahooks';
import { DashboardUpdate, DashboardsListEmitPayload } from '@/api/buster_socket/dashboards';
import isEqual from 'lodash/isEqual';
import { useMemoizedFn, useMount, useUnmount } from 'ahooks';
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
import { upgradeDashboardMetric } from './dashboardContextHelper';
import { useBusterNotifications } from '../BusterNotifications';
import { import {
useContextSelector, useContextSelector,
createContext, createContext,
ContextSelector ContextSelector
} from '@fluentui/react-context-selector'; } from '@fluentui/react-context-selector';
import { useBusterMessageDataContextSelector } from '../MessageData';
import { useDashboardLists } from './useDashboardLists'; import { useDashboardLists } from './useDashboardLists';
import { useDashboardIndividual } from './useDashboardIndividual'; import { useDashboardIndividual } from './useDashboardIndividual';
import { useDashboardMetrics } from './useDashboardMetrics'; import { useDashboardMetrics } from './useDashboardMetrics';

View File

@ -7,7 +7,7 @@ import {
import { BusterNewThreadsProvider } from './BusterNewThreadsProvider'; import { BusterNewThreadsProvider } from './BusterNewThreadsProvider';
import { BusterThreadsListProvider } from './BusterThreadsListProvider'; import { BusterThreadsListProvider } from './BusterThreadsListProvider';
import { defaultIBusterThread } from './config'; import { defaultIBusterThread } from './config';
import { useDebounceFn, useMemoizedFn, useMount, useWhyDidYouUpdate } from 'ahooks'; import { useDebounceFn, useMemoizedFn, useMount, useUnmount } from 'ahooks';
import type { IBusterThread, IBusterThreadMessage } from './interfaces'; import type { IBusterThread, IBusterThreadMessage } from './interfaces';
import type { BusterThread, BusterThreadUser, BusterVerificationStatus } from '@/api/buster_rest'; import type { BusterThread, BusterThreadUser, BusterVerificationStatus } from '@/api/buster_rest';
import { useBusterWebSocket } from '../BusterWebSocket'; import { useBusterWebSocket } from '../BusterWebSocket';