diff options
Diffstat (limited to 'extension/react-app')
18 files changed, 459 insertions, 223 deletions
| diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json index fb13dffd..7316581d 100644 --- a/extension/react-app/package-lock.json +++ b/extension/react-app/package-lock.json @@ -20,6 +20,7 @@          "react-redux": "^8.0.5",          "react-switch": "^7.0.0",          "react-syntax-highlighter": "^15.5.0", +        "react-tooltip": "^5.18.0",          "styled-components": "^5.3.6",          "vscode-webview": "^1.0.1-beta.1"        }, @@ -597,6 +598,19 @@          "node": ">=12"        }      }, +    "node_modules/@floating-ui/core": { +      "version": "1.3.1", +      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz", +      "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==" +    }, +    "node_modules/@floating-ui/dom": { +      "version": "1.4.4", +      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.4.tgz", +      "integrity": "sha512-21hhDEPOiWkGp0Ys4Wi6Neriah7HweToKra626CIK712B5m9qkdz54OP9gVldUg+URnBTpv/j/bi/skmGdstXQ==", +      "dependencies": { +        "@floating-ui/core": "^1.3.1" +      } +    },      "node_modules/@jridgewell/gen-mapping": {        "version": "0.3.2",        "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -1310,6 +1324,11 @@          "node": ">= 6"        }      }, +    "node_modules/classnames": { +      "version": "2.3.2", +      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", +      "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" +    },      "node_modules/color-convert": {        "version": "1.9.3",        "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2967,6 +2986,19 @@          "react": ">= 0.14.0"        }      }, +    "node_modules/react-tooltip": { +      "version": "5.18.0", +      "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.18.0.tgz", +      "integrity": "sha512-qjDK/skUJJ27sc9lTWeNxp2rLzmenBTskSsRiDOCPnupGSz2GhL5IZxDizK/sOsk0hn5iSCywt+3jKxUJ3Y4Sw==", +      "dependencies": { +        "@floating-ui/dom": "^1.0.0", +        "classnames": "^2.3.0" +      }, +      "peerDependencies": { +        "react": ">=16.14.0", +        "react-dom": ">=16.14.0" +      } +    },      "node_modules/read-cache": {        "version": "1.0.0",        "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -3886,6 +3918,19 @@        "dev": true,        "optional": true      }, +    "@floating-ui/core": { +      "version": "1.3.1", +      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz", +      "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==" +    }, +    "@floating-ui/dom": { +      "version": "1.4.4", +      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.4.tgz", +      "integrity": "sha512-21hhDEPOiWkGp0Ys4Wi6Neriah7HweToKra626CIK712B5m9qkdz54OP9gVldUg+URnBTpv/j/bi/skmGdstXQ==", +      "requires": { +        "@floating-ui/core": "^1.3.1" +      } +    },      "@jridgewell/gen-mapping": {        "version": "0.3.2",        "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -4350,6 +4395,11 @@          }        }      }, +    "classnames": { +      "version": "2.3.2", +      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", +      "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" +    },      "color-convert": {        "version": "1.9.3",        "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -5411,6 +5461,15 @@          "refractor": "^3.6.0"        }      }, +    "react-tooltip": { +      "version": "5.18.0", +      "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.18.0.tgz", +      "integrity": "sha512-qjDK/skUJJ27sc9lTWeNxp2rLzmenBTskSsRiDOCPnupGSz2GhL5IZxDizK/sOsk0hn5iSCywt+3jKxUJ3Y4Sw==", +      "requires": { +        "@floating-ui/dom": "^1.0.0", +        "classnames": "^2.3.0" +      } +    },      "read-cache": {        "version": "1.0.0",        "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/extension/react-app/package.json b/extension/react-app/package.json index 12701906..4bedb813 100644 --- a/extension/react-app/package.json +++ b/extension/react-app/package.json @@ -21,6 +21,7 @@      "react-redux": "^8.0.5",      "react-switch": "^7.0.0",      "react-syntax-highlighter": "^15.5.0", +    "react-tooltip": "^5.18.0",      "styled-components": "^5.3.6",      "vscode-webview": "^1.0.1-beta.1"    }, diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx index a51541d0..8785f88f 100644 --- a/extension/react-app/src/App.tsx +++ b/extension/react-app/src/App.tsx @@ -1,28 +1,33 @@  import DebugPanel from "./components/DebugPanel";  import MainTab from "./tabs/main"; -import { Provider } from "react-redux"; -import store from "./redux/store";  import WelcomeTab from "./tabs/welcome";  import ChatTab from "./tabs/chat";  import GUI from "./tabs/gui"; +import { createContext } from "react"; +import useContinueGUIProtocol from "./hooks/useWebsocket"; +import ContinueGUIClientProtocol from "./hooks/useContinueGUIProtocol"; + +export const GUIClientContext = createContext< +  ContinueGUIClientProtocol | undefined +>(undefined);  function App() { +  const client = useContinueGUIProtocol(); +    return ( -    <> -      <Provider store={store}> -        <DebugPanel -          tabs={[ -            { -              element: <GUI />, -              title: "GUI", -            }, -            // { element: <MainTab />, title: "Debug Panel" }, -            // { element: <WelcomeTab />, title: "Welcome" }, -            // { element: <ChatTab />, title: "Chat" }, -          ]} -        ></DebugPanel> -      </Provider> -    </> +    <GUIClientContext.Provider value={client}> +      <DebugPanel +        tabs={[ +          { +            element: <GUI />, +            title: "GUI", +          }, +          // { element: <MainTab />, title: "Debug Panel" }, +          // { element: <WelcomeTab />, title: "Welcome" }, +          // { element: <ChatTab />, title: "Chat" }, +        ]} +      /> +    </GUIClientContext.Provider>    );  } diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx index 17f5626b..fe9b3a95 100644 --- a/extension/react-app/src/components/CodeBlock.tsx +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -52,9 +52,9 @@ function CopyButton(props: { textToCopy: string; visible: boolean }) {          }}        >          {clicked ? ( -          <CheckCircle color="#00ff00" size="1.4em" /> +          <CheckCircle color="#00ff00" size="1.5em" />          ) : ( -          <Clipboard color={hovered ? "#00ff00" : "white"} size="1.4em" /> +          <Clipboard color={hovered ? "#00ff00" : "white"} size="1.5em" />          )}        </StyledCopyButton>      </> diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 81b148b9..af673b42 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -4,6 +4,7 @@ import styled from "styled-components";  import {    buttonColor,    defaultBorderRadius, +  lightGray,    secondaryDark,    vscBackground,  } from "."; @@ -16,11 +17,32 @@ import {    LockClosed,    LockOpen,    Plus, +  DocumentPlus,  } from "@styled-icons/heroicons-outline"; +import { HighlightedRangeContext } from "../../../schema/FullState";  // #region styled components  const mainInputFontSize = 16; +const EmptyPillDiv = styled.div` +  padding: 8px; +  border-radius: ${defaultBorderRadius}; +  border: 1px dashed ${lightGray}; +  color: ${lightGray}; +  background-color: ${vscBackground}; +  overflow: hidden; +  display: flex; +  align-items: center; +  text-align: center; +  cursor: pointer; +  font-size: 13px; + +  &:hover { +    background-color: ${lightGray}; +    color: ${vscBackground}; +  } +`; +  const ContextDropdown = styled.div`    position: absolute;    padding: 4px; @@ -41,21 +63,23 @@ const MainTextInput = styled.textarea`    padding: 8px;    font-size: ${mainInputFontSize}px; +  font-family: inherit; +  border: 1px solid transparent;    border-radius: ${defaultBorderRadius}; -  border: 1px solid white;    margin: 8px auto; +  height: auto;    width: 100%; -  background-color: ${vscBackground}; +  background-color: ${secondaryDark};    color: white;    z-index: 1;    &:focus { +    outline: 1px solid #ff000066;      border: 1px solid transparent; -    outline: 1px solid orange;    }  `; -const UlMaxHeight = 400; +const UlMaxHeight = 300;  const Ul = styled.ul<{    hidden: boolean;    showAbove: boolean; @@ -69,15 +93,21 @@ const Ul = styled.ul<{    background: ${vscBackground};    background-color: ${secondaryDark};    color: white; -  font-family: "Fira Code", monospace;    max-height: ${UlMaxHeight}px; -  overflow: scroll; +  overflow-y: scroll; +  overflow-x: hidden;    padding: 0;    ${({ hidden }) => hidden && "display: none;"}    border-radius: ${defaultBorderRadius}; -  overflow: hidden;    border: 0.5px solid gray;    z-index: 2; +  // Get rid of scrollbar and its padding +  scrollbar-width: none; +  -ms-overflow-style: none; +  &::-webkit-scrollbar { +    width: 0px; +    background: transparent; /* make scrollbar transparent */ +  }  `;  const Li = styled.li<{ @@ -85,7 +115,7 @@ const Li = styled.li<{    selected: boolean;    isLastItem: boolean;  }>` -  ${({ highlighted }) => highlighted && "background: #aa0000;"} +  ${({ highlighted }) => highlighted && "background: #ff000066;"}    ${({ selected }) => selected && "font-weight: bold;"}      padding: 0.5rem 0.75rem;    display: flex; @@ -102,7 +132,7 @@ interface ComboBoxProps {    onInputValueChange: (inputValue: string) => void;    disabled?: boolean;    onEnter: (e: React.KeyboardEvent<HTMLInputElement>) => void; -  highlightedCodeSections: (RangeInFile & { contents: string })[]; +  highlightedCodeSections: HighlightedRangeContext[];    deleteContextItems: (indices: number[]) => void;    onTogglePin: () => void;    onToggleAddContext: () => void; @@ -119,16 +149,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {      React.useState(false);    const [pinned, setPinned] = useState(false);    const [highlightedCodeSections, setHighlightedCodeSections] = React.useState( -    props.highlightedCodeSections || [ -      { -        filepath: "test.ts", -        range: { -          start: { line: 0, character: 0 }, -          end: { line: 0, character: 0 }, -        }, -        contents: "import * as a from 'a';", -      }, -    ] +    props.highlightedCodeSections || []    );    useEffect(() => { @@ -169,6 +190,71 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {    return (      <> +      <div className="px-2 flex gap-2 items-center flex-wrap mt-2"> +        {highlightedCodeSections.length > 1 && ( +          <> +            <HeaderButtonWithText +              text="Clear Context" +              onClick={() => { +                props.deleteContextItems( +                  highlightedCodeSections.map((_, idx) => idx) +                ); +              }} +            > +              <Trash size="1.6em" /> +            </HeaderButtonWithText> +          </> +        )} +        {highlightedCodeSections.map((section, idx) => ( +          <PillButton +            editing={section.editing} +            pinned={section.pinned} +            index={idx} +            key={`${section.filepath}${idx}`} +            title={`${section.range.filepath} (${ +              section.range.range.start.line + 1 +            }-${section.range.range.end.line + 1})`} +            onDelete={() => { +              if (props.deleteContextItems) { +                props.deleteContextItems([idx]); +              } +              setHighlightedCodeSections((prev) => { +                const newSections = [...prev]; +                newSections.splice(idx, 1); +                return newSections; +              }); +            }} +            onHover={(val: boolean) => { +              if (val) { +                setHoveringButton(val); +              } else { +                setTimeout(() => { +                  setHoveringButton(val); +                }, 100); +              } +            }} +          /> +        ))} +        {props.highlightedCodeSections.length > 0 && +          (props.addingHighlightedCode ? ( +            <EmptyPillDiv +              onClick={() => { +                props.onToggleAddContext(); +              }} +            > +              Highlight to Add Context +            </EmptyPillDiv> +          ) : ( +            <HeaderButtonWithText +              text="Add to Context" +              onClick={() => { +                props.onToggleAddContext(); +              }} +            > +              <DocumentPlus width="1.6em"></DocumentPlus> +            </HeaderButtonWithText> +          ))} +      </div>        <div className="flex px-2" ref={divRef} hidden={!isOpen}>          <MainTextInput            disabled={props.disabled} @@ -193,6 +279,12 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {                  event.key === "Enter" &&                  (!isOpen || items.length === 0)                ) { +                setInputValue(""); +                const value = event.currentTarget.value; +                if (value !== "") { +                  setPositionInHistory(history.length + 1); +                  setHistory([...history, value]); +                }                  // Prevent Downshift's default 'Enter' behavior.                  (event.nativeEvent as any).preventDownshiftDefault = true; @@ -201,33 +293,28 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {                    event.currentTarget.value = `/edit ${event.currentTarget.value}`;                  }                  if (props.onEnter) props.onEnter(event); -                setInputValue(""); -                const value = event.currentTarget.value; -                if (value !== "") { -                  setPositionInHistory(history.length + 1); -                  setHistory([...history, value]); -                }                } else if (event.key === "Tab" && items.length > 0) {                  setInputValue(items[0].name);                  event.preventDefault();                } else if ( -                event.key === "ArrowUp" || -                (event.key === "ArrowDown" && -                  event.currentTarget.value.split("\n").length > 1) -              ) { -                (event.nativeEvent as any).preventDownshiftDefault = true; -              } else if ( -                event.key === "ArrowUp" && +                (event.key === "ArrowUp" || event.key === "ArrowDown") &&                  event.currentTarget.value.split("\n").length > 1                ) { +                (event.nativeEvent as any).preventDownshiftDefault = true; +              } else if (event.key === "ArrowUp") { +                console.log("OWJFOIJO");                  if (positionInHistory == 0) return; +                else if ( +                  positionInHistory == history.length && +                  (history.length === 0 || +                    history[history.length - 1] !== event.currentTarget.value) +                ) { +                  setHistory([...history, event.currentTarget.value]); +                }                  setInputValue(history[positionInHistory - 1]);                  setPositionInHistory((prev) => prev - 1); -              } else if ( -                event.key === "ArrowDown" && -                event.currentTarget.value.split("\n").length > 1 -              ) { -                if (positionInHistory < history.length - 1) { +              } else if (event.key === "ArrowDown") { +                if (positionInHistory < history.length) {                    setInputValue(history[positionInHistory + 1]);                  }                  setPositionInHistory((prev) => @@ -248,6 +335,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {            {isOpen &&              items.map((item, index) => (                <Li +                style={{ borderTop: index === 0 ? "none" : undefined }}                  key={`${item.name}${index}`}                  {...getItemProps({ item, index })}                  highlighted={highlightedIndex === index} @@ -260,80 +348,10 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {              ))}          </Ul>        </div> -      <div className="px-2 flex gap-2 items-center flex-wrap"> -        {highlightedCodeSections.length === 0 && ( -          <HeaderButtonWithText -            text={ -              props.addingHighlightedCode ? "Adding Context" : "Add Context" -            } -            onClick={() => { -              props.onToggleAddContext(); -            }} -            inverted={props.addingHighlightedCode} -          > -            <Plus size="1.6em" /> -          </HeaderButtonWithText> -        )} -        {highlightedCodeSections.length > 0 && ( -          <> -            <HeaderButtonWithText -              text="Clear Context" -              onClick={() => { -                props.deleteContextItems( -                  highlightedCodeSections.map((_, idx) => idx) -                ); -              }} -            > -              <Trash size="1.6em" /> -            </HeaderButtonWithText> -            <HeaderButtonWithText -              text={pinned ? "Unpin Context" : "Pin Context"} -              inverted={pinned} -              onClick={() => { -                setPinned((prev) => !prev); -                props.onTogglePin(); -              }} -            > -              {pinned ? ( -                <LockClosed size="1.6em"></LockClosed> -              ) : ( -                <LockOpen size="1.6em"></LockOpen> -              )} -            </HeaderButtonWithText> -          </> -        )} -        {highlightedCodeSections.map((section, idx) => ( -          <PillButton -            title={`${section.filepath} (${section.range.start.line + 1}-${ -              section.range.end.line + 1 -            })`} -            onDelete={() => { -              if (props.deleteContextItems) { -                props.deleteContextItems([idx]); -              } -              setHighlightedCodeSections((prev) => { -                const newSections = [...prev]; -                newSections.splice(idx, 1); -                return newSections; -              }); -            }} -            onHover={(val: boolean) => { -              if (val) { -                setHoveringButton(val); -              } else { -                setTimeout(() => { -                  setHoveringButton(val); -                }, 100); -              } -            }} -          /> -        ))} - -        <span className="text-trueGray-400 ml-auto mr-4 text-xs text-right"> -          Highlight code to include as context. Currently open file included by -          default. {highlightedCodeSections.length === 0 && ""} -        </span> -      </div> +      {/* <span className="text-trueGray-400 ml-auto m-auto text-xs text-right"> +        Highlight code to include as context. Currently open file included by +        default. {highlightedCodeSections.length === 0 && ""} +      </span> */}        <ContextDropdown          onMouseEnter={() => {            setHoveringContextDropdown(true); @@ -345,9 +363,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {        >          {highlightedCodeSections.map((section, idx) => (            <> -            <p>{section.filepath}</p> +            <p>{section.range.filepath}</p>              <CodeBlock showCopy={false} key={idx}> -              {section.contents} +              {section.range.contents}              </CodeBlock>            </>          ))} diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx index 5295799a..462f2b46 100644 --- a/extension/react-app/src/components/ContinueButton.tsx +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -18,7 +18,7 @@ let StyledButton = styled(Button)`    &:hover {      transition-delay: 0.5s; -    transition-property: background; +    transition-property: "background";      background: linear-gradient(        45deg,        #be1a55 14.44%, diff --git a/extension/react-app/src/components/HeaderButtonWithText.tsx b/extension/react-app/src/components/HeaderButtonWithText.tsx index 72a653c5..de8e3c98 100644 --- a/extension/react-app/src/components/HeaderButtonWithText.tsx +++ b/extension/react-app/src/components/HeaderButtonWithText.tsx @@ -1,6 +1,7 @@  import React, { useState } from "react"; - -import { HeaderButton } from "."; +import { Tooltip } from "react-tooltip"; +import styled from "styled-components"; +import { HeaderButton, StyledTooltip, defaultBorderRadius } from ".";  interface HeaderButtonWithTextProps {    text: string; @@ -13,25 +14,28 @@ interface HeaderButtonWithTextProps {  const HeaderButtonWithText = (props: HeaderButtonWithTextProps) => {    const [hover, setHover] = useState(false); -  const paddingLeft = (props.disabled ? (props.active ?  "3px" : "1px"): (hover ? "4px" : "1px"));    return ( -    <HeaderButton -      inverted={props.inverted} -      disabled={props.disabled} -      style={{ padding: (props.active ?  "3px" : "1px"), paddingLeft, borderRadius: (props.active ?  "50%" : undefined) }} -      onMouseEnter={() => { -        if (!props.disabled) { -          setHover(true); -        } -      }} -      onMouseLeave={() => { -        setHover(false); -      }} -      onClick={props.onClick} -    > -      <span hidden={!hover}>{props.text}</span> -      {props.children} -    </HeaderButton> +    <> +      <HeaderButton +        data-tooltip-id={`header_button_${props.text}`} +        inverted={props.inverted} +        disabled={props.disabled} +        onMouseEnter={() => { +          if (!props.disabled) { +            setHover(true); +          } +        }} +        onMouseLeave={() => { +          setHover(false); +        }} +        onClick={props.onClick} +      > +        {props.children} +      </HeaderButton> +      <StyledTooltip id={`header_button_${props.text}`} place="bottom"> +        {props.text} +      </StyledTooltip> +    </>    );  }; diff --git a/extension/react-app/src/components/Loader.tsx b/extension/react-app/src/components/Loader.tsx new file mode 100644 index 00000000..90eff793 --- /dev/null +++ b/extension/react-app/src/components/Loader.tsx @@ -0,0 +1,40 @@ +import { Play } from "@styled-icons/heroicons-outline"; +import { useSelector } from "react-redux"; +import styled from "styled-components"; +import { RootStore } from "../redux/store"; + +const DEFAULT_SIZE = "28px"; + +const FlashingDiv = styled.div` +  margin: auto; +  width: ${DEFAULT_SIZE}; +  animation: flash 1.2s infinite ease-in-out; +  @keyframes flash { +    0% { +      opacity: 0.4; +    } +    50% { +      opacity: 1; +    } +    100% { +      opacity: 0.4; +    } +  } +`; + +function Loader(props: { size?: string }) { +  const vscMediaUrl = useSelector( +    (state: RootStore) => state.config.vscMediaUrl +  ); +  return ( +    <FlashingDiv> +      {vscMediaUrl ? ( +        <img src={`${vscMediaUrl}/play_button.png`} width="22px" /> +      ) : ( +        <Play width={props.size || DEFAULT_SIZE} /> +      )} +    </FlashingDiv> +  ); +} + +export default Loader; diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx index 5a02c6b2..a384832e 100644 --- a/extension/react-app/src/components/PillButton.tsx +++ b/extension/react-app/src/components/PillButton.tsx @@ -1,54 +1,136 @@ -import { useState } from "react"; +import { useContext, useState } from "react";  import styled from "styled-components"; -import { defaultBorderRadius } from "."; -import { XMark } from "@styled-icons/heroicons-outline"; +import { +  StyledTooltip, +  defaultBorderRadius, +  lightGray, +  secondaryDark, +} from "."; +import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline"; +import { GUIClientContext } from "../App";  const Button = styled.button`    border: none;    color: white; -  background-color: transparent; -  border: 1px solid white; +  background-color: ${secondaryDark};    border-radius: ${defaultBorderRadius}; -  padding: 3px 6px; +  padding: 8px; +  overflow: hidden; + +  cursor: pointer; +`; + +const GridDiv = styled.div` +  position: absolute; +  left: 0px; +  top: 0px; +  width: 100%; +  height: 100%; +  display: grid; +  grid-gap: 0; +  grid-template-columns: 1fr 1fr; +  align-items: center; +  border-radius: ${defaultBorderRadius}; +  overflow: hidden; + +  background-color: ${secondaryDark}; +`; + +const ButtonDiv = styled.div<{ backgroundColor: string }>` +  background-color: ${secondaryDark}; +  padding: 3px; +  height: 100%; +  display: flex; +  align-items: center;    &:hover { -    background-color: white; -    color: black; +    background-color: ${(props) => props.backgroundColor};    } - -  cursor: pointer;  `;  interface PillButtonProps {    onHover?: (arg0: boolean) => void;    onDelete?: () => void;    title: string; +  index: number; +  editing: boolean; +  pinned: boolean;  }  const PillButton = (props: PillButtonProps) => {    const [isHovered, setIsHovered] = useState(false); +  const client = useContext(GUIClientContext); +    return ( -    <Button -      onMouseEnter={() => { -        setIsHovered(true); -        if (props.onHover) { -          props.onHover(true); -        } -      }} -      onMouseLeave={() => { -        setIsHovered(false); -        if (props.onHover) { -          props.onHover(false); -        } -      }} -      onClick={() => { -        if (props.onDelete) { -          props.onDelete(); -        } -      }} -    > -      {props.title} -    </Button> +    <> +      <Button +        style={{ +          position: "relative", +          borderColor: props.editing +            ? "#8800aa" +            : props.pinned +            ? "#ffff0099" +            : "transparent", +          borderWidth: "1px", +          borderStyle: "solid", +        }} +        onMouseEnter={() => { +          setIsHovered(true); +          if (props.onHover) { +            props.onHover(true); +          } +        }} +        onMouseLeave={() => { +          setIsHovered(false); +          if (props.onHover) { +            props.onHover(false); +          } +        }} +      > +        {isHovered && ( +          <GridDiv> +            <ButtonDiv +              data-tooltip-id={`edit-${props.index}`} +              backgroundColor={"#8800aa55"} +              onClick={() => { +                client?.setEditingAtIndices([props.index]); +              }} +            > +              <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush> +            </ButtonDiv> + +            {/* <ButtonDiv +            data-tooltip-id={`pin-${props.index}`} +            backgroundColor={"#ffff0055"} +            onClick={() => { +              client?.setPinnedAtIndices([props.index]); +            }} +          > +            <MapPin style={{ margin: "auto" }} width="1.6em"></MapPin> +          </ButtonDiv> */} +            <StyledTooltip id={`pin-${props.index}`}> +              Edit this range +            </StyledTooltip> +            <ButtonDiv +              data-tooltip-id={`delete-${props.index}`} +              backgroundColor={"#cc000055"} +              onClick={() => { +                if (props.onDelete) { +                  props.onDelete(); +                } +              }} +            > +              <Trash style={{ margin: "auto" }} width="1.6em"></Trash> +            </ButtonDiv> +          </GridDiv> +        )} +        {props.title} +      </Button> +      <StyledTooltip id={`edit-${props.index}`}> +        {props.editing ? "Editing this range" : "Edit this range"} +      </StyledTooltip> +      <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip> +    </>    );  }; diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 2aed2e72..590b1166 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -13,7 +13,7 @@ import {    ArrowPath,    XMark,  } from "@styled-icons/heroicons-outline"; -import { Stop } from "@styled-icons/heroicons-solid"; +import { StopCircle } from "@styled-icons/heroicons-solid";  import { HistoryNode } from "../../../schema/HistoryNode";  import ReactMarkdown from "react-markdown";  import HeaderButtonWithText from "./HeaderButtonWithText"; @@ -67,7 +67,6 @@ const HeaderDiv = styled.div<{ error: boolean; loading: boolean }>`  const ContentDiv = styled.div<{ isUserInput: boolean }>`    padding: 8px; -  padding-left: 16px;    background-color: ${(props) =>      props.isUserInput ? secondaryDark : vscBackground};    font-size: 13px; @@ -167,7 +166,7 @@ function StepContainer(props: StepContainerProps) {                ? "#f00"                : props.historyNode.active                ? undefined -              : "white" +              : "transparent"            }            className="overflow-hidden cursor-pointer"            onClick={(e) => { @@ -182,7 +181,7 @@ function StepContainer(props: StepContainerProps) {              loading={props.historyNode.active as boolean | false}              error={props.historyNode.observation?.error ? true : false}            > -            <h4 className="m-2"> +            <div className="m-2">                {!isUserInput &&                  (props.open ? (                    <ChevronDown size="1.4em" /> @@ -191,7 +190,7 @@ function StepContainer(props: StepContainerProps) {                  ))}                {props.historyNode.observation?.title ||                  (props.historyNode.step.name as any)} -            </h4> +            </div>              {/* <HeaderButton                onClick={(e) => {                  e.stopPropagation(); @@ -203,16 +202,14 @@ function StepContainer(props: StepContainerProps) {              <>                <HeaderButtonWithText -                disabled={props.historyNode.active as boolean}                  onClick={(e) => {                    e.stopPropagation();                    props.onDelete();                  }} -                text={props.historyNode.active ? "Stop" : "Delete"} -                active={props.historyNode.active} +                text={props.historyNode.active ? "Stop (⌘⌫)" : "Delete"}                >                  {props.historyNode.active ? ( -                  <Stop size="1.2em" onClick={props.onDelete} /> +                  <StopCircle size="1.6em" onClick={props.onDelete} />                  ) : (                    <XMark size="1.6em" onClick={props.onDelete} />                  )} diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index a564f884..ea5727f0 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -8,6 +8,7 @@ const ScreenCover = styled.div`    width: 100%;    height: 100%;    background-color: rgba(168, 168, 168, 0.5); +  z-index: 100;  `;  const DialogContainer = styled.div` @@ -35,7 +36,6 @@ const TextArea = styled.textarea`    border-radius: 8px;    padding: 8px;    outline: 1px solid black; -  font-family: Arial, Helvetica, sans-serif;    resize: none;    &:focus { diff --git a/extension/react-app/src/components/UserInputContainer.tsx b/extension/react-app/src/components/UserInputContainer.tsx index 28437d35..f51f0cb5 100644 --- a/extension/react-app/src/components/UserInputContainer.tsx +++ b/extension/react-app/src/components/UserInputContainer.tsx @@ -15,12 +15,10 @@ interface UserInputContainerProps {  }  const StyledDiv = styled.div` -  background-color: rgb(45 45 45); +  background-color: ${secondaryDark};    padding: 8px;    padding-left: 16px;    padding-right: 16px; -  border-bottom: 1px solid white; -  border-top: 1px solid white;    font-size: 13px;    display: flex;    align-items: center; @@ -29,7 +27,7 @@ const StyledDiv = styled.div`  const UserInputContainer = (props: UserInputContainerProps) => {    return (      <StyledDiv> -      <b>{props.children}</b> +      {props.children}        <div style={{ marginLeft: "auto" }}>          <HeaderButtonWithText            onClick={(e) => { diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index db1925ed..9ae0f097 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -1,7 +1,9 @@ +import { Tooltip } from "react-tooltip";  import styled, { keyframes } from "styled-components";  export const defaultBorderRadius = "5px"; -export const secondaryDark = "rgb(42 42 42)"; +export const lightGray = "rgb(100 100 100)"; +export const secondaryDark = "rgb(45 45 45)";  export const vscBackground = "rgb(30 30 30)";  export const vscBackgroundTransparent = "#1e1e1ede";  export const buttonColor = "rgb(113 28 59)"; @@ -26,6 +28,16 @@ export const Button = styled.button`    }  `; +export const StyledTooltip = styled(Tooltip)` +  font-size: 12px; +  background-color: rgb(60 60 60); +  border-radius: ${defaultBorderRadius}; +  padding: 6px; +  padding-left: 12px; +  padding-right: 12px; +  z-index: 100; +`; +  export const TextArea = styled.textarea`    width: 100%;    border-radius: ${defaultBorderRadius}; @@ -128,19 +140,17 @@ export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`    background-color: ${({ inverted }) => (inverted ? "white" : "transparent")};    color: ${({ inverted }) => (inverted ? "black" : "white")}; -  border: 1px solid white; +  border: none;    border-radius: ${defaultBorderRadius};    cursor: pointer;    &:hover {      background-color: ${({ inverted }) => -      typeof inverted === "undefined" || inverted ? "white" : "transparent"}; -    color: ${({ inverted }) => -      typeof inverted === "undefined" || inverted ? "black" : "white"}; +      typeof inverted === "undefined" || inverted ? lightGray : "transparent"};    }    display: flex;    align-items: center;    justify-content: center;    gap: 4px; -  padding: 1px; +  padding: 2px;  `; diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index f123bb2b..a179c2bf 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -23,6 +23,10 @@ abstract class AbstractContinueGUIClientProtocol {    abstract deleteContextAtIndices(indices: number[]): void; +  abstract setEditingAtIndices(indices: number[]): void; + +  abstract setPinnedAtIndices(indices: number[]): void; +    abstract toggleAddingHighlightedCode(): void;  } diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts index 49f200ae..2060dd7f 100644 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -75,6 +75,14 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {      this.messenger.send("delete_context_at_indices", { indices });    } +  setEditingAtIndices(indices: number[]) { +    this.messenger.send("set_editing_at_indices", { indices }); +  } + +  setPinnedAtIndices(indices: number[]) { +    this.messenger.send("set_pinned_at_indices", { indices }); +  } +    toggleAddingHighlightedCode(): void {      this.messenger.send("toggle_adding_highlighted_code", {});    } diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css index 6dc514ec..682551f8 100644 --- a/extension/react-app/src/index.css +++ b/extension/react-app/src/index.css @@ -10,19 +10,12 @@    --def-border-radius: 5px;  } -@font-face { -  font-family: "Mona Sans"; -  src: url("assets/Mona-Sans.woff2") format("woff2 supports variations"), -    url("assets/Mona-Sans.woff2") format("woff2-variations"); -  font-weight: 200 900; -  font-stretch: 75% 85%; -} -  html,  body,  #root {    height: 100%;    background-color: var(--vsc-background); +  font-family: "Lexend", sans-serif;  }  body { diff --git a/extension/react-app/src/main.tsx b/extension/react-app/src/main.tsx index 0b02575c..a76bced6 100644 --- a/extension/react-app/src/main.tsx +++ b/extension/react-app/src/main.tsx @@ -1,6 +1,8 @@  import React from "react";  import ReactDOM from "react-dom/client";  import App from "./App"; +import { Provider } from "react-redux"; +import store from "./redux/store";  import "./index.css";  import posthog from "posthog-js"; @@ -17,7 +19,9 @@ posthog.init("phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs", {  ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(    <React.StrictMode>      <PostHogProvider client={posthog}> -      <App /> +      <Provider store={store}> +        <App /> +      </Provider>      </PostHogProvider>    </React.StrictMode>  ); diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index e5320c6a..ca369547 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -1,11 +1,13 @@  import styled from "styled-components"; -import { defaultBorderRadius, Loader } from "../components"; +import { defaultBorderRadius } from "../components"; +import Loader from "../components/Loader";  import ContinueButton from "../components/ContinueButton"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { FullState, HighlightedRangeContext } from "../../../schema/FullState"; +import { useCallback, useEffect, useRef, useState, useContext } from "react";  import { History } from "../../../schema/History";  import { HistoryNode } from "../../../schema/HistoryNode";  import StepContainer from "../components/StepContainer"; -import useContinueGUIProtocol from "../hooks/useWebsocket"; +import { GUIClientContext } from "../App";  import {    BookOpen,    ChatBubbleOvalLeftEllipsis, @@ -52,6 +54,7 @@ interface GUIProps {  }  function GUI(props: GUIProps) { +  const client = useContext(GUIClientContext);    const posthog = usePostHog();    const vscMachineId = useSelector(      (state: RootStore) => state.config.vscMachineId @@ -70,7 +73,9 @@ function GUI(props: GUIProps) {    const [usingFastModel, setUsingFastModel] = useState(false);    const [waitingForSteps, setWaitingForSteps] = useState(false);    const [userInputQueue, setUserInputQueue] = useState<string[]>([]); -  const [highlightedRanges, setHighlightedRanges] = useState([]); +  const [highlightedRanges, setHighlightedRanges] = useState< +    HighlightedRangeContext[] +  >([]);    const [addingHighlightedCode, setAddingHighlightedCode] = useState(false);    const [availableSlashCommands, setAvailableSlashCommands] = useState<      { name: string; description: string }[] @@ -112,7 +117,6 @@ function GUI(props: GUIProps) {    const [feedbackDialogMessage, setFeedbackDialogMessage] = useState("");    const topGuiDivRef = useRef<HTMLDivElement>(null); -  const client = useContinueGUIProtocol();    const [scrollTimeout, setScrollTimeout] = useState<NodeJS.Timeout | null>(      null @@ -135,9 +139,16 @@ function GUI(props: GUIProps) {    useEffect(() => {      const listener = (e: any) => { -      // Cmd + J to toggle fast model +      // Cmd + i to toggle fast model        if (e.key === "i" && e.metaKey && e.shiftKey) {          setUsingFastModel((prev) => !prev); +        // Cmd + backspace to stop currently running step +      } else if ( +        e.key === "Backspace" && +        e.metaKey && +        typeof history?.current_index !== "undefined" +      ) { +        client?.deleteAtIndex(history.current_index);        }      };      window.addEventListener("keydown", listener); @@ -145,10 +156,10 @@ function GUI(props: GUIProps) {      return () => {        window.removeEventListener("keydown", listener);      }; -  }, []); +  }, [client, history]);    useEffect(() => { -    client?.onStateUpdate((state) => { +    client?.onStateUpdate((state: FullState) => {        // Scroll only if user is at very bottom of the window.        setUsingFastModel(state.default_model === "gpt-3.5-turbo");        const shouldScrollToBottom = @@ -289,7 +300,7 @@ function GUI(props: GUIProps) {        >          {typeof client === "undefined" && (            <> -            <Loader></Loader> +            <Loader />              <p style={{ textAlign: "center" }}>Loading Continue server...</p>            </>          )} @@ -316,7 +327,8 @@ function GUI(props: GUIProps) {                  setStepsOpen(nextStepsOpen);                }}                onToggleAll={() => { -                setStepsOpen((prev) => prev.map((_, index) => !prev[index])); +                const shouldOpen = !stepsOpen[index]; +                setStepsOpen((prev) => prev.map(() => shouldOpen));                }}                key={index}                onUserInput={(input: string) => { @@ -381,6 +393,7 @@ function GUI(props: GUIProps) {            borderRadius: defaultBorderRadius,            padding: "16px",            margin: "16px", +          zIndex: 100,          }}          hidden={!showDataSharingInfo}        > | 
