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 {
IN_PROGRESS = 'in_progress',
@ -17,5 +17,9 @@ export type ChatPost_generatingTitle = {
} & BusterChatStepBase;
export type ChatPost_generatingMessage = {
message: BusterChatMessageResponse;
resposnse_message: BusterChatMessageResponse;
} & BusterChatStepBase;
export type ChatPost_complete = {
message: BusterChatMessage;
} & BusterChatStepBase;

View File

@ -8,6 +8,7 @@ export type ChatCreateNewChat = BusterSocketRequestBase<
chat_id?: string | null; //send if we are following up on a chat
suggestion_id?: string | null; //send if we clicked on a suggestion
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?
}
>;
@ -39,7 +40,7 @@ export type ChatsDuplicateChat = BusterSocketRequestBase<
'/chats/duplicate',
{
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;
role: ShareRole;
}[];
remove_users?: string[]; // user_id
team_permissions?: {
team_id: string;

View File

@ -23,52 +23,28 @@ export type MetricSubscribeToMetric = BusterSocketRequestBase<
{ 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<
'/metrics/update',
{
id: string; //metric id
title?: string;
save_to_dashboard?: string;
remove_from_dashboard?: string; // dashboard_id optional
add_to_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;
feedback?: 'negative';
status?: VerificationStatus;
}
chart_config?: BusterChartConfigProps;
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 MetricGetDataByMessageId = BusterSocketRequestBase<'/metrics/data', { id: string }>;
export type MetricSearch = BusterSocketRequestBase<
'/metrics/search',
{
prompt: string;
}
>;
export type MetricSearch = BusterSocketRequestBase<'/metrics/search', { prompt: string }>;
export type MetricDuplicate = BusterSocketRequestBase<
'/metrics/duplicate',
@ -84,9 +60,7 @@ export type MetricEmits =
| MetricListEmitPayload
| MetricUnsubscribeEmitPayload
| MetricUpdateMetric
| MetricCreateNewMetric
| MetricSubscribeToMetric
| MetricDelete
| MetricUpdateMessage
| MetricGetDataByMessageId
| MetricSearch;

View File

@ -1,3 +1,14 @@
export default function Page() {
return <></>;
import { MetricController } from '@appControllers/MetricController';
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({
params: { metricId },
@ -10,16 +11,8 @@ export default function MetricPage({
const embedView = embed === 'true';
return (
<AppAssetCheckLayout metricId={metricId} type="metric">
<></>
</AppAssetCheckLayout>
// <AppAssetCheckLayout metricId={metricId} type="metric">
<MetricController metricId={metricId} />
// </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]);
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} />
<ChatContent chatContentRef={chatContentRef} />
</div>

View File

@ -48,7 +48,7 @@ export const ChatInput: React.FC = React.memo(() => {
<div
className={cx(
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
className={cx(
@ -60,7 +60,7 @@ export const ChatInput: React.FC = React.memo(() => {
variant="borderless"
onBlur={onBlurInput}
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..."
value={inputValue}
autoFocus={true}
@ -78,9 +78,7 @@ export const ChatInput: React.FC = React.memo(() => {
</div>
</div>
<Text size="xs" type="tertiary">
Our AI may make mistakes. Check important info.
</Text>
<AIWarning />
</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,
useContextSelector
} 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 { ChatSplitterProps } from '../ChatLayout';
import { useMemoizedFn } from 'ahooks';
@ -30,8 +30,6 @@ export const useChatLayout = ({
const [isPending, startTransition] = useTransition();
const onChangePage = useAppLayoutContextSelector((state) => state.onChangePage);
const selectedLayout = defaultSelectedLayout;
const animateOpenSplitter = useMemoizedFn((side: 'left' | 'right' | 'both') => {
if (appSplitterRef.current) {
const { animateWidth, isSideClosed } = appSplitterRef.current;
@ -41,6 +39,8 @@ export const useChatLayout = ({
animateWidth('100%', 'right');
} else if (side === 'both' && (isSideClosed('right') || isSideClosed('left'))) {
animateWidth(DEFAULT_CHAT_OPTION, 'left');
setIsPureChat(false);
setIsPureFile(false);
}
}
});
@ -56,7 +56,6 @@ export const useChatLayout = ({
if (route) {
onChangePage(route);
setIsPureChat(false);
startTransition(() => {
animateOpenSplitter('both');
});
@ -64,15 +63,34 @@ export const useChatLayout = ({
});
const onCollapseFileClick = useMemoizedFn((close?: boolean) => {
const isCloseAction = close ?? selectedLayout === 'both';
if (isCloseAction) {
animateOpenSplitter('left');
const isCloseAction = close ?? isCollapseOpen;
if (defaultSelectedLayout === 'file') {
if (!isCloseAction && defaultSelectedFile) {
setIsCollapseOpen(true);
animateOpenSplitter('both');
} else {
setIsCollapseOpen(false);
animateOpenSplitter('right');
}
} else {
animateOpenSplitter('both');
if (isCloseAction) {
animateOpenSplitter('left');
} else {
animateOpenSplitter('both');
}
}
});
const { setIsPureChat, isPureFile, isPureChat } = useAutoSetLayout({
const {
setIsPureChat,
setIsCollapseOpen,
isPureFile,
isPureChat,
setIsPureFile,
collapseDirection,
isCollapseOpen
} = useAutoSetLayout({
defaultSelectedLayout
});
@ -83,6 +101,8 @@ export const useChatLayout = ({
return {
...fileLayoutContext,
collapseDirection,
isCollapseOpen,
isPureFile,
isPureChat,
onSetSelectedFile,

View File

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

View File

@ -1,29 +1,38 @@
import { AppMaterialIcons } from '@/components';
import { Button } from 'antd';
import React from 'react';
import React, { useMemo } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { useMemoizedFn } from 'ahooks';
const animation = {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 }
};
export const CollapseFileButton: React.FC<{
showCollapseButton: boolean;
isOpen: boolean;
}> = React.memo(({ showCollapseButton, isOpen }) => {
const onCollapseFileClick = useChatLayoutContextSelector((state) => state.onCollapseFileClick);
const icon = !isOpen ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right';
collapseDirection: 'left' | 'right';
onCollapseFileClick: (value?: boolean) => void;
}> = 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(() => {
onCollapseFileClick();
});
const animation = useMemo(() => {
const baseAnimation = {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 }
};
return baseAnimation;
}, [collapseDirection, isOpen]);
return (
<AnimatePresence mode="wait">
<AnimatePresence mode="wait" initial={false}>
{showCollapseButton && (
<motion.div variants={animation}>
<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 selectedFileType = useChatLayoutContextSelector((x) => x.selectedFileType);
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 isCollapseOpen = true; //I could get the defaultSelectedLayout from the context and check if it is 'both'?
const SelectedFileSegment = React.useMemo(
() => (selectedFileType ? SelectedFileSegmentRecord[selectedFileType] : () => <></>),
@ -39,9 +41,17 @@ export const FileContainerHeader: React.FC = React.memo(() => {
return (
<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">
<CollapseFileButton showCollapseButton={showCollapseButton} isOpen={isCollapseOpen} />
<CollapseFileButton
collapseDirection={collapseDirection}
showCollapseButton={showCollapseButton}
isOpen={isCollapseOpen}
onCollapseFileClick={onCollapseFileClick}
/>
{selectedFileView && <SelectedFileSegment selectedFileView={selectedFileView} />}
</div>
<SelectedFileButtons selectedFileView={selectedFileView} />

View File

@ -1,22 +1,31 @@
import { useUpdateLayoutEffect } from 'ahooks';
import { useState } from 'react';
import { useMemo, useState } from 'react';
export const useAutoSetLayout = ({
defaultSelectedLayout
}: {
defaultSelectedLayout: 'chat' | 'file' | 'both' | undefined;
}): {
isPureFile: boolean;
isPureChat: boolean;
setIsPureChat: (value: boolean) => void;
} => {
}) => {
const [isPureFile, setIsPureFile] = useState(defaultSelectedLayout === 'file');
const [isPureChat, setIsPureChat] = useState(defaultSelectedLayout === 'chat');
const [isCollapseOpen, setIsCollapseOpen] = useState(isPureChat ? true : false);
useUpdateLayoutEffect(() => {
if (isPureFile === true) setIsPureFile(defaultSelectedLayout === 'file');
if (isPureChat === true) setIsPureChat(defaultSelectedLayout === 'chat');
}, [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
title="You dont have any logs yet."
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}
/>
);
@ -215,7 +215,7 @@ const MetricsEmptyState: React.FC<{
<ListEmptyStateWithButton
title="You dont have any metrics yet."
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}
/>
);

View File

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