toggle for metric slideout

This commit is contained in:
Nate Kelley 2025-02-03 10:31:15 -07:00
parent 4ca917195c
commit d17899f3d4
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
18 changed files with 144 additions and 92 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,4 +1,4 @@
import type { BusterChatMessageResponse } from './chatMessageInterfaces'; import type { BusterChatMessage, BusterChatMessageResponse } from './chatMessageInterfaces';
enum BusterChatStepProgress { enum BusterChatStepProgress {
IN_PROGRESS = 'in_progress', IN_PROGRESS = 'in_progress',
@ -17,5 +17,9 @@ export type ChatPost_generatingTitle = {
} & BusterChatStepBase; } & BusterChatStepBase;
export type ChatPost_generatingMessage = { export type ChatPost_generatingMessage = {
message: BusterChatMessageResponse; resposnse_message: BusterChatMessageResponse;
} & BusterChatStepBase;
export type ChatPost_complete = {
message: BusterChatMessage;
} & BusterChatStepBase; } & BusterChatStepBase;

View File

@ -8,6 +8,7 @@ export type ChatCreateNewChat = BusterSocketRequestBase<
chat_id?: string | null; //send if we are following up on a chat chat_id?: string | null; //send if we are following up on a chat
suggestion_id?: string | null; //send if we clicked on a suggestion suggestion_id?: string | null; //send if we clicked on a suggestion
message_id?: string; //send if we want to REPLACE current message message_id?: string; //send if we want to REPLACE current message
metric_id?: string; //send if we want to create a chat from a metric
draft_session_id?: string; //TODO: do we need this? draft_session_id?: string; //TODO: do we need this?
} }
>; >;
@ -39,7 +40,7 @@ export type ChatsDuplicateChat = BusterSocketRequestBase<
'/chats/duplicate', '/chats/duplicate',
{ {
id: string; id: string;
message_id?: string; //send if we want to duplciate the chat starting from a specific message message_id: string; //send if we want to duplciate the chat starting from a specific message
} }
>; >;

View File

