summaryrefslogtreecommitdiff
path: root/extension/react-app/src/components
diff options
context:
space:
mode:
authorNate Sesti <sestinj@gmail.com>2023-07-19 00:33:50 -0700
committerNate Sesti <sestinj@gmail.com>2023-07-19 00:33:50 -0700
commit1b92180d4b7720bf1cf36dd63142760d421dabf8 (patch)
tree26e25e005b06526267c2a140c1fbf1cbf822f066 /extension/react-app/src/components
parent924a0c09259d25a4dfe62c0a626a9204df45daa9 (diff)
parenta7c57e1d1e4a0eff3e4b598f8bf0448ea6068353 (diff)
downloadsncontinue-1b92180d4b7720bf1cf36dd63142760d421dabf8.tar.gz
sncontinue-1b92180d4b7720bf1cf36dd63142760d421dabf8.tar.bz2
sncontinue-1b92180d4b7720bf1cf36dd63142760d421dabf8.zip
Merge branch 'main' into config-py
Diffstat (limited to 'extension/react-app/src/components')
-rw-r--r--extension/react-app/src/components/CodeMultiselect.tsx276
-rw-r--r--extension/react-app/src/components/ComboBox.tsx135
-rw-r--r--extension/react-app/src/components/InputAndButton.tsx10
-rw-r--r--extension/react-app/src/components/LoadingCover.tsx50
-rw-r--r--extension/react-app/src/components/Onboarding.tsx136
-rw-r--r--extension/react-app/src/components/PillButton.tsx170
-rw-r--r--extension/react-app/src/components/StepContainer.tsx116
-rw-r--r--extension/react-app/src/components/TextDialog.tsx21
-rw-r--r--extension/react-app/src/components/index.ts23
9 files changed, 405 insertions, 532 deletions
diff --git a/extension/react-app/src/components/CodeMultiselect.tsx b/extension/react-app/src/components/CodeMultiselect.tsx
deleted file mode 100644
index c0ab9400..00000000
--- a/extension/react-app/src/components/CodeMultiselect.tsx
+++ /dev/null
@@ -1,276 +0,0 @@
-import React, { useEffect, useState } from "react";
-import styled from "styled-components";
-import { Button, buttonColor, defaultBorderRadius, secondaryDark } from ".";
-import { useSelector } from "react-redux";
-import {
- selectDebugContext,
- selectAllRangesInFiles,
- selectRangesMask,
-} from "../redux/selectors/debugContextSelectors";
-import "../highlight/dark.min.css";
-import hljs from "highlight.js";
-import { postVscMessage } from "../vscode";
-import { RootStore } from "../redux/store";
-import { useDispatch } from "react-redux";
-import {
- addRangeInFile,
- deleteRangeInFileAt,
- toggleSelectionAt,
- updateFileSystem,
-} from "../redux/slices/debugContexSlice";
-import { RangeInFile } from "../../../src/client";
-import { readRangeInVirtualFileSystem } from "../util";
-
-//#region Styled Components
-
-const MultiSelectContainer = styled.div`
- border-radius: ${defaultBorderRadius};
- padding: 4px;
- display: flex;
- flex-direction: column;
- gap: 4px;
-`;
-
-const MultiSelectHeader = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: left;
- border-bottom: 1px solid gray;
- padding-left: 4px;
- padding-right: 4px;
- & p {
- overflow-wrap: break-word;
- word-wrap: break-word;
- -ms-wrap-flow: break-word;
- overflow: hidden;
- }
-`;
-
-const MultiSelectOption = styled.div`
- border-radius: ${defaultBorderRadius};
- padding-top: 4px;
- cursor: pointer;
- background-color: ${secondaryDark};
-`;
-
-const DeleteSelectedRangeButton = styled(Button)`
- align-self: right;
- padding: 0px;
- margin-top: 0;
- aspect-ratio: 1/1;
- height: 28px;
-`;
-
-const ToggleHighlightButton = styled(Button)`
- display: grid;
- justify-content: center;
- align-items: center;
- grid-template-columns: 30px 1fr;
- margin-left: 20px;
- order: 1;
- width: fit-content;
-`;
-
-//#endregion
-
-//#region Path Formatting
-
-const filenameToLanguageMap: any = {
- py: "python",
- js: "javascript",
- ts: "typescript",
- html: "html",
- css: "css",
- java: "java",
- c: "c",
- cpp: "cpp",
- cs: "csharp",
- go: "go",
- rb: "ruby",
- rs: "rust",
- swift: "swift",
- php: "php",
- scala: "scala",
- kt: "kotlin",
- dart: "dart",
- hs: "haskell",
- lua: "lua",
- pl: "perl",
- r: "r",
- sql: "sql",
- vb: "vb",
- xml: "xml",
- yaml: "yaml",
-};
-
-function filenameToLanguage(filename: string): string {
- const extension = filename.split(".").pop();
- if (extension === undefined) {
- return "";
- }
- return filenameToLanguageMap[extension] || "";
-}
-
-function formatPathRelativeToWorkspace(
- path: string,
- workspacePath: string | undefined
-) {
- if (workspacePath === undefined) {
- return path;
- }
- if (path.startsWith(workspacePath)) {
- return path.substring(workspacePath.length + 1);
- } else {
- return path;
- }
-}
-
-function formatFileRange(
- rangeInFile: RangeInFile,
- workspacePath: string | undefined
-) {
- return `${formatPathRelativeToWorkspace(
- rangeInFile.filepath,
- workspacePath
- )} (lines ${rangeInFile.range.start.line + 1}-${
- rangeInFile.range.end.line + 1
- })`;
- // +1 because VS Code Ranges are 0-indexed
-}
-
-//#endregion
-
-function CodeMultiselect(props: {}) {
- // State
- const [highlightLocked, setHighlightLocked] = useState(true);
-
- // Redux
- const dispatch = useDispatch();
- const workspacePath = useSelector(
- (state: RootStore) => state.config.workspacePath
- );
- const debugContext = useSelector(selectDebugContext);
- const rangesInFiles = useSelector(selectAllRangesInFiles);
- const rangesInFilesMask = useSelector(selectRangesMask);
-
- useEffect(() => {
- let eventListener = (event: any) => {
- switch (event.data.type) {
- case "highlightedCode":
- if (!highlightLocked) {
- dispatch(
- addRangeInFile({
- rangeInFile: event.data.rangeInFile,
- canUpdateLast: true,
- })
- );
- dispatch(updateFileSystem(event.data.filesystem));
- }
- break;
- case "findSuspiciousCode":
- for (let c of event.data.codeLocations) {
- dispatch(addRangeInFile({ rangeInFile: c, canUpdateLast: false }));
- }
- dispatch(updateFileSystem(event.data.filesystem));
- postVscMessage("listTenThings", { debugContext });
- break;
- }
- };
- window.addEventListener("message", eventListener);
- return () => window.removeEventListener("message", eventListener);
- }, [debugContext, highlightLocked]);
-
- useEffect(() => {
- hljs.highlightAll();
- }, [rangesInFiles]);
-
- return (
- <MultiSelectContainer>
- {rangesInFiles.map((range: RangeInFile, index: number) => {
- return (
- <MultiSelectOption
- key={index}
- style={{
- border: `1px solid ${
- rangesInFilesMask[index] ? buttonColor : "gray"
- }`,
- }}
- onClick={() => {
- dispatch(toggleSelectionAt(index));
- }}
- >
- <MultiSelectHeader>
- <p style={{ margin: "4px" }}>
- {formatFileRange(range, workspacePath)}
- </p>
- <DeleteSelectedRangeButton
- onClick={() => dispatch(deleteRangeInFileAt(index))}
- >
- x
- </DeleteSelectedRangeButton>
- </MultiSelectHeader>
- <pre>
- <code
- className={"language-" + filenameToLanguage(range.filepath)}
- >
- {readRangeInVirtualFileSystem(range, debugContext.filesystem)}
- </code>
- </pre>
- </MultiSelectOption>
- );
- })}
- {rangesInFiles.length === 0 && (
- <>
- <p>Highlight relevant code in the editor.</p>
- </>
- )}
- <ToggleHighlightButton
- onClick={() => {
- setHighlightLocked(!highlightLocked);
- }}
- >
- {highlightLocked ? (
- <>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="20px"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth="1.5"
- stroke="currentColor"
- className="w-6 h-6"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
- />
- </svg>{" "}
- Enable Highlight
- </>
- ) : (
- <>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="20px"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth="1.5"
- stroke="currentColor"
- className="w-6 h-6"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
- />
- </svg>{" "}
- Disable Highlight
- </>
- )}
- </ToggleHighlightButton>
- </MultiSelectContainer>
- );
-}
-
-export default CodeMultiselect;
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index e6632360..f327e3a3 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -1,30 +1,20 @@
-import React, {
- useCallback,
- useEffect,
- useImperativeHandle,
- useState,
-} from "react";
+import React, { useEffect, useImperativeHandle, useState } from "react";
import { useCombobox } from "downshift";
import styled from "styled-components";
import {
- buttonColor,
defaultBorderRadius,
lightGray,
secondaryDark,
vscBackground,
+ vscForeground,
} from ".";
import CodeBlock from "./CodeBlock";
-import { RangeInFile } from "../../../src/client";
import PillButton from "./PillButton";
import HeaderButtonWithText from "./HeaderButtonWithText";
-import {
- Trash,
- LockClosed,
- LockOpen,
- Plus,
- DocumentPlus,
-} from "@styled-icons/heroicons-outline";
+import { DocumentPlus } from "@styled-icons/heroicons-outline";
import { HighlightedRangeContext } from "../../../schema/FullState";
+import { postVscMessage } from "../vscode";
+import { getMetaKeyLabel } from "../util";
// #region styled components
const mainInputFontSize = 13;
@@ -48,21 +38,6 @@ const EmptyPillDiv = styled.div`
}
`;
-const ContextDropdown = styled.div`
- position: absolute;
- padding: 4px;
- width: calc(100% - 16px - 8px);
- background-color: ${secondaryDark};
- color: white;
- border-bottom-right-radius: ${defaultBorderRadius};
- border-bottom-left-radius: ${defaultBorderRadius};
- /* border: 1px solid white; */
- border-top: none;
- margin: 8px;
- outline: 1px solid orange;
- z-index: 5;
-`;
-
const MainTextInput = styled.textarea`
resize: none;
@@ -74,7 +49,7 @@ const MainTextInput = styled.textarea`
height: auto;
width: 100%;
background-color: ${secondaryDark};
- color: white;
+ color: ${vscForeground};
z-index: 1;
border: 1px solid transparent;
@@ -97,14 +72,15 @@ const Ul = styled.ul<{
position: absolute;
background: ${vscBackground};
background-color: ${secondaryDark};
- color: white;
+ color: ${vscForeground};
max-height: ${UlMaxHeight}px;
+ width: calc(100% - 16px);
overflow-y: scroll;
overflow-x: hidden;
padding: 0;
${({ hidden }) => hidden && "display: none;"}
border-radius: ${defaultBorderRadius};
- border: 0.5px solid gray;
+ outline: 0.5px solid gray;
z-index: 2;
// Get rid of scrollbar and its padding
scrollbar-width: none;
@@ -120,6 +96,7 @@ const Li = styled.li<{
selected: boolean;
isLastItem: boolean;
}>`
+ background-color: ${secondaryDark};
${({ highlighted }) => highlighted && "background: #ff000066;"}
${({ selected }) => selected && "font-weight: bold;"}
padding: 0.5rem 0.75rem;
@@ -149,10 +126,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
// The position of the current command you are typing now, so the one that will be appended to history once you press enter
const [positionInHistory, setPositionInHistory] = React.useState<number>(0);
const [items, setItems] = React.useState(props.items);
- const [hoveringButton, setHoveringButton] = React.useState(false);
- const [hoveringContextDropdown, setHoveringContextDropdown] =
- React.useState(false);
- const [pinned, setPinned] = useState(false);
const [highlightedCodeSections, setHighlightedCodeSections] = React.useState(
props.highlightedCodeSections || []
);
@@ -180,6 +153,27 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
useImperativeHandle(ref, () => downshiftProps, [downshiftProps]);
+ const [metaKeyPressed, setMetaKeyPressed] = useState(false);
+ const [focused, setFocused] = useState(false);
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === "Meta") {
+ setMetaKeyPressed(true);
+ }
+ };
+ const handleKeyUp = (e: KeyboardEvent) => {
+ if (e.key === "Meta") {
+ setMetaKeyPressed(false);
+ }
+ };
+ window.addEventListener("keydown", handleKeyDown);
+ window.addEventListener("keyup", handleKeyUp);
+ return () => {
+ window.removeEventListener("keydown", handleKeyDown);
+ window.removeEventListener("keyup", handleKeyUp);
+ };
+ });
+
useEffect(() => {
if (!inputRef.current) {
return;
@@ -221,11 +215,19 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
)} */}
{highlightedCodeSections.map((section, idx) => (
<PillButton
+ warning={
+ section.range.contents.length > 4000 && section.editing
+ ? "Editing such a large range may be slow"
+ : undefined
+ }
+ onlyShowDelete={
+ highlightedCodeSections.length <= 1 || section.editing
+ }
editing={section.editing}
pinned={section.pinned}
index={idx}
- key={`${section.filepath}${idx}`}
- title={`${section.range.filepath} (${
+ key={`${section.display_name}${idx}`}
+ title={`${section.display_name} (${
section.range.range.start.line + 1
}-${section.range.range.end.line + 1})`}
onDelete={() => {
@@ -238,15 +240,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
return newSections;
});
}}
- onHover={(val: boolean) => {
- if (val) {
- setHoveringButton(val);
- } else {
- setTimeout(() => {
- setHoveringButton(val);
- }, 100);
- }
- }}
/>
))}
{props.highlightedCodeSections.length > 0 &&
@@ -256,11 +249,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
props.onToggleAddContext();
}}
>
- Highlight to Add Context
+ Highlight code section
</EmptyPillDiv>
) : (
<HeaderButtonWithText
- text="Add to Context"
+ text="Add more code to context"
onClick={() => {
props.onToggleAddContext();
}}
@@ -272,7 +265,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
<div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}>
<MainTextInput
disabled={props.disabled}
- placeholder="Ask a question, give instructions, or type '/' to see slash commands"
+ placeholder={`Ask a question, give instructions, or type '/' to see slash commands`}
{...getInputProps({
onChange: (e) => {
const target = e.target as HTMLTextAreaElement;
@@ -285,6 +278,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
// setShowContextDropdown(target.value.endsWith("@"));
},
+ onFocus: (e) => {
+ setFocused(true);
+ },
+ onBlur: (e) => {
+ setFocused(false);
+ postVscMessage("blurContinueInput", {});
+ },
onKeyDown: (event) => {
if (event.key === "Enter" && event.shiftKey) {
// Prevent Downshift's default 'Enter' behavior.
@@ -311,7 +311,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
) {
(event.nativeEvent as any).preventDownshiftDefault = true;
} else if (event.key === "ArrowUp") {
- console.log("OWJFOIJO");
if (positionInHistory == 0) return;
else if (
positionInHistory == history.length &&
@@ -340,6 +339,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
})}
showAbove={showAbove()}
ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0}
+ hidden={!downshiftProps.isOpen || items.length === 0}
>
{downshiftProps.isOpen &&
items.map((item, index) => (
@@ -357,28 +357,15 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
))}
</Ul>
</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);
- }}
- onMouseLeave={() => {
- setHoveringContextDropdown(false);
- }}
- hidden={true || (!hoveringContextDropdown && !hoveringButton)}
- >
- {highlightedCodeSections.map((section, idx) => (
- <>
- <p>{section.range.filepath}</p>
- <CodeBlock showCopy={false} key={idx}>
- {section.range.contents}
- </CodeBlock>
- </>
- ))}
- </ContextDropdown>
+ {highlightedCodeSections.length === 0 &&
+ (downshiftProps.inputValue?.startsWith("/edit") ||
+ (focused &&
+ metaKeyPressed &&
+ downshiftProps.inputValue?.length > 0)) && (
+ <div className="text-trueGray-400 pr-4 text-xs text-right">
+ Inserting at cursor
+ </div>
+ )}
</>
);
});
diff --git a/extension/react-app/src/components/InputAndButton.tsx b/extension/react-app/src/components/InputAndButton.tsx
index 0a8592f2..8019d014 100644
--- a/extension/react-app/src/components/InputAndButton.tsx
+++ b/extension/react-app/src/components/InputAndButton.tsx
@@ -1,6 +1,6 @@
import React, { useRef } from "react";
import styled from "styled-components";
-import { vscBackground } from ".";
+import { vscBackground, vscForeground } from ".";
interface InputAndButtonProps {
onUserInput: (input: string) => void;
@@ -16,7 +16,7 @@ const Input = styled.input`
padding: 0.5rem;
border: 1px solid white;
background-color: ${vscBackground};
- color: white;
+ color: ${vscForeground};
border-radius: 4px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
@@ -27,7 +27,7 @@ const Button = styled.button`
padding: 0.5rem;
border: 1px solid white;
background-color: ${vscBackground};
- color: white;
+ color: ${vscForeground};
border-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
@@ -35,8 +35,8 @@ const Button = styled.button`
cursor: pointer;
&:hover {
- background-color: white;
- color: black;
+ background-color: ${vscForeground};
+ color: ${vscBackground};
}
`;
diff --git a/extension/react-app/src/components/LoadingCover.tsx b/extension/react-app/src/components/LoadingCover.tsx
deleted file mode 100644
index a0f8f7a2..00000000
--- a/extension/react-app/src/components/LoadingCover.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from "react";
-import styled from "styled-components";
-
-const StyledDiv = styled.div`
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100vh;
- background: linear-gradient(
- 101.79deg,
- #12887a 0%,
- #87245c 32%,
- #e12637 63%,
- #ffb215 100%
- );
- display: flex;
- justify-content: center;
- align-items: center;
- flex-direction: column;
- z-index: 10;
-`;
-
-const StyledImg = styled.img`
- /* add your styles here */
-`;
-
-const StyledDiv2 = styled.div`
- width: 50%;
- height: 5px;
- background: white;
- margin-top: 20px;
-`;
-
-interface LoadingCoverProps {
- message: string;
- hidden?: boolean;
-}
-
-const LoadingCover = (props: LoadingCoverProps) => {
- return (
- <StyledDiv style={{ display: props.hidden ? "none" : "inherit" }}>
- <StyledImg src="continue.gif" alt="centered image" width="50%" />
- <StyledDiv2></StyledDiv2>
- <p>{props.message}</p>
- </StyledDiv>
- );
-};
-
-export default LoadingCover;
diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx
new file mode 100644
index 00000000..231c1e93
--- /dev/null
+++ b/extension/react-app/src/components/Onboarding.tsx
@@ -0,0 +1,136 @@
+import { useSelector } from "react-redux";
+import { RootStore } from "../redux/store";
+import React, { useState, useEffect } from "react";
+import styled from "styled-components";
+import { ArrowLeft, ArrowRight } from "@styled-icons/heroicons-outline";
+import { defaultBorderRadius } from ".";
+import Loader from "./Loader";
+
+const StyledDiv = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: #1e1e1e;
+ z-index: 200;
+`;
+
+const StyledSpan = styled.span`
+ padding: 8px;
+ border-radius: ${defaultBorderRadius};
+ &:hover {
+ background-color: #ffffff33;
+ }
+ white-space: nowrap;
+`;
+
+const Onboarding = () => {
+ const [counter, setCounter] = useState(4);
+ const gifs = ["intro", "highlight", "question", "help"];
+ const topMessages = [
+ "Welcome!",
+ "Highlight code",
+ "Ask a question",
+ "Use /help to learn more",
+ ];
+
+ useEffect(() => {
+ const hasVisited = localStorage.getItem("hasVisited");
+ if (hasVisited) {
+ setCounter(4);
+ } else {
+ setCounter(0);
+ localStorage.setItem("hasVisited", "true");
+ }
+ }, []);
+
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ setLoading(true);
+ }, [counter]);
+
+ return (
+ <StyledDiv hidden={counter >= 4}>
+ <div
+ style={{
+ display: "grid",
+ justifyContent: "center",
+ alignItems: "center",
+ height: "100%",
+ textAlign: "center",
+ background: `linear-gradient(
+ 101.79deg,
+ #12887a66 0%,
+ #87245c66 32%,
+ #e1263766 63%,
+ #ffb21566 100%
+ )`,
+ paddingLeft: "16px",
+ paddingRight: "16px",
+ }}
+ >
+ <h1>{topMessages[counter]}</h1>
+ <div style={{ display: "flex", justifyContent: "center" }}>
+ {loading && (
+ <div style={{ margin: "auto", position: "absolute", zIndex: 0 }}>
+ <Loader />
+ </div>
+ )}
+ {counter % 2 === 0 ? (
+ <img
+ src={`https://github.com/continuedev/continue/blob/main/media/${gifs[counter]}.gif?raw=true`}
+ width="100%"
+ key={"even-gif"}
+ alt={topMessages[counter]}
+ onLoad={() => {
+ setLoading(false);
+ }}
+ style={{ zIndex: 1 }}
+ />
+ ) : (
+ <img
+ src={`https://github.com/continuedev/continue/blob/main/media/${gifs[counter]}.gif?raw=true`}
+ width="100%"
+ key={"odd-gif"}
+ alt={topMessages[counter]}
+ onLoad={() => {
+ setLoading(false);
+ }}
+ style={{ zIndex: 1 }}
+ />
+ )}
+ </div>
+ <p
+ style={{
+ paddingLeft: "50px",
+ paddingRight: "50px",
+ paddingBottom: "50px",
+ textAlign: "center",
+ cursor: "pointer",
+ whiteSpace: "nowrap",
+ }}
+ >
+ <StyledSpan
+ hidden={counter === 0}
+ onClick={() => setCounter((prev) => Math.max(prev - 1, 0))}
+ >
+ <ArrowLeft width="18px" strokeWidth="2px" /> Previous
+ </StyledSpan>
+ <span hidden={counter === 0}>{" | "}</span>
+ <StyledSpan onClick={() => setCounter((prev) => prev + 1)}>
+ {counter === 0
+ ? "Click to learn how to use Continue"
+ : counter === 3
+ ? "Get Started"
+ : "Next"}{" "}
+ <ArrowRight width="18px" strokeWidth="2px" />
+ </StyledSpan>
+ </p>
+ </div>
+ </StyledDiv>
+ );
+};
+
+export default Onboarding;
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
index 31d98c0f..c24dba83 100644
--- a/extension/react-app/src/components/PillButton.tsx
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -3,15 +3,19 @@ import styled from "styled-components";
import {
StyledTooltip,
defaultBorderRadius,
- lightGray,
secondaryDark,
+ vscForeground,
} from ".";
-import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline";
+import {
+ Trash,
+ PaintBrush,
+ ExclamationTriangle,
+} from "@styled-icons/heroicons-outline";
import { GUIClientContext } from "../App";
const Button = styled.button`
border: none;
- color: white;
+ color: ${vscForeground};
background-color: ${secondaryDark};
border-radius: ${defaultBorderRadius};
padding: 8px;
@@ -28,10 +32,8 @@ const GridDiv = styled.div`
height: 100%;
display: grid;
grid-gap: 0;
- grid-template-columns: 1fr 1fr;
align-items: center;
border-radius: ${defaultBorderRadius};
- overflow: hidden;
background-color: ${secondaryDark};
`;
@@ -48,6 +50,21 @@ const ButtonDiv = styled.div<{ backgroundColor: string }>`
}
`;
+const CircleDiv = styled.div`
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ background-color: red;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2px;
+`;
+
interface PillButtonProps {
onHover?: (arg0: boolean) => void;
onDelete?: () => void;
@@ -55,6 +72,8 @@ interface PillButtonProps {
index: number;
editing: boolean;
pinned: boolean;
+ warning?: string;
+ onlyShowDelete?: boolean;
}
const PillButton = (props: PillButtonProps) => {
@@ -63,75 +82,102 @@ const PillButton = (props: PillButtonProps) => {
return (
<>
- <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]);
+ <div style={{ position: "relative" }}>
+ <Button
+ style={{
+ position: "relative",
+ borderColor: props.warning
+ ? "red"
+ : 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
+ style={{
+ gridTemplateColumns: props.onlyShowDelete ? "1fr" : "1fr 1fr",
}}
>
- <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush>
- </ButtonDiv>
+ {props.onlyShowDelete || (
+ <ButtonDiv
+ data-tooltip-id={`edit-${props.index}`}
+ backgroundColor={"#8800aa55"}
+ onClick={() => {
+ client?.setEditingAtIndices([props.index]);
+ }}
+ >
+ <PaintBrush
+ style={{ margin: "auto" }}
+ width="1.6em"
+ ></PaintBrush>
+ </ButtonDiv>
+ )}
- {/* <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 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 section (with entire file as context)"
+ : "Edit this section"}
+ </StyledTooltip>
+ <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+ {props.warning && (
+ <>
+ <CircleDiv data-tooltip-id={`circle-div-${props.title}`}>
+ <ExclamationTriangle
+ style={{ margin: "auto" }}
+ width="1.0em"
+ strokeWidth={2}
+ />
+ </CircleDiv>
+ <StyledTooltip id={`circle-div-${props.title}`}>
+ {props.warning}
</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 (with rest of file as context)"
- : "Edit this range"}
- </StyledTooltip>
- <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+ </div>
</>
);
};
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index d480c565..bc8665fd 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useRef, useState } from "react";
+import { useContext, useEffect, useRef, useState } from "react";
import styled, { keyframes } from "styled-components";
import {
appear,
@@ -6,18 +6,21 @@ import {
secondaryDark,
vscBackground,
vscBackgroundTransparent,
+ vscForeground,
} from ".";
import {
ChevronDown,
ChevronRight,
ArrowPath,
XMark,
+ MagnifyingGlass,
} from "@styled-icons/heroicons-outline";
import { StopCircle } from "@styled-icons/heroicons-solid";
import { HistoryNode } from "../../../schema/HistoryNode";
-import ReactMarkdown from "react-markdown";
import HeaderButtonWithText from "./HeaderButtonWithText";
-import CodeBlock from "./CodeBlock";
+import MarkdownPreview from "@uiw/react-markdown-preview";
+import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util";
+import { GUIClientContext } from "../App";
interface StepContainerProps {
historyNode: HistoryNode;
@@ -31,6 +34,7 @@ interface StepContainerProps {
onToggle: () => void;
isFirst: boolean;
isLast: boolean;
+ index: number;
}
// #region styled components
@@ -38,7 +42,6 @@ interface StepContainerProps {
const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>`
opacity: ${(props) => (props.inFuture ? 0.3 : 1)};
animation: ${appear} 0.3s ease-in-out;
- /* padding-left: ${(props) => props.stepDepth * 20}px; */
overflow: hidden;
margin-left: 0px;
margin-right: 0px;
@@ -52,12 +55,7 @@ const StepContainerDiv = styled.div<{ open: boolean }>`
`;
const HeaderDiv = styled.div<{ error: boolean; loading: boolean }>`
- background-color: ${(props) =>
- props.error
- ? "#522"
- : props.loading
- ? vscBackgroundTransparent
- : vscBackground};
+ background-color: ${(props) => (props.error ? "#522" : vscBackground)};
display: grid;
grid-template-columns: 1fr auto auto;
grid-gap: 8px;
@@ -72,19 +70,6 @@ const ContentDiv = styled.div<{ isUserInput: boolean }>`
font-size: 13px;
`;
-const MarkdownPre = styled.pre`
- background-color: ${secondaryDark};
- padding: 10px;
- border-radius: ${defaultBorderRadius};
- border: 0.5px solid white;
-`;
-
-const StyledCode = styled.code`
- word-wrap: break-word;
- color: #f69292;
- background: transparent;
-`;
-
const gradient = keyframes`
0% {
background-position: 0px 0;
@@ -124,6 +109,33 @@ const GradientBorder = styled.div<{
background-size: 200% 200%;
`;
+const StyledMarkdownPreview = styled(MarkdownPreview)`
+ pre {
+ background-color: ${secondaryDark};
+ padding: 1px;
+ border-radius: ${defaultBorderRadius};
+ border: 0.5px solid white;
+ }
+
+ code {
+ color: #f78383;
+ word-wrap: break-word;
+ border-radius: ${defaultBorderRadius};
+ background-color: ${secondaryDark};
+ }
+
+ pre > code {
+ background-color: ${secondaryDark};
+ color: ${vscForeground};
+ }
+
+ background-color: ${vscBackground};
+ font-family: "Lexend", sans-serif;
+ font-size: 13px;
+ padding: 8px;
+ color: ${vscForeground};
+`;
+
// #endregion
function StepContainer(props: StepContainerProps) {
@@ -131,6 +143,7 @@ function StepContainer(props: StepContainerProps) {
const naturalLanguageInputRef = useRef<HTMLTextAreaElement>(null);
const userInputRef = useRef<HTMLInputElement>(null);
const isUserInput = props.historyNode.step.name === "UserInputStep";
+ const client = useContext(GUIClientContext);
useEffect(() => {
if (userInputRef?.current) {
@@ -158,7 +171,7 @@ function StepContainer(props: StepContainerProps) {
>
<StepContainerDiv open={props.open}>
<GradientBorder
- loading={props.historyNode.active as boolean | false}
+ loading={(props.historyNode.active as boolean) || false}
isFirst={props.isFirst}
isLast={props.isLast}
borderColor={
@@ -170,7 +183,7 @@ function StepContainer(props: StepContainerProps) {
}
className="overflow-hidden cursor-pointer"
onClick={(e) => {
- if (e.metaKey) {
+ if (isMetaEquivalentKeyPressed(e)) {
props.onToggleAll();
} else {
props.onToggle();
@@ -178,7 +191,7 @@ function StepContainer(props: StepContainerProps) {
}}
>
<HeaderDiv
- loading={props.historyNode.active as boolean | false}
+ loading={(props.historyNode.active as boolean) || false}
error={props.historyNode.observation?.error ? true : false}
>
<div className="m-2">
@@ -201,12 +214,27 @@ function StepContainer(props: StepContainerProps) {
</HeaderButton> */}
<>
+ {(props.historyNode.logs as any)?.length > 0 && (
+ <HeaderButtonWithText
+ text="Logs"
+ onClick={(e) => {
+ e.stopPropagation();
+ client?.showLogsAtIndex(props.index);
+ }}
+ >
+ <MagnifyingGlass size="1.4em" />
+ </HeaderButtonWithText>
+ )}
<HeaderButtonWithText
onClick={(e) => {
e.stopPropagation();
props.onDelete();
}}
- text={props.historyNode.active ? "Stop (⌘⌫)" : "Delete"}
+ text={
+ props.historyNode.active
+ ? `Stop (${getMetaKeyLabel()}⌫)`
+ : "Delete"
+ }
>
{props.historyNode.active ? (
<StopCircle size="1.6em" onClick={props.onDelete} />
@@ -242,31 +270,19 @@ function StepContainer(props: StepContainerProps) {
)}
{props.historyNode.observation?.error ? (
- <pre className="overflow-x-scroll">
- {props.historyNode.observation.error as string}
- </pre>
+ <details>
+ <summary>View Traceback</summary>
+ <pre className="overflow-x-scroll">
+ {props.historyNode.observation.error as string}
+ </pre>
+ </details>
) : (
- <ReactMarkdown
- key={1}
- className="overflow-x-scroll"
- components={{
- pre: ({ node, ...props }) => {
- return (
- <CodeBlock
- children={(props.children[0] as any).props.children[0]}
- />
- );
- },
- code: ({ node, ...props }) => {
- return <StyledCode children={props.children[0] as any} />;
- },
- ul: ({ node, ...props }) => {
- return <ul className="ml-0" {...props} />;
- },
+ <StyledMarkdownPreview
+ source={props.historyNode.step.description || ""}
+ wrapperElement={{
+ "data-color-mode": "dark",
}}
- >
- {props.historyNode.step.description as any}
- </ReactMarkdown>
+ />
)}
</ContentDiv>
</StepContainerDiv>
diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx
index ea5727f0..cba3852d 100644
--- a/extension/react-app/src/components/TextDialog.tsx
+++ b/extension/react-app/src/components/TextDialog.tsx
@@ -1,7 +1,8 @@
// Write a component that displays a dialog box with a text field and a button.
import React, { useEffect, useState } from "react";
import styled from "styled-components";
-import { Button, buttonColor, secondaryDark, vscBackground } from ".";
+import { Button, secondaryDark, vscBackground, vscForeground } from ".";
+import { isMetaEquivalentKeyPressed } from "../util";
const ScreenCover = styled.div`
position: absolute;
@@ -20,13 +21,13 @@ const DialogContainer = styled.div`
`;
const Dialog = styled.div`
- background-color: white;
+ color: ${vscForeground};
+ background-color: ${vscBackground};
border-radius: 8px;
padding: 8px;
display: flex;
flex-direction: column;
- /* box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.5); */
- border: 2px solid ${buttonColor};
+ box-shadow: 0 0 10px 0 ${vscForeground};
width: fit-content;
margin: auto;
`;
@@ -37,14 +38,16 @@ const TextArea = styled.textarea`
padding: 8px;
outline: 1px solid black;
resize: none;
+ background-color: ${secondaryDark};
+ color: ${vscForeground};
&:focus {
- outline: 1px solid ${buttonColor};
+ outline: 1px solid ${vscForeground};
}
`;
const P = styled.p`
- color: black;
+ color: ${vscForeground};
margin: 8px auto;
`;
@@ -81,7 +84,11 @@ const TextDialog = (props: {
rows={10}
ref={textAreaRef}
onKeyDown={(e) => {
- if (e.key === "Enter" && e.metaKey && textAreaRef.current) {
+ if (
+ e.key === "Enter" &&
+ isMetaEquivalentKeyPressed(e) &&
+ textAreaRef.current
+ ) {
props.onEnter(textAreaRef.current.value);
setText("");
} else if (e.key === "Escape") {
diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts
index 9ae0f097..cb5e7915 100644
--- a/extension/react-app/src/components/index.ts
+++ b/extension/react-app/src/components/index.ts
@@ -3,12 +3,16 @@ import styled, { keyframes } from "styled-components";
export const defaultBorderRadius = "5px";
export const lightGray = "rgb(100 100 100)";
-export const secondaryDark = "rgb(45 45 45)";
-export const vscBackground = "rgb(30 30 30)";
+// 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)";
export const buttonColorHover = "rgb(113 28 59 0.67)";
+export const secondaryDark = "var(--vscode-textBlockQuote-background)";
+export const vscBackground = "var(--vscode-editor-background)";
+export const vscForeground = "var(--vscode-editor-foreground)";
+
export const Button = styled.button`
padding: 10px 12px;
margin: 8px 0;
@@ -46,8 +50,8 @@ export const TextArea = styled.textarea`
resize: vertical;
padding: 4px;
- caret-color: white;
- color: white;
+ caret-color: ${vscForeground};
+ color: #{vscForeground};
&:focus {
outline: 1px solid ${buttonColor};
@@ -120,7 +124,7 @@ export const MainTextInput = styled.textarea`
border: 1px solid #ccc;
margin: 8px 8px;
background-color: ${vscBackground};
- color: white;
+ color: ${vscForeground};
outline: 1px solid orange;
resize: none;
`;
@@ -137,8 +141,9 @@ export const appear = keyframes`
`;
export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`
- background-color: ${({ inverted }) => (inverted ? "white" : "transparent")};
- color: ${({ inverted }) => (inverted ? "black" : "white")};
+ background-color: ${({ inverted }) =>
+ inverted ? vscForeground : "transparent"};
+ color: ${({ inverted }) => (inverted ? vscBackground : vscForeground)};
border: none;
border-radius: ${defaultBorderRadius};
@@ -146,7 +151,9 @@ export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`
&:hover {
background-color: ${({ inverted }) =>
- typeof inverted === "undefined" || inverted ? lightGray : "transparent"};
+ typeof inverted === "undefined" || inverted
+ ? secondaryDark
+ : "transparent"};
}
display: flex;
align-items: center;