diff options
author | Nate Sesti <sestinj@gmail.com> | 2023-07-03 22:18:14 -0700 |
---|---|---|
committer | Nate Sesti <sestinj@gmail.com> | 2023-07-03 22:18:14 -0700 |
commit | c4ab2657c07b23d2c62acde7829ad33f98c8cd7f (patch) | |
tree | 38bd1704461102f7b71219841b3b0e9511300ecc | |
parent | ed85171a423048a2a11a7d2a911dea8c4f332324 (diff) | |
download | sncontinue-c4ab2657c07b23d2c62acde7829ad33f98c8cd7f.tar.gz sncontinue-c4ab2657c07b23d2c62acde7829ad33f98c8cd7f.tar.bz2 sncontinue-c4ab2657c07b23d2c62acde7829ad33f98c8cd7f.zip |
slash commands and better designed user input box
-rw-r--r-- | continuedev/src/continuedev/core/autopilot.py | 11 | ||||
-rw-r--r-- | continuedev/src/continuedev/core/config.py | 6 | ||||
-rw-r--r-- | continuedev/src/continuedev/core/main.py | 5 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/gui.py | 9 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/chat.py | 4 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/core/core.py | 4 | ||||
-rw-r--r-- | extension/react-app/src/components/StepContainer.tsx | 31 | ||||
-rw-r--r-- | extension/react-app/src/components/UserInputContainer.tsx | 57 | ||||
-rw-r--r-- | extension/react-app/src/components/index.ts | 2 | ||||
-rw-r--r-- | extension/react-app/src/tabs/gui.tsx | 33 |
10 files changed, 116 insertions, 46 deletions
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 1a77ca64..b3b98b8b 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -66,11 +66,16 @@ class Autopilot(ContinueBaseModel): active=self._active, user_input_queue=self._main_user_input_queue, default_model=self.continue_sdk.config.default_model, - highlighted_ranges=self._highlighted_ranges + highlighted_ranges=self._highlighted_ranges, + slash_commands=self.get_available_slash_commands() ) - 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 [] + def get_available_slash_commands(self) -> List[Dict]: + custom_commands = list(map(lambda x: { + "name": x.name, "description": x.prompt}, self.continue_sdk.config.custom_commands)) or [] + slash_commands = list(map(lambda x: { + "name": x.name, "description": x.description}, self.continue_sdk.config.slash_commands)) or [] + return custom_commands + slash_commands async def change_default_model(self, model: str): self.continue_sdk.update_default_model(model) diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index ed5d785a..d268d247 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -12,6 +12,11 @@ class SlashCommand(BaseModel): params: Optional[Dict] = {} +class CustomCommand(BaseModel): + name: str + prompt: str + + class OnTracebackSteps(BaseModel): step_name: str params: Optional[Dict] = {} @@ -27,6 +32,7 @@ class ContinueConfig(BaseModel): allow_anonymous_telemetry: Optional[bool] = True default_model: Literal["gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4"] = 'gpt-4' + custom_commands: Optional[List[CustomCommand]] = [] slash_commands: Optional[List[SlashCommand]] = [ # SlashCommand( # name="pytest", diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 7dff6f53..9a6617f4 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -196,6 +196,10 @@ class History(ContinueBaseModel): return cls(timeline=[], current_index=-1) +class SlashCommandDescription(ContinueBaseModel): + name: str + description: str + class FullState(ContinueBaseModel): """A full state of the program, including the history""" history: History @@ -203,6 +207,7 @@ class FullState(ContinueBaseModel): user_input_queue: List[str] default_model: str highlighted_ranges: List[RangeInFileWithContents] + slash_commands: List[SlashCommandDescription] class ContinueSDK: diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 9a33fb6c..b2f23bac 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -94,12 +94,6 @@ 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)) @@ -147,8 +141,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() THIS WAS CAUSING A LOT OF ISSUES. Don't uncomment. + await protocol.send_state_update() while AppStatus.should_exit is False: message = await websocket.receive_text() diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py index 013cb578..49dd98e4 100644 --- a/continuedev/src/continuedev/steps/chat.py +++ b/continuedev/src/continuedev/steps/chat.py @@ -22,9 +22,9 @@ class SimpleChatStep(Step): user_input: str name: str = "Chat" manage_own_chat_context: bool = True + description: str = "" async def run(self, sdk: ContinueSDK): - self.description = f"`{self.user_input}`\n\n" if self.user_input.strip() == "": self.user_input = "Explain this code's function is a concise list of markdown bullets." self.description = "" @@ -148,9 +148,9 @@ class ChatWithFunctions(Step): DeleteFileStep(filename=""), RunTerminalCommandStep(command="")] name: str = "Input" manage_own_chat_context: bool = True + description: str = "" async def run(self, sdk: ContinueSDK): - self.description = f"```{self.user_input}```" await sdk.update_ui() step_name_step_class_map = { diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 4eb2445c..4ad47689 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -116,6 +116,7 @@ class DefaultModelEditCodeStep(Step): range_in_files: List[RangeInFile] name: str = "Editing Code" hide = False + description: str = "" _prompt: str = dedent("""\ Take the file prefix and suffix into account, but only rewrite the code_to_edit as specified in the user_request. The code you write in modified_code_to_edit will replace the code between the code_to_edit tags. Do NOT preface your answer or write anything other than code. The </modified_code_to_edit> tag should be written to indicate the end of the modified code section. Do not ever use nested tags. @@ -508,7 +509,6 @@ class DefaultModelEditCodeStep(Step): self._prompt_and_completion += prompt + completion async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - self.description = f"`{self.user_input}`" await sdk.update_ui() rif_with_contents = [] @@ -589,7 +589,7 @@ class ManualEditStep(ReversibleStep): class UserInputStep(Step): user_input: str name: str = "User Input" - hide: bool = True + hide: bool = False manage_own_chat_context: bool = True diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index cb83f20a..ab0d307f 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -22,7 +22,6 @@ interface StepContainerProps { historyNode: HistoryNode; onReverse: () => void; inFuture: boolean; - onRefinement: (input: string) => void; onUserInput: (input: string) => void; onRetry: () => void; onDelete: () => void; @@ -33,6 +32,8 @@ interface StepContainerProps { isLast: boolean; } +// #region styled components + const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` opacity: ${(props) => (props.inFuture ? 0.3 : 1)}; animation: ${appear} 0.3s ease-in-out; @@ -63,9 +64,12 @@ const HeaderDiv = styled.div<{ error: boolean; loading: boolean }>` padding-right: 8px; `; -const ContentDiv = styled.div` +const ContentDiv = styled.div<{ isUserInput: boolean }>` padding: 8px; padding-left: 16px; + background-color: ${(props) => + props.isUserInput ? secondaryDark : vscBackground}; + font-size: 13px; `; const MarkdownPre = styled.pre` @@ -119,10 +123,13 @@ const GradientBorder = styled.div<{ background-size: 200% 200%; `; +// #endregion + function StepContainer(props: StepContainerProps) { const [isHovered, setIsHovered] = useState(false); const naturalLanguageInputRef = useRef<HTMLTextAreaElement>(null); const userInputRef = useRef<HTMLInputElement>(null); + const isUserInput = props.historyNode.step.name === "UserInputStep"; useEffect(() => { if (userInputRef?.current) { @@ -136,13 +143,6 @@ function StepContainer(props: StepContainerProps) { } }, [isHovered]); - const onTextInput = useCallback(() => { - if (naturalLanguageInputRef.current) { - props.onRefinement(naturalLanguageInputRef.current.value); - naturalLanguageInputRef.current.value = ""; - } - }, [naturalLanguageInputRef]); - return ( <MainDiv stepDepth={(props.historyNode.depth as any) || 0} @@ -181,11 +181,12 @@ function StepContainer(props: StepContainerProps) { error={props.historyNode.observation?.error ? true : false} > <h4 className="m-2"> - {props.open ? ( - <ChevronDown size="1.4em" /> - ) : ( - <ChevronRight size="1.4em" /> - )} + {!isUserInput && + (props.open ? ( + <ChevronDown size="1.4em" /> + ) : ( + <ChevronRight size="1.4em" /> + ))} {props.historyNode.observation?.title || (props.historyNode.step.name as any)} </h4> @@ -225,7 +226,7 @@ function StepContainer(props: StepContainerProps) { </> </HeaderDiv> </GradientBorder> - <ContentDiv hidden={!props.open}> + <ContentDiv hidden={!props.open} isUserInput={isUserInput}> {props.open && false && ( <> <pre className="overflow-x-scroll"> diff --git a/extension/react-app/src/components/UserInputContainer.tsx b/extension/react-app/src/components/UserInputContainer.tsx new file mode 100644 index 00000000..7d6f0d4e --- /dev/null +++ b/extension/react-app/src/components/UserInputContainer.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import ReactMarkdown from "react-markdown"; +import styled from "styled-components"; +import { buttonColor, secondaryDark } from "."; +import HeaderButtonWithText from "./HeaderButtonWithText"; +import { Play, XMark } from "@styled-icons/heroicons-outline"; +import { RootStore } from "../redux/store"; +import { useSelector } from "react-redux"; + +interface UserInputContainerProps { + onDelete: () => void; + children: string; +} + +const StyledDiv = styled.div` + background-color: rgb(50 50 50); + padding: 8px; + padding-left: 16px; + border-bottom: 1px solid white; + border-top: 1px solid white; + font-size: 13px; + display: flex; + align-items: center; + gap: 2px; +`; + +const DeleteButton = styled.button` + position: absolute; + top: 0; + right: 0; + background: none; + border: none; + cursor: pointer; + margin-left: auto; +`; + +const UserInputContainer: React.FC<UserInputContainerProps> = ({ + children, + onDelete, +}) => { + return ( + <StyledDiv> + {children} + <HeaderButtonWithText + onClick={(e) => { + e.stopPropagation(); + onDelete(); + }} + text="Delete" + > + <XMark size="1.6em" onClick={onDelete} /> + </HeaderButtonWithText> + </StyledDiv> + ); +}; + +export default UserInputContainer; diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index fc94c51f..429a7df5 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -1,7 +1,7 @@ import styled, { keyframes } from "styled-components"; export const defaultBorderRadius = "5px"; -export const secondaryDark = "rgb(37 37 38)"; +export const secondaryDark = "rgb(42 42 42)"; export const vscBackground = "rgb(30 30 30)"; export const vscBackgroundTransparent = "#1e1e1ede"; export const buttonColor = "rgb(113 28 59)"; diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index cccd184e..40256f86 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -20,6 +20,7 @@ import { useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import LoadingCover from "../components/LoadingCover"; import { postVscMessage } from "../vscode"; +import UserInputContainer from "../components/UserInputContainer"; const TopGUIDiv = styled.div` overflow: hidden; @@ -152,6 +153,14 @@ function GUI(props: GUIProps) { setHistory(state.history); setHighlightedRanges(state.highlighted_ranges); setUserInputQueue(state.user_input_queue); + setAvailableSlashCommands( + state.slash_commands.map((c: any) => { + return { + name: `/${c.name}`, + description: c.description, + }; + }) + ); setStepsOpen((prev) => { const nextStepsOpen = [...prev]; for ( @@ -168,17 +177,6 @@ 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(() => { @@ -283,7 +281,15 @@ function GUI(props: GUIProps) { </> )} {history?.timeline.map((node: HistoryNode, index: number) => { - return ( + return node.step.name === "User Input" ? ( + <UserInputContainer + onDelete={() => { + client?.deleteAtIndex(index); + }} + > + {node.step.description as string} + </UserInputContainer> + ) : ( <StepContainer isLast={index === history.timeline.length - 1} isFirst={index === 0} @@ -302,9 +308,6 @@ function GUI(props: GUIProps) { }} inFuture={index > history?.current_index} historyNode={node} - onRefinement={(input: string) => { - client?.sendRefinementInput(input, index); - }} onReverse={() => { client?.reverseToIndex(index); }} |