import styled from "styled-components"; import { defaultBorderRadius } from "../components"; import Loader from "../components/Loader"; import ContinueButton from "../components/ContinueButton"; import { FullState } from "../../../schema/FullState"; import { useCallback, useEffect, useRef, useState, useContext } from "react"; import { HistoryNode } from "../../../schema/HistoryNode"; import StepContainer from "../components/StepContainer"; import { GUIClientContext } from "../App"; import ComboBox from "../components/ComboBox"; import { usePostHog } from "posthog-js/react"; import { useDispatch, useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import { postVscMessage } from "../vscode"; import UserInputContainer from "../components/UserInputContainer"; import { isMetaEquivalentKeyPressed } from "../util"; import { setBottomMessage, setDialogEntryOn, setDialogMessage, setDisplayBottomMessageOnBottom, setShowDialog, } from "../redux/slices/uiStateSlice"; import RingLoader from "../components/RingLoader"; import { setServerState, temporarilyPushToUserInputQueue, } from "../redux/slices/serverStateReducer"; const UserInputQueueItem = styled.div` border-radius: ${defaultBorderRadius}; color: gray; padding: 8px; margin: 8px; text-align: center; `; interface GUIProps { firstObservation?: any; } function GUI(props: GUIProps) { // #region Hooks const client = useContext(GUIClientContext); const posthog = usePostHog(); const dispatch = useDispatch(); // #endregion // #region Selectors const history = useSelector((state: RootStore) => state.serverState.history); const user_input_queue = useSelector( (state: RootStore) => state.serverState.user_input_queue ); const adding_highlighted_code = useSelector( (state: RootStore) => state.serverState.adding_highlighted_code ); const selected_context_items = useSelector( (state: RootStore) => state.serverState.selected_context_items ); // #endregion // #region State const [waitingForSteps, setWaitingForSteps] = useState(false); const [availableSlashCommands, setAvailableSlashCommands] = useState< { name: string; description: string }[] >([]); const [stepsOpen, setStepsOpen] = useState([ true, true, true, true, ]); const [waitingForClient, setWaitingForClient] = useState(true); const [showLoading, setShowLoading] = useState(false); // #endregion // #region Refs const mainTextInputRef = useRef(null); const topGuiDivRef = useRef(null); // #endregion // #region Effects // Set displayBottomMessageOnBottom const aboveComboBoxDivRef = useRef(null); const bottomMessage = useSelector( (state: RootStore) => state.uiState.bottomMessage ); useEffect(() => { if (!aboveComboBoxDivRef.current) return; dispatch( setDisplayBottomMessageOnBottom( aboveComboBoxDivRef.current.getBoundingClientRect().top < window.innerHeight / 2 ) ); }, [bottomMessage, aboveComboBoxDivRef.current]); 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(() => { // Cmd + Backspace to delete current step const listener = (e: any) => { if ( e.key === "Backspace" && isMetaEquivalentKeyPressed(e) && typeof history?.current_index !== "undefined" && history.timeline[history.current_index]?.active ) { client?.deleteAtIndex(history.current_index); } else if (e.key === "Escape") { dispatch(setBottomMessage(undefined)); } }; window.addEventListener("keydown", listener); return () => { window.removeEventListener("keydown", listener); }; }, [client, history]); useEffect(() => { client?.onStateUpdate((state: FullState) => { // Scroll only if user is at very bottom of the window. const shouldScrollToBottom = topGuiDivRef.current && topGuiDivRef.current?.offsetHeight - window.scrollY < 100; const waitingForSteps = state.active && state.history.current_index < state.history.timeline.length && state.history.timeline[state.history.current_index] && state.history.timeline[ state.history.current_index ].step.description?.trim() === ""; dispatch(setServerState(state)); setWaitingForSteps(waitingForSteps); 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]); // #endregion useEffect(() => { if (client && waitingForClient) { console.log("sending user input queue, ", user_input_queue); setWaitingForClient(false); for (const input of user_input_queue) { client.sendMainInput(input); } } }, [client, user_input_queue, waitingForClient]); const onMainTextInput = (event?: any) => { if (mainTextInputRef.current) { let input = (mainTextInputRef.current as any).inputValue; if (input.trim() === "") return; if (input.startsWith("#") && (input.length === 7 || input.length === 4)) { localStorage.setItem("continueButtonColor", input); (mainTextInputRef.current as any).setInputValue(""); return; } // cmd+enter to /edit if (isMetaEquivalentKeyPressed(event)) { input = `/edit ${input}`; } (mainTextInputRef.current as any).setInputValue(""); if (!client) { dispatch(temporarilyPushToUserInputQueue(input)); 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; } } client.sendMainInput(input); dispatch(temporarilyPushToUserInputQueue(input)); // Increment localstorage counter const counter = localStorage.getItem("mainTextEntryCounter"); if (counter) { let currentCount = parseInt(counter); localStorage.setItem( "mainTextEntryCounter", (currentCount + 1).toString() ); if (currentCount === 100) { dispatch( setDialogMessage(
👋 Thanks for using Continue. We are a beta product and love working closely with our first users. If you're interested in speaking, enter your name and email. We won't use this information for anything other than reaching out.

{ e.preventDefault(); posthog?.capture("user_interest_form", { name: e.target.elements[0].value, email: e.target.elements[1].value, }); dispatch( setDialogMessage(
Thanks! We'll be in touch soon.
) ); }} style={{ display: "flex", flexDirection: "column", gap: "10px", }} >
) ); dispatch(setDialogEntryOn(false)); dispatch(setShowDialog(true)); } } else { localStorage.setItem("mainTextEntryCounter", "1"); } } }; const onStepUserInput = (input: string, index: number) => { if (!client) return; client.sendStepUserInput(input, index); }; useEffect(() => { const timeout = setTimeout(() => { setShowLoading(true); }, 3000); return () => { clearTimeout(timeout); }; }, []); return (
{ if (e.key === "Enter" && e.ctrlKey) { onMainTextInput(); } }} > {showLoading && typeof client === "undefined" && ( <>

Continue Server Starting

Troubleshooting help

{ postVscMessage("toggleDevTools", {}); }} > View logs

{ postVscMessage("reloadWindow", {}); }} > Reload the window

