2023-05-05 01:10:02 +08:00
|
|
|
import * as React from 'react';
|
|
|
|
import { useState } from 'react';
|
2023-05-06 00:00:25 +08:00
|
|
|
import { Avatar, Container, Divider, Flex, Grid, Stack, TypographyStylesProvider } from '@mantine/core';
|
2023-05-05 01:10:02 +08:00
|
|
|
import { Input, Tooltip } from '@mantine/core';
|
|
|
|
import { List } from '@mantine/core';
|
|
|
|
import { ScrollArea } from '@mantine/core';
|
|
|
|
import { createStyles } from '@mantine/core';
|
|
|
|
import { ActionIcon } from '@mantine/core';
|
|
|
|
import { Menu, Button, Text } from '@mantine/core';
|
|
|
|
import { useViewportSize } from '@mantine/hooks';
|
2023-05-05 09:48:05 +08:00
|
|
|
import { IconEdit, IconRobot, IconSend, IconSquareRoundedPlus, IconUser } from '@tabler/icons-react';
|
2023-05-05 01:10:02 +08:00
|
|
|
import { IconSettings, IconSearch, IconPhoto, IconMessageCircle, IconTrash, IconArrowsLeftRight } from '@tabler/icons-react';
|
2023-05-05 09:48:05 +08:00
|
|
|
import { Prism } from '@mantine/prism';
|
2023-05-06 00:00:25 +08:00
|
|
|
import { useRemark } from 'react-remark';
|
2023-05-06 17:03:25 +08:00
|
|
|
import messageUtil from '../utils/MessageUtil';
|
2023-05-05 01:10:02 +08:00
|
|
|
|
|
|
|
const useStyles = createStyles((theme, _params, classNames) => ({
|
|
|
|
panel: {
|
|
|
|
height: '100%',
|
|
|
|
backgroundColor: theme.colors.gray[0],
|
|
|
|
},
|
2023-05-05 07:41:56 +08:00
|
|
|
plusMenu: {
|
2023-05-05 01:10:02 +08:00
|
|
|
top: 'unset !important',
|
|
|
|
left: '31px !important',
|
|
|
|
bottom: 60,
|
|
|
|
},
|
2023-05-05 07:41:56 +08:00
|
|
|
commandMenu: {
|
|
|
|
top: 'unset !important',
|
|
|
|
left: '31px !important',
|
|
|
|
bottom: 60,
|
|
|
|
},
|
|
|
|
commandText: {
|
|
|
|
fontSize: '1.0rem',
|
|
|
|
fontWeight: 'bolder',
|
|
|
|
},
|
|
|
|
commandDesc: {
|
|
|
|
fontSize: '0.8rem',
|
|
|
|
color: theme.colors.gray[6],
|
|
|
|
},
|
2023-05-06 00:00:25 +08:00
|
|
|
responseContent: {
|
|
|
|
marginTop: 8,
|
|
|
|
marginLeft: 0,
|
|
|
|
marginRight: 0,
|
|
|
|
},
|
2023-05-05 01:10:02 +08:00
|
|
|
icon: {
|
|
|
|
pointerEvents: 'all',
|
|
|
|
},
|
2023-05-06 00:00:25 +08:00
|
|
|
avatar: {
|
|
|
|
marginTop: 8,
|
|
|
|
marginLeft: 8,
|
|
|
|
},
|
|
|
|
messageBody: {
|
|
|
|
},
|
2023-05-05 01:10:02 +08:00
|
|
|
}));
|
|
|
|
|
|
|
|
const chatPanel = () => {
|
|
|
|
|
2023-05-06 00:00:25 +08:00
|
|
|
const [reactContent, setMarkdownSource] = useRemark();
|
2023-05-05 01:10:02 +08:00
|
|
|
const [opened, setOpened] = useState(false);
|
2023-05-06 00:32:53 +08:00
|
|
|
const [input, setInput] = useState('');
|
2023-05-05 07:41:56 +08:00
|
|
|
const [commandOpened, setCommandOpened] = useState(false);
|
2023-05-05 01:10:02 +08:00
|
|
|
const { classes } = useStyles();
|
|
|
|
const { height, width } = useViewportSize();
|
2023-05-06 00:00:25 +08:00
|
|
|
|
2023-05-05 01:10:02 +08:00
|
|
|
const handlePlusBottonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
|
|
setOpened(!opened);
|
|
|
|
event.stopPropagation();
|
|
|
|
};
|
|
|
|
const handleContainerClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
if (opened) { setOpened(false); }
|
|
|
|
};
|
2023-05-06 00:32:53 +08:00
|
|
|
const handleSendClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
|
|
const message = input;
|
|
|
|
if (message) {
|
|
|
|
// Add the user's message to the chat UI
|
|
|
|
// addMessageToUI('user', message);
|
|
|
|
|
|
|
|
// Clear the input field
|
|
|
|
event.currentTarget.value = '';
|
|
|
|
|
|
|
|
// Process and send the message to the extension
|
|
|
|
messageUtil.sendMessage({
|
|
|
|
command: 'sendMessage',
|
|
|
|
text: message
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Register message handlers for receiving messages from the extension
|
|
|
|
messageUtil.registerHandler('receiveMessage', (message: { text: string; }) => {
|
|
|
|
console.log(`receiveMessage: ${message.text}`);
|
|
|
|
// Add the received message to the chat UI as a bot message
|
|
|
|
setMarkdownSource(message.text);
|
|
|
|
});
|
|
|
|
|
|
|
|
messageUtil.registerHandler('receiveMessagePartial', (message: { text: string; }) => {
|
|
|
|
console.log(`receiveMessagePartial: ${message.text}`);
|
|
|
|
// Add the received message to the chat UI as a bot message
|
|
|
|
setMarkdownSource(message.text);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2023-05-05 07:41:56 +08:00
|
|
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
2023-05-06 00:32:53 +08:00
|
|
|
const value = event.target.value;
|
2023-05-05 07:41:56 +08:00
|
|
|
// if value start with '/' command show menu
|
|
|
|
if (value.startsWith('/')) {
|
|
|
|
setCommandOpened(true);
|
|
|
|
} else {
|
|
|
|
setCommandOpened(false);
|
|
|
|
}
|
2023-05-06 00:32:53 +08:00
|
|
|
setInput(value);
|
|
|
|
};
|
2023-05-05 01:10:02 +08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<Container className={classes.panel} onClick={handleContainerClick}>
|
|
|
|
<ScrollArea h={height - 70} type="never">
|
2023-05-05 09:48:05 +08:00
|
|
|
<Flex
|
|
|
|
mih={50}
|
|
|
|
gap="md"
|
|
|
|
justify="flex-start"
|
2023-05-06 00:00:25 +08:00
|
|
|
align="flex-start"
|
2023-05-05 09:48:05 +08:00
|
|
|
direction="row"
|
|
|
|
wrap="wrap"
|
2023-05-06 00:00:25 +08:00
|
|
|
className={classes.messageBody}
|
2023-05-05 09:48:05 +08:00
|
|
|
>
|
2023-05-06 00:00:25 +08:00
|
|
|
<Avatar color="indigo" size='md' radius="xl" className={classes.avatar}>
|
|
|
|
<IconUser size="1.5rem" />
|
2023-05-05 09:48:05 +08:00
|
|
|
</Avatar>
|
2023-05-06 00:00:25 +08:00
|
|
|
<Container className={classes.responseContent}>
|
|
|
|
<Text>
|
|
|
|
Write a hello world, and explain it.
|
|
|
|
</Text>
|
|
|
|
</Container>
|
|
|
|
{/* <ActionIcon>
|
|
|
|
<IconEdit size="1.5rem" />
|
|
|
|
</ActionIcon> */}
|
2023-05-05 09:48:05 +08:00
|
|
|
</Flex>
|
2023-05-06 00:00:25 +08:00
|
|
|
<Divider my="sm" label="Mar 4, 2023" labelPosition="center" />
|
2023-05-05 09:48:05 +08:00
|
|
|
<Flex
|
|
|
|
mih={50}
|
|
|
|
gap="md"
|
|
|
|
justify="flex-start"
|
2023-05-06 00:00:25 +08:00
|
|
|
align="flex-start"
|
2023-05-05 09:48:05 +08:00
|
|
|
direction="row"
|
|
|
|
wrap="wrap"
|
2023-05-06 00:00:25 +08:00
|
|
|
className={classes.messageBody}
|
2023-05-05 09:48:05 +08:00
|
|
|
>
|
2023-05-06 00:00:25 +08:00
|
|
|
<Avatar color="blue" size='md' radius="xl" className={classes.avatar}>
|
|
|
|
<IconRobot size="1.5rem" />
|
2023-05-05 09:48:05 +08:00
|
|
|
</Avatar>
|
2023-05-06 00:00:25 +08:00
|
|
|
<Container className={classes.responseContent}>
|
|
|
|
{reactContent}
|
|
|
|
</Container>
|
2023-05-05 09:48:05 +08:00
|
|
|
</Flex>
|
2023-05-05 01:10:02 +08:00
|
|
|
</ScrollArea>
|
2023-05-05 07:41:56 +08:00
|
|
|
<Menu id='plusMenu' shadow="md" width={200} opened={opened} onChange={setOpened} >
|
|
|
|
<Menu.Dropdown className={classes.plusMenu}>
|
2023-05-05 01:10:02 +08:00
|
|
|
<Menu.Label>Application</Menu.Label>
|
|
|
|
<Menu.Item icon={<IconSettings size={14} />}>Settings</Menu.Item>
|
|
|
|
<Menu.Item icon={<IconMessageCircle size={14} />}>Messages</Menu.Item>
|
|
|
|
<Menu.Item icon={<IconPhoto size={14} />}>Gallery</Menu.Item>
|
|
|
|
<Menu.Item
|
|
|
|
icon={<IconSearch size={14} />}
|
|
|
|
rightSection={<Text size="xs" color="dimmed">⌘K</Text>}
|
|
|
|
>
|
|
|
|
Search
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
|
|
<Menu.Divider />
|
|
|
|
|
|
|
|
<Menu.Label>Danger zone</Menu.Label>
|
|
|
|
<Menu.Item icon={<IconArrowsLeftRight size={14} />}>Transfer my data</Menu.Item>
|
|
|
|
<Menu.Item color="red" icon={<IconTrash size={14} />}>Delete my account</Menu.Item>
|
|
|
|
</Menu.Dropdown>
|
|
|
|
</Menu>
|
2023-05-05 07:41:56 +08:00
|
|
|
<Menu id='commandMenu' shadow="md" width={500} opened={commandOpened} onChange={setCommandOpened} >
|
|
|
|
<Menu.Dropdown className={classes.commandMenu}>
|
|
|
|
<Menu.Label>Context Commands</Menu.Label>
|
|
|
|
<Menu.Item>
|
|
|
|
<Text className={classes.commandText}>
|
|
|
|
/ref
|
|
|
|
</Text>
|
|
|
|
<Text className={classes.commandDesc}>
|
|
|
|
Run a local CLI and add its output to the context (e.g., pytest .).
|
|
|
|
</Text>
|
|
|
|
</Menu.Item>
|
|
|
|
<Menu.Item>
|
|
|
|
<Text className={classes.commandText}>
|
|
|
|
/local
|
|
|
|
</Text>
|
|
|
|
<Text className={classes.commandDesc}>
|
|
|
|
Bypass AI and run a local CLI to check its output (e.g., git status).
|
|
|
|
</Text>
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
|
|
<Menu.Divider />
|
|
|
|
|
|
|
|
<Menu.Label>DevChat Bots</Menu.Label>
|
|
|
|
|
|
|
|
<Menu.Item>
|
|
|
|
<Text className={classes.commandText}>
|
|
|
|
/code
|
|
|
|
</Text>
|
|
|
|
<Text className={classes.commandDesc}>
|
|
|
|
Generate or update code.
|
|
|
|
</Text>
|
|
|
|
</Menu.Item>
|
|
|
|
<Menu.Item>
|
|
|
|
<Text className={classes.commandText}>
|
|
|
|
/commit_message
|
|
|
|
</Text>
|
|
|
|
<Text className={classes.commandDesc}>
|
|
|
|
Write a commit message.
|
|
|
|
</Text>
|
|
|
|
</Menu.Item>
|
|
|
|
<Menu.Item>
|
|
|
|
<Text className={classes.commandText}>
|
|
|
|
/doc
|
|
|
|
</Text>
|
|
|
|
<Text className={classes.commandDesc}>
|
|
|
|
Write a doc for reference, wiki, or discussion.
|
|
|
|
</Text>
|
|
|
|
</Menu.Item>
|
|
|
|
</Menu.Dropdown>
|
|
|
|
</Menu>
|
2023-05-05 01:10:02 +08:00
|
|
|
<Input
|
|
|
|
multiline={true}
|
|
|
|
radius="md"
|
|
|
|
placeholder="Send a message."
|
|
|
|
icon={
|
|
|
|
<ActionIcon className={classes.icon} onClick={handlePlusBottonClick}>
|
|
|
|
<IconSquareRoundedPlus size="1rem" />
|
|
|
|
</ActionIcon>
|
|
|
|
}
|
|
|
|
rightSection={
|
2023-05-06 00:32:53 +08:00
|
|
|
<ActionIcon onClick={handleSendClick}>
|
2023-05-05 01:10:02 +08:00
|
|
|
<IconSend size="1rem" />
|
|
|
|
</ActionIcon>
|
|
|
|
}
|
2023-05-05 07:41:56 +08:00
|
|
|
onChange={handleInputChange}
|
2023-05-05 01:10:02 +08:00
|
|
|
/>
|
|
|
|
</Container>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default chatPanel;
|