import * as React from 'react'; import { useState, useEffect, useRef } from 'react'; import { Avatar, Center, Container, CopyButton, Divider, Flex, Grid, Stack, Textarea, TypographyStylesProvider, px, rem, useMantineTheme } from '@mantine/core'; import { Input, Tooltip } from '@mantine/core'; import { List } from '@mantine/core'; import { ScrollArea } from '@mantine/core'; import { createStyles, keyframes } from '@mantine/core'; import { ActionIcon } from '@mantine/core'; import { Menu, Button, Text } from '@mantine/core'; import { useElementSize, useListState, useResizeObserver, useViewportSize } from '@mantine/hooks'; import { IconBulb, IconCheck, IconClick, IconCopy, IconEdit, IconFolder, IconGitCompare, IconMessageDots, IconMessagePlus, IconPrompt, IconRobot, IconSend, IconSquareRoundedPlus, IconTerminal2, IconUser } from '@tabler/icons-react'; import { IconSettings, IconSearch, IconPhoto, IconMessageCircle, IconTrash, IconArrowsLeftRight } from '@tabler/icons-react'; import { Prism } from '@mantine/prism'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import okaidia from 'react-syntax-highlighter/dist/esm/styles/prism/okaidia'; import messageUtil from '../util/MessageUtil'; const blink = keyframes({ '50%': { opacity: 0 }, }); const useStyles = createStyles((theme, _params, classNames) => ({ menu: { }, avatar: { marginTop: 8, marginLeft: 8, }, })); const chatPanel = () => { const theme = useMantineTheme(); const chatContainerRef = useRef(null); const scrollViewport = useRef(null); const [messages, messageHandlers] = useListState<{ type: string; message: string; }>([]); const [commands, commandHandlers] = useListState<{ pattern: string; description: string; name: string }>([]); const [contexts, contextHandlers] = useListState<{ pattern: string; description: string; name: string }>([]); const [currentMessage, setCurrentMessage] = useState(''); const [generating, setGenerating] = useState(false); const [responsed, setResponsed] = useState(false); const [registed, setRegisted] = useState(false); const [input, setInput] = useState(''); const [menuOpend, setMenuOpend] = useState(false); const [menuType, setMenuType] = useState(''); // contexts or commands const { classes } = useStyles(); const { height, width } = useViewportSize(); const [inputRef, inputRect] = useResizeObserver(); const handlePlusClick = (event: React.MouseEvent) => { setMenuType('contexts'); setMenuOpend(!menuOpend); event.stopPropagation(); }; const handleSendClick = (event: React.MouseEvent) => { if (input) { // Add the user's message to the chat UI messageHandlers.append({ type: 'user', message: input }); // Clear the input field setInput(''); // Process and send the message to the extension messageUtil.sendMessage({ command: 'sendMessage', text: input }); // start generating setGenerating(true); setResponsed(false); setCurrentMessage(''); } }; const scrollToBottom = () => scrollViewport?.current?.scrollTo({ top: scrollViewport.current.scrollHeight, behavior: 'smooth' }); useEffect(() => { inputRef.current.focus(); messageUtil.sendMessage({ command: 'regContextList' }); messageUtil.sendMessage({ command: 'regCommandList' }); }, []); useEffect(() => { if (generating) { // new a bot message messageHandlers.append({ type: 'bot', message: currentMessage }); } }, [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 messageHandlers.setItem(lastIndex, { type: 'bot', message: currentMessage }); } }, [currentMessage]); // Add the received message to the chat UI as a bot message useEffect(() => { if (registed) return; setRegisted(true); messageUtil.registerHandler('receiveMessagePartial', (message: { text: string; }) => { setCurrentMessage(message.text); setResponsed(true); scrollToBottom(); }); messageUtil.registerHandler('receiveMessage', (message: { text: string; }) => { setCurrentMessage(message.text); setGenerating(false); setResponsed(true); scrollToBottom(); }); messageUtil.registerHandler('regCommandList', (message: { result: { pattern: string; description: string; name: string }[] }) => { commandHandlers.append(...message.result); console.log(`commands:${commands}`); }); messageUtil.registerHandler('regContextList', (message: { result: { pattern: string; description: string; name: string }[] }) => { contextHandlers.append(...message.result); console.log(`contexts:${commands}`); }); }, [registed]); const handleInputChange = (event: React.ChangeEvent) => { const value = event.target.value; // if value start with '/' command show menu if (value === '/') { setMenuOpend(true); setMenuType('commands'); } else { setMenuOpend(false); } setInput(value); }; const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && event.ctrlKey) { handleSendClick(event as any); } }; const defaultMessages = (
No messages yet
); const commandMenus = commands.map(({ pattern, description, name }, index) => { return ( { setInput(`/${pattern} `); }} icon={} > /{pattern} {description} ); }); const contextMenus = contexts.map(({ pattern, description, name }, index) => { return ( { setInput(`/${name} `); }} icon={} > /{name} {description} ); }); const messageList = messages.map(({ message: messageText, type: messageType }, index) => { // setMessage(messageText); return (<> { messageType === 'bot' ? : } { setCopied(true); setTimeout(() => setCopied(false), 2000); }; return !inline && match ? (
{match[1] && (
{match[1]}
)}
{({ copied, copy }) => ( {copied ? : } )}
{value}
) : ( {children} ); } }} > {messageText}
{(generating && messageType === 'bot' && index === messages.length - 1) ? | : ''}
{index !== messages.length - 1 && } ); }); return ( {messageList.length > 0 ? messageList : defaultMessages} setMenuType('')} onOpen={() => menuType !== '' ? setMenuOpend(true) : setMenuOpend(false)} returnFocus={true}>