@ -37,7 +37,6 @@ export type ShareRequest = {
user_email: string; user_email: string;
role: ShareRole; role: ShareRole;
}[]; }[];
remove_users?: string[]; // user_id remove_users?: string[]; // user_id
team_permissions?: { team_permissions?: {
team_id: string; team_id: string;

View File

@ -23,52 +23,28 @@ export type MetricSubscribeToMetric = BusterSocketRequestBase<
{ id: string; password?: string } { id: string; password?: string }
>; >;
export type MetricCreateNewMetric = BusterSocketRequestBase<
'/metrics/post',
{
dataset_id: string | null;
metric_id: string | null;
suggestion_id?: string | null;
prompt?: string;
message_id?: string; //only send if we want to REPLACE current message
draft_session_id?: string;
}
>;
export type MetricUpdateMetric = BusterSocketRequestBase< export type MetricUpdateMetric = BusterSocketRequestBase<
'/metrics/update', '/metrics/update',
{ {
id: string; //metric id id: string; //metric id
title?: string;
save_to_dashboard?: string; save_to_dashboard?: string;
remove_from_dashboard?: string; // dashboard_id optional remove_from_dashboard?: string; // dashboard_id optional
add_to_collections?: string[]; // collection_id add_to_collections?: string[]; // collection_id
remove_from_collections?: string[]; // collection_id remove_from_collections?: string[]; // collection_id
save_draft?: boolean;
} & ShareRequest
>;
export type MetricUpdateMessage = BusterSocketRequestBase<
'/metrics/messages/update',
{
id: string; //messageid id
chart_config?: BusterChartConfigProps;
title?: string;
sql?: string; sql?: string;
feedback?: 'negative'; chart_config?: BusterChartConfigProps;
status?: VerificationStatus; save_draft?: boolean; //send if we want the metric (which is currently in draft) to be saved
} feedback?: 'negative'; //send if we want to update the feedback of the metric
status?: VerificationStatus; //admin only: send if we want to update the status of the metric
} & ShareRequest
>; >;
export type MetricDelete = BusterSocketRequestBase<'/metrics/delete', { ids: string[] }>; export type MetricDelete = BusterSocketRequestBase<'/metrics/delete', { ids: string[] }>;
export type MetricGetDataByMessageId = BusterSocketRequestBase<'/metrics/data', { id: string }>; export type MetricGetDataByMessageId = BusterSocketRequestBase<'/metrics/data', { id: string }>;
export type MetricSearch = BusterSocketRequestBase< export type MetricSearch = BusterSocketRequestBase<'/metrics/search', { prompt: string }>;
'/metrics/search',
{
prompt: string;
}
>;
export type MetricDuplicate = BusterSocketRequestBase< export type MetricDuplicate = BusterSocketRequestBase<
'/metrics/duplicate', '/metrics/duplicate',
@ -84,9 +60,7 @@ export type MetricEmits =
| MetricListEmitPayload | MetricListEmitPayload
| MetricUnsubscribeEmitPayload | MetricUnsubscribeEmitPayload
| MetricUpdateMetric | MetricUpdateMetric
| MetricCreateNewMetric
| MetricSubscribeToMetric | MetricSubscribeToMetric
| MetricDelete | MetricDelete
| MetricUpdateMessage
| MetricGetDataByMessageId | MetricGetDataByMessageId
| MetricSearch; | MetricSearch;

View File

@ -1,3 +1,14 @@
export default function Page() { import { MetricController } from '@appControllers/MetricController';
return <></>; import { AppAssetCheckLayout } from '@appLayouts/AppAssetCheckLayout';
export default function Page({
params: { chatId, metricId }
}: {
params: { chatId: string; metricId: string };
}) {
return (
<AppAssetCheckLayout metricId={metricId} type="metric">
<MetricController metricId={metricId} />
</AppAssetCheckLayout>
);
} }

View File

@ -1,4 +1,5 @@
import { AppAssetCheckLayout } from '@/app/app/_layouts/AppAssetCheckLayout'; import { MetricController } from '@appControllers/MetricController';
import { AppAssetCheckLayout } from '@appLayouts/AppAssetCheckLayout';
export default function MetricPage({ export default function MetricPage({
params: { metricId }, params: { metricId },
@ -10,16 +11,8 @@ export default function MetricPage({
const embedView = embed === 'true'; const embedView = embed === 'true';
return ( return (
<AppAssetCheckLayout metricId={metricId} type="metric"> // <AppAssetCheckLayout metricId={metricId} type="metric">
<></> <MetricController metricId={metricId} />
</AppAssetCheckLayout> // </AppAssetCheckLayout>
); );
} }
{
/* <MetricContentController
metricLayout={metricLayout}
metricId={metricId}
chartOnlyView={embedView}
/> */
}

View File

@ -0,0 +1,11 @@
'use client';
import React from 'react';
export const MetricController: React.FC<{
metricId: string;
}> = React.memo(({ metricId }) => {
return <div>MetricController</div>;
});
MetricController.displayName = 'MetricController';

View File

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

View File

@ -17,7 +17,7 @@ export const ChatContainer = React.memo(
}, [chatContentRef, scroll?.top]); }, [chatContentRef, scroll?.top]);
return ( return (
<div ref={ref} className="flex h-full w-full flex-col"> <div ref={ref} className="flex h-full w-full min-w-[225px] flex-col">
<ChatHeader showScrollOverflow={showScrollOverflow} /> <ChatHeader showScrollOverflow={showScrollOverflow} />
<ChatContent chatContentRef={chatContentRef} /> <ChatContent chatContentRef={chatContentRef} />
</div> </div>

View File

@ -48,7 +48,7 @@ export const ChatInput: React.FC = React.memo(() => {
<div <div
className={cx( className={cx(
styles.inputCard, styles.inputCard,
'z-10 flex flex-col items-center space-y-1.5 px-3 pb-2 pt-0.5' 'z-10 mx-3 mt-0.5 flex flex-col items-center space-y-1.5 overflow-hidden pb-2'
)}> )}>
<div <div
className={cx( className={cx(
@ -60,7 +60,7 @@ export const ChatInput: React.FC = React.memo(() => {
variant="borderless" variant="borderless"
onBlur={onBlurInput} onBlur={onBlurInput}
onFocus={onFocusInput} onFocus={onFocusInput}
className="inline-block !pl-3.5 !pr-9 align-middle" className="inline-block !pl-3.5 !pr-9 !pt-2 align-middle"
placeholder="Ask a follow up..." placeholder="Ask a follow up..."
value={inputValue} value={inputValue}
autoFocus={true} autoFocus={true}
@ -78,9 +78,7 @@ export const ChatInput: React.FC = React.memo(() => {
</div> </div>
</div> </div>
<Text size="xs" type="tertiary"> <AIWarning />
Our AI may make mistakes. Check important info.
</Text>
</div> </div>
); );
}); });
@ -105,3 +103,15 @@ const useStyles = createStyles(({ token, css }) => ({
} }
` `
})); }));
const AIWarning = React.memo(() => {
return (
<div className="w-full overflow-hidden truncate text-center">
<Text size="xs" type="tertiary" className="truncate">
Our AI may make mistakes. Check important info.
</Text>
</div>
);
});
AIWarning.displayName = 'AIWarning';

View File

@ -3,7 +3,7 @@ import {
createContext, createContext,
useContextSelector useContextSelector
} from '@fluentui/react-context-selector'; } from '@fluentui/react-context-selector';
import React, { PropsWithChildren, useTransition } from 'react'; import React, { PropsWithChildren, useState, useTransition } from 'react';
import type { SelectedFile } from '../interfaces'; import type { SelectedFile } from '../interfaces';
import type { ChatSplitterProps } from '../ChatLayout'; import type { ChatSplitterProps } from '../ChatLayout';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
@ -30,8 +30,6 @@ export const useChatLayout = ({
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
const onChangePage = useAppLayoutContextSelector((state) => state.onChangePage); const onChangePage = useAppLayoutContextSelector((state) => state.onChangePage);
const selectedLayout = defaultSelectedLayout;
const animateOpenSplitter = useMemoizedFn((side: 'left' | 'right' | 'both') => { const animateOpenSplitter = useMemoizedFn((side: 'left' | 'right' | 'both') => {
if (appSplitterRef.current) { if (appSplitterRef.current) {
const { animateWidth, isSideClosed } = appSplitterRef.current; const { animateWidth, isSideClosed } = appSplitterRef.current;
@ -41,6 +39,8 @@ export const useChatLayout = ({
animateWidth('100%', 'right'); animateWidth('100%', 'right');
} else if (side === 'both' && (isSideClosed('right') || isSideClosed('left'))) { } else if (side === 'both' && (isSideClosed('right') || isSideClosed('left'))) {
animateWidth(DEFAULT_CHAT_OPTION, 'left'); animateWidth(DEFAULT_CHAT_OPTION, 'left');
setIsPureChat(false);
setIsPureFile(false);
} }
} }
}); });
@ -56,7 +56,6 @@ export const useChatLayout = ({
if (route) { if (route) {
onChangePage(route); onChangePage(route);
setIsPureChat(false);
startTransition(() => { startTransition(() => {
animateOpenSplitter('both'); animateOpenSplitter('both');
}); });
@ -64,15 +63,34 @@ export const useChatLayout = ({
}); });
const onCollapseFileClick = useMemoizedFn((close?: boolean) => { const onCollapseFileClick = useMemoizedFn((close?: boolean) => {
const isCloseAction = close ?? selectedLayout === 'both'; const isCloseAction = close ?? isCollapseOpen;
if (defaultSelectedLayout === 'file') {
if (!isCloseAction && defaultSelectedFile) {
setIsCollapseOpen(true);
animateOpenSplitter('both');
} else {
setIsCollapseOpen(false);
animateOpenSplitter('right');
}
} else {
if (isCloseAction) { if (isCloseAction) {
animateOpenSplitter('left'); animateOpenSplitter('left');
} else { } else {
animateOpenSplitter('both'); animateOpenSplitter('both');
} }
}
}); });
const { setIsPureChat, isPureFile, isPureChat } = useAutoSetLayout({ const {
setIsPureChat,
setIsCollapseOpen,
isPureFile,
isPureChat,
setIsPureFile,
collapseDirection,
isCollapseOpen
} = useAutoSetLayout({
defaultSelectedLayout defaultSelectedLayout
}); });
@ -83,6 +101,8 @@ export const useChatLayout = ({
return { return {
...fileLayoutContext, ...fileLayoutContext,
collapseDirection,
isCollapseOpen,
isPureFile, isPureFile,
isPureChat, isPureChat,
onSetSelectedFile, onSetSelectedFile,

View File

@ -7,7 +7,7 @@ interface FileContainerProps {
export const FileContainer: React.FC<FileContainerProps> = React.memo(({ children }) => { export const FileContainer: React.FC<FileContainerProps> = React.memo(({ children }) => {
return ( return (
<div className="flex min-w-[370px] flex-col"> <div className="flex min-w-[325px] flex-col">
<FileContainerHeader /> <FileContainerHeader />
{children} {children}
</div> </div>

View File

@ -1,29 +1,38 @@
import { AppMaterialIcons } from '@/components'; import { AppMaterialIcons } from '@/components';
import { Button } from 'antd'; import { Button } from 'antd';
import React from 'react'; import React, { useMemo } from 'react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
const animation = {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 }
};
export const CollapseFileButton: React.FC<{ export const CollapseFileButton: React.FC<{
showCollapseButton: boolean; showCollapseButton: boolean;
isOpen: boolean; isOpen: boolean;
}> = React.memo(({ showCollapseButton, isOpen }) => { collapseDirection: 'left' | 'right';
const onCollapseFileClick = useChatLayoutContextSelector((state) => state.onCollapseFileClick); onCollapseFileClick: (value?: boolean) => void;
const icon = !isOpen ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'; }> = React.memo(({ showCollapseButton, isOpen, collapseDirection, onCollapseFileClick }) => {
const icon = useMemo(() => {
if (collapseDirection === 'left') {
return isOpen ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right';
}
return isOpen ? 'keyboard_double_arrow_right' : 'keyboard_double_arrow_left';
}, [isOpen, collapseDirection]);
const onClick = useMemoizedFn(() => { const onClick = useMemoizedFn(() => {
onCollapseFileClick(); onCollapseFileClick();
}); });
const animation = useMemo(() => {
const baseAnimation = {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 }
};
return baseAnimation;
}, [collapseDirection, isOpen]);
return ( return (
<AnimatePresence mode="wait"> <AnimatePresence mode="wait" initial={false}>
{showCollapseButton && ( {showCollapseButton && (
<motion.div variants={animation}> <motion.div variants={animation}>
<Button onClick={onClick} type="text" icon={<AppMaterialIcons icon={icon} />}></Button> <Button onClick={onClick} type="text" icon={<AppMaterialIcons icon={icon} />}></Button>

View File

@ -23,9 +23,11 @@ export const FileContainerHeader: React.FC = React.memo(() => {
const { styles, cx } = useStyles(); const { styles, cx } = useStyles();
const selectedFileType = useChatLayoutContextSelector((x) => x.selectedFileType); const selectedFileType = useChatLayoutContextSelector((x) => x.selectedFileType);
const selectedFileView = useChatLayoutContextSelector((x) => x.selectedFileView); const selectedFileView = useChatLayoutContextSelector((x) => x.selectedFileView);
const onCollapseFileClick = useChatLayoutContextSelector((state) => state.onCollapseFileClick);
const collapseDirection = useChatLayoutContextSelector((state) => state.collapseDirection);
const isCollapseOpen = useChatLayoutContextSelector((state) => state.isCollapseOpen);
const showCollapseButton = true; const showCollapseButton = true;
const isCollapseOpen = true; //I could get the defaultSelectedLayout from the context and check if it is 'both'?
const SelectedFileSegment = React.useMemo( const SelectedFileSegment = React.useMemo(
() => (selectedFileType ? SelectedFileSegmentRecord[selectedFileType] : () => <></>), () => (selectedFileType ? SelectedFileSegmentRecord[selectedFileType] : () => <></>),
@ -39,9 +41,17 @@ export const FileContainerHeader: React.FC = React.memo(() => {
return ( return (
<div <div
className={cx(styles.container, 'flex w-full items-center justify-between space-x-3.5 px-3')}> className={cx(
styles.container,
'flex w-full items-center justify-between space-x-1 overflow-hidden px-3'
)}>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<CollapseFileButton showCollapseButton={showCollapseButton} isOpen={isCollapseOpen} /> <CollapseFileButton
collapseDirection={collapseDirection}
showCollapseButton={showCollapseButton}
isOpen={isCollapseOpen}
onCollapseFileClick={onCollapseFileClick}
/>
{selectedFileView && <SelectedFileSegment selectedFileView={selectedFileView} />} {selectedFileView && <SelectedFileSegment selectedFileView={selectedFileView} />}
</div> </div>
<SelectedFileButtons selectedFileView={selectedFileView} /> <SelectedFileButtons selectedFileView={selectedFileView} />

View File

@ -1,22 +1,31 @@
import { useUpdateLayoutEffect } from 'ahooks'; import { useUpdateLayoutEffect } from 'ahooks';
import { useState } from 'react'; import { useMemo, useState } from 'react';
export const useAutoSetLayout = ({ export const useAutoSetLayout = ({
defaultSelectedLayout defaultSelectedLayout
}: { }: {
defaultSelectedLayout: 'chat' | 'file' | 'both' | undefined; defaultSelectedLayout: 'chat' | 'file' | 'both' | undefined;
}): { }) => {
isPureFile: boolean;
isPureChat: boolean;
setIsPureChat: (value: boolean) => void;
} => {
const [isPureFile, setIsPureFile] = useState(defaultSelectedLayout === 'file'); const [isPureFile, setIsPureFile] = useState(defaultSelectedLayout === 'file');
const [isPureChat, setIsPureChat] = useState(defaultSelectedLayout === 'chat'); const [isPureChat, setIsPureChat] = useState(defaultSelectedLayout === 'chat');
const [isCollapseOpen, setIsCollapseOpen] = useState(isPureChat ? true : false);
useUpdateLayoutEffect(() => { useUpdateLayoutEffect(() => {
if (isPureFile === true) setIsPureFile(defaultSelectedLayout === 'file'); if (isPureFile === true) setIsPureFile(defaultSelectedLayout === 'file');
if (isPureChat === true) setIsPureChat(defaultSelectedLayout === 'chat'); if (isPureChat === true) setIsPureChat(defaultSelectedLayout === 'chat');
}, [defaultSelectedLayout]); }, [defaultSelectedLayout]);
return { isPureFile, isPureChat, setIsPureChat }; const collapseDirection: 'left' | 'right' = useMemo(() => {
return defaultSelectedLayout === 'file' ? 'left' : 'right';
}, [defaultSelectedLayout]);
return {
isPureFile,
isPureChat,
setIsPureChat,
setIsPureFile,
collapseDirection,
setIsCollapseOpen,
isCollapseOpen
};
}; };

View File

@ -205,7 +205,7 @@ const MetricsEmptyState: React.FC<{
<ListEmptyStateWithButton <ListEmptyStateWithButton
title="You dont have any logs yet." title="You dont have any logs yet."
description="You dont have any logs. As soon as you do, they will start to appear here." description="You dont have any logs. As soon as you do, they will start to appear here."
buttonText="New metric" buttonText="New chat"
onClick={openNewMetricModal} onClick={openNewMetricModal}
/> />
); );
@ -215,7 +215,7 @@ const MetricsEmptyState: React.FC<{
<ListEmptyStateWithButton <ListEmptyStateWithButton
title="You dont have any metrics yet." title="You dont have any metrics yet."
description="You dont have any metrics. As soon as you do, they will start to appear here." description="You dont have any metrics. As soon as you do, they will start to appear here."
buttonText="New metric" buttonText="New chat"
onClick={openNewMetricModal} onClick={openNewMetricModal}
/> />
); );

View File

@ -37,7 +37,7 @@ export const MetricSidebarHeader: React.FC<{
icon={<AppMaterialIcons icon="edit_square" />} icon={<AppMaterialIcons icon="edit_square" />}
type="default" type="default"
onClick={onToggleChatsModalPreflight}> onClick={onToggleChatsModalPreflight}>
New metric New Chat
</Button> </Button>
</div> </div>
</div> </div>