diff --git a/web/src/app/app/(chat_experience)/chat/[chatId]/layout.tsx b/web/src/app/app/(chat_experience)/chat/[chatId]/layout.tsx deleted file mode 100644 index 227516f0b..000000000 --- a/web/src/app/app/(chat_experience)/chat/[chatId]/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export default function Layout({ children }: { children: React.ReactNode }) { - return <>{children}; -} diff --git a/web/src/app/app/(chat_experience)/chat/[chatId]/page.tsx b/web/src/app/app/(chat_experience)/chat/[chatId]/page.tsx index 7d1e0b717..3938ede50 100644 --- a/web/src/app/app/(chat_experience)/chat/[chatId]/page.tsx +++ b/web/src/app/app/(chat_experience)/chat/[chatId]/page.tsx @@ -1,3 +1,3 @@ export default function Page() { - return <>; + return <>; //we need this page to be able to load the chat page } diff --git a/web/src/app/app/_components/NewChatModal/NewChatModal.tsx b/web/src/app/app/_components/NewChatModal/NewChatModal.tsx index ae191241e..d9a6f525b 100644 --- a/web/src/app/app/_components/NewChatModal/NewChatModal.tsx +++ b/web/src/app/app/_components/NewChatModal/NewChatModal.tsx @@ -10,7 +10,6 @@ import type { BusterSearchResult } from '@/api/asset_interfaces'; import { useBusterNotifications } from '@/context/BusterNotifications'; import { NewChatModalDataSourceSelect } from './NewChatModalDatasourceSelect'; import { NoDatasets } from './NoDatasets'; -import { useParams } from 'next/navigation'; import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; import { useGetDatasets } from '@/api/buster_rest/datasets'; import { NewDatasetModal } from '../NewDatasetModal'; @@ -35,11 +34,9 @@ export const NewChatModal = React.memo<{ onClose: () => void; }>(({ open, onClose }) => { const token = useAntToken(); - const searchParams = useParams(); const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage); const { openErrorNotification } = useBusterNotifications(); const { isFetched: isFetchedDatasets, data: datasetsList } = useGetDatasets(); - const onStartNewChat = useBusterNewChatContextSelector((x) => x.onStartNewChat); const onSetSelectedChatDataSource = useBusterNewChatContextSelector( (x) => x.onSetSelectedChatDataSource ); @@ -192,13 +189,18 @@ const NewChatInput: React.FC<{ const token = useAntToken(); const inputRef = useRef(null); const loadingNewMetric = useBusterNewChatContextSelector((x) => x.loadingNewChat); - const prompt = useBusterNewChatContextSelector((x) => x.prompt); 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) => { const value = e.currentTarget.value; - onSetPrompt(value); + setPrompt(value); if (value.length < 1) { setSuggestedPrompts([]); } else { @@ -216,7 +218,10 @@ const NewChatInput: React.FC<{ shownPrompts[activeItem]?.name && lastKeyPressedWasUpOrDown ) { - onStartNewChat(shownPrompts[activeItem]?.name); + const foundAsset = shownPrompts[activeItem]; + if (foundAsset) { + onSelectSearchAsset(foundAsset); + } v.stopPropagation(); v.preventDefault(); return; @@ -224,17 +229,17 @@ const NewChatInput: React.FC<{ if (v.shiftKey) { return; } - onStartNewChat(value); + onStartNewChatPreflight(); }); const onClickSubmitButton = useMemoizedFn(() => { - onStartNewChat(prompt); + onStartNewChatPreflight(); }); useMount(() => { setTimeout(() => { inputRef.current?.focus(); - }, 200); + }, 175); }); const autoSizeMemoized = useMemo(() => { diff --git a/web/src/app/app/_controllers/MetricController/MetricViewController/MetricViewController.tsx b/web/src/app/app/_controllers/MetricController/MetricViewController/MetricViewController.tsx index dcddc0004..c8d847340 100644 --- a/web/src/app/app/_controllers/MetricController/MetricViewController/MetricViewController.tsx +++ b/web/src/app/app/_controllers/MetricController/MetricViewController/MetricViewController.tsx @@ -3,17 +3,19 @@ import { MetricFileView } from '@appLayouts/ChatLayout/ChatLayoutContext'; import { MetricViewComponents } from './config'; import { IndeterminateLinearLoader } from '@/components/loaders/IndeterminateLinearLoader'; import { useBusterMetricIndividual } from '@/context/Metrics'; +import { useBusterNewChatContextSelector } from '@/context/Chats'; export const MetricViewController: React.FC<{ metricId: string; selectedFileView: MetricFileView | undefined; }> = React.memo(({ metricId, selectedFileView = 'chart' }) => { const { metric, metricData } = useBusterMetricIndividual({ metricId }); + const loadingNewChat = useBusterNewChatContextSelector((x) => x.loadingNewChat); const isFetchedConfig = metric.fetched; const isFetchedData = metricData.fetched; - const showLoader = !isFetchedConfig || !isFetchedData; + const showLoader = !isFetchedConfig || !isFetchedData || loadingNewChat; const Component = selectedFileView ? MetricViewComponents[selectedFileView] : () => null; diff --git a/web/src/app/app/_controllers/MetricController/useMetricControllerLayout.ts b/web/src/app/app/_controllers/MetricController/useMetricControllerLayout.ts index 0d5c63b72..2ba9dcb26 100644 --- a/web/src/app/app/_controllers/MetricController/useMetricControllerLayout.ts +++ b/web/src/app/app/_controllers/MetricController/useMetricControllerLayout.ts @@ -18,7 +18,7 @@ export const useMetricControllerLayout = ({ if (side === 'metric') { appSplitterRef.current.animateWidth('100%', 'left'); } else if (side === 'both') { - appSplitterRef.current.animateWidth('265px', 'right'); + appSplitterRef.current.animateWidth('310px', 'right'); } } }); diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/ChatInput.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/ChatInput.tsx index 3b1ec39ee..2def7036a 100644 --- a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/ChatInput.tsx +++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/ChatInput.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { Input } from 'antd'; import { createStyles } from 'antd-style'; import { useMemoizedFn } from 'ahooks'; @@ -7,12 +7,14 @@ import { useBusterNewChatContextSelector } from '@/context/Chats'; import { AIWarning } from './AIWarning'; import { SubmitButton } from './SubmitButton'; import { useChatInputFlow } from './useChatInputFlow'; +import type { TextAreaRef } from 'antd/es/input/TextArea'; const autoSize = { minRows: 3, maxRows: 16 }; export const ChatInput: React.FC<{}> = React.memo(({}) => { const { styles, cx } = useStyles(); const loading = useBusterNewChatContextSelector((state) => state.loadingNewChat); + const inputRef = useRef(null); const [inputValue, setInputValue] = useState(''); const [isFocused, setIsFocused] = React.useState(false); @@ -24,7 +26,9 @@ export const ChatInput: React.FC<{}> = React.memo(({}) => { const { onSubmitPreflight } = useChatInputFlow({ disableSendButton, inputValue, - setInputValue + setInputValue, + loading, + inputRef }); const onPressEnter = useMemoizedFn((e: React.KeyboardEvent) => { @@ -59,6 +63,7 @@ export const ChatInput: React.FC<{}> = React.memo(({}) => { 'relative flex w-full items-center overflow-hidden' )}> void; + inputRef: React.RefObject; + loading: boolean; }) => { const hasChat = useChatContextSelector((x) => x.hasChat); + const chatId = useChatContextSelector((x) => x.chatId); const selectedFileType = useChatContextSelector((x) => x.selectedFileType); const selectedFileId = useChatContextSelector((x) => x.selectedFileId); const onStartNewChat = useBusterNewChatContextSelector((state) => state.onStartNewChat); const onFollowUpChat = useBusterNewChatContextSelector((state) => state.onFollowUpChat); const onStartChatFromFile = useBusterNewChatContextSelector((state) => state.onStartChatFromFile); + const onStopChat = useBusterNewChatContextSelector((state) => state.onStopChat); const currentMessageId = useChatContextSelector((x) => x.currentMessageId); const flow: FlowType = useMemo(() => { @@ -32,6 +39,11 @@ export const useChatInputFlow = ({ const onSubmitPreflight = useMemoizedFn(async () => { if (disableSendButton) return; + if (loading) { + onStopChat({ chatId: chatId! }); + return; + } + switch (flow) { case 'followup-chat': await onFollowUpChat({ prompt: inputValue, messageId: currentMessageId! }); @@ -62,6 +74,10 @@ export const useChatInputFlow = ({ } setInputValue(''); + + setTimeout(() => { + inputRef.current?.focus(); + }, 50); }); return { onSubmitPreflight }; diff --git a/web/src/app/app/_layouts/ChatLayout/FileContainer/FileContainerHeader/CreateChatButtont.tsx b/web/src/app/app/_layouts/ChatLayout/FileContainer/FileContainerHeader/CreateChatButtont.tsx new file mode 100644 index 000000000..72dfca943 --- /dev/null +++ b/web/src/app/app/_layouts/ChatLayout/FileContainer/FileContainerHeader/CreateChatButtont.tsx @@ -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 ( + + + + ); +}); +CreateChatButton.displayName = 'CreateChatButton'; diff --git a/web/src/app/app/_layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons.tsx b/web/src/app/app/_layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons.tsx index aef8d02d9..705dc1121 100644 --- a/web/src/app/app/_layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons.tsx +++ b/web/src/app/app/_layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons.tsx @@ -10,6 +10,7 @@ import { ShareMetricButton } from '@appComponents/Buttons/ShareMetricButton'; import { useChatContextSelector } from '../../ChatContext'; import { HideButtonContainer } from './HideButtonContainer'; import { FileButtonContainer } from './FileButtonContainer'; +import { CreateChatButton } from './CreateChatButtont'; export const MetricContainerHeaderButtons: React.FC = React.memo(() => { const selectedFileView = useChatLayoutContextSelector( @@ -78,21 +79,3 @@ const ShareMetricButtonLocal = React.memo(() => { return ; }); ShareMetricButtonLocal.displayName = 'ShareMetricButtonLocal'; - -const CreateChatButton = React.memo(() => { - const onCollapseFileClick = useChatLayoutContextSelector((x) => x.onCollapseFileClick); - - return ( - - ); -}); -CreateChatButton.displayName = 'CreateChatButton'; diff --git a/web/src/components/tooltip/AppTooltipShortcutPill.tsx b/web/src/components/tooltip/AppTooltipShortcutPill.tsx index f280fc746..b9095195a 100644 --- a/web/src/components/tooltip/AppTooltipShortcutPill.tsx +++ b/web/src/components/tooltip/AppTooltipShortcutPill.tsx @@ -25,7 +25,7 @@ const TooltipShortcut: React.FC<{ shortcut: string }> = ({ shortcut }) => {
{ const [selectedChatDataSource, setSelectedChatDataSource] = useState(null); const [loadingNewChat, setLoadingNewChat] = useState(false); - const [prompt, setPrompt] = useState(''); - - const onSetPrompt = useMemoizedFn((prompt: string) => { - setPrompt(prompt); - }); const onSelectSearchAsset = useMemoizedFn(async (asset: BusterSearchResult) => { setLoadingNewChat(true); console.log('select search asset'); await new Promise((resolve) => setTimeout(resolve, 1000)); setLoadingNewChat(false); - onSetPrompt(''); }); const onStartNewChat = useMemoizedFn(async (prompt: string) => { @@ -30,7 +24,6 @@ export const useBusterNewChat = () => { console.log('start new chat'); await new Promise((resolve) => setTimeout(resolve, 1000)); setLoadingNewChat(false); - onSetPrompt(''); }); const onStartChatFromFile = useMemoizedFn( @@ -39,7 +32,6 @@ export const useBusterNewChat = () => { console.log('start chat from file'); await new Promise((resolve) => setTimeout(resolve, 1000)); setLoadingNewChat(false); - onSetPrompt(''); } ); @@ -49,7 +41,6 @@ export const useBusterNewChat = () => { console.log('follow up chat'); await new Promise((resolve) => setTimeout(resolve, 1000)); setLoadingNewChat(false); - onSetPrompt(''); } ); @@ -59,10 +50,14 @@ export const useBusterNewChat = () => { console.log('replace message in chat'); await new Promise((resolve) => setTimeout(resolve, 1000)); setLoadingNewChat(false); - onSetPrompt(''); } ); + const onStopChat = useMemoizedFn(({ chatId }: { chatId: string }) => { + setLoadingNewChat(false); + console.log('stop current chat'); + }); + const onSetSelectedChatDataSource = useMemoizedFn((dataSource: BusterDatasetListItem | null) => { setSelectedChatDataSource(dataSource); }); @@ -73,11 +68,10 @@ export const useBusterNewChat = () => { onSelectSearchAsset, selectedChatDataSource, onSetSelectedChatDataSource, - onSetPrompt, onFollowUpChat, - prompt, onStartChatFromFile, - onReplaceMessageInChat + onReplaceMessageInChat, + onStopChat }; };