update how we pass inputs

This commit is contained in:
Nate Kelley 2025-02-05 09:55:09 -07:00
parent b984e9863f
commit 39d5601158
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
11 changed files with 84 additions and 53 deletions

View File

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

View File

@ -1,3 +1,3 @@
export default function Page() { export default function Page() {
return <></>; return <></>; //we need this page to be able to load the chat page
} }

View File

@ -10,7 +10,6 @@ import type { BusterSearchResult } from '@/api/asset_interfaces';
import { useBusterNotifications } from '@/context/BusterNotifications'; import { useBusterNotifications } from '@/context/BusterNotifications';
import { NewChatModalDataSourceSelect } from './NewChatModalDatasourceSelect'; import { NewChatModalDataSourceSelect } from './NewChatModalDatasourceSelect';
import { NoDatasets } from './NoDatasets'; import { NoDatasets } from './NoDatasets';
import { useParams } from 'next/navigation';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { useGetDatasets } from '@/api/buster_rest/datasets'; import { useGetDatasets } from '@/api/buster_rest/datasets';
import { NewDatasetModal } from '../NewDatasetModal'; import { NewDatasetModal } from '../NewDatasetModal';
@ -35,11 +34,9 @@ export const NewChatModal = React.memo<{
onClose: () => void; onClose: () => void;
}>(({ open, onClose }) => { }>(({ open, onClose }) => {
const token = useAntToken(); const token = useAntToken();
const searchParams = useParams();
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage); const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
const { openErrorNotification } = useBusterNotifications(); const { openErrorNotification } = useBusterNotifications();
const { isFetched: isFetchedDatasets, data: datasetsList } = useGetDatasets(); const { isFetched: isFetchedDatasets, data: datasetsList } = useGetDatasets();
const onStartNewChat = useBusterNewChatContextSelector((x) => x.onStartNewChat);
const onSetSelectedChatDataSource = useBusterNewChatContextSelector( const onSetSelectedChatDataSource = useBusterNewChatContextSelector(
(x) => x.onSetSelectedChatDataSource (x) => x.onSetSelectedChatDataSource
); );
@ -192,13 +189,18 @@ const NewChatInput: React.FC<{
const token = useAntToken(); const token = useAntToken();
const inputRef = useRef<InputRef>(null); const inputRef = useRef<InputRef>(null);
const loadingNewMetric = useBusterNewChatContextSelector((x) => x.loadingNewChat); const loadingNewMetric = useBusterNewChatContextSelector((x) => x.loadingNewChat);
const prompt = useBusterNewChatContextSelector((x) => x.prompt);
const onStartNewChat = useBusterNewChatContextSelector((x) => x.onStartNewChat); const onStartNewChat = useBusterNewChatContextSelector((x) => x.onStartNewChat);
const onSetPrompt = useBusterNewChatContextSelector((x) => x.onSetPrompt); const onSelectSearchAsset = useBusterNewChatContextSelector((x) => x.onSelectSearchAsset);
const [prompt, setPrompt] = useState('');
const onStartNewChatPreflight = useMemoizedFn(async () => {
await onStartNewChat(prompt);
setPrompt('');
});
const onChangeText = useMemoizedFn((e: React.ChangeEvent<HTMLTextAreaElement>) => { const onChangeText = useMemoizedFn((e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.currentTarget.value; const value = e.currentTarget.value;
onSetPrompt(value); setPrompt(value);
if (value.length < 1) { if (value.length < 1) {
setSuggestedPrompts([]); setSuggestedPrompts([]);
} else { } else {
@ -216,7 +218,10 @@ const NewChatInput: React.FC<{
shownPrompts[activeItem]?.name && shownPrompts[activeItem]?.name &&
lastKeyPressedWasUpOrDown lastKeyPressedWasUpOrDown
) { ) {
onStartNewChat(shownPrompts[activeItem]?.name); const foundAsset = shownPrompts[activeItem];
if (foundAsset) {
onSelectSearchAsset(foundAsset);
}
v.stopPropagation(); v.stopPropagation();
v.preventDefault(); v.preventDefault();
return; return;
@ -224,17 +229,17 @@ const NewChatInput: React.FC<{
if (v.shiftKey) { if (v.shiftKey) {
return; return;
} }
onStartNewChat(value); onStartNewChatPreflight();
}); });
const onClickSubmitButton = useMemoizedFn(() => { const onClickSubmitButton = useMemoizedFn(() => {
onStartNewChat(prompt); onStartNewChatPreflight();
}); });
useMount(() => { useMount(() => {
setTimeout(() => { setTimeout(() => {
inputRef.current?.focus(); inputRef.current?.focus();
}, 200); }, 175);
}); });
const autoSizeMemoized = useMemo(() => { const autoSizeMemoized = useMemo(() => {

View File

@ -3,17 +3,19 @@ import { MetricFileView } from '@appLayouts/ChatLayout/ChatLayoutContext';
import { MetricViewComponents } from './config'; import { MetricViewComponents } from './config';
import { IndeterminateLinearLoader } from '@/components/loaders/IndeterminateLinearLoader'; import { IndeterminateLinearLoader } from '@/components/loaders/IndeterminateLinearLoader';
import { useBusterMetricIndividual } from '@/context/Metrics'; import { useBusterMetricIndividual } from '@/context/Metrics';
import { useBusterNewChatContextSelector } from '@/context/Chats';
export const MetricViewController: React.FC<{ export const MetricViewController: React.FC<{
metricId: string; metricId: string;
selectedFileView: MetricFileView | undefined; selectedFileView: MetricFileView | undefined;
}> = React.memo(({ metricId, selectedFileView = 'chart' }) => { }> = React.memo(({ metricId, selectedFileView = 'chart' }) => {
const { metric, metricData } = useBusterMetricIndividual({ metricId }); const { metric, metricData } = useBusterMetricIndividual({ metricId });
const loadingNewChat = useBusterNewChatContextSelector((x) => x.loadingNewChat);
const isFetchedConfig = metric.fetched; const isFetchedConfig = metric.fetched;
const isFetchedData = metricData.fetched; const isFetchedData = metricData.fetched;
const showLoader = !isFetchedConfig || !isFetchedData; const showLoader = !isFetchedConfig || !isFetchedData || loadingNewChat;
const Component = selectedFileView ? MetricViewComponents[selectedFileView] : () => null; const Component = selectedFileView ? MetricViewComponents[selectedFileView] : () => null;

View File

@ -18,7 +18,7 @@ export const useMetricControllerLayout = ({
if (side === 'metric') { if (side === 'metric') {
appSplitterRef.current.animateWidth('100%', 'left'); appSplitterRef.current.animateWidth('100%', 'left');
} else if (side === 'both') { } else if (side === 'both') {
appSplitterRef.current.animateWidth('265px', 'right'); appSplitterRef.current.animateWidth('310px', 'right');
} }
} }
}); });

View File

@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useRef, useState } from 'react';
import { Input } from 'antd'; import { Input } from 'antd';
import { createStyles } from 'antd-style'; import { createStyles } from 'antd-style';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
@ -7,12 +7,14 @@ import { useBusterNewChatContextSelector } from '@/context/Chats';
import { AIWarning } from './AIWarning'; import { AIWarning } from './AIWarning';
import { SubmitButton } from './SubmitButton'; import { SubmitButton } from './SubmitButton';
import { useChatInputFlow } from './useChatInputFlow'; import { useChatInputFlow } from './useChatInputFlow';
import type { TextAreaRef } from 'antd/es/input/TextArea';
const autoSize = { minRows: 3, maxRows: 16 }; const autoSize = { minRows: 3, maxRows: 16 };
export const ChatInput: React.FC<{}> = React.memo(({}) => { export const ChatInput: React.FC<{}> = React.memo(({}) => {
const { styles, cx } = useStyles(); const { styles, cx } = useStyles();
const loading = useBusterNewChatContextSelector((state) => state.loadingNewChat); const loading = useBusterNewChatContextSelector((state) => state.loadingNewChat);
const inputRef = useRef<TextAreaRef>(null);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [isFocused, setIsFocused] = React.useState(false); const [isFocused, setIsFocused] = React.useState(false);
@ -24,7 +26,9 @@ export const ChatInput: React.FC<{}> = React.memo(({}) => {
const { onSubmitPreflight } = useChatInputFlow({ const { onSubmitPreflight } = useChatInputFlow({
disableSendButton, disableSendButton,
inputValue, inputValue,
setInputValue setInputValue,
loading,
inputRef
}); });
const onPressEnter = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => { const onPressEnter = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
@ -59,6 +63,7 @@ export const ChatInput: React.FC<{}> = React.memo(({}) => {
'relative flex w-full items-center overflow-hidden' 'relative flex w-full items-center overflow-hidden'
)}> )}>
<Input.TextArea <Input.TextArea
ref={inputRef}
variant="borderless" variant="borderless"
onBlur={onBlurInput} onBlur={onBlurInput}
onFocus={onFocusInput} onFocus={onFocusInput}

View File

@ -2,24 +2,31 @@ import { useMemo } from 'react';
import { useChatContextSelector } from '../../../ChatContext'; import { useChatContextSelector } from '../../../ChatContext';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { useBusterNewChatContextSelector } from '@/context/Chats'; import { useBusterNewChatContextSelector } from '@/context/Chats';
import type { TextAreaRef } from 'antd/es/input/TextArea';
type FlowType = 'followup-chat' | 'followup-metric' | 'followup-dashboard' | 'new'; type FlowType = 'followup-chat' | 'followup-metric' | 'followup-dashboard' | 'new';
export const useChatInputFlow = ({ export const useChatInputFlow = ({
disableSendButton, disableSendButton,
inputValue, inputValue,
setInputValue setInputValue,
inputRef,
loading
}: { }: {
disableSendButton: boolean; disableSendButton: boolean;
inputValue: string; inputValue: string;
setInputValue: (value: string) => void; setInputValue: (value: string) => void;
inputRef: React.RefObject<TextAreaRef>;
loading: boolean;
}) => { }) => {
const hasChat = useChatContextSelector((x) => x.hasChat); const hasChat = useChatContextSelector((x) => x.hasChat);
const chatId = useChatContextSelector((x) => x.chatId);
const selectedFileType = useChatContextSelector((x) => x.selectedFileType); const selectedFileType = useChatContextSelector((x) => x.selectedFileType);
const selectedFileId = useChatContextSelector((x) => x.selectedFileId); const selectedFileId = useChatContextSelector((x) => x.selectedFileId);
const onStartNewChat = useBusterNewChatContextSelector((state) => state.onStartNewChat); const onStartNewChat = useBusterNewChatContextSelector((state) => state.onStartNewChat);
const onFollowUpChat = useBusterNewChatContextSelector((state) => state.onFollowUpChat); const onFollowUpChat = useBusterNewChatContextSelector((state) => state.onFollowUpChat);
const onStartChatFromFile = useBusterNewChatContextSelector((state) => state.onStartChatFromFile); const onStartChatFromFile = useBusterNewChatContextSelector((state) => state.onStartChatFromFile);
const onStopChat = useBusterNewChatContextSelector((state) => state.onStopChat);
const currentMessageId = useChatContextSelector((x) => x.currentMessageId); const currentMessageId = useChatContextSelector((x) => x.currentMessageId);
const flow: FlowType = useMemo(() => { const flow: FlowType = useMemo(() => {
@ -32,6 +39,11 @@ export const useChatInputFlow = ({
const onSubmitPreflight = useMemoizedFn(async () => { const onSubmitPreflight = useMemoizedFn(async () => {
if (disableSendButton) return; if (disableSendButton) return;
if (loading) {
onStopChat({ chatId: chatId! });
return;
}
switch (flow) { switch (flow) {
case 'followup-chat': case 'followup-chat':
await onFollowUpChat({ prompt: inputValue, messageId: currentMessageId! }); await onFollowUpChat({ prompt: inputValue, messageId: currentMessageId! });
@ -62,6 +74,10 @@ export const useChatInputFlow = ({
} }
setInputValue(''); setInputValue('');
setTimeout(() => {
inputRef.current?.focus();
}, 50);
}); });
return { onSubmitPreflight }; return { onSubmitPreflight };

View File

@ -0,0 +1,31 @@
import { AppMaterialIcons } from '@/components/icons';
import { Button } from 'antd';
import React from 'react';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { useHotkeys } from 'react-hotkeys-hook';
import { useMemoizedFn } from 'ahooks';
import { AppTooltip } from '@/components';
export const CreateChatButton = React.memo(() => {
const onCollapseFileClick = useChatLayoutContextSelector((x) => x.onCollapseFileClick);
const onCollapseFileClickPreflight = useMemoizedFn(() => {
onCollapseFileClick(false);
});
useHotkeys('e', onCollapseFileClickPreflight, { preventDefault: true });
return (
<AppTooltip title={'Start chat'} shortcuts={['e']}>
<Button
onClick={onCollapseFileClickPreflight}
color="default"
variant="solid"
type="primary"
icon={<AppMaterialIcons icon="stars" />}>
Edit
</Button>
</AppTooltip>
);
});
CreateChatButton.displayName = 'CreateChatButton';

View File

@ -10,6 +10,7 @@ import { ShareMetricButton } from '@appComponents/Buttons/ShareMetricButton';
import { useChatContextSelector } from '../../ChatContext'; import { useChatContextSelector } from '../../ChatContext';
import { HideButtonContainer } from './HideButtonContainer'; import { HideButtonContainer } from './HideButtonContainer';
import { FileButtonContainer } from './FileButtonContainer'; import { FileButtonContainer } from './FileButtonContainer';
import { CreateChatButton } from './CreateChatButtont';
export const MetricContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(() => { export const MetricContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(() => {
const selectedFileView = useChatLayoutContextSelector( const selectedFileView = useChatLayoutContextSelector(
@ -78,21 +79,3 @@ const ShareMetricButtonLocal = React.memo(() => {
return <ShareMetricButton />; return <ShareMetricButton />;
}); });
ShareMetricButtonLocal.displayName = 'ShareMetricButtonLocal'; ShareMetricButtonLocal.displayName = 'ShareMetricButtonLocal';
const CreateChatButton = React.memo(() => {
const onCollapseFileClick = useChatLayoutContextSelector((x) => x.onCollapseFileClick);
return (
<Button
onClick={() => {
onCollapseFileClick(false);
}}
color="default"
variant="solid"
type="primary"
icon={<AppMaterialIcons icon="stars" />}>
Edit
</Button>
);
});
CreateChatButton.displayName = 'CreateChatButton';

View File

@ -25,7 +25,7 @@ const TooltipShortcut: React.FC<{ shortcut: string }> = ({ shortcut }) => {
<div <div
className="relative flex justify-center" className="relative flex justify-center"
style={{ style={{
lineHeight: 1.25, lineHeight: 1,
fontSize: 12, fontSize: 12,
borderRadius: borderRadius, borderRadius: borderRadius,
border: `0.5px solid ${colorBorder}`, border: `0.5px solid ${colorBorder}`,

View File

@ -11,18 +11,12 @@ export const useBusterNewChat = () => {
const [selectedChatDataSource, setSelectedChatDataSource] = const [selectedChatDataSource, setSelectedChatDataSource] =
useState<BusterDatasetListItem | null>(null); useState<BusterDatasetListItem | null>(null);
const [loadingNewChat, setLoadingNewChat] = useState(false); const [loadingNewChat, setLoadingNewChat] = useState(false);
const [prompt, setPrompt] = useState('');
const onSetPrompt = useMemoizedFn((prompt: string) => {
setPrompt(prompt);
});
const onSelectSearchAsset = useMemoizedFn(async (asset: BusterSearchResult) => { const onSelectSearchAsset = useMemoizedFn(async (asset: BusterSearchResult) => {
setLoadingNewChat(true); setLoadingNewChat(true);
console.log('select search asset'); console.log('select search asset');
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
setLoadingNewChat(false); setLoadingNewChat(false);
onSetPrompt('');
}); });
const onStartNewChat = useMemoizedFn(async (prompt: string) => { const onStartNewChat = useMemoizedFn(async (prompt: string) => {
@ -30,7 +24,6 @@ export const useBusterNewChat = () => {
console.log('start new chat'); console.log('start new chat');
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
setLoadingNewChat(false); setLoadingNewChat(false);
onSetPrompt('');
}); });
const onStartChatFromFile = useMemoizedFn( const onStartChatFromFile = useMemoizedFn(
@ -39,7 +32,6 @@ export const useBusterNewChat = () => {
console.log('start chat from file'); console.log('start chat from file');
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
setLoadingNewChat(false); setLoadingNewChat(false);
onSetPrompt('');
} }
); );
@ -49,7 +41,6 @@ export const useBusterNewChat = () => {
console.log('follow up chat'); console.log('follow up chat');
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
setLoadingNewChat(false); setLoadingNewChat(false);
onSetPrompt('');
} }
); );
@ -59,10 +50,14 @@ export const useBusterNewChat = () => {
console.log('replace message in chat'); console.log('replace message in chat');
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
setLoadingNewChat(false); setLoadingNewChat(false);
onSetPrompt('');
} }
); );
const onStopChat = useMemoizedFn(({ chatId }: { chatId: string }) => {
setLoadingNewChat(false);
console.log('stop current chat');
});
const onSetSelectedChatDataSource = useMemoizedFn((dataSource: BusterDatasetListItem | null) => { const onSetSelectedChatDataSource = useMemoizedFn((dataSource: BusterDatasetListItem | null) => {
setSelectedChatDataSource(dataSource); setSelectedChatDataSource(dataSource);
}); });
@ -73,11 +68,10 @@ export const useBusterNewChat = () => {
onSelectSearchAsset, onSelectSearchAsset,
selectedChatDataSource, selectedChatDataSource,
onSetSelectedChatDataSource, onSetSelectedChatDataSource,
onSetPrompt,
onFollowUpChat, onFollowUpChat,
prompt,
onStartChatFromFile, onStartChatFromFile,
onReplaceMessageInChat onReplaceMessageInChat,
onStopChat
}; };
}; };