import styled from "styled-components"; import { defaultBorderRadius, Loader } from "../components"; import ContinueButton from "../components/ContinueButton"; import { useCallback, useEffect, useRef, useState } from "react"; import { History } from "../../../schema/History"; import { HistoryNode } from "../../../schema/HistoryNode"; import StepContainer from "../components/StepContainer"; import useContinueGUIProtocol from "../hooks/useWebsocket"; import { BookOpen, ChatBubbleOvalLeftEllipsis, Trash, } from "@styled-icons/heroicons-outline"; import ComboBox from "../components/ComboBox"; import TextDialog from "../components/TextDialog"; import HeaderButtonWithText from "../components/HeaderButtonWithText"; import ReactSwitch from "react-switch"; import { usePostHog } from "posthog-js/react"; import { useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import LoadingCover from "../components/LoadingCover"; import { postVscMessage } from "../vscode"; import UserInputContainer from "../components/UserInputContainer"; const TopGUIDiv = styled.div` overflow: hidden; `; const UserInputQueueItem = styled.div` border-radius: ${defaultBorderRadius}; color: gray; padding: 8px; margin: 8px; text-align: center; `; const Footer = styled.footer<{ dataSwitchChecked: boolean }>` display: flex; flex-direction: row; gap: 8px; justify-content: right; padding: 8px; align-items: center; margin-top: 8px; border-top: 0.1px solid gray; background-color: ${(props) => props.dataSwitchChecked ? "#12887a33" : "transparent"}; `; interface GUIProps { firstObservation?: any; } function GUI(props: GUIProps) { const posthog = usePostHog(); const vscMachineId = useSelector( (state: RootStore) => state.config.vscMachineId ); const [dataSwitchChecked, setDataSwitchChecked] = useState(false); const dataSwitchOn = useSelector( (state: RootStore) => state.config.dataSwitchOn ); useEffect(() => { if (typeof dataSwitchOn !== "undefined") { setDataSwitchChecked(dataSwitchOn); } }, [dataSwitchOn]); const [usingFastModel, setUsingFastModel] = useState(false); const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState([]); const [highlightedRanges, setHighlightedRanges] = useState([]); const [availableSlashCommands, setAvailableSlashCommands] = useState< { name: string; description: string }[] >([]); const [pinned, setPinned] = useState(false); const [showDataSharingInfo, setShowDataSharingInfo] = useState(false); const [stepsOpen, setStepsOpen] = useState([ true, true, true, true, ]); const [history, setHistory] = useState({ timeline: [ { step: { name: "Welcome to Continue", hide: false, description: `- Highlight code and ask a question or give instructions - Use \`cmd+k\` (Mac) / \`ctrl+k\` (Windows) to open Continue - Use \`cmd+shift+e\` / \`ctrl+shift+e\` to open file Explorer - Add your own OpenAI API key to VS Code Settings with \`cmd+,\` - Use slash commands when you want fine-grained control - Past steps are included as part of the context by default`, system_message: null, chat_context: [], manage_own_chat_context: false, message: "", }, depth: 0, deleted: false, active: false, }, ], current_index: 3, } as any); const [showFeedbackDialog, setShowFeedbackDialog] = useState(false); const [feedbackDialogMessage, setFeedbackDialogMessage] = useState(""); const topGuiDivRef = useRef(null); const client = useContinueGUIProtocol(); const [scrollTimeout, setScrollTimeout] = useState( null ); const scrollToBottom = useCallback(() => { if (scrollTimeout) { clearTimeout(scrollTimeout); } // Debounced smooth scroll to bottom of screen if (topGuiDivRef.current) { const timeout = setTimeout(() => { window.scrollTo({ top: topGuiDivRef.current!.offsetHeight, behavior: "smooth", }); }, 200); setScrollTimeout(timeout); } }, [topGuiDivRef.current, scrollTimeout]); useEffect(() => { const listener = (e: any) => { // Cmd + J to toggle fast model if (e.key === "i" && e.metaKey && e.shiftKey) { setUsingFastModel((prev) => !prev); } }; window.addEventListener("keydown", listener); return () => { window.removeEventListener("keydown", listener); }; }, []); useEffect(() => { client?.onStateUpdate((state) => { // Scroll only if user is at very bottom of the window. setUsingFastModel(state.default_model === "gpt-3.5-turbo"); const shouldScrollToBottom = topGuiDivRef.current && topGuiDivRef.current?.offsetHeight - window.scrollY < 100; setWaitingForSteps(state.active); setHistory(state.history); setHighlightedRanges(state.highlighted_ranges); setUserInputQueue(state.user_input_queue); setAvailableSlashCommands( state.slash_commands.map((c: any) => { return { name: `/${c.name}`, description: c.description, }; }) ); setStepsOpen((prev) => { const nextStepsOpen = [...prev]; for ( let i = nextStepsOpen.length; i < state.history.timeline.length; i++ ) { nextStepsOpen.push(true); } return nextStepsOpen; }); if (shouldScrollToBottom) { scrollToBottom(); } }); }, [client]); useEffect(() => { scrollToBottom(); }, [waitingForSteps]); const mainTextInputRef = useRef(null); const deleteContextItems = useCallback( (indices: number[]) => { client?.deleteContextAtIndices(indices); }, [client] ); useEffect(() => { if (mainTextInputRef.current) { mainTextInputRef.current.focus(); let handler = (event: any) => { if (event.data.type === "focusContinueInput") { mainTextInputRef.current?.focus(); } }; window.addEventListener("message", handler); return () => { window.removeEventListener("message", handler); }; } }, [mainTextInputRef]); const onMainTextInput = () => { if (mainTextInputRef.current) { let input = mainTextInputRef.current.value; mainTextInputRef.current.value = ""; if (!client) return; setWaitingForSteps(true); if ( history && history.current_index >= 0 && history.current_index < history.timeline.length ) { if ( history.timeline[history.current_index].step.name === "Waiting for user input" ) { if (input.trim() === "") return; onStepUserInput(input, history!.current_index); return; } else if ( history.timeline[history.current_index].step.name === "Waiting for user confirmation" ) { onStepUserInput("ok", history!.current_index); return; } } if (input.trim() === "") return; client.sendMainInput(input); setUserInputQueue((queue) => { return [...queue, input]; }); // Delete all context items unless locked if (!pinned) { client?.deleteContextAtIndices( highlightedRanges.map((_, index) => index) ); } } }; const onStepUserInput = (input: string, index: number) => { if (!client) return; console.log("Sending step user input", input, index); client.sendStepUserInput(input, index); }; // const iterations = useSelector(selectIterations); return ( <>