From f53768612b1e2268697b5444e502032ef9f3fb3c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 23 May 2023 23:45:12 -0400 Subject: copying from old repo --- extension/react-app/src/components/CodeBlock.tsx | 57 +++++ .../react-app/src/components/CodeMultiselect.tsx | 276 +++++++++++++++++++++ .../react-app/src/components/ContinueButton.tsx | 37 +++ extension/react-app/src/components/DebugPanel.tsx | 121 +++++++++ .../src/components/IterationContainer.tsx | 77 ++++++ .../react-app/src/components/StepContainer.tsx | 208 ++++++++++++++++ .../react-app/src/components/SubContainer.tsx | 24 ++ .../react-app/src/components/VSCodeFileLink.tsx | 17 ++ extension/react-app/src/components/index.ts | 136 ++++++++++ 9 files changed, 953 insertions(+) create mode 100644 extension/react-app/src/components/CodeBlock.tsx create mode 100644 extension/react-app/src/components/CodeMultiselect.tsx create mode 100644 extension/react-app/src/components/ContinueButton.tsx create mode 100644 extension/react-app/src/components/DebugPanel.tsx create mode 100644 extension/react-app/src/components/IterationContainer.tsx create mode 100644 extension/react-app/src/components/StepContainer.tsx create mode 100644 extension/react-app/src/components/SubContainer.tsx create mode 100644 extension/react-app/src/components/VSCodeFileLink.tsx create mode 100644 extension/react-app/src/components/index.ts (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 new file mode 100644 index 00000000..4c10ab23 --- /dev/null +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -0,0 +1,57 @@ +import hljs from "highlight.js"; +import { useEffect } from "react"; +import styled from "styled-components"; +import { defaultBorderRadius, vscBackground } from "."; + +import { Clipboard } from "@styled-icons/heroicons-outline"; + +const StyledPre = styled.pre` + overflow: scroll; + border: 1px solid gray; + border-radius: ${defaultBorderRadius}; + background-color: ${vscBackground}; +`; + +const StyledCode = styled.code` + background-color: ${vscBackground}; +`; + +const StyledCopyButton = styled.button` + float: right; + border: none; + background-color: ${vscBackground}; + cursor: pointer; + padding: 0; + margin: 4px; + &:hover { + color: #fff; + } +`; + +function CopyButton(props: { textToCopy: string }) { + return ( + <> + { + navigator.clipboard.writeText(props.textToCopy); + }} + > + + + + ); +} + +function CodeBlock(props: { language?: string; children: string }) { + useEffect(() => { + hljs.highlightAll(); + }, [props.children]); + return ( + + + {props.children} + + ); +} + +export default CodeBlock; diff --git a/extension/react-app/src/components/CodeMultiselect.tsx b/extension/react-app/src/components/CodeMultiselect.tsx new file mode 100644 index 00000000..626ae42f --- /dev/null +++ b/extension/react-app/src/components/CodeMultiselect.tsx @@ -0,0 +1,276 @@ +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 VSCode 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 ( + + {rangesInFiles.map((range: RangeInFile, index: number) => { + return ( + { + dispatch(toggleSelectionAt(index)); + }} + > + +

+ {formatFileRange(range, workspacePath)} +

+ dispatch(deleteRangeInFileAt(index))} + > + x + +
+
+              
+                {readRangeInVirtualFileSystem(range, debugContext.filesystem)}
+              
+            
+
+ ); + })} + {rangesInFiles.length === 0 && ( + <> +

Highlight relevant code in the editor.

+ + )} + { + setHighlightLocked(!highlightLocked); + }} + > + {highlightLocked ? ( + <> + + + {" "} + Enable Highlight + + ) : ( + <> + + + {" "} + Disable Highlight + + )} + +
+ ); +} + +export default CodeMultiselect; diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx new file mode 100644 index 00000000..11dc7a92 --- /dev/null +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -0,0 +1,37 @@ +import styled, { keyframes } from "styled-components"; +import { Button } from "."; +import { Play } from "@styled-icons/heroicons-outline"; + +let StyledButton = styled(Button)` + margin: auto; + display: grid; + grid-template-columns: 30px 1fr; + align-items: center; + background: linear-gradient( + 95.23deg, + #be1a55 14.44%, + rgba(203, 27, 90, 0.4) 82.21% + ); + + &:hover { + transition-delay: 0.5s; + transition-property: background; + background: linear-gradient( + 45deg, + #be1a55 14.44%, + rgba(203, 27, 90, 0.4) 82.21% + ); + } +`; + +function ContinueButton(props: { onClick?: () => void }) { + return ( + + + {/* */} + Continue + + ); +} + +export default ContinueButton; diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx new file mode 100644 index 00000000..ed00571b --- /dev/null +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -0,0 +1,121 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import { postVscMessage } from "../vscode"; +import { useDispatch } from "react-redux"; +import { + setApiUrl, + setVscMachineId, + setSessionId, +} from "../redux/slices/configSlice"; +import { setHighlightedCode } from "../redux/slices/miscSlice"; +import { updateFileSystem } from "../redux/slices/debugContexSlice"; +import { buttonColor, defaultBorderRadius, vscBackground } from "."; +interface DebugPanelProps { + tabs: { + element: React.ReactElement; + title: string; + }[]; +} + +const GradientContainer = styled.div` + // Uncomment to get gradient border + background: linear-gradient( + 101.79deg, + #12887a 0%, + #87245c 37.64%, + #e12637 65.98%, + #ffb215 110.45% + ); + /* padding: 10px; */ + margin: 0; + height: 100%; + /* border: 1px solid white; */ + border-radius: ${defaultBorderRadius}; +`; + +const MainDiv = styled.div` + height: 100%; + border-radius: ${defaultBorderRadius}; + overflow: scroll; + scrollbar-base-color: transparent; + /* background: ${vscBackground}; */ + background-color: #1e1e1ede; +`; + +const TabBar = styled.div<{ numTabs: number }>` + display: grid; + grid-template-columns: repeat(${(props) => props.numTabs}, 1fr); +`; + +const TabsAndBodyDiv = styled.div` + display: grid; + grid-template-rows: auto 1fr; + height: 100%; +`; + +function DebugPanel(props: DebugPanelProps) { + const dispatch = useDispatch(); + useEffect(() => { + const eventListener = (event: any) => { + switch (event.data.type) { + case "onLoad": + dispatch(setApiUrl(event.data.apiUrl)); + dispatch(setVscMachineId(event.data.vscMachineId)); + dispatch(setSessionId(event.data.sessionId)); + break; + case "highlightedCode": + dispatch(setHighlightedCode(event.data.rangeInFile)); + dispatch(updateFileSystem(event.data.filesystem)); + break; + } + }; + window.addEventListener("message", eventListener); + postVscMessage("onLoad", {}); + return () => window.removeEventListener("message", eventListener); + }, []); + + const [currentTab, setCurrentTab] = useState(0); + + return ( + + + + {props.tabs.length > 1 && ( + + {props.tabs.map((tab, index) => { + return ( +
setCurrentTab(index)} + > + {tab.title} +
+ ); + })} +
+ )} + {props.tabs.map((tab, index) => { + return ( + + ); + })} +
+
+
+ ); +} + +export default DebugPanel; diff --git a/extension/react-app/src/components/IterationContainer.tsx b/extension/react-app/src/components/IterationContainer.tsx new file mode 100644 index 00000000..a0053519 --- /dev/null +++ b/extension/react-app/src/components/IterationContainer.tsx @@ -0,0 +1,77 @@ +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 new file mode 100644 index 00000000..03649b66 --- /dev/null +++ b/extension/react-app/src/components/StepContainer.tsx @@ -0,0 +1,208 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import styled, { keyframes } from "styled-components"; +import { + appear, + buttonColor, + defaultBorderRadius, + MainContainerWithBorder, + MainTextInput, + secondaryDark, + vscBackground, + GradientBorder, +} from "."; +import { RangeInFile, FileEdit } from "../../../src/client"; +import CodeBlock from "./CodeBlock"; +import SubContainer from "./SubContainer"; + +import { + ChevronDown, + ChevronRight, + Backward, +} from "@styled-icons/heroicons-outline"; +import { HistoryNode } from "../../../schema/HistoryNode"; +import ReactMarkdown from "react-markdown"; +import ContinueButton from "./ContinueButton"; + +interface StepContainerProps { + historyNode: HistoryNode; + onReverse: () => void; + inFuture: boolean; + onRefinement: (input: string) => void; + onUserInput: (input: string) => void; +} + +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; +`; + +const StepContainerDiv = styled.div<{ open: boolean }>` + background-color: ${(props) => (props.open ? vscBackground : secondaryDark)}; + border-radius: ${defaultBorderRadius}; + padding: 8px; +`; + +const HeaderDiv = styled.div` + display: grid; + grid-template-columns: 1fr auto; + align-items: center; +`; + +const HeaderButton = styled.button` + background-color: transparent; + border: 1px solid white; + border-radius: ${defaultBorderRadius}; + padding: 2px; + cursor: pointer; + color: white; + + &:hover { + background-color: white; + color: black; + } +`; + +const OnHoverDiv = styled.div` + text-align: center; + padding: 10px; + animation: ${appear} 0.3s ease-in-out; +`; + +const NaturalLanguageInput = styled(MainTextInput)` + width: 80%; +`; + +function StepContainer(props: StepContainerProps) { + const [open, setOpen] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const naturalLanguageInputRef = useRef(null); + + useEffect(() => { + if (isHovered) { + naturalLanguageInputRef.current?.focus(); + } + }, [isHovered]); + + const onTextInput = useCallback(() => { + if (naturalLanguageInputRef.current) { + props.onRefinement(naturalLanguageInputRef.current.value); + naturalLanguageInputRef.current.value = ""; + } + }, [naturalLanguageInputRef]); + + return ( + { + setIsHovered(true); + }} + onMouseLeave={() => { + setIsHovered(false); + }} + hidden={props.historyNode.step.hide as any} + > + setOpen((prev) => !prev)} + > + + +

+ {open ? ( + + ) : ( + + )} + {props.historyNode.step.name as any}: +

+ { + e.stopPropagation(); + props.onReverse(); + }} + > + + +
+ + + {props.historyNode.step.description as any} + + + {props.historyNode.step.name === "Waiting for user input" && ( + { + if (e.key === "Enter") { + props.onUserInput(e.currentTarget.value); + } + }} + type="text" + onSubmit={(ev) => { + props.onUserInput(ev.currentTarget.value); + }} + /> + )} + {props.historyNode.step.name === "Waiting for user confirmation" && ( + <> + + { + props.onUserInput("ok"); + e.preventDefault(); + e.stopPropagation(); + }} + type="button" + value="Confirm" + /> + + )} + + {open && ( + <> + {/* {props.historyNode.observation && ( + + Error Here + + )} */} + {/* {props.iterationContext.suggestedChanges.map((sc) => { + return ( + + {sc.filepath} + {sc.replacement} + + ); + })} */} + + )} +
+
+ + +
+ ); +} + +export default StepContainer; diff --git a/extension/react-app/src/components/SubContainer.tsx b/extension/react-app/src/components/SubContainer.tsx new file mode 100644 index 00000000..87e2094c --- /dev/null +++ b/extension/react-app/src/components/SubContainer.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import styled from "styled-components"; +import { defaultBorderRadius, secondaryDark, appear } from "."; + +const SubContainerDiv = styled.div` + margin: 4px; + padding: 8px; + border-radius: ${defaultBorderRadius}; + background-color: ${secondaryDark}; + + animation: ${appear} 0.3s ease-in-out; +`; + +function SubContainer(props: { children: React.ReactNode; title: string }) { + return ( + + {props.title} +

+ {props.children} +
+ ); +} + +export default SubContainer; diff --git a/extension/react-app/src/components/VSCodeFileLink.tsx b/extension/react-app/src/components/VSCodeFileLink.tsx new file mode 100644 index 00000000..6219654d --- /dev/null +++ b/extension/react-app/src/components/VSCodeFileLink.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { postVscMessage } from "../vscode"; + +function VSCodeFileLink(props: { path: string; text?: string }) { + return ( + { + postVscMessage("openFile", { path: props.path }); + }} + > + {props.text || props.path} + + ); +} + +export default VSCodeFileLink; diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts new file mode 100644 index 00000000..e37c97f3 --- /dev/null +++ b/extension/react-app/src/components/index.ts @@ -0,0 +1,136 @@ +import styled, { keyframes } from "styled-components"; + +export const defaultBorderRadius = "5px"; +export const secondaryDark = "rgb(37 37 38)"; +export const vscBackground = "rgb(30 30 30)"; +export const buttonColor = "rgb(113 28 59)"; +export const buttonColorHover = "rgb(113 28 59 0.67)"; + +export const Button = styled.button` + padding: 10px 12px; + margin: 8px 0; + border-radius: ${defaultBorderRadius}; + cursor: pointer; + + border: none; + color: white; + background-color: ${buttonColor}; + + &:disabled { + color: gray; + } + + &:hover:enabled { + background-color: ${buttonColorHover}; + } +`; + +export const TextArea = styled.textarea` + width: 100%; + border-radius: ${defaultBorderRadius}; + border: none; + background-color: ${secondaryDark}; + resize: vertical; + + padding: 4px; + caret-color: white; + color: white; + + &:focus { + outline: 1px solid ${buttonColor}; + } +`; + +export const Pre = styled.pre` + border-radius: ${defaultBorderRadius}; + padding: 8px; + max-height: 150px; + overflow: scroll; + margin: 0; + background-color: ${secondaryDark}; + border: none; + + /* text wrapping */ + white-space: pre-wrap; /* Since CSS 2.1 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +`; + +export const H3 = styled.h3` + background-color: ${secondaryDark}; + border-radius: ${defaultBorderRadius}; + padding: 4px 8px; + width: fit-content; +`; + +export const TextInput = styled.input.attrs({ type: "text" })` + width: 100%; + padding: 12px 20px; + margin: 8px 0; + box-sizing: border-box; + border-radius: ${defaultBorderRadius}; + border: 2px solid gray; +`; + +const spin = keyframes` + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + } +`; + +export const Loader = styled.div` + border: 4px solid transparent; + border-radius: 50%; + border-top: 4px solid white; + width: 36px; + height: 36px; + -webkit-animation: ${spin} 1s ease-in-out infinite; + animation: ${spin} 1s ease-in-out infinite; + margin: auto; +`; + +export const GradientBorder = styled.div<{ borderWidth?: string }>` + border-radius: ${defaultBorderRadius}; + padding: ${(props) => props.borderWidth || "1px"}; + background: linear-gradient( + 101.79deg, + #12887a 0%, + #87245c 37.64%, + #e12637 65.98%, + #ffb215 110.45% + ); +`; + +export const MainContainerWithBorder = styled.div<{ borderWidth?: string }>` + border-radius: ${defaultBorderRadius}; + padding: ${(props) => props.borderWidth || "1px"}; + background-color: white; +`; + +export const MainTextInput = styled.textarea` + padding: 8px; + font-size: 16px; + border-radius: ${defaultBorderRadius}; + border: 1px solid #ccc; + margin: 8px 8px; + background-color: ${vscBackground}; + color: white; + outline: 1px solid orange; + resize: none; +`; + +export const appear = keyframes` + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0px); + } +`; -- cgit v1.2.3-70-g09d2 From 25770db64e613140f9c060f28f7e79e3ca593519 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 22:18:18 -0400 Subject: UI Cleanup --- extension/react-app/src/components/CodeBlock.tsx | 3 ++- extension/react-app/src/components/DebugPanel.tsx | 4 +++- extension/react-app/src/index.css | 2 +- extension/react-app/src/tabs/chat/MessageDiv.tsx | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) (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 4c10ab23..e0336554 100644 --- a/extension/react-app/src/components/CodeBlock.tsx +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -6,7 +6,8 @@ import { defaultBorderRadius, vscBackground } from "."; import { Clipboard } from "@styled-icons/heroicons-outline"; const StyledPre = styled.pre` - overflow: scroll; + overflow-y: scroll; + word-wrap: normal; border: 1px solid gray; border-radius: ${defaultBorderRadius}; background-color: ${vscBackground}; diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx index ed00571b..9dacc624 100644 --- a/extension/react-app/src/components/DebugPanel.tsx +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -36,7 +36,8 @@ const GradientContainer = styled.div` const MainDiv = styled.div` height: 100%; border-radius: ${defaultBorderRadius}; - overflow: scroll; + overflow-y: scroll; + scrollbar-gutter: stable both-edges; scrollbar-base-color: transparent; /* background: ${vscBackground}; */ background-color: #1e1e1ede; @@ -107,6 +108,7 @@ function DebugPanel(props: DebugPanelProps) { className={ tab.title === "Chat" ? "overflow-hidden" : "overflow-scroll" } + style={{ scrollbarGutter: "stable both-edges" }} > {tab.element} diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css index dd38eec3..20599d30 100644 --- a/extension/react-app/src/index.css +++ b/extension/react-app/src/index.css @@ -21,7 +21,7 @@ html, body, #root { - height: calc(100% - 7px); + height: calc(100%); } body { diff --git a/extension/react-app/src/tabs/chat/MessageDiv.tsx b/extension/react-app/src/tabs/chat/MessageDiv.tsx index ab632220..ad81f5e9 100644 --- a/extension/react-app/src/tabs/chat/MessageDiv.tsx +++ b/extension/react-app/src/tabs/chat/MessageDiv.tsx @@ -20,7 +20,8 @@ const Container = styled.div` margin: 3px; width: fit-content; max-width: 75%; - overflow: scroll; + overflow-y: scroll; + scrollbar-gutter: stable both-edges; word-wrap: break-word; -ms-word-wrap: break-word; height: fit-content; -- cgit v1.2.3-70-g09d2 From 22245d2cbf90daa9033d8551207aa986069d8b24 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 29 May 2023 18:31:25 -0400 Subject: (much!) faster inference with starcoder --- continuedev/src/continuedev/core/agent.py | 3 +- continuedev/src/continuedev/core/env.py | 19 +++++++- continuedev/src/continuedev/core/policy.py | 4 +- continuedev/src/continuedev/core/sdk.py | 25 ++++++++++ .../src/continuedev/libs/llm/hf_inference_api.py | 25 ++++++++++ continuedev/src/continuedev/libs/steps/main.py | 57 ++++++++++++++++++++-- continuedev/src/continuedev/server/notebook.py | 4 +- .../react-app/src/components/StepContainer.tsx | 21 ++------ 8 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 continuedev/src/continuedev/libs/llm/hf_inference_api.py (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 6d1f542e..329e3d4c 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -49,9 +49,10 @@ class Agent(ContinueBaseModel): async def wait_for_user_input(self) -> str: self._active = False self.update_subscribers() - await self._user_input_queue.get(self.history.current_index) + user_input = await self._user_input_queue.get(self.history.current_index) self._active = True self.update_subscribers() + return user_input _manual_edits_buffer: List[FileEditWithFullContents] = [] diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index d7275b41..6267ed60 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -1,7 +1,22 @@ from dotenv import load_dotenv import os -load_dotenv() +def get_env_var(var_name: str): + load_dotenv() + return os.getenv(var_name) -openai_api_key = os.getenv("OPENAI_API_KEY") + +def save_env_var(var_name: str, var_value: str): + with open('.env', 'r') as f: + lines = f.readlines() + with open('.env', 'w') as f: + values = {} + for line in lines: + key, value = line.split('=') + value = value.replace('"', '') + values[key] = value + + values[var_name] = var_value + for key, value in values.items(): + f.write(f'{key}="{value}"\n') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 504d5ff1..07101576 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -6,7 +6,7 @@ from ..models.main import ContinueBaseModel from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep @@ -32,7 +32,7 @@ class DemoPolicy(Policy): return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) - return FasterEditHighlightedCodeStep(user_input=observation.user_input) + return StarCoderEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() if state is None or not self.ran_code_last: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 3559e9d7..750b335d 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -3,10 +3,12 @@ from typing import Coroutine, Union from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM +from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..libs.steps.core.core import * +from .env import get_env_var, save_env_var class Agent: @@ -18,11 +20,22 @@ class ContinueSDKSteps: self.sdk = sdk +class Models: + def __init__(self, sdk: "ContinueSDK"): + self.sdk = sdk + + async def starcoder(self): + api_key = await self.sdk.get_user_secret( + 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') + return HuggingFaceInferenceAPI(api_key=api_key) + + class ContinueSDK: """The SDK provided as parameters to a step""" llm: LLM ide: AbstractIdeProtocolServer steps: ContinueSDKSteps + models: Models __agent: Agent def __init__(self, agent: Agent, llm: Union[LLM, None] = None): @@ -33,6 +46,7 @@ class ContinueSDK: self.ide = agent.ide self.__agent = agent self.steps = ContinueSDKSteps(self) + self.models = Models(self) @property def history(self) -> History: @@ -80,3 +94,14 @@ class ContinueSDK: async def delete_directory(self, path: str): return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) + + async def get_user_secret(self, env_var: str, prompt: str) -> str: + try: + val = get_env_var(env_var) + if val is not None: + return val + except: + pass + val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text + save_env_var(env_var, val) + return val diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py new file mode 100644 index 00000000..83852d27 --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py @@ -0,0 +1,25 @@ +from ..llm import LLM +import requests + +DEFAULT_MAX_TOKENS = 2048 +DEFAULT_MAX_TIME = 120. + + +class HuggingFaceInferenceAPI(LLM): + api_key: str + model: str = "bigcode/starcoder" + + def complete(self, prompt: str, **kwargs): + """Return the completion of the text with the given temperature.""" + API_URL = f"https://api-inference.huggingface.co/models/{self.model}" + headers = { + "Authorization": f"Bearer {self.api_key}"} + + response = requests.post(API_URL, headers=headers, json={ + "inputs": prompt, "parameters": { + "max_new_tokens": DEFAULT_MAX_TOKENS, + "max_time": DEFAULT_MAX_TIME, + "return_full_text": False, + } + }) + return response.json()[0]["generated_text"] diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 4f4f80e3..c8a85800 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -1,4 +1,6 @@ -from typing import Callable, Coroutine, List, Union +from typing import Coroutine, List, Union + +from pydantic import BaseModel from ..util.traceback_parsers import parse_python_traceback from ..llm import LLM @@ -8,9 +10,10 @@ from ...models.filesystem import RangeInFile, RangeInFileWithContents from ...core.observation import Observation, TextObservation, TracebackObservation from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent -from ...core.main import History, Policy, Step, ContinueSDK, Observation +from ...core.main import Step +from ...core.sdk import ContinueSDK +from ...core.observation import Observation import subprocess -import json from .core.core import EditCodeStep @@ -36,6 +39,10 @@ class RunCodeStep(Step): return None +class Policy(BaseModel): + pass + + class RunPolicyUntilDoneStep(Step): policy: "Policy" @@ -206,6 +213,50 @@ class FasterEditHighlightedCodeStep(Step): return None +class StarCoderEditHighlightedCodeStep(Step): + user_input: str + hide = True + _prompt: str = "{code}{user_request}" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Editing highlighted code" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + range_in_files = await sdk.ide.getHighlightedCode() + if len(range_in_files) == 0: + # Get the full contents of all open files + files = await sdk.ide.getOpenFiles() + contents = {} + for file in files: + contents[file] = await sdk.ide.readFile(file) + + range_in_files = [RangeInFile.from_entire_file( + filepath, content) for filepath, content in contents.items()] + + rif_with_contents = [] + for range_in_file in range_in_files: + file_contents = await sdk.ide.readRangeInFile(range_in_file) + rif_with_contents.append( + RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) + + rif_dict = {} + for rif in rif_with_contents: + rif_dict[rif.filepath] = rif.contents + + for rif in rif_with_contents: + prompt = self._prompt.format( + code=rif.contents, user_request=self.user_input) + completion = str((await sdk.models.starcoder()).complete(prompt)) + eot_token = "<|endoftext|>" + if completion.endswith(eot_token): + completion = completion[:completion.rindex(eot_token)] + + await sdk.ide.applyFileSystemEdit( + FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion)) + await sdk.ide.saveFile(rif.filepath) + await sdk.ide.setFileOpen(rif.filepath) + + class EditHighlightedCodeStep(Step): user_input: str hide = True diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index bfd7a09c..c5dcea31 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -12,7 +12,7 @@ from ..libs.steps.nate import ImplementAbstractMethodStep from ..core.observation import Observation from ..libs.llm.openai import OpenAI from .ide_protocol import AbstractIdeProtocolServer -from ..core.env import openai_api_key +from ..core.env import get_env_var import asyncio import nest_asyncio nest_asyncio.apply() @@ -75,7 +75,7 @@ class SessionManager: def new_session(self, ide: AbstractIdeProtocolServer) -> str: cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" - agent = DemoAgent(llm=OpenAI(api_key=openai_api_key), + agent = DemoAgent(llm=OpenAI(api_key=get_env_var("OPENAI_API_KEY")), policy=DemoPolicy(cmd=cmd), ide=ide) session_id = str(uuid4()) session = Session(session_id=session_id, agent=agent) diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 03649b66..36b3d99a 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -144,6 +144,9 @@ function StepContainer(props: StepContainerProps) { onSubmit={(ev) => { props.onUserInput(ev.currentTarget.value); }} + onClick={(e) => { + e.stopPropagation(); + }} /> )} {props.historyNode.step.name === "Waiting for user confirmation" && ( @@ -165,24 +168,6 @@ function StepContainer(props: StepContainerProps) { /> )} - - {open && ( - <> - {/* {props.historyNode.observation && ( - - Error Here - - )} */} - {/* {props.iterationContext.suggestedChanges.map((sc) => { - return ( - - {sc.filepath} - {sc.replacement} - - ); - })} */} - - )} -- cgit v1.2.3-70-g09d2 From 5ce90853586fcfaa7d795e2593aaaecce4bbed49 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 19:03:10 -0400 Subject: polishing dlt recipe --- continuedev/src/continuedev/core/agent.py | 13 ++++++++---- .../src/continuedev/libs/steps/core/core.py | 3 ++- .../src/continuedev/libs/steps/draft/dlt.py | 24 ++++++++++++++++++++-- continuedev/src/continuedev/libs/steps/main.py | 1 + .../react-app/src/components/StepContainer.tsx | 2 ++ extension/react-app/src/components/index.ts | 2 +- extension/src/activation/environmentSetup.ts | 6 +++--- 7 files changed, 40 insertions(+), 11 deletions(-) (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 0dba6122..cf5c9781 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -10,6 +10,7 @@ from ..models.main import ContinueBaseModel from .main import Policy, History, FullState, Step, HistoryNode from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep from .sdk import ContinueSDK +import asyncio class Agent(ContinueBaseModel): @@ -88,6 +89,9 @@ class Agent(ContinueBaseModel): self.history.add_node(HistoryNode( step=step, observation=None, depth=self._step_depth)) + # Call all subscribed callbacks + await self.update_subscribers() + # Run step self._step_depth += 1 observation = await step(ContinueSDK(self)) @@ -98,10 +102,11 @@ class Agent(ContinueBaseModel): self._step_depth, include_current=True).observation = observation # Update its description - step._set_description(await step.describe(ContinueSDK(self).models)) - - # Call all subscribed callbacks - await self.update_subscribers() + async def update_description(): + step._set_description(await step.describe(ContinueSDK(self).models)) + # Update subscribers with new description + await self.update_subscribers() + asyncio.create_task(update_description()) return observation diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 1761cbfd..9a5d54f0 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -44,7 +44,8 @@ class ShellCommandsStep(Step): name: str = "Run Shell Commands" async def describe(self, models: Models) -> Coroutine[str, None, None]: - return "\n".join(self.cmds) + cmds_str = "\n".join(self.cmds) + return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:") async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 94ca2323..73762327 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -1,16 +1,27 @@ from textwrap import dedent +from ....core.sdk import Models + from ....core.observation import DictObservation from ....models.filesystem_edit import AddFile from ....core.main import Step from ....core.sdk import ContinueSDK from ..core.core import WaitForUserInputStep +from ..main import MessageStep class SetupPipelineStep(Step): + hide: bool = True + name: str = "Setup dlt Pipeline" api_description: str # e.g. "I want to load data from the weatherapi.com API" + async def describe(self, models: Models): + return dedent(f"""\ + This step will create a new dlt pipeline that loads data from an API, as per your request: + {self.api_description} + """) + async def run(self, sdk: ContinueSDK): source_name = (await sdk.models.gpt35()).complete( f"Write a snake_case name for the data source described by {self.api_description}: ").strip() @@ -34,12 +45,11 @@ class SetupPipelineStep(Step): # wait for user to put API key in secrets.toml await sdk.ide.setFileOpen(await sdk.ide.getWorkspaceDirectory() + "/.dlt/secrets.toml") - await sdk.wait_for_user_confirmation("Please add the API key to the `secrets.toml` file and then press `Continue`") + await sdk.wait_for_user_confirmation("If this service requires an API key, please add it to the `secrets.toml` file and then press `Continue`") return DictObservation(values={"source_name": source_name}) class ValidatePipelineStep(Step): - async def run(self, sdk: ContinueSDK): source_name = sdk.history.last_observation().values["source_name"] filename = f'{source_name}.py' @@ -77,9 +87,19 @@ class ValidatePipelineStep(Step): class CreatePipelineStep(Step): + hide: bool = True async def run(self, sdk: ContinueSDK): await sdk.run_step( + MessageStep(message=dedent("""\ + This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will: + - Create a Python virtual environment with dlt installed + - Run `dlt init` to generate a pipeline template + - Write the code to call the API + - Add any required API keys to the `secrets.toml` file + - Test that the API call works + - Load the data into a local DuckDB instance + - Write a query to view the data""")) >> WaitForUserInputStep(prompt="What API do you want to load data from?") >> SetupPipelineStep(api_description="WeatherAPI.com API") >> ValidatePipelineStep() diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 6a7f14c7..c70d5c2c 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -334,6 +334,7 @@ class SolveTracebackStep(Step): class MessageStep(Step): + name: str = "Message" message: str async def describe(self, models: Models) -> Coroutine[str, None, None]: diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 36b3d99a..ec601ea7 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -36,6 +36,8 @@ const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` animation: ${appear} 0.3s ease-in-out; /* padding-left: ${(props) => props.stepDepth * 20}px; */ overflow: hidden; + margin-left: 0px; + margin-right: 0px; `; const StepContainerDiv = styled.div<{ open: boolean }>` diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index e37c97f3..7ba60467 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -45,7 +45,7 @@ export const Pre = styled.pre` border-radius: ${defaultBorderRadius}; padding: 8px; max-height: 150px; - overflow: scroll; + overflow-y: scroll; margin: 0; background-color: ${secondaryDark}; border: none; diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 419d32ed..170426e1 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -66,11 +66,11 @@ function checkEnvExists() { getExtensionUri().fsPath, "scripts", "env", - "bin" + process.platform == "win32" ? "Scripts" : "bin" ); return ( - fs.existsSync(envBinPath + "/activate") && - fs.existsSync(envBinPath + "/pip") + fs.existsSync(path.join(envBinPath, "activate")) && + fs.existsSync(path.join(envBinPath, "pip")) ); } -- cgit v1.2.3-70-g09d2 From ef0fcedffc53b5ba455b230b112306a46ee0235f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 20:59:10 -0400 Subject: enter secrets directly into .env file --- continuedev/src/continuedev/core/env.py | 10 ++++-- continuedev/src/continuedev/core/sdk.py | 37 +++++++++++++++------- .../src/continuedev/models/filesystem_edit.py | 4 +++ continuedev/src/continuedev/models/main.py | 8 +++++ .../react-app/src/components/StepContainer.tsx | 8 +++++ 5 files changed, 53 insertions(+), 14 deletions(-) (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index edd3297c..2692c348 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -7,11 +7,15 @@ def get_env_var(var_name: str): return os.getenv(var_name) -def save_env_var(var_name: str, var_value: str): +def make_sure_env_exists(): if not os.path.exists('.env'): with open('.env', 'w') as f: - f.write(f'{var_name}="{var_value}"\n') - return + f.write('') + + +def save_env_var(var_name: str, var_value: str): + make_sure_env_exists() + with open('.env', 'r') as f: lines = f.readlines() with open('.env', 'w') as f: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index b9b20422..ce0c53fd 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -2,7 +2,7 @@ import os from typing import Coroutine, Union from .config import ContinueConfig, load_config -from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory +from ..models.filesystem_edit import FileEdit, FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI @@ -11,7 +11,7 @@ from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..libs.steps.core.core import * -from .env import get_env_var, save_env_var +from .env import get_env_var, make_sure_env_exists class Agent: @@ -29,12 +29,12 @@ class Models: async def starcoder(self): api_key = await self.sdk.get_user_secret( - 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') + 'HUGGING_FACE_TOKEN', 'Please add your Hugging Face token to the .env file') return HuggingFaceInferenceAPI(api_key=api_key) async def gpt35(self): api_key = await self.sdk.get_user_secret( - 'OPENAI_API_KEY', 'Please enter your OpenAI API key') + 'OPENAI_API_KEY', 'Please add your OpenAI API key to the .env file') return OpenAI(api_key=api_key, default_model="gpt-3.5-turbo") @@ -86,6 +86,12 @@ class ContinueSDK: prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n' )) + async def append_to_file(self, filename: str, content: str): + filepath = await self._ensure_absolute_path(filename) + previous_content = await self.ide.readFile(filepath) + file_edit = FileEdit.from_append(filepath, previous_content, content) + await self.ide.applyFileSystemEdit(file_edit) + async def add_file(self, filename: str, content: str | None): return await self.run_step(FileSystemEditStep(edit=AddFile(filename=filename, content=content))) @@ -99,14 +105,23 @@ class ContinueSDK: return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) async def get_user_secret(self, env_var: str, prompt: str) -> str: - try: + make_sure_env_exists() + + val = None + while val is None: + try: + val = get_env_var(env_var) + if val is not None: + return val + except: + pass + server_dir = os.getcwd() + env_path = os.path.join(server_dir, ".env") + await self.ide.setFileOpen(env_path) + await self.append_to_file(env_path, f'\n{env_var}=""') + await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) val = get_env_var(env_var) - if val is not None: - return val - except: - pass - val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text - save_env_var(env_var, val) + return val async def get_config(self) -> ContinueConfig: diff --git a/continuedev/src/continuedev/models/filesystem_edit.py b/continuedev/src/continuedev/models/filesystem_edit.py index 7526d4c9..8e74b819 100644 --- a/continuedev/src/continuedev/models/filesystem_edit.py +++ b/continuedev/src/continuedev/models/filesystem_edit.py @@ -37,6 +37,10 @@ class FileEdit(AtomicFileSystemEdit): def from_insertion(filepath: str, position: Position, content: str) -> "FileEdit": return FileEdit(filepath=filepath, range=Range.from_shorthand(position.line, position.character, position.line, position.character), replacement=content) + @staticmethod + def from_append(filepath: str, previous_content: str, appended_content: str) -> "FileEdit": + return FileEdit(filepath=filepath, range=Range.from_position(Position.from_end_of_file(previous_content)), replacement=appended_content) + class FileEditWithFullContents(BaseModel): fileEdit: FileEdit diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 1bc51ff1..02c44aae 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -39,6 +39,10 @@ class Position(BaseModel): return Position(line=line, character=character) + @staticmethod + def from_end_of_file(contents: str) -> "Position": + return Position.from_index(contents, len(contents)) + class Range(BaseModel): """A range in a file. 0-indexed.""" @@ -117,6 +121,10 @@ class Range(BaseModel): return Range.from_shorthand(start_line, 0, end_line, len(content_lines[end_line]) - 1) + @staticmethod + def from_position(position: Position) -> "Range": + return Range(start=position, end=position) + class AbstractModel(ABC, BaseModel): @root_validator(pre=True) diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index ec601ea7..5e979b34 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -80,6 +80,13 @@ function StepContainer(props: StepContainerProps) { const [open, setOpen] = useState(false); const [isHovered, setIsHovered] = useState(false); const naturalLanguageInputRef = useRef(null); + const userInputRef = useRef(null); + + useEffect(() => { + if (userInputRef?.current) { + userInputRef.current.focus(); + } + }, [userInputRef]); useEffect(() => { if (isHovered) { @@ -136,6 +143,7 @@ function StepContainer(props: StepContainerProps) { {props.historyNode.step.name === "Waiting for user input" && ( { if (e.key === "Enter") { -- cgit v1.2.3-70-g09d2 From 5fb7e9931eef72eeb83405edfade978c556c2689 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 23:47:38 -0400 Subject: Edits and braindumps in docs --- docs/docs/concepts/autopilot.md | 20 ++++-- docs/docs/concepts/core.md | 14 ++-- docs/docs/concepts/gui.md | 15 ++-- docs/docs/concepts/history.md | 4 +- docs/docs/concepts/ide.md | 6 +- docs/docs/concepts/llm.md | 4 +- docs/docs/concepts/policy.md | 4 +- docs/docs/concepts/recipe.md | 4 +- docs/docs/concepts/sdk.md | 6 +- docs/docs/concepts/step.md | 6 +- docs/docs/getting-started.md | 27 +++---- docs/docs/how-continue-works.md | 18 +++-- docs/docs/install.md | 13 ++-- docs/docs/intro.md | 18 +++-- docs/docusaurus.config.js | 80 ++++++++++----------- docs/src/components/HomepageFeatures/index.js | 35 ++++----- docs/static/img/continue-architecture.png | Bin 136666 -> 1023648 bytes extension/README.md | 2 +- .../react-app/src/components/CodeMultiselect.tsx | 2 +- .../react-app/src/components/VSCodeFileLink.tsx | 4 +- extension/react-app/src/tabs/chat/MessageDiv.tsx | 2 +- extension/scripts/README.md | 2 +- extension/src/README.md | 14 ++-- 23 files changed, 153 insertions(+), 147 deletions(-) (limited to 'extension/react-app/src/components') diff --git a/docs/docs/concepts/autopilot.md b/docs/docs/concepts/autopilot.md index 5073f7cb..f3ed6fc9 100644 --- a/docs/docs/concepts/autopilot.md +++ b/docs/docs/concepts/autopilot.md @@ -8,13 +8,23 @@ The **autopilot** is the main loop, completing steps and then deciding the next ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +The Autopilot class is the center of Continue. Every step is initiated from the Autopilot, which provides it with a ContinueSDK. + +- Records history +- Allows reversal +- Injects SDK +- Has policy to decide what step to take next +- Accepts user input and acts on it +- Main event loop +- Contains main classes that are provided through the SDK, including LLM, History, IDE + +--- - An autopilot takes user input from the React app - You can see this happening in `server/notebook.py` - It basically queues user inputs, pops off the most recent, runs that as a "UserInputStep", uses its Policy to run other steps until the next step is None, and then pops off the next user input. When nothing left, just waits for more - `Autopilot` contains the - - History - - LLM - - Policy - - IDE \ No newline at end of file + - History + - LLM + - Policy + - IDE diff --git a/docs/docs/concepts/core.md b/docs/docs/concepts/core.md index 766dbca0..e9757b36 100644 --- a/docs/docs/concepts/core.md +++ b/docs/docs/concepts/core.md @@ -3,16 +3,16 @@ **TODO: Better explain in one sentence what this is and what its purpose is** :::info -The **Continue Core** connects the [SDK](./sdk.md) and [GUI](./gui.md) with the [IDE](./ide.md) (i.e. in VS Code, GitHub Codespaces, a web browser text editor, etc), enabling the steps to make changes to your code and accelerate your software development workflows +The **Continue Core** holds the main event loop, responsible for connecting [IDE](./ide.md) (i.e. in VS Code, GitHub Codespaces, a web browser text editor, etc), [SDK](./sdk.md), and [GUI](./gui.md), and deciding which steps to take next. ::: ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +I tried a rewrite of the info box above. The core doesn't really mean that much except for maybe the Autopilot class and the small set of classes in core.py, including History, Step, Policy mostly. Maybe best referred to as a set of abstractions. Autopilot runs the main event loop, basically queueing user input and asking the policy what to do next, and injecting the SDK, and recording history. I suppose you could also say it includes the protocols, in which case you might say "connects IDE and GUI through the SDK", though lots of 3-letter acronyms going on here. Notes below are correct. - The `Core` includes - - IDE protocol - - GUI protocol - - SDK - - Autopilot -- There is a two-way sync between an IDE and the GUI that happens through Core \ No newline at end of file + - IDE protocol + - GUI protocol + - SDK + - Autopilot +- There is a two-way sync between an IDE and the GUI that happens through Core diff --git a/docs/docs/concepts/gui.md b/docs/docs/concepts/gui.md index 7ba16a63..ff99839f 100644 --- a/docs/docs/concepts/gui.md +++ b/docs/docs/concepts/gui.md @@ -2,20 +2,17 @@ **TODO: Make sure codebase aligns with this terminology** -**TODO: Explain in one sentence what this is and what its purpose is** - :::info -The **Continue GUI** enables you to guide steps and makes everything transparent, so you can review all steps that were automated, giving you the opportunity to undo and rerun any that ran incorrectly +The **Continue GUI** lets you transparently review every automated step, providing the opportunity to undo and rerun any that ran incorrectly. ::: - ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +GUI displays every step taken by Continue in a way that lets you easily review, reverse, refine, re-run. Provides a natural language prompt where you can request edits in natural language or initiate recipes with slash commands. Communicates with the Continue server via GUI Protocol. Is a React app, which will eventually be published as a standalone npm package, importable as a simple React component. - **From GUI to Core** - - Natural language instructions from the developer - - Hover / clicked on a step - - Other user input + - Natural language instructions from the developer + - Hover / clicked on a step + - Other user input - **From Core to GUI** - - Updates to state (e.g. a new step) \ No newline at end of file + - Updates to state (e.g. a new step) diff --git a/docs/docs/concepts/history.md b/docs/docs/concepts/history.md index bf72d2b4..7f028e14 100644 --- a/docs/docs/concepts/history.md +++ b/docs/docs/concepts/history.md @@ -8,6 +8,6 @@ The **history** is the ordered record of all past steps ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +History stores a list ("timeline") of HistoryNodes. Each HistoryNode contains the Step with parameters, the Observation returned by the step, and the depth of the step (just so we can understand the tree structure, and what called what, and display as a tree if we wanted). History has a current_index, which can be smaller than the length of the timeline if some steps have been reversed (in which case GUI displays them with lower opacity). History class mostly responsible for maintaining order of history during reversals, grabbing most recent steps, or other things that should be bottlenecked through reliable access methods. -- What step data and metadata is stored in the history? \ No newline at end of file +- What step data and metadata is stored in the history? diff --git a/docs/docs/concepts/ide.md b/docs/docs/concepts/ide.md index f7ff2429..dc20518e 100644 --- a/docs/docs/concepts/ide.md +++ b/docs/docs/concepts/ide.md @@ -8,10 +8,10 @@ The **IDE** is the text editor where you manually edit your code. ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +SDK provides "IDEProtocol" class so that steps can interact with VS Code, etc... in an IDE-agnostic way. Communicates with editor through websockets protocol. All that's needed to make continue work with a new IDE/editor is to implement the protocol on the side of the editor. - `ide_protocol.py` is just the abstract version of what is implemented in `ide.py`, and `main.py` runs both `notebook.py` and `ide.py` as a single FastAPI server. This is the entry point to the Continue server, and acts as a bridge between IDE and React app -- extension directory contains 1. The VS Code extension, whose code is in `extension/src`, with `extension.ts` being the entry point, and 2. the Continue React app, in the `extension/react-app` folder. This is displayed in the sidebar of VSCode, but is designed to work with any IDE that implements the protocol as is done in `extension/src/continueIdeClient.ts`. +- extension directory contains 1. The VS Code extension, whose code is in `extension/src`, with `extension.ts` being the entry point, and 2. the Continue React app, in the `extension/react-app` folder. This is displayed in the sidebar of VS Code, but is designed to work with any IDE that implements the protocol as is done in `extension/src/continueIdeClient.ts`. ## Supported IDEs @@ -95,4 +95,4 @@ Apply a file edit ### saveFile -Save a file \ No newline at end of file +Save a file diff --git a/docs/docs/concepts/llm.md b/docs/docs/concepts/llm.md index fb3b2a33..6e5fccc1 100644 --- a/docs/docs/concepts/llm.md +++ b/docs/docs/concepts/llm.md @@ -8,7 +8,7 @@ An **LLM** is short for Large Language Model, which includes models like GPT-4, ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +Just a class with a "complete" method. Right now have HuggingFaceInferenceAPI and OpenAI subclasses. Need credentials as of now. Different models useful in different places, so we provide easy access to multiple of them, right now just gpt3.5 and starcoder, but can add others super easily. - `LLM` is the large language model that can be used in steps to automate that require some judgement based on context (e.g. generating code based on docs, explaining an error given a stack trace, etc) - Steps and recipes are implemented with specific models @@ -20,4 +20,4 @@ An **LLM** is short for Large Language Model, which includes models like GPT-4, ### `gpt-turbo-3.5` -### `StarCoder` \ No newline at end of file +### `StarCoder` diff --git a/docs/docs/concepts/policy.md b/docs/docs/concepts/policy.md index 79dbca56..ea515673 100644 --- a/docs/docs/concepts/policy.md +++ b/docs/docs/concepts/policy.md @@ -8,7 +8,7 @@ A **policy** is decides what step to run next and is associated with a [autopilo ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +A relic of my original plan that ended up being the place to define slash commands, the command run on startup, and other weird stuff that you might want to inject after certain other steps. This may be the place where "hooks" turn out to be implemented. Much of this may be configurable through `continue.json/yaml` config file (this is where steps that run on GUI opening are currently configured.). Simply takes the history and returns a single step to run next. Can return None if no step to take next. Then user input will kick it off again eventually. Autopilot has a single policy that it follows, so definitely a global/user-configurable type of thing. - The Policy is where slash commands are defined -- The Policy is a global thing, so probably something we'll want to make user-configurable if we don't significantly change it \ No newline at end of file +- The Policy is a global thing, so probably something we'll want to make user-configurable if we don't significantly change it diff --git a/docs/docs/concepts/recipe.md b/docs/docs/concepts/recipe.md index 05d5eb0b..da2b6264 100644 --- a/docs/docs/concepts/recipe.md +++ b/docs/docs/concepts/recipe.md @@ -8,8 +8,8 @@ A **recipe** is an ordered sequence of [steps](./step.md) that are intended to a ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** +When enough steps are strung together they become a recipe. Can kick off with slash command, can share/download somehow. - Although technically just a step itself, since they also subclass the Step class, recipes differentiate themselves from normal steps by ending their name with `Recipe` by - Technically, everything is a step since everything subclasses the step class. Steps can be composed together. Once steps are composed into a workflow that developers use and share with others, that step is called a recipe and, by convention, it ends with Recipe to signal this -- Actually just a step that is composed of only other steps / recipes. \ No newline at end of file +- Actually just a step that is composed of only other steps / recipes. diff --git a/docs/docs/concepts/sdk.md b/docs/docs/concepts/sdk.md index aab97f5f..30da2e79 100644 --- a/docs/docs/concepts/sdk.md +++ b/docs/docs/concepts/sdk.md @@ -8,8 +8,6 @@ The **Continue SDK** gives you all the tools you need to automate software devel ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** - - The ContinueSDK has a `run_step` method, which allows Steps to be composable - The reason you want to run it with `run_step` instead of creating a Step and calling `step.run(...)` is so Continue can automatically keep track of the order of all steps run, and allow for reversibility, etc... - The ContinueSDK also contains functions for very common steps, like `edit_file`, `add_file`, `run` (to run shell commands), and a few others @@ -21,7 +19,7 @@ The **Continue SDK** gives you all the tools you need to automate software devel ### `sdk.ide` -`sdk.ide` is an instance of the class `AbstractIdeProtocolServer`, which contains all the methods you might need to interact with the IDE. This includes things like reading, editing, saving, and opening files as well as getting the workspace directory, displaying suggestions, and more. The goal is to have an IDE agnostic way of interacting with IDEs, so that Steps are portable across VSCode, Sublime, Code, or any other editor you use. +`sdk.ide` is an instance of the class `AbstractIdeProtocolServer`, which contains all the methods you might need to interact with the IDE. This includes things like reading, editing, saving, and opening files as well as getting the workspace directory, displaying suggestions, and more. The goal is to have an IDE agnostic way of interacting with IDEs, so that Steps are portable across VS Code, Sublime, Code, or any other editor you use. ### `sdk.models` @@ -61,4 +59,4 @@ The below methods are all just shorthand for very common steps. ### `get_user_secret` -`get_user_secret` will retrieve a secret from the local .env file or, if it doesn't exist, prompt the user to enter the secret, then store this in the .env file. \ No newline at end of file +`get_user_secret` will retrieve a secret from the local .env file or, if it doesn't exist, prompt the user to enter the secret before moving on. diff --git a/docs/docs/concepts/step.md b/docs/docs/concepts/step.md index 61761344..b92e6faf 100644 --- a/docs/docs/concepts/step.md +++ b/docs/docs/concepts/step.md @@ -8,8 +8,6 @@ A **step** is a simple action that the LLM should take as part of a sequence tha ## Details -**TODO: Nate to brain dump anything important to know and Ty to shape into paragraphs** - - A `Step` is a Pydantic class inheriting from `Step` - Steps implement the `run` method, which takes a ContinueSDK as its only parameter - The ContinueSDK gives all the utilities you need to easily write recipes (Steps) @@ -60,7 +58,7 @@ Create and run an alembic migration #### Parameters -- `edited_file`: +- `edited_file`: ### WritePytestsStep @@ -68,4 +66,4 @@ Write unit tests for this file. #### Parameters -- for_filepath (required): the path of the file that unit tests should be created for \ No newline at end of file +- for_filepath (required): the path of the file that unit tests should be created for diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index bec9961c..39becd1a 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -2,15 +2,15 @@ ## GitHub Codespaces Demo -**TODO: Add `Open in GitHub Codespaces` badge here** +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/continuedev/continue-codespaces-demo?quickstart=1) 1. Click the `Open in GitHub Codespaces` badge above :::tip - We don't want to waste your time with install and env setup before you try Continue, so we set up a GitHub Codespaces dev container for you, which **wonโ€™t cost you anything**. If you are using GitHub Free for personal accounts, you can [use Codespaces for 120 hours per month for free](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces#monthly-included-storage-and-core-hours-for-personal-accounts) +We don't want to waste your time with install and env setup before you try Continue, so we set up a GitHub Codespace for you, which **wonโ€™t cost you anything**. If you are using GitHub Free for personal accounts, you can [use Codespaces for 120 hours per month for free](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces#monthly-included-storage-and-core-hours-for-personal-accounts) ::: -2. Select the `Create new codespace` button and wait a couple minutes for it to launch and then install the Continue extension. It should look like this one it is complete: +2. Select the `Create new codespace` button and wait a couple minutes while it launches and installs the Continue extension. Once complete, it should look like this: **TODO: Insert an image of Continue when it has opened** @@ -19,9 +19,9 @@ **TODO: Design and set up Pandas stuff scenario in codespaces** 4. There are a few recipes you should also try -a. In the first directory, try out X recipe -b. In the second directory, try out Y recipe -c. In the third directory, try out Z recipe + a. In the first directory, try out X recipe + b. In the second directory, try out Y recipe + c. In the third directory, try out Z recipe - database migrations - something super simple (libaries) @@ -31,10 +31,13 @@ c. In the third directory, try out Z recipe ## Next Steps -If you would prefer to use Continue locally, we reccommend installing `Continue` packaged as a VS Code extension as described [here](./install.md). +If you're ready to use Continue locally, install `Continue` packaged as a VS Code extension, as described [here](./install.md). -Otherwise, if you would like to continue to use Continue on GitHub Codespaces, then you should now go through the walkthroughs: -- How to [use the GUI](./walkthroughs/use-the-gui.md) -- How to [use a recipe](./walkthroughs/use-a-recipe.md) -- How to [create a recipe](./walkthroughs/create-a-recipe.md) -- How to [share a recipe](./walkthroughs/share-a-recipe.md) \ No newline at end of file +If you'd like to continue exploring in GitHub Codespaces, you can learn more with our walkthroughs: + +How to... + +- [Use the GUI](./walkthroughs/use-the-gui.md) +- [Invoke a recipe](./walkthroughs/use-a-recipe.md) +- [Create a recipe](./walkthroughs/create-a-recipe.md) +- [Share a recipe](./walkthroughs/share-a-recipe.md) diff --git a/docs/docs/how-continue-works.md b/docs/docs/how-continue-works.md index e032c7a4..32bf6347 100644 --- a/docs/docs/how-continue-works.md +++ b/docs/docs/how-continue-works.md @@ -2,17 +2,15 @@ ![Continue Architecture Diagram](/img/continue-architecture.png) -**TODO: Rename notebook protocol in this diagram** - ## Overview -The `Continue` library consists of a [SDK](./concepts/sdk.md), a [GUI](./concepts/gui.md), and a [Core](./concepts/core.md) that brings everything together. +The `Continue` library consists of an [SDK](./concepts/sdk.md), a [GUI](./concepts/gui.md), and a [Core](./concepts/core.md) that brings everything together. -The [SDK](./concepts/sdk.md) gives you access to tools (e.g. open a directory, edit a file, call a model, etc), which you can use when defining how a step should work and composing them with other steps. +The [SDK](./concepts/sdk.md) gives you access to the tools (e.g. open a directory, edit a file, call a model, etc.) needed to define steps that integrate LLMs into your IDE. -The [GUI](./concepts/gui.md) enables you to guide steps and makes everything transparent, so you can review all steps that were automated, giving you the opportunity to undo and rerun any that ran incorrectly. +The [GUI](./concepts/gui.md) lets you transparently review every automated step, providing the opportunity to undo and rerun any that ran incorrectly. -The [Core](./concepts/core.md) connects the SDK and GUI with the IDE (i.e. in VS Code, a web browser, etc), enabling the steps to make changes to your code and accelerate your software development workflows. +The [Core](./concepts/core.md) holds the main event loop, responsible for connecting IDE, SDK, and GUI and deciding which steps to take next. ## Details @@ -20,9 +18,9 @@ The [Core](./concepts/core.md) connects the SDK and GUI with the IDE (i.e. in VS - Continue connects any code editor (primarily VS Code right now) to a server (the Continue server) that can take actions in the editor in accordance with defined recipes at the request of a user through the GUI - What this looks like: - - The Continue VS Code extension runs the ContinueIdeProtocol, launches the Continue Python server in the background, and opens the Continue GUI in a side-panel. - - The Continue server is the brain, communication center, and source of truth, interacting with VS Code through the ContinueIdeProtocol and with the GUI through the NotebookProtocol. - - Communication between the extension and GUI happens through the Continue server. + - The Continue VS Code extension runs the ContinueIdeProtocol, launches the Continue Python server in the background, and opens the Continue GUI in a side-panel. + - The Continue server is the brain, communication center, and source of truth, interacting with VS Code through the ContinueIdeProtocol and with the GUI through the NotebookProtocol. + - Communication between the extension and GUI happens through the Continue server. - When you type a natural language command in the GUI, this is sent to the Continue server, where the `Autopilot` class takes action, potentially using the ContinueIdeProtocol to request actions be taken in the IDE, and then updates the GUI to display the new history. - `core` directory contains major concepts - This includes Autopilot, Policy, SDK (all in their own files so far) @@ -36,4 +34,4 @@ The [Core](./concepts/core.md) connects the SDK and GUI with the IDE (i.e. in VS - `models` contains all the Pydantic models and `generate_json_schema.py`, a script that converts them to JSONSchema .json files in `schema/json` - `server` runs the servers that communicate with a) the React app (`notebook.py`) and b) the IDE (`ide.py`) - `ide_protocol.py` is just the abstract version of what is implemented in `ide.py`, and `main.py` runs both `notebook.py` and `ide.py` as a single FastAPI server. This is the entry point to the Continue server, and acts as a bridge between IDE and React app -- We use OpenAPI/JSONSchema to define types so that it's really easy to bring them across language barriers. Use Pydantic types, then run `poetry run typegen` from the root of continuedev folder to generate JSONSchema json files in the `schema/json` folder. Then `npm run typegen` from the extension folder generates the types that are used within the extension. \ No newline at end of file +- We use OpenAPI/JSONSchema to define types so that it's really easy to bring them across language barriers. Use Pydantic types, then run `poetry run typegen` from the root of continuedev folder to generate JSONSchema json files in the `schema/json` folder. Then `npm run typegen` from the extension folder generates the types that are used within the extension. diff --git a/docs/docs/install.md b/docs/docs/install.md index b9916b73..6dce5da4 100644 --- a/docs/docs/install.md +++ b/docs/docs/install.md @@ -20,8 +20,11 @@ Please follow the [README instructions in the repo](https://github.com/continued ## Next steps -Now that you have installed the VS Code extension, you should go through the walkthroughs: -- How to [use the GUI](./walkthroughs/use-the-gui.md) -- How to [use a recipe](./walkthroughs/use-a-recipe.md) -- How to [create a recipe](./walkthroughs/create-a-recipe.md) -- How to [share a recipe](./walkthroughs/share-a-recipe.md) \ No newline at end of file +Now that you have installed the VS Code extension, you can learn more with our walkthroughs: + +How to... + +- [Use the GUI](./walkthroughs/use-the-gui.md) +- [Invoke a recipe](./walkthroughs/use-a-recipe.md) +- [Create a recipe](./walkthroughs/create-a-recipe.md) +- [Share a recipe](./walkthroughs/share-a-recipe.md) diff --git a/docs/docs/intro.md b/docs/docs/intro.md index e9f1dffe..5d73a256 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -2,21 +2,19 @@ ![continue-cover-logo](/img/continue-cover-logo.png) -**TODO: Nate's review of this page** +## Quickstart + +1. Try out `Continue` in the [GitHub Codespaces Demo](./getting-started.md) +2. Install `Continue` packaged as a [VS Code extension](./install.md) ## What is `Continue`? -**`Continue` is the open-source library for accelerating your use of LLMs while coding.** +**`Continue` is the open-source library for accelerating software development with language models** -You define the scenarios where Large Language Models ([LLMs](./concepts/llm.md)) like GPT-4 and StarCoder should act as an autopilot that helps you complete software development tasks. You use [recipes](./concepts/recipe.md) created by others to automate more steps in your development workflows. If a [recipe](./concepts/recipe.md) does not exist or work exactly like you want, you can use the [Continue SDK](./concepts/sdk.md) to create custom [steps](./concepts/step.md) and compose them into personalized [recipes](./concepts/recipe.md). Whether you are using a [recipe](./concepts/recipe.md) created by yourself or someone else, you can also review, reverse, and rerun [steps](./concepts/step.md) with the [Continue GUI](./concepts/gui.md), which helps you guide the work done by LLMs and learn when to use and trust them. +You define the scenarios where Large Language Models ([LLMs](./concepts/llm.md)) like GPT-4 and StarCoder should act as an autopilot, helping you complete software development tasks. You use [recipes](./concepts/recipe.md) created by others to automate more steps in your workflows. If a [recipe](./concepts/recipe.md) does not exist or work exactly like you want, you can use the [Continue SDK](./concepts/sdk.md) to create custom [steps](./concepts/step.md) and compose them into personalized [recipes](./concepts/recipe.md). Whether you are using a [recipe](./concepts/recipe.md) created by yourself or someone else, you can review, reverse, and rerun [steps](./concepts/step.md) with the [Continue GUI](./concepts/gui.md), which helps you guide the work done by LLMs and learn when to use and trust them. ## Why do developers use `Continue`? -Many developers have begun to use models like [GPT-4](https://openai.com/research/gpt-4) through [ChatGPT](https://openai.com/blog/chatgpt) while coding; however, this is quite a painful experience because of how much manual copy, paste, and editing is required to construct context for LLMs and then incorporate the generations from LLMs. Many other developers prefer to use open source models or work at organizations where they are unable to use ChatGPT, so they are using [StarCoder](https://huggingface.co/blog/starcoder) [Chat](https://huggingface.co/chat/) and are running into the same issues. - -`Continue` eliminates the manual copy, paste, and editing required when using LLMs while coding. This accelerates how developers build, ship, and maintain software, while giving them the control to define when LLMs should take actions and the confidence to trust LLMs. In short, it enables developers to do what they have always done: work together to create better and better abstractions that make it easier and easier to automate the repetitive work that people want computers to do. +Many developers have begun to use models like [GPT-4](https://openai.com/research/gpt-4) through [ChatGPT](https://openai.com/blog/chatgpt) while coding; however, the experience is painful because of how much manual copying, pasting, and editing is required to supply them with context and transfer the generated solutions to your codebase. `Continue` eliminates this pain by deeply integrating LLMs into your IDE. -## Getting started - -1. Try out `Continue` in the [GitHub Codespaces Demo](./getting-started.md) -2. Install `Continue` packaged as a [VS Code extension](./install.md) \ No newline at end of file +`Continue` accelerates how developers build, ship, and maintain software, while giving them the control to define when LLMs should take actions and the confidence to trust LLMs. In short, it enables developers to do what they have always done: work together to create better and better abstractions that make it easier and easier to automate the repetitive work that people want computers to do. diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 7ca00f8d..c2991841 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -1,49 +1,49 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion -const lightCodeTheme = require('prism-react-renderer/themes/github'); -const darkCodeTheme = require('prism-react-renderer/themes/dracula'); +const lightCodeTheme = require("prism-react-renderer/themes/github"); +const darkCodeTheme = require("prism-react-renderer/themes/dracula"); /** @type {import('@docusaurus/types').Config} */ const config = { - title: 'Continue', - tagline: 'the open-source library for accelerating your use of LLMs while coding', - favicon: 'img/favicon.ico', + title: "Continue", + tagline: + "the open-source library for accelerating software development with language models", + favicon: "img/favicon.ico", // Set the production url of your site here - url: 'https://continue.dev', + url: "https://continue.dev", // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' - baseUrl: '/', + baseUrl: "/", // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. - organizationName: 'continuedev', // Usually your GitHub org/user name. - projectName: 'continue', // Usually your repo name. + organizationName: "continuedev", // Usually your GitHub org/user name. + projectName: "continue", // Usually your repo name. - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "warn", // Even if you don't use internalization, you can use this field to set useful // metadata like html lang. For example, if your site is Chinese, you may want // to replace "en" with "zh-Hans". i18n: { - defaultLocale: 'en', - locales: ['en'], + defaultLocale: "en", + locales: ["en"], }, presets: [ [ - 'classic', + "classic", /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { - sidebarPath: require.resolve('./sidebars.js'), - editUrl: - 'https://github.com/continuedev/continue/', + sidebarPath: require.resolve("./sidebars.js"), + editUrl: "https://github.com/continuedev/continue/", }, theme: { - customCss: require.resolve('./src/css/custom.css'), + customCss: require.resolve("./src/css/custom.css"), }, }), ], @@ -53,54 +53,54 @@ const config = { /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ // Replace with your project's social card - image: 'img/docusaurus-social-card.jpg', + image: "img/docusaurus-social-card.jpg", navbar: { - title: 'Continue', + title: "Continue", logo: { - alt: 'My Site Logo', - src: 'img/logo.svg', + alt: "My Site Logo", + src: "img/logo.svg", }, items: [ { - type: 'docSidebar', - sidebarId: 'docsSidebar', - position: 'left', - label: 'Docs', + type: "docSidebar", + sidebarId: "docsSidebar", + position: "left", + label: "Docs", }, { - href: 'https://github.com/continuedev/continue', - label: 'GitHub', - position: 'right', + href: "https://github.com/continuedev/continue", + label: "GitHub", + position: "right", }, ], }, footer: { - style: 'dark', + style: "dark", links: [ { - title: 'Docs', + title: "Docs", items: [ { - label: 'Introduction', - to: '/docs/intro', + label: "Introduction", + to: "/docs/intro", }, ], }, { - title: 'Community', + title: "Community", items: [ { - label: 'Twitter', - href: 'https://twitter.com/continuedev', + label: "Twitter", + href: "https://twitter.com/continuedev", }, ], }, { - title: 'More', + title: "More", items: [ { - label: 'GitHub', - href: 'https://github.com/continuedev/continue', + label: "GitHub", + href: "https://github.com/continuedev/continue", }, ], }, @@ -114,4 +114,4 @@ const config = { }), }; -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/docs/src/components/HomepageFeatures/index.js b/docs/src/components/HomepageFeatures/index.js index 764ca891..0c5d8272 100644 --- a/docs/src/components/HomepageFeatures/index.js +++ b/docs/src/components/HomepageFeatures/index.js @@ -1,43 +1,44 @@ -import React from 'react'; -import clsx from 'clsx'; -import styles from './styles.module.css'; +import React from "react"; +import clsx from "clsx"; +import styles from "./styles.module.css"; const FeatureList = [ { - title: 'Define the scenarios where LLMs should step into accelerate you', - Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, + title: "Tell LLMs when to step in", + Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default, description: ( <> - Enable LLMs to be an autopilot for parts of your software development tasks - by leveraging recipes created by others in the workflows you use when coding + Seamlessly put your repetitive software development tasks on autopilot + by leveraging recipes created by others ), }, { - title: 'Create your own workflows to show LLMs exactly what to do', - Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, + title: "Write your own recipes", + Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default, description: ( <> - Use the Continue SDK to create your own custom steps and compose them into - personalized recipes, so that using LLMs seamlessly fits into your workflows + Use the Continue SDK to create your own custom steps and compose them + into personalized recipes, guiding LLMs through common sequences of + tasks ), }, { - title: 'Guide the steps taken by LLMs to learn when to use and trust them', - Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, + title: "Wield LLMs with confidence", + Svg: require("@site/static/img/undraw_docusaurus_react.svg").default, description: ( <> - Use the Continue GUI to review, reverse, and rerun steps or even entire recipes, - incorporating LLMs with confidence into your software development workflows + Use the Continue GUI to review, reverse, and rerun steps or even entire + recipes, allowing you to build trust in LLMs ), }, ]; -function Feature({Svg, title, description}) { +function Feature({ Svg, title, description }) { return ( -
+
diff --git a/docs/static/img/continue-architecture.png b/docs/static/img/continue-architecture.png index 7a38c904..58366d8f 100644 Binary files a/docs/static/img/continue-architecture.png and b/docs/static/img/continue-architecture.png differ diff --git a/extension/README.md b/extension/README.md index 0a003145..df158440 100644 --- a/extension/README.md +++ b/extension/README.md @@ -29,7 +29,7 @@ Once Continue has code snippets to work with, it can generate a fix. Just click ### Stacktrace Parsing -Any stacktrace that appears in your VSCode terminal will be caught by us so we can immediately begin the debugging process. For small bugs that you might have quickly solved, we'll just speed up the process to be nearly instantaneous. +Any stacktrace that appears in your VS Code terminal will be caught by us so we can immediately begin the debugging process. For small bugs that you might have quickly solved, we'll just speed up the process to be nearly instantaneous. ### Generate Unit Tests and Docstrings diff --git a/extension/react-app/src/components/CodeMultiselect.tsx b/extension/react-app/src/components/CodeMultiselect.tsx index 626ae42f..c0ab9400 100644 --- a/extension/react-app/src/components/CodeMultiselect.tsx +++ b/extension/react-app/src/components/CodeMultiselect.tsx @@ -135,7 +135,7 @@ function formatFileRange( )} (lines ${rangeInFile.range.start.line + 1}-${ rangeInFile.range.end.line + 1 })`; - // +1 because VSCode Ranges are 0-indexed + // +1 because VS Code Ranges are 0-indexed } //#endregion diff --git a/extension/react-app/src/components/VSCodeFileLink.tsx b/extension/react-app/src/components/VSCodeFileLink.tsx index 6219654d..12ce5af8 100644 --- a/extension/react-app/src/components/VSCodeFileLink.tsx +++ b/extension/react-app/src/components/VSCodeFileLink.tsx @@ -1,7 +1,7 @@ import React from "react"; import { postVscMessage } from "../vscode"; -function VSCodeFileLink(props: { path: string; text?: string }) { +function VS CodeFileLink(props: { path: string; text?: string }) { return ( requirements.txt` whenever you add a new requirement. diff --git a/extension/src/README.md b/extension/src/README.md index bb10f5c8..76b96ea0 100644 --- a/extension/src/README.md +++ b/extension/src/README.md @@ -22,23 +22,23 @@ 10. Then run `npm run compile` -7. Open `src/activate.ts` file (or any TypeScript file) +11. Open `src/activate.ts` file (or any TypeScript file) -7. Press `F5` on your keyboard to start `Run and Debug` mode +12. Press `F5` on your keyboard to start `Run and Debug` mode -8. `cmd+shift+p` to look at developer console and select Continue commands +13. `cmd+shift+p` to look at developer console and select Continue commands -9. Every time you make changes to the code, you need to run `npm run compile` +14. Every time you make changes to the code, you need to run `npm run compile` -10. If you run into a "command not found" error, try running `npm run rebuild` and then `npm run compile` +15. If you run into a "command not found" error, try running `npm run rebuild` and then `npm run compile` ## Alternative: Install a packaged version -You should always have a packaged version installed in VSCode, because when Continue is broken you'll want a stable version to help you debug. There are four key commands in the `package.json`: +You should always have a packaged version installed in VS Code, because when Continue is broken you'll want a stable version to help you debug. There are four key commands in the `package.json`: 1. `npm run package` will create a .vsix file in the `build/` folder that can then be installed. It is this same file that you can share with others who want to try the extension. -2. `npm run install-extension` will install the extension to VSCode. You should then see it in your installed extensions in the VSCode sidebar. +2. `npm run install-extension` will install the extension to VS Code. You should then see it in your installed extensions in the VS Code sidebar. 3. `npm run uninstall` will uninstall the extension. You don't always have to do this thanks to the reinstall command, but can be useful when you want to do so manually. -- cgit v1.2.3-70-g09d2 From 529279abf16cc8d7f2e4340819b9a8e945202185 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 00:21:52 -0400 Subject: forgotten changes --- extension/package-lock.json | 4 ++-- extension/package.json | 4 ++-- extension/react-app/src/components/VSCodeFileLink.tsx | 4 ++-- extension/react-app/src/tabs/chat/MessageDiv.tsx | 2 +- extension/scripts/continuedev-0.1.1-py3-none-any.whl | Bin 0 -> 56962 bytes 5 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 extension/scripts/continuedev-0.1.1-py3-none-any.whl (limited to 'extension/react-app/src/components') diff --git a/extension/package-lock.json b/extension/package-lock.json index 15ea6428..2db9cac5 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.14", + "version": "0.0.16", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.14", + "version": "0.0.16", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 2d83a58c..13086954 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.14", + "version": "0.0.16", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -161,7 +161,7 @@ "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && cp ./config/dev_config.json ./config/config.json", - "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", + "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.1-py3-none-any.whl ../extension/scripts/continuedev-0.1.1-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.8.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" diff --git a/extension/react-app/src/components/VSCodeFileLink.tsx b/extension/react-app/src/components/VSCodeFileLink.tsx index 12ce5af8..6219654d 100644 --- a/extension/react-app/src/components/VSCodeFileLink.tsx +++ b/extension/react-app/src/components/VSCodeFileLink.tsx @@ -1,7 +1,7 @@ import React from "react"; import { postVscMessage } from "../vscode"; -function VS CodeFileLink(props: { path: string; text?: string }) { +function VSCodeFileLink(props: { path: string; text?: string }) { return ( Date: Sat, 3 Jun 2023 11:33:05 -0400 Subject: API tokens set through global Vsc settings --- continuedev/src/continuedev/core/sdk.py | 20 +------------ continuedev/src/continuedev/server/ide.py | 35 ++++++++++------------ continuedev/src/continuedev/server/ide_protocol.py | 4 +++ continuedev/src/continuedev/steps/core/core.py | 1 + continuedev/src/continuedev/steps/main.py | 2 +- extension/package.json | 10 +++++++ .../react-app/src/components/StepContainer.tsx | 10 +++---- extension/src/continueIdeClient.ts | 27 +++++++++++++++++ 8 files changed, 64 insertions(+), 45 deletions(-) (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 5d0f03fe..5ae471c4 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -11,7 +11,6 @@ from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..steps.core.core import * -from .env import get_env_var, make_sure_env_exists class Autopilot: @@ -105,24 +104,7 @@ class ContinueSDK: return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) async def get_user_secret(self, env_var: str, prompt: str) -> str: - make_sure_env_exists() - - val = None - while val is None: - try: - val = get_env_var(env_var) - if val is not None: - return val - except: - pass - server_dir = os.getcwd() - env_path = os.path.join(server_dir, ".env") - await self.ide.setFileOpen(env_path) - await self.append_to_file(env_path, f'\n{env_var}=""') - await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) - val = get_env_var(env_var) - - return val + return await self.ide.getUserSecret(env_var) async def get_config(self) -> ContinueConfig: dir = await self.ide.getWorkspaceDirectory() diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 71017ce0..eec5b607 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -40,46 +40,42 @@ Server.handle_exit = AppStatus.handle_exit class FileEditsUpdate(BaseModel): - messageType: str = "fileEdits" fileEdits: List[FileEditWithFullContents] class OpenFilesResponse(BaseModel): - messageType: str = "openFiles" openFiles: List[str] class HighlightedCodeResponse(BaseModel): - messageType: str = "highlightedCode" highlightedCode: List[RangeInFile] class ShowSuggestionRequest(BaseModel): - messageType: str = "showSuggestion" suggestion: FileEdit class ShowSuggestionResponse(BaseModel): - messageType: str = "showSuggestion" suggestion: FileEdit accepted: bool class ReadFileResponse(BaseModel): - messageType: str = "readFile" contents: str class EditFileResponse(BaseModel): - messageType: str = "editFile" fileEdit: FileEditWithFullContents class WorkspaceDirectoryResponse(BaseModel): - messageType: str = "workspaceDirectory" workspaceDirectory: str +class GetUserSecretResponse(BaseModel): + value: str + + T = TypeVar("T", bound=BaseModel) @@ -114,7 +110,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): fileEdits = list( map(lambda d: FileEditWithFullContents.parse_obj(d), data["fileEdits"])) self.onFileEdits(fileEdits) - elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: + elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret"]: self.sub_queue.post(message_type, data) else: raise ValueError("Unknown message type", message_type) @@ -183,31 +179,31 @@ class IdeProtocolServer(AbstractIdeProtocolServer): # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: - resp = await self._send_and_receive_json({ - "messageType": "openFiles" - }, OpenFilesResponse, "openFiles") + resp = await self._send_and_receive_json({}, OpenFilesResponse, "openFiles") return resp.openFiles async def getWorkspaceDirectory(self) -> str: - resp = await self._send_and_receive_json({ - "messageType": "workspaceDirectory" - }, WorkspaceDirectoryResponse, "workspaceDirectory") + resp = await self._send_and_receive_json({}, WorkspaceDirectoryResponse, "workspaceDirectory") return resp.workspaceDirectory async def getHighlightedCode(self) -> List[RangeInFile]: - resp = await self._send_and_receive_json({ - "messageType": "highlightedCode" - }, HighlightedCodeResponse, "highlightedCode") + resp = await self._send_and_receive_json({}, HighlightedCodeResponse, "highlightedCode") return resp.highlightedCode async def readFile(self, filepath: str) -> str: """Read a file""" resp = await self._send_and_receive_json({ - "messageType": "readFile", "filepath": filepath }, ReadFileResponse, "readFile") return resp.contents + async def getUserSecret(self, key: str) -> str: + """Get a user secret""" + resp = await self._send_and_receive_json({ + "key": key + }, GetUserSecretResponse, "getUserSecret") + return resp.value + async def saveFile(self, filepath: str): """Save a file""" await self._send_json("saveFile", { @@ -222,7 +218,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def editFile(self, edit: FileEdit) -> FileEditWithFullContents: """Edit a file""" resp = await self._send_and_receive_json({ - "messageType": "editFile", "edit": edit.dict() }, EditFileResponse, "editFile") return resp.fileEdit diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 4f505e80..8f155415 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -78,3 +78,7 @@ class AbstractIdeProtocolServer(ABC): @abstractmethod async def saveFile(self, filepath: str): """Save a file""" + + @abstractmethod + async def getUserSecret(self, key: str): + """Get a user secret""" diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index e54a9a21..0f513f3e 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -205,4 +205,5 @@ class WaitForUserConfirmationStep(Step): async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: self._description = self.prompt resp = await sdk.wait_for_user_input() + self.hide = True return TextObservation(text=resp) diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index bb720b20..dfb4f3be 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -190,7 +190,7 @@ class FasterEditHighlightedCodeStep(Step): class StarCoderEditHighlightedCodeStep(Step): user_input: str - name: str = "Editing code" + name: str = "Editing Code" hide = False _prompt: str = "{code}{user_request}" diff --git a/extension/package.json b/extension/package.json index 13086954..cc8e18c4 100644 --- a/extension/package.json +++ b/extension/package.json @@ -39,6 +39,16 @@ "type": "string", "default": "http://localhost:8000", "description": "The URL of the Continue server to use." + }, + "continue.OPENAI_API_KEY": { + "type": "string", + "default": "", + "description": "The OpenAI API key to use for code generation." + }, + "continue.HUGGING_FACE_TOKEN": { + "type": "string", + "default": "", + "description": "The Hugging Face API token to use for code generation." } } }, diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 5e979b34..fd29f21b 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -114,12 +114,12 @@ function StepContainer(props: StepContainerProps) { hidden={props.historyNode.step.hide as any} > setOpen((prev) => !prev)} + className="m-2 overflow-hidden" + // onClick={() => setOpen((prev) => !prev)} > -

