diff options
Diffstat (limited to 'extension/react-app/src')
| -rw-r--r-- | extension/react-app/src/components/ComboBox.tsx | 146 | ||||
| -rw-r--r-- | extension/react-app/src/hooks/ContinueGUIClientProtocol.ts | 4 | ||||
| -rw-r--r-- | extension/react-app/src/hooks/useContinueGUIProtocol.ts | 10 | ||||
| -rw-r--r-- | extension/react-app/src/tabs/gui.tsx | 296 | 
4 files changed, 313 insertions, 143 deletions
| diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx new file mode 100644 index 00000000..1b7c60e6 --- /dev/null +++ b/extension/react-app/src/components/ComboBox.tsx @@ -0,0 +1,146 @@ +import React, { useCallback } from "react"; +import { useCombobox } from "downshift"; +import styled from "styled-components"; +import { +  buttonColor, +  defaultBorderRadius, +  secondaryDark, +  vscBackground, +} from "."; + +const mainInputFontSize = 16; +const MainTextInput = styled.input` +  padding: 8px; +  font-size: ${mainInputFontSize}px; +  border-radius: ${defaultBorderRadius}; +  border: 1px solid #ccc; +  margin: 8px auto; +  width: 100%; +  background-color: ${vscBackground}; +  color: white; +  outline: 1px solid orange; +`; + +const UlMaxHeight = 200; +const Ul = styled.ul<{ +  hidden: boolean; +  showAbove: boolean; +  ulHeightPixels: number; +}>` +  ${(props) => +    props.showAbove +      ? `transform: translateY(-${props.ulHeightPixels + 8}px);` +      : `transform: translateY(${2 * mainInputFontSize}px);`} +  position: absolute; +  background: ${vscBackground}; +  background-color: ${secondaryDark}; +  color: white; +  font-family: "Fira Code", monospace; +  max-height: ${UlMaxHeight}px; +  overflow: scroll; +  padding: 0; +  ${({ hidden }) => hidden && "display: none;"} +  border-radius: ${defaultBorderRadius}; +  overflow: hidden; +  border: 0.5px solid gray; +`; + +const Li = styled.li<{ +  highlighted: boolean; +  selected: boolean; +  isLastItem: boolean; +}>` +  ${({ highlighted }) => highlighted && "background: #aa0000;"} +  ${({ selected }) => selected && "font-weight: bold;"} +    padding: 0.5rem 0.75rem; +  display: flex; +  flex-direction: column; +  ${({ isLastItem }) => isLastItem && "border-bottom: 1px solid gray;"} +  border-top: 1px solid gray; +  cursor: pointer; +`; + +interface ComboBoxProps { +  items: { name: string; description: string }[]; +  onInputValueChange: (inputValue: string) => void; +  disabled?: boolean; +  onEnter?: (e: React.KeyboardEvent<HTMLInputElement>) => void; +} + +const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { +  const [items, setItems] = React.useState(props.items); +  const { +    isOpen, +    getToggleButtonProps, +    getLabelProps, +    getMenuProps, +    getInputProps, +    highlightedIndex, +    getItemProps, +    selectedItem, +    setInputValue, +  } = useCombobox({ +    onInputValueChange({ inputValue }) { +      if (!inputValue) return; +      props.onInputValueChange(inputValue); +      setItems( +        props.items.filter((item) => +          item.name.toLowerCase().startsWith(inputValue.toLowerCase()) +        ) +      ); +    }, +    items, +    itemToString(item) { +      return item ? item.name : ""; +    }, +  }); + +  const divRef = React.useRef<HTMLDivElement>(null); +  const ulRef = React.useRef<HTMLUListElement>(null); +  const showAbove = () => { +    return (divRef.current?.getBoundingClientRect().top || 0) > UlMaxHeight; +  }; + +  return ( +    <div className="flex px-2" ref={divRef} hidden={!isOpen}> +      <MainTextInput +        disabled={props.disabled} +        placeholder="Ask anything:" +        {...getInputProps({ +          onKeyDown: (event) => { +            if (event.key === "Enter" && (!isOpen || items.length === 0)) { +              // Prevent Downshift's default 'Enter' behavior. +              (event.nativeEvent as any).preventDownshiftDefault = true; +              if (props.onEnter) props.onEnter(event); +              setInputValue(""); +            } +          }, +          ref: ref as any, +        })} +      /> +      <Ul +        {...getMenuProps({ +          ref: ulRef, +        })} +        showAbove={showAbove()} +        ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0} +      > +        {isOpen && +          items.map((item, index) => ( +            <Li +              key={`${item.name}${index}`} +              {...getItemProps({ item, index })} +              highlighted={highlightedIndex === index} +              selected={selectedItem === item} +            > +              <span> +                {item.name}: {item.description} +              </span> +            </Li> +          ))} +      </Ul> +    </div> +  ); +}); + +export default ComboBox; diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index 71303c70..824bb086 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -9,6 +9,10 @@ abstract class AbstractContinueGUIClientProtocol {    abstract onStateUpdate(state: any): void; +  abstract onAvailableSlashCommands( +    callback: (commands: { name: string; description: string }[]) => void +  ): void; +    abstract sendClear(): void;    abstract retryAtIndex(index: number): void; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts index a8e28fc5..59397742 100644 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -45,6 +45,16 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {      });    } +  onAvailableSlashCommands( +    callback: (commands: { name: string; description: string }[]) => void +  ) { +    this.messenger.onMessageType("available_slash_commands", (data: any) => { +      if (data.commands) { +        callback(data.commands); +      } +    }); +  } +    sendClear() {      this.messenger.send("clear_history", {});    } diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index cb7a5440..1569c178 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -12,7 +12,8 @@ import { History } from "../../../schema/History";  import { HistoryNode } from "../../../schema/HistoryNode";  import StepContainer from "../components/StepContainer";  import useContinueGUIProtocol from "../hooks/useWebsocket"; -import { Trash } from "@styled-icons/heroicons-outline"; +import { BookOpen, Trash } from "@styled-icons/heroicons-outline"; +import ComboBox from "../components/ComboBox";  let TopGUIDiv = styled.div`    display: grid;    grid-template-columns: 1fr; @@ -42,128 +43,132 @@ interface GUIProps {  function GUI(props: GUIProps) {    const [waitingForSteps, setWaitingForSteps] = useState(false);    const [userInputQueue, setUserInputQueue] = useState<string[]>([]); -  const [history, setHistory] = useState<History | undefined>({ -    timeline: [ -      { -        step: { -          name: "Waiting for user input", -          cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", -          description: -            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", -        }, -        observation: { -          title: "ERROR FOUND", -          error: -            "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", -        }, -        output: [ -          { -            traceback: { -              frames: [ -                { -                  filepath: -                    "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", -                  lineno: 7, -                  function: "<module>", -                  code: "print(sum(first, second))", -                }, -              ], -              message: "unsupported operand type(s) for +: 'int' and 'str'", -              error_type: -                '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError', -              full_traceback: -                "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", -            }, -          }, -          null, -        ], -      }, -      { -        step: { -          name: "EditCodeStep", -          range_in_files: [ -            { -              filepath: -                "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", -              range: { -                start: { -                  line: 0, -                  character: 0, -                }, -                end: { -                  line: 6, -                  character: 25, -                }, -              }, -            }, -          ], -          prompt: -            "I ran into this problem with my Python code:\n\n                Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n                Below are the files that might need to be fixed:\n\n                {code}\n\n                This is what the code should be in order to avoid the problem:\n", -          description: -            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", -        }, -        output: [ -          null, -          { -            reversible: true, -            actions: [ -              { -                reversible: true, -                filesystem: {}, -                filepath: -                  "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", -                range: { -                  start: { -                    line: 0, -                    character: 0, -                  }, -                  end: { -                    line: 6, -                    character: 25, -                  }, -                }, -                replacement: -                  "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", -              }, -            ], -          }, -        ], -      }, -      { -        step: { -          name: "SolveTracebackStep", -          traceback: { -            frames: [ -              { -                filepath: -                  "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", -                lineno: 7, -                function: "<module>", -                code: "print(sum(first, second))", -              }, -            ], -            message: "unsupported operand type(s) for +: 'int' and 'str'", -            error_type: -              '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError', -            full_traceback: -              "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", -          }, -          description: "Running step: SolveTracebackStep", -        }, -        output: [null, null], -      }, -      { -        step: { -          name: "RunCodeStep", -          cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", -          description: -            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", -        }, -        output: [null, null], -      }, -    ], -    current_index: 3, -  } as any); +  const [availableSlashCommands, setAvailableSlashCommands] = useState< +    { name: string; description: string }[] +  >([]); +  const [history, setHistory] = useState<History | undefined>(); +  // { +  //   timeline: [ +  //     { +  //       step: { +  //         name: "Waiting for user input", +  //         cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", +  //         description: +  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", +  //       }, +  //       observation: { +  //         title: "ERROR FOUND", +  //         error: +  //           "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", +  //       }, +  //       output: [ +  //         { +  //           traceback: { +  //             frames: [ +  //               { +  //                 filepath: +  //                   "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", +  //                 lineno: 7, +  //                 function: "<module>", +  //                 code: "print(sum(first, second))", +  //               }, +  //             ], +  //             message: "unsupported operand type(s) for +: 'int' and 'str'", +  //             error_type: +  //               '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError', +  //             full_traceback: +  //               "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", +  //           }, +  //         }, +  //         null, +  //       ], +  //     }, +  //     { +  //       step: { +  //         name: "EditCodeStep", +  //         range_in_files: [ +  //           { +  //             filepath: +  //               "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", +  //             range: { +  //               start: { +  //                 line: 0, +  //                 character: 0, +  //               }, +  //               end: { +  //                 line: 6, +  //                 character: 25, +  //               }, +  //             }, +  //           }, +  //         ], +  //         prompt: +  //           "I ran into this problem with my Python code:\n\n                Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n                Below are the files that might need to be fixed:\n\n                {code}\n\n                This is what the code should be in order to avoid the problem:\n", +  //         description: +  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", +  //       }, +  //       output: [ +  //         null, +  //         { +  //           reversible: true, +  //           actions: [ +  //             { +  //               reversible: true, +  //               filesystem: {}, +  //               filepath: +  //                 "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", +  //               range: { +  //                 start: { +  //                   line: 0, +  //                   character: 0, +  //                 }, +  //                 end: { +  //                   line: 6, +  //                   character: 25, +  //                 }, +  //               }, +  //               replacement: +  //                 "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", +  //             }, +  //           ], +  //         }, +  //       ], +  //     }, +  //     { +  //       step: { +  //         name: "SolveTracebackStep", +  //         traceback: { +  //           frames: [ +  //             { +  //               filepath: +  //                 "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", +  //               lineno: 7, +  //               function: "<module>", +  //               code: "print(sum(first, second))", +  //             }, +  //           ], +  //           message: "unsupported operand type(s) for +: 'int' and 'str'", +  //           error_type: +  //             '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError', +  //           full_traceback: +  //             "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", +  //         }, +  //         description: "Running step: SolveTracebackStep", +  //       }, +  //       output: [null, null], +  //     }, +  //     { +  //       step: { +  //         name: "RunCodeStep", +  //         cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", +  //         description: +  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", +  //       }, +  //       output: [null, null], +  //     }, +  //   ], +  //   current_index: 3, +  // } as any);    const topGuiDivRef = useRef<HTMLDivElement>(null);    const client = useContinueGUIProtocol(); @@ -197,13 +202,24 @@ function GUI(props: GUIProps) {        scrollToBottom();      }); +    client?.onAvailableSlashCommands((commands) => { +      console.log("Received available slash commands: ", commands); +      setAvailableSlashCommands( +        commands.map((c) => { +          return { +            name: "/" + c.name, +            description: c.description, +          }; +        }) +      ); +    });    }, [client]);    useEffect(() => {      scrollToBottom();    }, [waitingForSteps]); -  const mainTextInputRef = useRef<HTMLTextAreaElement>(null); +  const mainTextInputRef = useRef<HTMLInputElement>(null);    useEffect(() => {      if (mainTextInputRef.current) { @@ -246,8 +262,6 @@ function GUI(props: GUIProps) {            return [...queue, input];          });        } -      mainTextInputRef.current.value = ""; -      mainTextInputRef.current.style.height = "";      }      setWaitingForSteps(true); @@ -270,7 +284,12 @@ function GUI(props: GUIProps) {        }}      >        <TopBar> -        <h3>Continue</h3> +        <a href="https://continue.dev/docs" className="no-underline"> +          <HeaderButton style={{ padding: "3px" }}> +            Continue Docs +            <BookOpen size="1.6em" /> +          </HeaderButton> +        </a>          <HeaderButton style={{ padding: "3px" }}>            Clear History            <Trash @@ -323,7 +342,7 @@ function GUI(props: GUIProps) {          })}        </div> -      <MainTextInput +      <ComboBox          disabled={            history              ? history.timeline[history.current_index].step.name === @@ -331,22 +350,13 @@ function GUI(props: GUIProps) {              : false          }          ref={mainTextInputRef} -        onKeyDown={(e) => { -          if (e.key === "Enter") { -            onMainTextInput(); -            e.stopPropagation(); -            e.preventDefault(); -          } -        }} -        rows={1} -        onChange={() => { -          const textarea = mainTextInputRef.current!; -          textarea.style.height = ""; /* Reset the height*/ -          textarea.style.height = `${Math.min( -            textarea.scrollHeight - 15, -            500 -          )}px`; +        onEnter={(e) => { +          onMainTextInput(); +          e.stopPropagation(); +          e.preventDefault();          }} +        onInputValueChange={() => {}} +        items={availableSlashCommands}        />        <ContinueButton onClick={onMainTextInput} />      </TopGUIDiv> | 
