import { useMantineTheme, Flex, Stack, ActionIcon, ScrollArea, Popover, Textarea, Text, Indicator, Drawer, Group, Button, Menu, createStyles, } from "@mantine/core"; import { useDisclosure, useResizeObserver } from "@mantine/hooks"; import { IconSend, IconPaperclip, IconChevronDown, IconRobot, } from "@tabler/icons-react"; import React, { useState, useEffect, useRef } from "react"; import messageUtil from "@/util/MessageUtil"; import InputContexts from "./InputContexts"; import Topic from "./Topic"; import AddTopic from "./AddTopic"; import { observer } from "mobx-react-lite"; import { useMst } from "@/views/stores/RootStore"; import { ChatContext } from "@/views/stores/InputStore"; import { Trans, useTranslation } from "react-i18next"; import getModelShowName from "@/util/getModelShowName"; const useStyles = createStyles(() => ({ actionIcon: { color: "var(--vscode-dropdown-foreground)", borderColor: "var(--vscode-dropdown-border)", backgroundColor: "var(--vscode-dropdown-background)", "&:hover": { color: "var(--vscode-dropdown-foreground)", borderColor: "var(--vscode-dropdown-border)", backgroundColor: "var(--vscode-dropdown-background)", }, "&[data-disabled]": { borderColor: "transparent", backgroundColor: "#e9ecef", color: "#adb5bd", cursor: "not-allowed", backgroundImage: "none", pointervents: "none", }, }, })); const InputMessage = observer((props: any) => { const { classes } = useStyles(); const { input, chat, config } = useMst(); const { t } = useTranslation(); const { contexts, menuOpend, menuType, currentMenuIndex, contextMenus, commandMenus } = input; const { generating } = chat; const viewport = useRef(null); const [drawerOpened, { open: openDrawer, close: closeDrawer }] = useDisclosure(false); const theme = useMantineTheme(); const [commandMenusNode, setCommandMenusNode] = useState(null); const [inputRef, inputRect] = useResizeObserver(); const handleInputChange = (event: React.ChangeEvent) => { const value = event.target.value; // if value start with '/' command show menu if (value.startsWith("/")) { input.openMenu("commands"); input.setCurrentMenuIndex(0); } else { input.closeMenu(); } input.setValue(value); }; const handleSendClick = (event: React.MouseEvent) => { const inputValue = input.value; if (inputValue) { if (inputValue.trim() === "/help") { chat.helpMessage(); } else { const text = inputValue; const chatContexts = contexts ? [...contexts].map((item) => ({ ...item })) : undefined; if (inputValue.trim().startsWith("/ask-code")) { chat.devchatAsk(text, chatContexts); } else { chat.commonMessage(text, chatContexts); } } // Clear the input field input.setValue(""); input.clearContexts(); // event.preventDefault(); } }; 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") { input.closeMenu(); } if (menuType === "commands") { if (event.key === "ArrowDown") { event.preventDefault(); const newIndex = currentMenuIndex + 1; input.setCurrentMenuIndex( newIndex < commandMenusNode.length ? newIndex : 0 ); viewport.current ?.querySelectorAll("[data-list-item]") ?.[newIndex]?.scrollIntoView({ block: "nearest" }); } if (event.key === "ArrowUp") { event.preventDefault(); const newIndex = currentMenuIndex - 1; input.setCurrentMenuIndex( newIndex < 0 ? commandMenusNode.length - 1 : newIndex ); viewport.current ?.querySelectorAll("[data-list-item]") ?.[newIndex]?.scrollIntoView({ block: "nearest" }); } if ((event.key === "Enter" || event.key === "Tab") && !event.shiftKey) { const commandNode = commandMenusNode[currentMenuIndex]; const commandPattern = commandNode.props["data-pattern"]; if (commandPattern === "help") { chat.helpMessage(); input.setValue(""); } else { input.setValue(`/${commandPattern} `); } input.closeMenu(); event.preventDefault(); } } } else { if ( event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing ) { handleSendClick(event as any); } } }; useEffect(() => { messageUtil.registerHandler( "chatWithDevChat", (message: { command: string; message: string }) => { chat.commonMessage(message.message, []); } ); messageUtil.registerHandler( "focusDevChatInput", (message: { command: string; message: string }) => { inputRef.current.focus(); } ); 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) { const chatContext = ChatContext.create({ file: message.file, path: context.path, command: context.command, content: context.content, }); input.newContext(chatContext); } } ); inputRef.current.focus(); }, []); useEffect(() => { let filtered; if (input.value) { filtered = commandMenus.filter((item) => `/${item.pattern}`.startsWith(input.value) ); } else { filtered = commandMenus; } const node = filtered.map(({ pattern, description, name }, index) => { return ( { input.setValue(`/${pattern} `); input.closeMenu(); }} aria-checked={index === currentMenuIndex} data-pattern={pattern} > /{pattern} {description} ); }); setCommandMenusNode(node); if (node.length === 0) { input.closeMenu(); } }, [input.value, commandMenus, currentMenuIndex]); useEffect(() => { if (drawerOpened && (!contexts || contexts.length === 0)) { closeDrawer(); } }, [contexts.length]); const changeModel = (value) => { config.setConfigValue("default_model", value); }; const menuStyles = { arrow: { borderColor: "var(--vscode-menu-border)", }, dropdown: { borderColor: "var(--vscode-menu-border)", backgroundColor: "var(--vscode-menu-background)", }, itemLabel: { color: "var(--vscode-menu-foreground)", }, item: { padding: 5, backgroundColor: "var(--vscode-menu-background)", "&:hover,&[data-hovered=true]": { color: "var(--vscode-commandCenter-activeForeground)", borderColor: "var(--vscode-commandCenter-border)", backgroundColor: "var(--vscode-commandCenter-activeBackground)", }, }, }; const buttonStyles = { root: { color: "var(--vscode-dropdown-foreground)", borderColor: "var(--vscode-dropdown-border)", backgroundColor: "var(--vscode-dropdown-background)", "&:hover": { color: "var(--vscode-dropdown-foreground)", borderColor: "var(--vscode-dropdown-border)", backgroundColor: "var(--vscode-dropdown-background)", }, }, }; // DC.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdfaWQiOjI1MTYwMzg1MzQ2LCJqdGkiOjczMjc0OTkzMjcwMzc1MjkyNjB9.e-KAl0QM0Ooe6NdLAV3vaJAiA4Zr_DAgqkE0cl7MKZw return ( {config.chatModels.map((model) => { return ( changeModel(model.name)} > {getModelShowName(model.name)} ); })} <> {contexts && contexts.length > 0 && ( }} styles={{ content: { background: "var(--vscode-sideBar-background)", color: "var(--vscode-editor-foreground)", }, header: { background: "var(--vscode-sideBar-background)", color: "var(--vscode-editor-foreground)", }, }} > )} { input.closeMenu(); inputRef.current.focus(); }} >