+

{open ? ( ) : ( @@ -127,14 +127,14 @@ function StepContainer(props: StepContainerProps) { )} {props.historyNode.step.name as any}:

- { e.stopPropagation(); props.onReverse(); }} > - + */}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 03e5fbc5..a5a1c5dc 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -74,6 +74,12 @@ class IdeProtocolClient { this.messenger?.send("workspaceDirectory", { workspaceDirectory: this.getWorkspaceDirectory(), }); + break; + case "getUserSecret": + this.messenger?.send("getUserSecret", { + value: await this.getUserSecret(data.key), + }); + break; case "openFiles": this.messenger?.send("openFiles", { openFiles: this.getOpenFiles(), @@ -130,6 +136,27 @@ class IdeProtocolClient { openEditorAndRevealRange(filepath, undefined, vscode.ViewColumn.One); } + async getUserSecret(key: string) { + // Check if secret already exists in VS Code settings (global) + let secret = vscode.workspace.getConfiguration("continue").get(key); + if (secret && secret !== "") return secret; + + // If not, ask user for secret + while (typeof secret === "undefined" || secret === "") { + secret = await vscode.window.showInputBox({ + prompt: `Enter secret for ${key}`, + password: true, + }); + } + + // Add secret to VS Code settings + vscode.workspace + .getConfiguration("continue") + .update(key, secret, vscode.ConfigurationTarget.Global); + + return secret; + } + // ------------------------------------ // // Initiate Request -- cgit v1.2.3-70-g09d2 From 4f3ceee573268fbe9db80fea372198523b5757a6 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 11:56:09 -0400 Subject: show step details on toggle --- continuedev/src/continuedev/steps/core/core.py | 6 ++++++ extension/package-lock.json | 4 ++-- extension/package.json | 2 +- .../react-app/src/components/StepContainer.tsx | 16 ++++++++++++---- .../scripts/continuedev-0.1.1-py3-none-any.whl | Bin 56962 -> 56858 bytes 5 files changed, 21 insertions(+), 7 deletions(-) (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 0f513f3e..d7f7a307 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -43,7 +43,12 @@ class ShellCommandsStep(Step): cwd: str | None = None name: str = "Run Shell Commands" + _err_text: str | None = None + async def describe(self, models: Models) -> Coroutine[str, None, None]: + if self._err_text is not None: + return f"Error when running shell commands:\n```\n{self._err_text}\n```" + cmds_str = "\n".join(self.cmds) return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:") @@ -58,6 +63,7 @@ class ShellCommandsStep(Step): # If it fails, return the error if err is not None and err != "": + self._err_text = err return TextObservation(text=err) return None diff --git a/extension/package-lock.json b/extension/package-lock.json index 2db9cac5..32f47246 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.16", + "version": "0.0.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.16", + "version": "0.0.17", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index cc8e18c4..be10be80 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.16", + "version": "0.0.17", "publisher": "Continue", "engines": { "vscode": "^1.74.0" diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index fd29f21b..f962cbc9 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -114,8 +114,8 @@ function StepContainer(props: StepContainerProps) { hidden={props.historyNode.step.hide as any} > setOpen((prev) => !prev)} + className="m-2 overflow-hidden cursor-pointer" + onClick={() => setOpen((prev) => !prev)} > @@ -137,6 +137,14 @@ function StepContainer(props: StepContainerProps) { */} + {open && ( +
+              Step Details:
+              
+ {JSON.stringify(props.historyNode.step, null, 2)} +
+ )} + {props.historyNode.step.description as any} @@ -181,7 +189,7 @@ function StepContainer(props: StepContainerProps) {
- */} ); } diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl index 663eb14a..d143bcf9 100644 Binary files a/extension/scripts/continuedev-0.1.1-py3-none-any.whl and b/extension/scripts/continuedev-0.1.1-py3-none-any.whl differ -- cgit v1.2.3-70-g09d2 From b8067876bc5dd425d491863bd5338f325fea35ed Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 14:20:21 -0400 Subject: error handling and step retry --- continuedev/src/continuedev/core/agent.py | 180 --------------------- continuedev/src/continuedev/core/autopilot.py | 52 +++--- continuedev/src/continuedev/core/main.py | 9 +- continuedev/src/continuedev/core/observation.py | 4 + .../src/continuedev/libs/llm/hf_inference_api.py | 9 +- continuedev/src/continuedev/server/gui.py | 6 + continuedev/src/continuedev/server/gui_protocol.py | 4 + .../react-app/src/components/StepContainer.tsx | 43 ++++- .../react-app/src/hooks/useContinueGUIProtocol.ts | 4 + extension/react-app/src/tabs/gui.tsx | 4 + 10 files changed, 106 insertions(+), 209 deletions(-) delete mode 100644 continuedev/src/continuedev/core/agent.py (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py deleted file mode 100644 index 1996abb1..00000000 --- a/continuedev/src/continuedev/core/agent.py +++ /dev/null @@ -1,180 +0,0 @@ -import traceback -import time -from typing import Callable, Coroutine, List -from ..models.filesystem_edit import FileEditWithFullContents -from ..libs.llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from ..libs.util.queue import AsyncSubscriptionQueue -from ..models.main import ContinueBaseModel -from .main import Policy, History, FullState, Step, HistoryNode -from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep -from .sdk import ContinueSDK - - -class Autopilot(ContinueBaseModel): - llm: LLM - policy: Policy - ide: AbstractIdeProtocolServer - history: History = History.from_empty() - _on_update_callbacks: List[Callable[[FullState], None]] = [] - - _active: bool = False - _should_halt: bool = False - _main_user_input_queue: List[str] = [] - - _user_input_queue = AsyncSubscriptionQueue() - - class Config: - arbitrary_types_allowed = True - - def get_full_state(self) -> FullState: - return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - - def on_update(self, callback: Callable[["FullState"], None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(autopilot=self, llm=self.llm.with_system_message(step.system_message)) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) - - async def wait_for_user_input(self) -> str: - self._active = False - self.update_subscribers() - user_input = await self._user_input_queue.get(self.history.current_index) - self._active = True - self.update_subscribers() - return user_input - - _manual_edits_buffer: List[FileEditWithFullContents] = [] - - async def reverse_to_index(self, index: int): - try: - while self.history.get_current_index() >= index: - current_step = self.history.get_current().step - self.history.step_back() - if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(self.__get_step_params(current_step)) - - self.update_subscribers() - except Exception as e: - print(e) - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self._manual_edits_buffer.append(edit) - # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - - def handle_traceback(self, traceback: str): - raise NotImplementedError - - _step_depth: int = 0 - - async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: - if not is_future_step: - # Check manual edits buffer, clear out if needed by creating a ManualEditStep - if len(self._manual_edits_buffer) > 0: - manualEditsStep = ManualEditStep.from_sequence( - self._manual_edits_buffer) - self._manual_edits_buffer = [] - await self._run_singular_step(manualEditsStep) - - # Update history - do this first so we get top-first tree ordering - self.history.add_node(HistoryNode( - step=step, observation=None, depth=self._step_depth)) - - # Run step - self._step_depth += 1 - observation = await step(self.__get_step_params(step)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_current().observation = observation - - # Update its description - step._set_description(await step.describe(self.llm)) - - # Call all subscribed callbacks - self.update_subscribers() - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Autopilot is already running") - self._active = True - - next_step = step - is_future_step = False - while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") - next_step = None - - self._active = False - - # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools - for callback in self._on_update_callbacks: - callback(None) - - async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) - await self.run_from_step(next_step) - - async def run_policy(self): - first_step = self.policy.next(self.history) - await self.run_from_step(first_step) - - async def _request_halt(self): - if self._active: - self._should_halt = True - while self._active: - time.sleep(0.1) - self._should_halt = False - return None - - async def accept_user_input(self, user_input: str): - self._main_user_input_queue.append(user_input) - self.update_subscribers() - - if len(self._main_user_input_queue) > 1: - return - - # await self._request_halt() - # Just run the step that takes user input, and - # then up to the policy to decide how to deal with it. - self._main_user_input_queue.pop(0) - self.update_subscribers() - await self.run_from_step(UserInputStep(user_input=user_input)) - - while len(self._main_user_input_queue) > 0: - await self.run_from_step(UserInputStep( - user_input=self._main_user_input_queue.pop(0))) - - async def accept_refinement_input(self, user_input: str, index: int): - await self._request_halt() - await self.reverse_to_index(index) - await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 85f65dc3..db06c975 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -3,7 +3,7 @@ import time from typing import Callable, Coroutine, List from ..models.filesystem_edit import FileEditWithFullContents from ..libs.llm import LLM -from .observation import Observation +from .observation import Observation, InternalErrorObservation from ..server.ide_protocol import AbstractIdeProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue from ..models.main import ContinueBaseModel @@ -77,6 +77,11 @@ class Autopilot(ContinueBaseModel): _step_depth: int = 0 + async def retry_at_index(self, index: int): + last_step = self.history.pop_last_step() + await self.update_subscribers() + await self._run_singular_step(last_step) + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: capture_event( 'step run', {'step_name': step.name, 'params': step.dict()}) @@ -96,14 +101,28 @@ class Autopilot(ContinueBaseModel): # Call all subscribed callbacks await self.update_subscribers() - # Run step + # Try to run step and handle errors self._step_depth += 1 - observation = await step(ContinueSDK(self)) + + try: + observation = await step(ContinueSDK(self)) + except Exception as e: + # Attach an InternalErrorObservation to the step and unhide it. + error_string = '\n\n'.join( + traceback.format_tb(e.__traceback__)) + f"\n\n{e.__repr__()}" + print( + f"Error while running step: \n{error_string}\n{e}") + + observation = InternalErrorObservation( + error=error_string) + step.hide = False + self._step_depth -= 1 # Add observation to history self.history.get_last_at_depth( self._step_depth, include_current=True).observation = observation + await self.update_subscribers() # Update its description async def update_description(): @@ -122,22 +141,17 @@ class Autopilot(ContinueBaseModel): next_step = step is_future_step = False while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + await self._run_singular_step(next_step, is_future_step) + + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: next_step = None self._active = False diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index a2336671..b2b97bae 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -67,6 +67,13 @@ class History(ContinueBaseModel): return None return state.observation + def pop_last_step(self) -> Union[HistoryNode, None]: + if self.current_index < 0: + return None + node = self.timeline.pop(self.current_index) + self.current_index -= 1 + return node.step + @classmethod def from_empty(cls): return cls(timeline=[], current_index=-1) @@ -118,7 +125,7 @@ class Step(ContinueBaseModel): if self._description is not None: d["description"] = self._description else: - d["description"] = self.name + d["description"] = "`Description loading...`" return d @validator("name", pre=True, always=True) diff --git a/continuedev/src/continuedev/core/observation.py b/continuedev/src/continuedev/core/observation.py index fef04311..b6117236 100644 --- a/continuedev/src/continuedev/core/observation.py +++ b/continuedev/src/continuedev/core/observation.py @@ -33,3 +33,7 @@ class TextObservation(Observation): if v is None: return "" return v + + +class InternalErrorObservation(Observation): + error: str diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py index 83852d27..734da160 100644 --- a/continuedev/src/continuedev/libs/llm/hf_inference_api.py +++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py @@ -22,4 +22,11 @@ class HuggingFaceInferenceAPI(LLM): "return_full_text": False, } }) - return response.json()[0]["generated_text"] + data = response.json() + + # Error if the response is not a list + if not isinstance(data, list): + raise Exception( + "Hugging Face returned an error response: \n\n", data) + + return data[0]["generated_text"] diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 3d1a5a82..b873a88f 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -75,6 +75,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer): self.on_refinement_input(data["input"], data["index"]) elif message_type == "reverse_to_index": self.on_reverse_to_index(data["index"]) + elif message_type == "retry_at_index": + self.on_retry_at_index(data["index"]) except Exception as e: print(e) @@ -100,6 +102,10 @@ class GUIProtocolServer(AbstractGUIProtocolServer): asyncio.create_task( self.session.autopilot.accept_refinement_input(input, index)) + def on_retry_at_index(self, index: int): + asyncio.create_task( + self.session.autopilot.retry_at_index(index)) + @router.websocket("/ws") async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py index e32d80ef..287f9e3b 100644 --- a/continuedev/src/continuedev/server/gui_protocol.py +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -26,3 +26,7 @@ class AbstractGUIProtocolServer(ABC): @abstractmethod async def send_state_update(self, state: dict): """Send a state update to the client""" + + @abstractmethod + def on_retry_at_index(self, index: int): + """Called when the user requests a retry at a previous index""" diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index f962cbc9..903f9b94 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -18,6 +18,7 @@ import { ChevronDown, ChevronRight, Backward, + ArrowPath, } from "@styled-icons/heroicons-outline"; import { HistoryNode } from "../../../schema/HistoryNode"; import ReactMarkdown from "react-markdown"; @@ -29,6 +30,7 @@ interface StepContainerProps { inFuture: boolean; onRefinement: (input: string) => void; onUserInput: (input: string) => void; + onRetry: () => void; } const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` @@ -135,19 +137,44 @@ function StepContainer(props: StepContainerProps) { > */} + + {props.historyNode.observation?.error ? ( + { + e.stopPropagation(); + props.onRetry(); + }} + > + + + ) : ( + <> + )} {open && ( -
-              Step Details:
-              
- {JSON.stringify(props.historyNode.step, null, 2)} -
+ <> +
+                Step Details:
+                
+ {JSON.stringify(props.historyNode.step, null, 2)} +
+ )} - - {props.historyNode.step.description as any} - + {props.historyNode.observation?.error ? ( + <> + Error while running step: +
+
+                {props.historyNode.observation.error as string}
+              
+ + ) : ( + + {props.historyNode.step.description as any} + + )} {props.historyNode.step.name === "Waiting for user input" && ( { client?.reverseToIndex(index); }} + onRetry={() => { + client?.retryAtIndex(index); + setWaitingForSteps(true); + }} /> ); })} -- cgit v1.2.3-70-g09d2 From 9613750fd1ff48343e8d80ce5154733a4dbc55d7 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 5 Jun 2023 12:09:13 -0400 Subject: Enter button on user input --- continuedev/src/continuedev/core/policy.py | 5 +- .../recipes/CreatePipelineRecipe/main.py | 9 ++- continuedev/src/continuedev/steps/core/core.py | 7 +- .../react-app/src/components/ContinueButton.tsx | 8 ++- .../react-app/src/components/InputAndButton.tsx | 77 ++++++++++++++++++++++ .../react-app/src/components/StepContainer.tsx | 20 ++---- extension/react-app/src/tabs/gui.tsx | 6 +- 7 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 extension/react-app/src/components/InputAndButton.tsx (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 4287bb6e..e65b6c9d 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -17,7 +17,10 @@ class DemoPolicy(Policy): def next(self, history: History) -> Step: # At the very start, run initial Steps spcecified in the config if history.get_current() is None: - return MessageStep(message="Welcome to Continue!") >> SetupContinueWorkspaceStep() >> CreateCodebaseIndexChroma() >> StepsOnStartupStep() + return (MessageStep(message="Welcome to Continue!") >> + SetupContinueWorkspaceStep() >> + CreateCodebaseIndexChroma() >> + StepsOnStartupStep()) observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py index 4a4604d6..55c25da4 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py @@ -11,7 +11,7 @@ class CreatePipelineRecipe(Step): hide: bool = True async def run(self, sdk: ContinueSDK): - await sdk.run_step( + text_observation = await sdk.run_step( MessageStep(message=dedent("""\ This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will: - Create a Python virtual environment with dlt installed @@ -21,7 +21,10 @@ class CreatePipelineRecipe(Step): - Test that the API call works - Load the data into a local DuckDB instance - Write a query to view the data""")) >> - WaitForUserInputStep(prompt="What API do you want to load data from?") >> - SetupPipelineStep(api_description="WeatherAPI.com API") >> + WaitForUserInputStep( + prompt="What API do you want to load data from?") + ) + await sdk.run_step( + SetupPipelineStep(api_description=text_observation.text) >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index d7f7a307..04446787 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -191,13 +191,18 @@ class WaitForUserInputStep(Step): name: str = "Waiting for user input" _description: Union[str, None] = None + _response: Union[str, None] = None async def describe(self, models: Models) -> Coroutine[str, None, None]: - return self.prompt + if self._response is None: + return self.prompt + else: + return self.prompt + "\n\n" + self._response async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: self._description = self.prompt resp = await sdk.wait_for_user_input() + self._response = resp return TextObservation(text=resp) diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx index 11dc7a92..c6117bf9 100644 --- a/extension/react-app/src/components/ContinueButton.tsx +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -24,9 +24,13 @@ let StyledButton = styled(Button)` } `; -function ContinueButton(props: { onClick?: () => void }) { +function ContinueButton(props: { onClick?: () => void; hidden?: boolean }) { return ( - +
+              
                 Step Details:
                 
{JSON.stringify(props.historyNode.step, null, 2)} diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index bbaf5f08..42671ade 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -176,7 +176,7 @@ class IdeProtocolClient { // If not, ask user for secret while (typeof secret === "undefined" || secret === "") { secret = await vscode.window.showInputBox({ - prompt: `Enter secret for ${key}`, + prompt: `Enter secret for ${key}. You can edit this later in the Continue VS Code settings.`, password: true, }); } -- cgit v1.2.3-70-g09d2 From cc0d73c2c1351a08c95654f7792f56c1d3d0ab54 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 12 Jun 2023 21:30:49 -0700 Subject: slash commands dropdown! --- continuedev/src/continuedev/core/abstract_sdk.py | 4 +- continuedev/src/continuedev/core/autopilot.py | 9 +- continuedev/src/continuedev/core/config.py | 34 ++- continuedev/src/continuedev/core/main.py | 6 +- continuedev/src/continuedev/core/policy.py | 73 +---- continuedev/src/continuedev/core/sdk.py | 6 +- .../continuedev/libs/util/step_name_to_steps.py | 27 ++ .../recipes/CreatePipelineRecipe/main.py | 2 +- .../recipes/CreatePipelineRecipe/steps.py | 3 +- continuedev/src/continuedev/server/gui.py | 7 + continuedev/src/continuedev/server/gui_protocol.py | 6 +- continuedev/src/continuedev/steps/main.py | 4 +- .../src/continuedev/steps/steps_on_startup.py | 24 +- extension/package-lock.json | 64 ++++- extension/package.json | 3 +- extension/react-app/package-lock.json | 60 +++++ extension/react-app/package.json | 1 + extension/react-app/src/components/ComboBox.tsx | 146 ++++++++++ .../src/hooks/ContinueGUIClientProtocol.ts | 4 + .../react-app/src/hooks/useContinueGUIProtocol.ts | 10 + extension/react-app/src/tabs/gui.tsx | 296 +++++++++++---------- .../scripts/continuedev-0.1.1-py3-none-any.whl | Bin 74791 -> 78916 bytes 22 files changed, 547 insertions(+), 242 deletions(-) create mode 100644 continuedev/src/continuedev/libs/util/step_name_to_steps.py create mode 100644 extension/react-app/src/components/ComboBox.tsx (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py index 3b85708d..0658f1b8 100644 --- a/continuedev/src/continuedev/core/abstract_sdk.py +++ b/continuedev/src/continuedev/core/abstract_sdk.py @@ -76,8 +76,8 @@ class AbstractContinueSDK(ABC): async def get_user_secret(self, env_var: str, prompt: str) -> str: pass - @abstractmethod - async def get_config(self) -> ContinueConfig: + @abstractproperty + def config(self) -> ContinueConfig: pass @abstractmethod diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 1642003c..0874bbc5 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -40,6 +40,9 @@ class Autopilot(ContinueBaseModel): def get_full_state(self) -> FullState: return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + async def get_available_slash_commands(self) -> List[Dict]: + return list(map(lambda x: {"name": x.name, "description": x.description}, self.continue_sdk.config.slash_commands)) or [] + async def clear_history(self): self.history = History.from_empty() self._main_user_input_queue = [] @@ -202,7 +205,7 @@ class Autopilot(ContinueBaseModel): await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): + if next_step := self.policy.next(self.continue_sdk.config, self.history): is_future_step = False elif next_step := self.history.take_next_step(): is_future_step = True @@ -215,11 +218,11 @@ class Autopilot(ContinueBaseModel): await self.update_subscribers() async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) + next_step = self.policy.next(self.continue_sdk.config, self.history) await self.run_from_step(next_step) async def run_policy(self): - first_step = self.policy.next(self.history) + first_step = self.policy.next(self.continue_sdk.config, self.history) await self.run_from_step(first_step) async def _request_halt(self): diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index 8ed41a82..cf723984 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -1,9 +1,18 @@ import json import os -from pydantic import BaseModel +from pydantic import BaseModel, validator from typing import List, Optional, Dict import yaml +from .main import Step + + +class SlashCommand(BaseModel): + name: str + description: str + step_name: str + params: Optional[Dict] = {} + class ContinueConfig(BaseModel): """ @@ -12,6 +21,29 @@ class ContinueConfig(BaseModel): steps_on_startup: Optional[Dict[str, Dict]] = {} server_url: Optional[str] = None allow_anonymous_telemetry: Optional[bool] = True + slash_commands: Optional[List[SlashCommand]] = [ + # SlashCommand( + # name="pytest", + # description="Write pytest unit tests for the current file", + # step_name="WritePytestsRecipe", + # params=??) + + SlashCommand( + name="dlt", + description="Create a dlt pipeline", + step_name="CreatePipelineRecipe", + ), + SlashCommand( + name="ddtobq", + description="Create a dlt pipeline to load data from a data source into BigQuery", + step_name="DDtoBQRecipe", + ), + SlashCommand( + name="deployairflow", + description="Deploy a dlt pipeline to Airflow", + step_name="DeployPipelineAirflowRecipe", + ), + ] def load_config(config_file: str) -> ContinueConfig: diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 81aaaf2e..f6b26d69 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -118,11 +118,15 @@ class Models: pass +class ContinueConfig: + pass + + class Policy(ContinueBaseModel): """A rule that determines which step to take next""" # Note that history is mutable, kinda sus - def next(self, history: History = History.from_empty()) -> "Step": + def next(self, config: ContinueConfig, history: History = History.from_empty()) -> "Step": raise NotImplementedError diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 00b5427c..37a10e36 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,5 +1,6 @@ from typing import List, Tuple, Type +from .config import ContinueConfig from ..steps.chroma import AnswerQuestionChroma, EditFileChroma, CreateCodebaseIndexChroma from ..steps.steps_on_startup import StepsOnStartupStep from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe @@ -15,12 +16,13 @@ from ..steps.react import NLDecisionStep from ..steps.chat import SimpleChatStep from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe from ..steps.core.core import MessageStep +from ..libs.util.step_name_to_steps import get_step_from_name class DemoPolicy(Policy): ran_code_last: bool = False - def next(self, history: History) -> Step: + def next(self, config: ContinueConfig, history: History) -> Step: # At the very start, run initial Steps spcecified in the config if history.get_current() is None: return ( @@ -33,20 +35,18 @@ class DemoPolicy(Policy): if observation is not None and isinstance(observation, UserInputObservation): # This could be defined with ObservationTypePolicy. Ergonomics not right though. user_input = observation.user_input + + if user_input.startswith("/"): + command_name = user_input.split(" ")[0] + after_command = " ".join(user_input.split(" ")[1:]) + for slash_command in config.slash_commands: + if slash_command.name == command_name[1:]: + return get_step_from_name(slash_command.step_name, slash_command.params) + if "/pytest" in user_input.lower(): return WritePytestsRecipe(instructions=user_input) - elif "/dlt" in user_input.lower() or " dlt" in user_input.lower(): - return CreatePipelineRecipe() if "/pytest" in observation.user_input.lower(): return WritePytestsRecipe(instructions=observation.user_input) - elif "/dlt" in observation.user_input.lower(): - return CreatePipelineRecipe() - elif "/ddtobq" in observation.user_input.lower(): - return DDtoBQRecipe() - elif "/airflow" in observation.user_input.lower(): - return DeployPipelineAirflowRecipe() - elif "/transform" in observation.user_input.lower(): - return AddTransformRecipe() elif "/comment" in observation.user_input.lower(): return CommentCodeStep() elif "/ask" in user_input: @@ -72,54 +72,3 @@ class DemoPolicy(Policy): return SolveTracebackStep(traceback=observation.traceback) else: return None - - -class ObservationTypePolicy(Policy): - def __init__(self, base_policy: Policy, observation_type: Type[Observation], step_type: Type[Step]): - self.observation_type = observation_type - self.step_type = step_type - self.base_policy = base_policy - - def next(self, history: History) -> Step: - observation = history.last_observation() - if observation is not None and isinstance(observation, self.observation_type): - return self.step_type(observation) - return self.base_policy.next(history) - - -class PolicyWrappedWithValidators(Policy): - """Default is to stop, unless the validator tells what to do next""" - index: int - stage: int - - def __init__(self, base_policy: Policy, pairs: List[Tuple[Validator, Type[Step]]]): - # Want to pass Type[Validator], or just the Validator? Question of where params are coming from. - self.pairs = pairs - self.index = len(pairs) - self.validating = 0 - self.base_policy = base_policy - - def next(self, history: History) -> Step: - if self.index == len(self.pairs): - self.index = 0 - return self.base_policy.next(history) - - if self.stage == 0: - # Running the validator at the current index for the first time - validator, step = self.pairs[self.index] - self.stage = 1 - return validator - elif self.stage == 1: - # Previously ran the validator at the current index, now receiving its ValidatorObservation - observation = history.last_observation() - if observation.passed: - self.stage = 0 - self.index += 1 - if self.index == len(self.pairs): - self.index = 0 - return self.base_policy.next(history) - else: - return self.pairs[self.index][0] - else: - _, step_type = self.pairs[self.index] - return step_type(observation) diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 2849b0c8..1f4cdfb2 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,4 +1,3 @@ -from abc import ABC, abstractmethod import asyncio from functools import cached_property from typing import Coroutine, Union @@ -119,8 +118,9 @@ class ContinueSDK(AbstractContinueSDK): async def get_user_secret(self, env_var: str, prompt: str) -> str: return await self.ide.getUserSecret(env_var) - async def get_config(self) -> ContinueConfig: - dir = await self.ide.getWorkspaceDirectory() + @property + def config(self) -> ContinueConfig: + dir = self.ide.workspace_directory yaml_path = os.path.join(dir, '.continue', 'config.yaml') json_path = os.path.join(dir, '.continue', 'config.json') if os.path.exists(yaml_path): diff --git a/continuedev/src/continuedev/libs/util/step_name_to_steps.py b/continuedev/src/continuedev/libs/util/step_name_to_steps.py new file mode 100644 index 00000000..4023b73b --- /dev/null +++ b/continuedev/src/continuedev/libs/util/step_name_to_steps.py @@ -0,0 +1,27 @@ +from typing import Dict + +from ...core.main import Step +from ...steps.core.core import UserInputStep +from ...recipes.CreatePipelineRecipe.main import CreatePipelineRecipe +from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe +from ...recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe +from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe +from ...recipes.AddTransformRecipe.main import AddTransformRecipe + +step_name_to_step_class = { + "UserInputStep": UserInputStep, + "CreatePipelineRecipe": CreatePipelineRecipe, + "DDtoBQRecipe": DDtoBQRecipe, + "DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe, + "AddTransformRecipe": AddTransformRecipe, + "DDtoBQRecipe": DDtoBQRecipe +} + + +def get_step_from_name(step_name: str, params: Dict) -> Step: + try: + return step_name_to_step_class[step_name](**params) + except: + print( + f"Incorrect parameters for step {step_name}. Parameters provided were: {params}") + raise diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py index 818168ba..92bddc98 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py @@ -1,7 +1,7 @@ from textwrap import dedent -from ...core.main import Step from ...core.sdk import ContinueSDK +from ...core.main import Step from ...steps.core.core import WaitForUserInputStep from ...steps.core.core import MessageStep from .steps import SetupPipelineStep, ValidatePipelineStep, RunQueryStep diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py index e59cc51c..ea4607da 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py @@ -6,11 +6,10 @@ import time from ...models.main import Range from ...models.filesystem import RangeInFile from ...steps.core.core import MessageStep -from ...core.sdk import Models from ...core.observation import DictObservation, InternalErrorObservation from ...models.filesystem_edit import AddFile, FileEdit from ...core.main import Step -from ...core.sdk import ContinueSDK +from ...core.sdk import ContinueSDK, Models AI_ASSISTED_STRING = "(โœจ AI-Assisted โœจ)" diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index e8b52004..cf046734 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -90,6 +90,12 @@ class GUIProtocolServer(AbstractGUIProtocolServer): "state": state }) + async def send_available_slash_commands(self): + commands = await self.session.autopilot.get_available_slash_commands() + await self._send_json("available_slash_commands", { + "commands": commands + }) + def on_main_input(self, input: str): # Do something with user input asyncio.create_task(self.session.autopilot.accept_user_input(input)) @@ -127,6 +133,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we protocol.websocket = websocket # Update any history that may have happened before connection + await protocol.send_available_slash_commands() await protocol.send_state_update() while AppStatus.should_exit is False: diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py index 889c6761..d9506c6f 100644 --- a/continuedev/src/continuedev/server/gui_protocol.py +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Dict, List from abc import ABC, abstractmethod @@ -27,6 +27,10 @@ class AbstractGUIProtocolServer(ABC): async def send_state_update(self, state: dict): """Send a state update to the client""" + @abstractmethod + async def send_available_slash_commands(self, commands: List[Dict]): + """Send a list of available slash commands to the client""" + @abstractmethod def on_retry_at_index(self, index: int): """Called when the user requests a retry at a previous index""" diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 9634c726..36e4f519 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -63,10 +63,10 @@ class RunPolicyUntilDoneStep(Step): policy: "Policy" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - next_step = self.policy.next(sdk.history) + next_step = self.policy.next(sdk.config, sdk.history) while next_step is not None: observation = await sdk.run_step(next_step) - next_step = self.policy.next(sdk.history) + next_step = self.policy.next(sdk.config, sdk.history) return observation diff --git a/continuedev/src/continuedev/steps/steps_on_startup.py b/continuedev/src/continuedev/steps/steps_on_startup.py index eae8b558..365cbe1a 100644 --- a/continuedev/src/continuedev/steps/steps_on_startup.py +++ b/continuedev/src/continuedev/steps/steps_on_startup.py @@ -1,19 +1,12 @@ -from ..core.main import ContinueSDK, Models, Step +from ..core.main import Step +from ..core.sdk import Models, ContinueSDK from .main import UserInputStep from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe from ..recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe from ..recipes.AddTransformRecipe.main import AddTransformRecipe - -step_name_to_step_class = { - "UserInputStep": UserInputStep, - "CreatePipelineRecipe": CreatePipelineRecipe, - "DDtoBQRecipe": DDtoBQRecipe, - "DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe, - "AddTransformRecipe": AddTransformRecipe, - "DDtoBQRecipe": DDtoBQRecipe -} +from ..libs.util.step_name_to_steps import get_step_from_name class StepsOnStartupStep(Step): @@ -23,13 +16,8 @@ class StepsOnStartupStep(Step): return "Running steps on startup" async def run(self, sdk: ContinueSDK): - steps_descriptions = (await sdk.get_config()).steps_on_startup + steps_on_startup = sdk.config.steps_on_startup - for step_name, step_params in steps_descriptions.items(): - try: - step = step_name_to_step_class[step_name](**step_params) - except: - print( - f"Incorrect parameters for step {step_name}. Parameters provided were: {step_params}") - continue + for step_name, step_params in steps_on_startup.items(): + step = get_step_from_name(step_name, step_params) await sdk.run_step(step) diff --git a/extension/package-lock.json b/extension/package-lock.json index aebd0803..b02c4544 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.34", + "version": "0.0.35", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.34", + "version": "0.0.35", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", @@ -15,6 +15,7 @@ "@styled-icons/heroicons-outline": "^10.47.0", "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.2.5", + "downshift": "^7.6.0", "highlight.js": "^11.7.0", "posthog-js": "^1.63.3", "react-markdown": "^8.0.7", @@ -2845,6 +2846,11 @@ "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==", "dev": true }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3261,6 +3267,31 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/downshift": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz", + "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==", + "dependencies": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/downshift/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/downshift/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + }, "node_modules/dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", @@ -10615,6 +10646,11 @@ "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==", "dev": true }, + "compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -10911,6 +10947,30 @@ "domhandler": "^5.0.1" } }, + "downshift": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz", + "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==", + "requires": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "dependencies": { + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + } + } + }, "dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", diff --git a/extension/package.json b/extension/package.json index 8ee8cb4c..8ccb4b13 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.34", + "version": "0.0.35", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -134,6 +134,7 @@ "@styled-icons/heroicons-outline": "^10.47.0", "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.2.5", + "downshift": "^7.6.0", "highlight.js": "^11.7.0", "posthog-js": "^1.63.3", "react-markdown": "^8.0.7", diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json index dbcbc5cc..64440da6 100644 --- a/extension/react-app/package-lock.json +++ b/extension/react-app/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@styled-icons/heroicons-outline": "^10.47.0", "@types/vscode-webview": "^1.57.1", + "downshift": "^7.6.0", "posthog-js": "^1.58.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -1288,6 +1289,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, "node_modules/css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -1405,6 +1411,26 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, + "node_modules/downshift": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz", + "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==", + "dependencies": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/downshift/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "node_modules/electron-to-chromium": { "version": "1.4.311", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz", @@ -3003,6 +3029,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -4050,6 +4081,11 @@ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" }, + "compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, "css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -4131,6 +4167,25 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, + "downshift": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz", + "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==", + "requires": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "dependencies": { + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, "electron-to-chromium": { "version": "1.4.311", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz", @@ -5138,6 +5193,11 @@ "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==" }, + "tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", diff --git a/extension/react-app/package.json b/extension/react-app/package.json index 7d1211de..a53fbec8 100644 --- a/extension/react-app/package.json +++ b/extension/react-app/package.json @@ -11,6 +11,7 @@ "dependencies": { "@styled-icons/heroicons-outline": "^10.47.0", "@types/vscode-webview": "^1.57.1", + "downshift": "^7.6.0", "posthog-js": "^1.58.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx new file mode 100644 index 00000000..1b7c60e6 --- /dev/null +++ b/extension/react-app/src/components/ComboBox.tsx @@ -0,0 +1,146 @@ +import React, { useCallback } from "react"; +import { useCombobox } from "downshift"; +import styled from "styled-components"; +import { + buttonColor, + defaultBorderRadius, + secondaryDark, + vscBackground, +} from "."; + +const mainInputFontSize = 16; +const MainTextInput = styled.input` + padding: 8px; + font-size: ${mainInputFontSize}px; + border-radius: ${defaultBorderRadius}; + border: 1px solid #ccc; + margin: 8px auto; + width: 100%; + background-color: ${vscBackground}; + color: white; + outline: 1px solid orange; +`; + +const UlMaxHeight = 200; +const Ul = styled.ul<{ + hidden: boolean; + showAbove: boolean; + ulHeightPixels: number; +}>` + ${(props) => + props.showAbove + ? `transform: translateY(-${props.ulHeightPixels + 8}px);` + : `transform: translateY(${2 * mainInputFontSize}px);`} + position: absolute; + background: ${vscBackground}; + background-color: ${secondaryDark}; + color: white; + font-family: "Fira Code", monospace; + max-height: ${UlMaxHeight}px; + overflow: scroll; + padding: 0; + ${({ hidden }) => hidden && "display: none;"} + border-radius: ${defaultBorderRadius}; + overflow: hidden; + border: 0.5px solid gray; +`; + +const Li = styled.li<{ + highlighted: boolean; + selected: boolean; + isLastItem: boolean; +}>` + ${({ highlighted }) => highlighted && "background: #aa0000;"} + ${({ selected }) => selected && "font-weight: bold;"} + padding: 0.5rem 0.75rem; + display: flex; + flex-direction: column; + ${({ isLastItem }) => isLastItem && "border-bottom: 1px solid gray;"} + border-top: 1px solid gray; + cursor: pointer; +`; + +interface ComboBoxProps { + items: { name: string; description: string }[]; + onInputValueChange: (inputValue: string) => void; + disabled?: boolean; + onEnter?: (e: React.KeyboardEvent) => void; +} + +const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { + const [items, setItems] = React.useState(props.items); + const { + isOpen, + getToggleButtonProps, + getLabelProps, + getMenuProps, + getInputProps, + highlightedIndex, + getItemProps, + selectedItem, + setInputValue, + } = useCombobox({ + onInputValueChange({ inputValue }) { + if (!inputValue) return; + props.onInputValueChange(inputValue); + setItems( + props.items.filter((item) => + item.name.toLowerCase().startsWith(inputValue.toLowerCase()) + ) + ); + }, + items, + itemToString(item) { + return item ? item.name : ""; + }, + }); + + const divRef = React.useRef(null); + const ulRef = React.useRef(null); + const showAbove = () => { + return (divRef.current?.getBoundingClientRect().top || 0) > UlMaxHeight; + }; + + return ( + + ); +}); + +export default ComboBox; diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index 71303c70..824bb086 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -9,6 +9,10 @@ abstract class AbstractContinueGUIClientProtocol { abstract onStateUpdate(state: any): void; + abstract onAvailableSlashCommands( + callback: (commands: { name: string; description: string }[]) => void + ): void; + abstract sendClear(): void; abstract retryAtIndex(index: number): void; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts index a8e28fc5..59397742 100644 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -45,6 +45,16 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { }); } + onAvailableSlashCommands( + callback: (commands: { name: string; description: string }[]) => void + ) { + this.messenger.onMessageType("available_slash_commands", (data: any) => { + if (data.commands) { + callback(data.commands); + } + }); + } + sendClear() { this.messenger.send("clear_history", {}); } diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index cb7a5440..1569c178 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -12,7 +12,8 @@ import { History } from "../../../schema/History"; import { HistoryNode } from "../../../schema/HistoryNode"; import StepContainer from "../components/StepContainer"; import useContinueGUIProtocol from "../hooks/useWebsocket"; -import { Trash } from "@styled-icons/heroicons-outline"; +import { BookOpen, Trash } from "@styled-icons/heroicons-outline"; +import ComboBox from "../components/ComboBox"; let TopGUIDiv = styled.div` display: grid; grid-template-columns: 1fr; @@ -42,128 +43,132 @@ interface GUIProps { function GUI(props: GUIProps) { const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState([]); - const [history, setHistory] = useState({ - timeline: [ - { - step: { - name: "Waiting for user input", - cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - description: - "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", - }, - observation: { - title: "ERROR FOUND", - error: - "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - }, - output: [ - { - traceback: { - frames: [ - { - filepath: - "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - lineno: 7, - function: "", - code: "print(sum(first, second))", - }, - ], - message: "unsupported operand type(s) for +: 'int' and 'str'", - error_type: - ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', - full_traceback: - "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - }, - }, - null, - ], - }, - { - step: { - name: "EditCodeStep", - range_in_files: [ - { - filepath: - "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - range: { - start: { - line: 0, - character: 0, - }, - end: { - line: 6, - character: 25, - }, - }, - }, - ], - prompt: - "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", - description: - "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", - }, - output: [ - null, - { - reversible: true, - actions: [ - { - reversible: true, - filesystem: {}, - filepath: - "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - range: { - start: { - line: 0, - character: 0, - }, - end: { - line: 6, - character: 25, - }, - }, - replacement: - "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", - }, - ], - }, - ], - }, - { - step: { - name: "SolveTracebackStep", - traceback: { - frames: [ - { - filepath: - "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - lineno: 7, - function: "", - code: "print(sum(first, second))", - }, - ], - message: "unsupported operand type(s) for +: 'int' and 'str'", - error_type: - ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', - full_traceback: - "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - }, - description: "Running step: SolveTracebackStep", - }, - output: [null, null], - }, - { - step: { - name: "RunCodeStep", - cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - description: - "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", - }, - output: [null, null], - }, - ], - current_index: 3, - } as any); + const [availableSlashCommands, setAvailableSlashCommands] = useState< + { name: string; description: string }[] + >([]); + const [history, setHistory] = useState(); + // { + // timeline: [ + // { + // step: { + // name: "Waiting for user input", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", + // }, + // observation: { + // title: "ERROR FOUND", + // error: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // output: [ + // { + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // }, + // null, + // ], + // }, + // { + // step: { + // name: "EditCodeStep", + // range_in_files: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // }, + // ], + // prompt: + // "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3", + // }, + // output: [ + // null, + // { + // reversible: true, + // actions: [ + // { + // reversible: true, + // filesystem: {}, + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // replacement: + // "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", + // }, + // ], + // }, + // ], + // }, + // { + // step: { + // name: "SolveTracebackStep", + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // description: "Running step: SolveTracebackStep", + // }, + // output: [null, null], + // }, + // { + // step: { + // name: "RunCodeStep", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", + // }, + // output: [null, null], + // }, + // ], + // current_index: 3, + // } as any); const topGuiDivRef = useRef(null); const client = useContinueGUIProtocol(); @@ -197,13 +202,24 @@ function GUI(props: GUIProps) { scrollToBottom(); }); + client?.onAvailableSlashCommands((commands) => { + console.log("Received available slash commands: ", commands); + setAvailableSlashCommands( + commands.map((c) => { + return { + name: "/" + c.name, + description: c.description, + }; + }) + ); + }); }, [client]); useEffect(() => { scrollToBottom(); }, [waitingForSteps]); - const mainTextInputRef = useRef(null); + const mainTextInputRef = useRef(null); useEffect(() => { if (mainTextInputRef.current) { @@ -246,8 +262,6 @@ function GUI(props: GUIProps) { return [...queue, input]; }); } - mainTextInputRef.current.value = ""; - mainTextInputRef.current.style.height = ""; } setWaitingForSteps(true); @@ -270,7 +284,12 @@ function GUI(props: GUIProps) { }} > -

Continue

+
+ + Continue Docs + + + Clear History - { - if (e.key === "Enter") { - onMainTextInput(); - e.stopPropagation(); - e.preventDefault(); - } - }} - rows={1} - onChange={() => { - const textarea = mainTextInputRef.current!; - textarea.style.height = ""; /* Reset the height*/ - textarea.style.height = `${Math.min( - textarea.scrollHeight - 15, - 500 - )}px`; + onEnter={(e) => { + onMainTextInput(); + e.stopPropagation(); + e.preventDefault(); }} + onInputValueChange={() => {}} + items={availableSlashCommands} /> diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl index 42f3d4a3..3d7639a9 100644 Binary files a/extension/scripts/continuedev-0.1.1-py3-none-any.whl and b/extension/scripts/continuedev-0.1.1-py3-none-any.whl differ -- cgit v1.2.3-70-g09d2 From 276a7ab42a6cbeea931b6eeb7a27f56f60b3fb86 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 13 Jun 2023 12:00:35 -0700 Subject: env to .env to be caught by .gitignore --- continuedev/src/continuedev/core/policy.py | 2 +- .../continuedev/recipes/AddTransformRecipe/steps.py | 8 ++++---- .../recipes/CreatePipelineRecipe/steps.py | 10 +++++----- .../src/continuedev/recipes/DDtoBQRecipe/steps.py | 10 +++++----- .../recipes/DeployPipelineAirflowRecipe/steps.py | 8 ++++---- .../continuedev/recipes/WritePytestsRecipe/main.py | 3 +++ docs/docs/walkthroughs/create-a-recipe.md | 2 +- extension/react-app/src/components/ComboBox.tsx | 2 +- extension/react-app/src/components/StepContainer.tsx | 20 +++++++++++--------- 9 files changed, 35 insertions(+), 30 deletions(-) (limited to 'extension/react-app/src/components') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 51135f70..2b50307a 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -58,7 +58,7 @@ class DemoPolicy(Policy): # AnswerQuestionChroma(question=user_input), # EditFileChroma(request=user_input), (SimpleChatStep(user_input=user_input), - "Respond to the user with a chat message"), + "Respond to the user with a chat message. Can answer questions about code or anything else."), ], default_step=EditHighlightedCodeStep(user_input=user_input)) state = history.get_current() diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py index f042424c..6a743fb5 100644 --- a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py @@ -24,15 +24,15 @@ class SetUpChessPipelineStep(Step): # running commands to get started when creating a new dlt pipeline await sdk.run([ - 'python3 -m venv env', - 'source env/bin/activate', + 'python3 -m venv .env', + 'source .env/bin/activate', 'pip install dlt', 'dlt --non-interactive init chess duckdb', 'pip install -r requirements.txt', 'pip install pandas streamlit' # Needed for the pipeline show step later ], name="Set up Python environment", description=dedent(f"""\ - - Create a Python virtual environment: `python3 -m venv env` - - Activate the virtual environment: `source env/bin/activate` + - Create a Python virtual environment: `python3 -m venv .env` + - Activate the virtual environment: `source .env/bin/activate` - Install dlt: `pip install dlt` - Create a new dlt pipeline called "chess" that loads data into a local DuckDB instance: `dlt init chess duckdb` - Install the Python dependencies for the pipeline: `pip install -r requirements.txt`""")) diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py index ea4607da..88e27d2a 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py @@ -35,15 +35,15 @@ class SetupPipelineStep(Step): # running commands to get started when creating a new dlt pipeline await sdk.run([ - 'python3 -m venv env', - 'source env/bin/activate', + 'python3 -m venv .env', + 'source .env/bin/activate', 'pip install dlt', f'dlt --non-interactive init {source_name} duckdb', 'pip install -r requirements.txt' ], description=dedent(f"""\ Running the following commands: - - `python3 -m venv env`: Create a Python virtual environment - - `source env/bin/activate`: Activate the virtual environment + - `python3 -m venv .env`: Create a Python virtual environment + - `source .env/bin/activate`: Activate the virtual environment - `pip install dlt`: Install dlt - `dlt init {source_name} duckdb`: Create a new dlt pipeline called {source_name} that loads data into a local DuckDB instance - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline"""), name="Setup Python environment") @@ -156,7 +156,7 @@ class RunQueryStep(Step): hide: bool = True async def run(self, sdk: ContinueSDK): - output = await sdk.run('env/bin/python3 query.py', name="Run test query", description="Running `env/bin/python3 query.py` to test that the data was loaded into DuckDB as expected", handle_error=False) + output = await sdk.run('.env/bin/python3 query.py', name="Run test query", description="Running `.env/bin/python3 query.py` to test that the data was loaded into DuckDB as expected", handle_error=False) if "Traceback" in output or "SyntaxError" in output: suggestion = sdk.models.gpt35.complete(dedent(f"""\ diff --git a/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py b/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py index 5cf89ccf..4b8971c2 100644 --- a/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py @@ -27,15 +27,15 @@ class SetUpChessPipelineStep(Step): # running commands to get started when creating a new dlt pipeline await sdk.run([ - 'python3 -m venv env', - 'source env/bin/activate', + 'python3 -m venv .env', + 'source .env/bin/activate', 'pip install dlt', 'dlt --non-interactive init chess duckdb', 'pip install -r requirements.txt', ], name="Set up Python environment", description=dedent(f"""\ Running the following commands: - - `python3 -m venv env`: Create a Python virtual environment - - `source env/bin/activate`: Activate the virtual environment + - `python3 -m venv .env`: Create a Python virtual environment + - `source .env/bin/activate`: Activate the virtual environment - `pip install dlt`: Install dlt - `dlt init chess duckdb`: Create a new dlt pipeline called "chess" that loads data into a local DuckDB instance - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline""")) @@ -75,7 +75,7 @@ class LoadDataStep(Step): async def run(self, sdk: ContinueSDK): # Run the pipeline again to load data to BigQuery - output = await sdk.run('env/bin/python3 chess_pipeline.py', name="Load data to BigQuery", description="Running `env/bin/python3 chess_pipeline.py` to load data to Google BigQuery") + output = await sdk.run('.env/bin/python3 chess_pipeline.py', name="Load data to BigQuery", description="Running `.env/bin/python3 chess_pipeline.py` to load data to Google BigQuery") if "Traceback" in output or "SyntaxError" in output: with open(os.path.join(os.path.dirname(__file__), "dlt_duckdb_to_bigquery_docs.md"), "r") as f: diff --git a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py b/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py index ee3275e7..c9749348 100644 --- a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py @@ -28,15 +28,15 @@ class SetupPipelineStep(Step): async def run(self, sdk: ContinueSDK): await sdk.run([ - 'python3 -m venv env', - 'source env/bin/activate', + 'python3 -m venv .env', + 'source .env/bin/activate', 'pip install dlt', f'dlt --non-interactive init {self.source_name} duckdb', 'pip install -r requirements.txt' ], description=dedent(f"""\ Running the following commands: - - `python3 -m venv env`: Create a Python virtual environment - - `source env/bin/activate`: Activate the virtual environment + - `python3 -m venv .env`: Create a Python virtual environment + - `source .env/bin/activate`: Activate the virtual environment - `pip install dlt`: Install dlt - `dlt init {self.source_name} duckdb`: Create a new dlt pipeline called {self.source_name} that loads data into a local DuckDB instance - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline"""), name="Setup Python environment") diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py index a19caf8b..688f44c3 100644 --- a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py +++ b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py @@ -9,6 +9,9 @@ class WritePytestsRecipe(Step): for_filepath: Union[str, None] = None user_input: str = "Write unit tests for this file." + async def describe(self, models): + return f"Writing unit tests for {self.for_filepath}" + async def run(self, sdk: ContinueSDK): if self.for_filepath is None: self.for_filepath = (await sdk.ide.getOpenFiles())[0] diff --git a/docs/docs/walkthroughs/create-a-recipe.md b/docs/docs/walkthroughs/create-a-recipe.md index 3b80df8a..5d80d083 100644 --- a/docs/docs/walkthroughs/create-a-recipe.md +++ b/docs/docs/walkthroughs/create-a-recipe.md @@ -93,5 +93,5 @@ class SetUpVenvStep(Step): if os == "Windows": await sdk.run("python -m venv env; .\\env\\Scripts\\activate") else: - await sdk.run("python3 -m venv env && source env/bin/activate") # MacOS and Linux + await sdk.run("python3 -m venv .env && source .env/bin/activate") # MacOS and Linux ``` diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 1b7c60e6..46d8c4d3 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -105,7 +105,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {