2023-05-05 01:10:02 +08:00
|
|
|
import * as React from 'react';
|
2023-05-07 01:19:48 +08:00
|
|
|
import { useState, useEffect, useRef } from 'react';
|
2023-06-05 22:18:51 +08:00
|
|
|
import { Alert, Center, Container, Stack, px } from '@mantine/core';
|
2023-05-05 01:10:02 +08:00
|
|
|
import { ScrollArea } from '@mantine/core';
|
2023-06-05 19:27:25 +08:00
|
|
|
import { Button } from '@mantine/core';
|
2023-05-18 13:58:17 +08:00
|
|
|
import { useListState, useResizeObserver, useTimeout, useViewportSize } from '@mantine/hooks';
|
2023-06-08 20:11:00 +08:00
|
|
|
import { IconPlayerStop, IconRotateDot } from '@tabler/icons-react';
|
2023-06-13 17:03:48 +08:00
|
|
|
import messageUtil from '@/util/MessageUtil';
|
|
|
|
import { useAppDispatch, useAppSelector } from '@/views/hooks';
|
2023-05-07 00:50:03 +08:00
|
|
|
|
2023-06-08 17:32:57 +08:00
|
|
|
import {
|
|
|
|
setValue
|
|
|
|
} from './inputSlice';
|
2023-06-08 19:27:51 +08:00
|
|
|
import {
|
2023-06-08 20:11:00 +08:00
|
|
|
reGenerating,
|
2023-06-08 19:27:51 +08:00
|
|
|
stopGenerating,
|
|
|
|
startResponsing,
|
|
|
|
happendError,
|
2023-06-08 19:54:21 +08:00
|
|
|
newMessage,
|
|
|
|
updateMessage,
|
|
|
|
shiftMessage,
|
2023-06-08 19:27:51 +08:00
|
|
|
selectGenerating,
|
|
|
|
selectCurrentMessage,
|
|
|
|
selectErrorMessage,
|
2023-06-08 19:54:21 +08:00
|
|
|
selectMessages,
|
2023-06-13 11:11:29 +08:00
|
|
|
selectMessageCount,
|
2023-06-13 11:54:31 +08:00
|
|
|
selectIsBottom,
|
|
|
|
selectIsTop,
|
|
|
|
selectIsMiddle,
|
|
|
|
onMessagesBottom,
|
|
|
|
onMessagesTop,
|
|
|
|
onMessagesMiddle,
|
2023-06-13 11:11:29 +08:00
|
|
|
fetchHistoryMessages,
|
2023-06-08 19:27:51 +08:00
|
|
|
} from './chatSlice';
|
2023-06-08 17:32:57 +08:00
|
|
|
|
2023-06-05 19:21:10 +08:00
|
|
|
import InputMessage from './InputMessage';
|
2023-06-05 19:27:25 +08:00
|
|
|
import MessageContainer from './MessageContainer';
|
2023-06-05 18:20:08 +08:00
|
|
|
|
2023-06-08 20:11:00 +08:00
|
|
|
const RegenerationButton = () => {
|
2023-06-09 09:26:37 +08:00
|
|
|
const dispatch = useAppDispatch();
|
2023-06-06 10:55:14 +08:00
|
|
|
return (<Button
|
|
|
|
size='xs'
|
|
|
|
leftIcon={<IconRotateDot color='var(--vscode-button-foreground)' />}
|
|
|
|
sx={{
|
|
|
|
backgroundColor: 'var(--vscode-button-background)',
|
|
|
|
}}
|
|
|
|
styles={{
|
|
|
|
icon: {
|
|
|
|
color: 'var(--vscode-button-foreground)'
|
|
|
|
},
|
|
|
|
label: {
|
|
|
|
color: 'var(--vscode-button-foreground)',
|
|
|
|
fontSize: 'var(--vscode-editor-font-size)',
|
|
|
|
}
|
|
|
|
}}
|
2023-06-08 20:11:00 +08:00
|
|
|
onClick={() => dispatch(reGenerating())}
|
|
|
|
variant="white" >
|
2023-06-06 10:55:14 +08:00
|
|
|
Regeneration
|
2023-06-08 20:11:00 +08:00
|
|
|
</Button >);
|
2023-06-06 10:55:14 +08:00
|
|
|
};
|
|
|
|
|
2023-06-08 20:11:00 +08:00
|
|
|
const StopButton = () => {
|
2023-06-09 09:26:37 +08:00
|
|
|
const dispatch = useAppDispatch();
|
2023-06-06 10:55:14 +08:00
|
|
|
return (
|
|
|
|
<Button
|
|
|
|
size='xs'
|
|
|
|
leftIcon={<IconPlayerStop color='var(--vscode-button-foreground)' />}
|
|
|
|
sx={{
|
|
|
|
backgroundColor: 'var(--vscode-button-background)',
|
|
|
|
}}
|
|
|
|
styles={{
|
|
|
|
icon: {
|
|
|
|
color: 'var(--vscode-button-foreground)'
|
|
|
|
},
|
|
|
|
label: {
|
|
|
|
color: 'var(--vscode-button-foreground)',
|
|
|
|
fontSize: 'var(--vscode-editor-font-size)',
|
|
|
|
}
|
|
|
|
}}
|
2023-06-09 12:01:56 +08:00
|
|
|
onClick={() => {
|
|
|
|
dispatch(stopGenerating());
|
|
|
|
messageUtil.sendMessage({
|
|
|
|
command: 'stopDevChat'
|
|
|
|
});
|
|
|
|
}}
|
2023-06-06 10:55:14 +08:00
|
|
|
variant="white">
|
|
|
|
Stop generating
|
|
|
|
</Button>);
|
|
|
|
};
|
|
|
|
|
2023-06-05 19:12:58 +08:00
|
|
|
const chatPanel = () => {
|
2023-06-09 09:26:37 +08:00
|
|
|
const dispatch = useAppDispatch();
|
|
|
|
const generating = useAppSelector(selectGenerating);
|
|
|
|
const currentMessage = useAppSelector(selectCurrentMessage);
|
|
|
|
const errorMessage = useAppSelector(selectErrorMessage);
|
|
|
|
const messages = useAppSelector(selectMessages);
|
2023-06-13 11:11:29 +08:00
|
|
|
const messageCount = useAppSelector(selectMessageCount);
|
2023-06-13 11:54:31 +08:00
|
|
|
const isTop = useAppSelector(selectIsTop);
|
|
|
|
const isBottom = useAppSelector(selectIsBottom);
|
|
|
|
const isMiddle = useAppSelector(selectIsMiddle);
|
2023-06-05 19:12:58 +08:00
|
|
|
const [chatContainerRef, chatContainerRect] = useResizeObserver();
|
|
|
|
const scrollViewport = useRef<HTMLDivElement>(null);
|
|
|
|
const { height, width } = useViewportSize();
|
|
|
|
|
|
|
|
const scrollToBottom = () =>
|
|
|
|
scrollViewport?.current?.scrollTo({ top: scrollViewport.current.scrollHeight, behavior: 'smooth' });
|
|
|
|
|
|
|
|
const timer = useTimeout(() => {
|
2023-06-13 11:54:31 +08:00
|
|
|
if (isBottom) {
|
2023-06-05 19:12:58 +08:00
|
|
|
scrollToBottom();
|
|
|
|
}
|
|
|
|
}, 1000);
|
|
|
|
|
2023-06-13 11:54:31 +08:00
|
|
|
const onScrollPositionChange = ({ x, y }) => {
|
|
|
|
const sh = scrollViewport.current?.scrollHeight || 0;
|
|
|
|
const vh = scrollViewport.current?.clientHeight || 0;
|
|
|
|
const gap = sh - vh - y;
|
|
|
|
const isBottom = sh < vh ? true : gap < 100;
|
|
|
|
const isTop = y === 0;
|
|
|
|
// console.log(`sh:${sh},vh:${vh},x:${x},y:${y},gap:${gap}`);
|
|
|
|
if (isBottom) {
|
|
|
|
dispatch(onMessagesBottom());
|
|
|
|
} else if (isTop) {
|
|
|
|
dispatch(onMessagesTop());
|
|
|
|
} else {
|
|
|
|
dispatch(onMessagesMiddle());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-06-05 19:12:58 +08:00
|
|
|
useEffect(() => {
|
2023-06-13 11:11:29 +08:00
|
|
|
dispatch(fetchHistoryMessages());
|
2023-06-05 20:41:37 +08:00
|
|
|
messageUtil.registerHandler('receiveMessagePartial', (message: { text: string; }) => {
|
2023-06-08 19:27:51 +08:00
|
|
|
dispatch(startResponsing(message.text));
|
2023-06-05 20:41:37 +08:00
|
|
|
});
|
|
|
|
messageUtil.registerHandler('receiveMessage', (message: { text: string; isError: boolean }) => {
|
2023-06-08 19:27:51 +08:00
|
|
|
dispatch(stopGenerating());
|
2023-06-05 20:41:37 +08:00
|
|
|
if (message.isError) {
|
2023-06-08 19:27:51 +08:00
|
|
|
dispatch(happendError(message.text));
|
2023-06-05 20:41:37 +08:00
|
|
|
}
|
|
|
|
});
|
2023-06-05 19:12:58 +08:00
|
|
|
timer.start();
|
|
|
|
return () => {
|
|
|
|
timer.clear();
|
|
|
|
};
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (generating) {
|
|
|
|
// new a bot message
|
2023-06-08 19:54:21 +08:00
|
|
|
dispatch(newMessage({ type: 'bot', message: currentMessage }));
|
2023-06-05 19:12:58 +08:00
|
|
|
}
|
|
|
|
}, [generating]);
|
|
|
|
|
|
|
|
// Add the received message to the chat UI as a bot message
|
|
|
|
useEffect(() => {
|
|
|
|
const lastIndex = messages?.length - 1;
|
|
|
|
const lastMessage = messages[lastIndex];
|
|
|
|
if (currentMessage && lastMessage?.type === 'bot') {
|
|
|
|
// update the last one bot message
|
2023-06-08 19:54:21 +08:00
|
|
|
// messageHandlers.setItem();
|
|
|
|
dispatch(updateMessage({
|
|
|
|
index: lastIndex,
|
|
|
|
newMessage: { type: 'bot', message: currentMessage }
|
|
|
|
}));
|
2023-06-05 19:12:58 +08:00
|
|
|
}
|
|
|
|
timer.start();
|
|
|
|
}, [currentMessage]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (messages.length > messageCount * 2) {
|
2023-06-08 19:54:21 +08:00
|
|
|
dispatch(shiftMessage());
|
2023-06-05 19:12:58 +08:00
|
|
|
}
|
|
|
|
timer.start();
|
|
|
|
}, [messages]);
|
|
|
|
|
2023-05-05 01:10:02 +08:00
|
|
|
return (
|
2023-05-09 19:10:46 +08:00
|
|
|
<Container
|
|
|
|
ref={chatContainerRef}
|
|
|
|
sx={{
|
|
|
|
height: '100%',
|
2023-05-18 23:07:36 +08:00
|
|
|
margin: 0,
|
2023-05-19 00:21:53 +08:00
|
|
|
padding: 10,
|
|
|
|
background: 'var(--vscode-sideBar-background)',
|
2023-05-18 23:07:36 +08:00
|
|
|
color: 'var(--vscode-editor-foreground)',
|
|
|
|
minWidth: 240
|
2023-05-09 19:10:46 +08:00
|
|
|
}}>
|
|
|
|
<ScrollArea
|
|
|
|
type="never"
|
2023-05-18 23:07:36 +08:00
|
|
|
sx={{
|
|
|
|
height: generating ? height - px('8rem') : height - px('5rem'),
|
|
|
|
width: chatContainerRect.width,
|
|
|
|
padding: 0,
|
|
|
|
margin: 0,
|
|
|
|
}}
|
2023-06-08 10:47:24 +08:00
|
|
|
onScrollPositionChange={onScrollPositionChange}
|
2023-05-09 19:10:46 +08:00
|
|
|
viewportRef={scrollViewport}>
|
2023-06-06 18:13:26 +08:00
|
|
|
<MessageContainer
|
2023-06-08 19:54:21 +08:00
|
|
|
width={chatContainerRect.width} />
|
2023-06-08 19:27:51 +08:00
|
|
|
{errorMessage &&
|
2023-06-06 10:55:14 +08:00
|
|
|
<Alert styles={{ message: { fontSize: 'var(--vscode-editor-font-size)' } }} w={chatContainerRect.width} mb={20} color="gray" variant="filled">
|
2023-06-08 19:27:51 +08:00
|
|
|
{errorMessage}
|
2023-06-05 22:18:51 +08:00
|
|
|
</Alert>
|
|
|
|
}
|
2023-05-09 19:10:46 +08:00
|
|
|
</ScrollArea>
|
2023-05-10 17:38:24 +08:00
|
|
|
<Stack
|
2023-05-18 19:58:29 +08:00
|
|
|
spacing={5}
|
2023-06-05 21:13:06 +08:00
|
|
|
sx={{ position: 'absolute', bottom: 10, width: 'calc(100% - 20px)' }}>
|
2023-05-11 15:10:05 +08:00
|
|
|
{generating &&
|
|
|
|
<Center>
|
2023-06-08 20:11:00 +08:00
|
|
|
<StopButton />
|
2023-05-11 15:10:05 +08:00
|
|
|
</Center>
|
|
|
|
}
|
2023-06-08 19:27:51 +08:00
|
|
|
{errorMessage &&
|
2023-05-23 21:13:49 +08:00
|
|
|
<Center>
|
2023-06-08 20:11:00 +08:00
|
|
|
<RegenerationButton />
|
2023-05-23 21:13:49 +08:00
|
|
|
</Center>
|
|
|
|
}
|
2023-06-05 19:12:58 +08:00
|
|
|
<InputMessage
|
2023-06-08 20:11:00 +08:00
|
|
|
width={chatContainerRect.width} />
|
2023-05-10 17:38:24 +08:00
|
|
|
</Stack>
|
2023-06-08 17:32:57 +08:00
|
|
|
</Container>
|
2023-05-05 01:10:02 +08:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default chatPanel;
|