import { useMantineTheme, Flex, Stack, Accordion, Box, ActionIcon, ScrollArea, Center, Popover, Textarea, Text, Divider } from "@mantine/core"; import { useListState, useResizeObserver } from "@mantine/hooks"; import { IconGitBranch, IconBook, IconX, IconSquareRoundedPlus, IconSend } from "@tabler/icons-react"; import React, { useState, useEffect } from "react"; import { IconGitBranchChecked, IconShellCommand, IconMouseRightClick } from "@/views/components/ChatIcons"; import messageUtil from '@/util/MessageUtil'; import { useAppDispatch, useAppSelector } from '@/views/hooks'; import InputContexts from './InputContexts'; import { setValue, selectValue, selectContexts, selectMenuOpend, selectMenuType, selectCurrentMenuIndex, selectContextMenus, selectCommandMenus, setCurrentMenuIndex, clearContexts, newContext, openMenu, closeMenu, fetchContextMenus, fetchCommandMenus, } from '@/views/reducers/inputSlice'; import { selectGenerating, newMessage, startGenerating, } from '@/views/reducers/chatSlice'; const InputMessage = (props: any) => { const { width } = props; const dispatch = useAppDispatch(); const input = useAppSelector(selectValue); const generating = useAppSelector(selectGenerating); const contexts = useAppSelector(selectContexts); const menuOpend = useAppSelector(selectMenuOpend); const menuType = useAppSelector(selectMenuType); const currentMenuIndex = useAppSelector(selectCurrentMenuIndex); const contextMenus = useAppSelector(selectContextMenus); const commandMenus = useAppSelector(selectCommandMenus); const theme = useMantineTheme(); const [commandMenusNode, setCommandMenusNode] = useState(null); const [inputRef, inputRect] = useResizeObserver(); const handlePlusClick = (event: React.MouseEvent) => { dispatch(openMenu('contexts')); inputRef.current.focus(); event.stopPropagation(); }; const handleInputChange = (event: React.ChangeEvent) => { const value = event.target.value; // if value start with '/' command show menu if (value.startsWith('/')) { dispatch(openMenu('commands')); dispatch(setCurrentMenuIndex(0)); } else { dispatch(closeMenu()); } dispatch(setValue(value)); }; const handleSendClick = (event: React.MouseEvent) => { if (input) { // Process and send the message to the extension const contextInfo = contexts.map((item: any, index: number) => { const { file, context } = item; return { file, context }; }); const text = input; // Add the user's message to the chat UI dispatch(newMessage({ type: 'user', message: input, contexts: contexts ? [...contexts].map((item) => ({ ...item })) : undefined })); // start generating dispatch(startGenerating({ text, contextInfo })); // Clear the input field dispatch(setValue('')); dispatch(clearContexts()); } }; const handleContextClick = (contextName: string) => { // Process and send the message to the extension messageUtil.sendMessage({ command: 'addContext', selected: contextName }); }; const handleKeyDown = (event: React.KeyboardEvent) => { if (menuOpend) { if (event.key === 'Escape') { dispatch(closeMenu()); } if (menuType === 'commands') { if (event.key === 'ArrowDown') { const newIndex = currentMenuIndex + 1; dispatch(setCurrentMenuIndex(newIndex < commandMenusNode.length ? newIndex : 0)); event.preventDefault(); } if (event.key === 'ArrowUp') { const newIndex = currentMenuIndex - 1; dispatch(setCurrentMenuIndex(newIndex < 0 ? commandMenusNode.length - 1 : newIndex)); event.preventDefault(); } if ((event.key === 'Enter' || event.key === 'Tab') && !event.shiftKey) { const commandNode = commandMenusNode[currentMenuIndex]; dispatch(setValue(`/${commandNode.props['data-pattern']} `)); dispatch(closeMenu()); event.preventDefault(); } } } else { if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) { handleSendClick(event as any); } } }; const contextMenuIcon = (name: string) => { if (name === 'git diff --cached') { return (); } if (name === 'git diff HEAD') { return (); } return (); }; const contextMenusNode = [...contextMenus] .sort((a, b) => { if (a.name === '') { return 1; // Placing '' at the end } else if (b.name === '') { return -1; // Placing '' at the front } else { return (a.name || "").localeCompare(b.name || ""); // Sorting alphabetically for other cases } }) .map(({ pattern, description, name }, index) => { return ( { handleContextClick(name); dispatch(closeMenu()); }} > {contextMenuIcon(name)} {name} {description} ); }); const commandMenuIcon = (pattern: string) => { if (pattern === 'commit_message') { return (); } return (); }; useEffect(() => { dispatch(fetchContextMenus()); dispatch(fetchCommandMenus()); messageUtil.registerHandler('appendContext', (message: { command: string; context: string }) => { // context is a temp file path const match = /\|([^]+?)\]/.exec(message.context); // Process and send the message to the extension messageUtil.sendMessage({ command: 'contextDetail', file: match && match[1], }); }); messageUtil.registerHandler('contextDetailResponse', (message: { command: string; file: string; result: string }) => { //result is a content json // 1. diff json structure // { // languageId: languageId, // path: fileSelected, // content: codeSelected // }; // 2. command json structure // { // command: commandString, // content: stdout // }; const context = JSON.parse(message.result); if (typeof context !== 'undefined' && context) { dispatch(newContext({ file: message.file, context: context, })); } }); inputRef.current.focus(); }, []); useEffect(() => { let filtered = commandMenus; if (input) { filtered = commandMenus.filter((item) => `/${item.pattern}`.startsWith(input)); } const node = filtered.map(({ pattern, description, name }, index) => { return ( { dispatch(setValue(`/${pattern} `)); dispatch(closeMenu()); }} aria-checked={index === currentMenuIndex} data-pattern={pattern} > {commandMenuIcon(pattern)} /{pattern} {description} ); }); setCommandMenusNode(node); if (node.length === 0) { dispatch(closeMenu()); } }, [input, commandMenus, currentMenuIndex]); return ( <> {contexts && contexts.length > 0 && } { dispatch(closeMenu()); inputRef.current.focus(); }} onClose={() => dispatch(closeMenu())} onOpen={() => menuType !== '' ? dispatch(openMenu(menuType)) : dispatch(closeMenu())} returnFocus={true}>