From 36577b8e94809da47a540499132774a0fe2c085d Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 1 Jul 2023 12:36:57 -0700 Subject: explicit context pill buttons --- extension/react-app/src/components/CodeBlock.tsx | 73 +++--- extension/react-app/src/components/ComboBox.tsx | 258 ++++++++++++++------- .../src/components/IterationContainer.tsx | 77 ------ .../react-app/src/components/StepContainer.tsx | 8 +- 4 files changed, 215 insertions(+), 201 deletions(-) delete mode 100644 extension/react-app/src/components/IterationContainer.tsx (limited to 'extension/react-app/src/components') diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx index 1624b986..17f5626b 100644 --- a/extension/react-app/src/components/CodeBlock.tsx +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -61,7 +61,7 @@ function CopyButton(props: { textToCopy: string; visible: boolean }) { ); } -function CodeBlock(props: { children: React.ReactNode }) { +function CodeBlock(props: { children: string; showCopy?: boolean }) { const [result, setResult] = useState( undefined ); @@ -78,39 +78,36 @@ function CodeBlock(props: { children: React.ReactNode }) { setHighlightTimeout( setTimeout(() => { - const result = hljs.highlightAuto( - (props.children as any).props.children[0], - [ - "python", - "javascript", - "typescript", - "bash", - "html", - "css", - "json", - "yaml", - "markdown", - "sql", - "java", - "c", - "cpp", - "csharp", - "go", - "kotlin", - "php", - "ruby", - "rust", - "scala", - "swift", - "dart", - "haskell", - "perl", - "r", - "julia", - "objectivec", - "ocaml", - ] - ); + const result = hljs.highlightAuto(props.children, [ + "python", + "javascript", + "typescript", + "bash", + "html", + "css", + "json", + "yaml", + "markdown", + "sql", + "java", + "c", + "cpp", + "csharp", + "go", + "kotlin", + "php", + "ruby", + "rust", + "scala", + "swift", + "dart", + "haskell", + "perl", + "r", + "julia", + "objectivec", + "ocaml", + ]); setResult(result); setHighlightTimeout(undefined); }, 100) @@ -129,13 +126,11 @@ function CodeBlock(props: { children: React.ReactNode }) { > - - {(props.children as any).props.children[0]} - + {props.children} ); } diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 3816cee8..34027a42 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useEffect } from "react"; import { useCombobox } from "downshift"; import styled from "styled-components"; import { @@ -7,8 +7,44 @@ import { secondaryDark, vscBackground, } from "."; +import CodeBlock from "./CodeBlock"; +import { RangeInFile } from "../../../src/client"; const mainInputFontSize = 16; + +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-left: 8px; + margin-right: 8px; + margin-top: -12px; + outline: 1px solid orange; +`; + +const PillButton = styled.button` + display: flex; + justify-content: space-between; + align-items: center; + border: none; + color: white; + background-color: gray; + border-radius: 50px; + padding: 5px 10px; + margin: 5px 0; + cursor: pointer; + + &:hover { + background-color: ${buttonColor}; + } +`; + const MainTextInput = styled.textarea` resize: none; @@ -20,6 +56,7 @@ const MainTextInput = styled.textarea` width: 100%; background-color: ${vscBackground}; color: white; + z-index: 1; &:focus { border: 1px solid transparent; @@ -49,6 +86,7 @@ const Ul = styled.ul<{ border-radius: ${defaultBorderRadius}; overflow: hidden; border: 0.5px solid gray; + z-index: 2; `; const Li = styled.li<{ @@ -71,6 +109,8 @@ interface ComboBoxProps { onInputValueChange: (inputValue: string) => void; disabled?: boolean; onEnter?: (e: React.KeyboardEvent) => void; + highlightedCodeSections?: (RangeInFile & { contents: string })[]; + deleteContextItem?: (idx: number) => void; } const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { @@ -78,6 +118,24 @@ 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(0); const [items, setItems] = React.useState(props.items); + const [showContextDropdown, setShowContextDropdown] = React.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';", + }, + ] + ); + + useEffect(() => { + setHighlightedCodeSections(props.highlightedCodeSections || []); + }, [props.highlightedCodeSections]); + const { isOpen, getToggleButtonProps, @@ -111,90 +169,124 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { }; return ( - + ); }); diff --git a/extension/react-app/src/components/IterationContainer.tsx b/extension/react-app/src/components/IterationContainer.tsx deleted file mode 100644 index a0053519..00000000 --- a/extension/react-app/src/components/IterationContainer.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useState } from "react"; -import styled from "styled-components"; -import { - defaultBorderRadius, - MainContainerWithBorder, - secondaryDark, - vscBackground, -} from "."; -import { RangeInFile, FileEdit } from "../../../src/client"; -import CodeBlock from "./CodeBlock"; -import SubContainer from "./SubContainer"; - -import { ChevronDown, ChevronRight } from "@styled-icons/heroicons-outline"; - -export interface IterationContext { - codeSelections: RangeInFile[]; - instruction: string; - suggestedChanges: FileEdit[]; - status: "waiting" | "accepted" | "rejected"; - summary?: string; - action: string; - error?: string; -} - -interface IterationContainerProps { - iterationContext: IterationContext; -} - -const IterationContainerDiv = styled.div<{ open: boolean }>` - background-color: ${(props) => (props.open ? vscBackground : secondaryDark)}; - border-radius: ${defaultBorderRadius}; - padding: ${(props) => (props.open ? "2px" : "8px")}; -`; - -function IterationContainer(props: IterationContainerProps) { - const [open, setOpen] = useState(false); - - return ( - - -

setOpen((prev) => !prev)} - > - {open ? : } - {props.iterationContext.summary || - props.iterationContext.codeSelections - .map((cs) => cs.filepath) - .join("\n")} -

- - {open && ( - <> - - {props.iterationContext.action} - - {props.iterationContext.error && ( - - {props.iterationContext.error} - - )} - {props.iterationContext.suggestedChanges.map((sc) => { - return ( - - {sc.filepath} - {sc.replacement} - - ); - })} - - )} -
-
- ); -} - -export default IterationContainer; diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 02c04548..cb83f20a 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -246,10 +246,14 @@ function StepContainer(props: StepContainerProps) { className="overflow-x-scroll" components={{ pre: ({ node, ...props }) => { - return ; + return ( + + ); }, code: ({ node, ...props }) => { - return ; + return ; }, }} > -- cgit v1.2.3-70-g09d2 From a606c13ca75f0c9177b3d04f20dcf7211d81f083 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 2 Jul 2023 20:14:27 -0700 Subject: finishing up explicit context --- continuedev/src/continuedev/core/autopilot.py | 17 ++--- continuedev/src/continuedev/models/filesystem.py | 16 ++--- continuedev/src/continuedev/models/main.py | 5 ++ continuedev/src/continuedev/steps/chat.py | 2 +- extension/react-app/src/components/ComboBox.tsx | 83 ++++++++++++---------- .../react-app/src/components/ContinueButton.tsx | 1 + extension/react-app/src/components/PillButton.tsx | 66 +++++++++++++++++ 7 files changed, 131 insertions(+), 59 deletions(-) create mode 100644 extension/react-app/src/components/PillButton.tsx (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index b9e61c63..1a77ca64 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -139,21 +139,22 @@ class Autopilot(ContinueBaseModel): for rif in range_in_files: rif.filepath = os.path.relpath(rif.filepath, workspace_path) + old_ranges = self._highlighted_ranges + range_in_files new_ranges = [] - for rif in range_in_files: + + while len(old_ranges) > 0: + old_range = old_ranges.pop(0) found_overlap = False - for i in range(len(self._highlighted_ranges)): - hr = self._highlighted_ranges[i] - if hr.filepath == rif.filepath and hr.range.overlaps_with(rif.range): - new_ranges.append(rif.union(hr)) + for i in range(len(new_ranges)): + if old_range.filepath == new_ranges[i].filepath and old_range.range.overlaps_with(new_ranges[i].range): + new_ranges[i] = old_range.union(new_ranges[i]) found_overlap = True - self._highlighted_ranges.pop(i) break if not found_overlap: - new_ranges.append(rif) + new_ranges.append(old_range) - self._highlighted_ranges += new_ranges + self._highlighted_ranges = new_ranges await self.update_subscribers() _step_depth: int = 0 diff --git a/continuedev/src/continuedev/models/filesystem.py b/continuedev/src/continuedev/models/filesystem.py index fc1c3f13..df0b15d7 100644 --- a/continuedev/src/continuedev/models/filesystem.py +++ b/continuedev/src/continuedev/models/filesystem.py @@ -40,21 +40,15 @@ class RangeInFileWithContents(RangeInFile): assert first.filepath == second.filepath - # Calculate the start and end positions of the overlap - overlap_start = max(first.range.start, - second.range.start) - first.range.start - overlap_end = min(first.range.end, second.range.end) - \ - first.range.start - - # Calculate the new contents by removing the overlap - union_contents = first.contents[:overlap_start] + \ - second.contents[overlap_start:overlap_end] + \ - first.contents[overlap_end:] + # Calculate union of contents + num_overlapping_lines = first.range.end.line - second.range.start.line + 1 + union_lines = first.contents.splitlines()[:-num_overlapping_lines] + \ + second.contents.splitlines() return RangeInFileWithContents( filepath=first.filepath, range=first.range.union(second.range), - contents=union_contents + contents="\n".join(union_lines) ) @staticmethod diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 101be4ae..fa736772 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -43,6 +43,11 @@ class Position(BaseModel): def from_end_of_file(contents: str) -> "Position": return Position.from_index(contents, len(contents)) + def to_index(self, string: str) -> int: + """Convert line and character to index in string""" + lines = string.splitlines() + return sum(map(len, lines[:self.line])) + self.character + class Range(BaseModel): """A range in a file. 0-indexed.""" diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py index 8494563b..b10ec3d7 100644 --- a/continuedev/src/continuedev/steps/chat.py +++ b/continuedev/src/continuedev/steps/chat.py @@ -106,7 +106,7 @@ class RunTerminalCommandStep(Step): class ViewDirectoryTreeStep(Step): name: str = "View Directory Tree" - description: str = "View the directory tree to learn which folder and files exist." + description: str = "View the directory tree to learn which folder and files exist. You should always do this before adding new files." async def describe(self, models: Models) -> Coroutine[Any, Any, Coroutine[str, None, None]]: return f"Viewed the directory tree." diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 34027a42..f299c3a2 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -9,6 +9,7 @@ import { } from "."; import CodeBlock from "./CodeBlock"; import { RangeInFile } from "../../../src/client"; +import PillButton from "./PillButton"; const mainInputFontSize = 16; @@ -22,27 +23,9 @@ const ContextDropdown = styled.div` border-bottom-left-radius: ${defaultBorderRadius}; /* border: 1px solid white; */ border-top: none; - margin-left: 8px; - margin-right: 8px; - margin-top: -12px; + margin: 8px; outline: 1px solid orange; -`; - -const PillButton = styled.button` - display: flex; - justify-content: space-between; - align-items: center; - border: none; - color: white; - background-color: gray; - border-radius: 50px; - padding: 5px 10px; - margin: 5px 0; - cursor: pointer; - - &:hover { - background-color: ${buttonColor}; - } + z-index: 5; `; const MainTextInput = styled.textarea` @@ -118,7 +101,9 @@ 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(0); const [items, setItems] = React.useState(props.items); - const [showContextDropdown, setShowContextDropdown] = React.useState(false); + const [hoveringButton, setHoveringButton] = React.useState(false); + const [hoveringContextDropdown, setHoveringContextDropdown] = + React.useState(false); const [highlightedCodeSections, setHighlightedCodeSections] = React.useState( props.highlightedCodeSections || [ { @@ -184,7 +169,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { 300 ).toString()}px`; - setShowContextDropdown(target.value.endsWith("@")); + // setShowContextDropdown(target.value.endsWith("@")); }, onKeyDown: (event) => { if (event.key === "Enter" && event.shiftKey) { @@ -256,22 +241,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { ))} - -
+
{highlightedCodeSections.map((section, idx) => ( { - console.log("delete context item", idx); + title={section.filepath} + onDelete={() => { if (props.deleteContextItem) { props.deleteContextItem(idx); } @@ -281,11 +255,42 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { return newSections; }); }} - > - {section.filepath} - + onHover={(val: boolean) => { + if (val) { + setHoveringButton(val); + } else { + setTimeout(() => { + setHoveringButton(val); + }, 100); + } + }} + /> ))} + + + Highlight code to include as context.{" "} + {highlightedCodeSections.length > 0 && + "Otherwise using entire currently open file."} +
+ { + setHoveringContextDropdown(true); + }} + onMouseLeave={() => { + setHoveringContextDropdown(false); + }} + hidden={!hoveringContextDropdown && !hoveringButton} + > + {highlightedCodeSections.map((section, idx) => ( + <> +

{section.filepath}

+ + {section.contents} + + + ))} +
); }); diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx index ef6719b7..5295799a 100644 --- a/extension/react-app/src/components/ContinueButton.tsx +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -6,6 +6,7 @@ import { RootStore } from "../redux/store"; let StyledButton = styled(Button)` margin: auto; + margin-top: 8px; display: grid; grid-template-columns: 30px 1fr; align-items: center; diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx new file mode 100644 index 00000000..33451db5 --- /dev/null +++ b/extension/react-app/src/components/PillButton.tsx @@ -0,0 +1,66 @@ +import { useState } from "react"; +import styled from "styled-components"; +import { defaultBorderRadius } from "."; + +const Button = styled.button` + border: none; + color: white; + background-color: transparent; + border: 1px solid white; + border-radius: ${defaultBorderRadius}; + padding: 3px 6px; + + &:hover { + background-color: white; + color: black; + } +`; + +interface PillButtonProps { + onHover?: (arg0: boolean) => void; + onDelete?: () => void; + title: string; +} + +const PillButton = (props: PillButtonProps) => { + const [isHovered, setIsHovered] = useState(false); + return ( + + ); +}; + +export default PillButton; -- cgit v1.2.3-70-g09d2