{/* Tip: Drag the Continue logo from the far left of the window to the right, then toggle Continue using option/alt+command+m. */} {/* Tip: If there is an error in the terminal, use COMMAND+D to automatically debug */}
)} {history?.timeline.map((node: HistoryNode, index: number) => { return node.step.name === "User Input" ? ( node.step.hide || ( { client?.deleteAtIndex(index); }} historyNode={node} > {node.step.description as string} ) ) : ( { const nextStepsOpen = [...stepsOpen]; nextStepsOpen[index] = !nextStepsOpen[index]; setStepsOpen(nextStepsOpen); }} onToggleAll={() => { const shouldOpen = !stepsOpen[index]; setStepsOpen((prev) => prev.map(() => shouldOpen)); }} key={index} onUserInput={(input: string) => { onStepUserInput(input, index); }} inFuture={index > history?.current_index} historyNode={node} onReverse={() => { client?.reverseToIndex(index); }} onRetry={() => { client?.retryAtIndex(index); setWaitingForSteps(true); }} onDelete={() => { client?.deleteAtIndex(index); }} /> ); })} {waitingForSteps && }
{user_input_queue?.map?.((input) => { return {input}; })}
{ onMainTextInput(e); e.stopPropagation(); e.preventDefault(); }} onInputValueChange={() => {}} items={availableSlashCommands} selectedContextItems={selected_context_items} onToggleAddContext={() => { client?.toggleAddingHighlightedCode(); }} addingHighlightedCode={adding_highlighted_code} />
); } export default GUI;