diff options
12 files changed, 211 insertions, 114 deletions
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 5f17b4d2..cee7a2f9 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -464,7 +464,9 @@ class Autopilot(ContinueBaseModel): # Update its description async def update_description(): - step.description = await step.describe(self.continue_sdk.models) + description = await step.describe(self.continue_sdk.models) + if description is not None: + step.description = description # Update subscribers with new description await self.update_subscribers() diff --git a/continuedev/src/continuedev/libs/util/edit_config.py b/continuedev/src/continuedev/libs/util/edit_config.py index 7c8ee76f..f4285bc9 100644 --- a/continuedev/src/continuedev/libs/util/edit_config.py +++ b/continuedev/src/continuedev/libs/util/edit_config.py @@ -79,6 +79,8 @@ filtered_attrs = { "llm", } +filtered_attrs_when_new = {"timeout", "prompt_templates"} + def escape_string(string: str) -> str: return string.replace('"', '\\"').replace("'", "\\'") @@ -90,20 +92,27 @@ def display_val(v: Any): return str(v) -def display_llm_class(llm): - args = ", ".join( +def display_llm_class(llm, new: bool = False): + sep = ",\n\t\t\t" + args = sep.join( [ f"{k}={display_val(v)}" for k, v in llm.dict().items() if k not in filtered_attrs and v is not None ] ) - return f"{llm.__class__.__name__}({args})" + return f"{llm.__class__.__name__}(\n\t\t\t{args}\n\t\t)" -def create_obj_node(class_name: str, args: Dict[str, str]) -> redbaron.RedBaron: +def create_obj_node( + class_name: str, args: Dict[str, str], tabs: int = 1 +) -> redbaron.RedBaron: args = [f"{key}={value}" for key, value in args.items()] - return redbaron.RedBaron(f"{class_name}({', '.join(args)})")[0] + t = "\t" * tabs + new_line = "\n\t" + t + sep = "," + new_line + + return redbaron.RedBaron(f"{class_name}({new_line}{sep.join(args)}\n{t})")[0] def create_string_node(string: str) -> redbaron.RedBaron: diff --git a/continuedev/src/continuedev/plugins/steps/core/core.py b/continuedev/src/continuedev/plugins/steps/core/core.py index 7766a887..97235e6f 100644 --- a/continuedev/src/continuedev/plugins/steps/core/core.py +++ b/continuedev/src/continuedev/plugins/steps/core/core.py @@ -183,33 +183,15 @@ class DefaultModelEditCodeStep(Step): summary_prompt: str = "Please give brief a description of the changes made above using markdown bullet points. Be concise:" async def describe(self, models: Models) -> Coroutine[str, None, None]: - if self._previous_contents.strip() == self._new_contents.strip(): - description = "No edits were made" - else: - changes = "\n".join( - difflib.ndiff( - self._previous_contents.splitlines(), - self._new_contents.splitlines(), - ) - ) - description = await models.medium.complete( - dedent( - f"""\ - Diff summary: "{self.user_input}" - - ```diff - {changes} - ``` - - {self.summary_prompt}""" - ) - ) name = await models.medium.complete( f"Write a very short title to describe this requested change (no quotes): '{self.user_input}'. This is the title:" ) self.name = remove_quotes_and_escapes(name) - return f"{remove_quotes_and_escapes(description)}" + if self._previous_contents.strip() == self._new_contents.strip(): + return "No edits were made" + else: + return None async def get_prompt_parts( self, rif: RangeInFileWithContents, sdk: ContinueSDK, full_file_contents: str @@ -821,6 +803,29 @@ Please output the code to be inserted at the cursor in order to fulfill the user self.name = "Generating summary" + changes = "\n".join( + difflib.ndiff( + self._previous_contents.splitlines(), + self._new_contents.splitlines(), + ) + ) + + self.description = "" + async for chunk in sdk.models.medium.stream_complete( + dedent( + f"""\ + Diff summary: "{self.user_input}" + + ```diff + {changes} + ``` + + {self.summary_prompt}""" + ) + ): + self.description += chunk + await sdk.update_ui() + class EditFileStep(Step): filepath: str diff --git a/continuedev/src/continuedev/plugins/steps/setup_model.py b/continuedev/src/continuedev/plugins/steps/setup_model.py index 2e1fdc14..3dae39c2 100644 --- a/continuedev/src/continuedev/plugins/steps/setup_model.py +++ b/continuedev/src/continuedev/plugins/steps/setup_model.py @@ -1,6 +1,8 @@ from ...core.main import Step from ...core.sdk import ContinueSDK from ...libs.util.paths import getConfigFilePath +from ...models.filesystem import RangeInFile +from ...models.main import Range MODEL_CLASS_TO_MESSAGE = { "OpenAI": "Obtain your OpenAI API key from [here](https://platform.openai.com/account/api-keys) and paste it into the `api_key` field at config.models.default.api_key in `config.py`. Then reload the VS Code window for changes to take effect.", @@ -23,3 +25,12 @@ class SetupModelStep(Step): self.description = MODEL_CLASS_TO_MESSAGE.get( self.model_class, "Please finish setting up this model in `config.py`" ) + + config_contents = await sdk.ide.readFile(getConfigFilePath()) + start = config_contents.find("default=") + len("default=") + end = config_contents.find("unused=") - 1 + range = Range.from_indices(config_contents, start, end) + range.end.line -= 1 + await sdk.ide.highlightCode( + RangeInFile(filepath=getConfigFilePath(), range=range) + ) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 49541b76..770065ac 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -221,6 +221,7 @@ class GUIProtocolServer: ), self.on_error, ) + posthog_logger.capture_event("set_system_message", {"system_message": message}) def set_temperature(self, temperature: float): self.session.autopilot.continue_sdk.config.temperature = temperature @@ -230,6 +231,7 @@ class GUIProtocolServer: ), self.on_error, ) + posthog_logger.capture_event("set_temperature", {"temperature": temperature}) def set_model_for_role_from_index(self, role: str, index: int): async def async_stuff(): @@ -242,7 +244,7 @@ class GUIProtocolServer: await self.session.autopilot.continue_sdk.start_model(models.default) # Set models in config.py - JOINER = ", " + JOINER = ",\n\t\t" models_args = { "unused": f"[{JOINER.join([display_llm_class(llm) for llm in models.unused])}]", ("default" if role == "*" else role): display_llm_class(models.default), @@ -285,9 +287,9 @@ class GUIProtocolServer: if val is None: continue # no pun intended - models_args[role] = display_llm_class(val) + models_args[role] = display_llm_class(val, True) - JOINER = ", " + JOINER = ",\n\t\t" models_args[ "unused" ] = f"[{JOINER.join([display_llm_class(llm) for llm in unused_models])}]" @@ -346,7 +348,7 @@ async def websocket_endpoint( while AppStatus.should_exit is False: message = await websocket.receive_text() logger.debug(f"Received GUI message {message}") - if type(message) is str: + if isinstance(message, str): message = json.loads(message) if "messageType" not in message or "data" not in message: diff --git a/extension/react-app/src/components/Layout.tsx b/extension/react-app/src/components/Layout.tsx index de8b86d8..065b77c6 100644 --- a/extension/react-app/src/components/Layout.tsx +++ b/extension/react-app/src/components/Layout.tsx @@ -3,7 +3,7 @@ import { defaultBorderRadius, secondaryDark, vscForeground } from "."; import { Outlet } from "react-router-dom"; import Onboarding from "./Onboarding"; import TextDialog from "./TextDialog"; -import { useContext, useEffect } from "react"; +import { useContext, useEffect, useState } from "react"; import { GUIClientContext } from "../App"; import { useDispatch, useSelector } from "react-redux"; import { RootStore } from "../redux/store"; @@ -145,29 +145,37 @@ const Layout = () => { <GridDiv> <Outlet /> <Footer> - {localStorage.getItem("hideFeature") === "true" || ( - <SparklesIcon - className="mr-auto cursor-pointer" - onClick={() => { - localStorage.setItem("hideFeature", "true"); - }} - onMouseEnter={() => { - dispatch( - setBottomMessage( - "🎁 New Feature: Use ⌘⇧R automatically debug errors in the terminal (you can click the sparkle icon to make it go away)" - ) - ); - }} - onMouseLeave={() => { - dispatch(setBottomMessage(undefined)); - }} - width="1.3em" - height="1.3em" - color="yellow" - /> + {(localStorage.getItem("hideFeature") === "true" && false) || ( + <div className="mr-auto flex gap-2 items-center"> + <SparklesIcon + className="cursor-pointer" + onClick={() => { + localStorage.setItem("hideFeature", "true"); + }} + onMouseEnter={() => { + dispatch( + setBottomMessage( + "🎁 New Feature: Use ⌘⇧R automatically debug errors in the terminal (you can click the sparkle icon to make it go away)" + ) + ); + }} + onMouseLeave={() => { + dispatch( + setBottomMessageCloseTimeout( + setTimeout(() => { + dispatch(setBottomMessage(undefined)); + }, 2000) + ) + ); + }} + width="1.3em" + height="1.3em" + color="yellow" + /> + + <ModelSelect /> + </div> )} - - <ModelSelect /> <HeaderButtonWithText onClick={() => { client?.loadSession(undefined); diff --git a/extension/react-app/src/components/ModelSelect.tsx b/extension/react-app/src/components/ModelSelect.tsx index 317c164a..dc58da9e 100644 --- a/extension/react-app/src/components/ModelSelect.tsx +++ b/extension/react-app/src/components/ModelSelect.tsx @@ -1,18 +1,21 @@ import styled from "styled-components"; import { defaultBorderRadius, + lightGray, secondaryDark, vscBackground, vscForeground, } from "."; -import { useContext, useEffect } from "react"; +import { useContext } from "react"; import { GUIClientContext } from "../App"; import { RootStore } from "../redux/store"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; +import { PlusIcon } from "@heroicons/react/24/outline"; +import { setDialogMessage, setShowDialog } from "../redux/slices/uiStateSlice"; const MODEL_INFO: { title: string; class: string; args: any }[] = [ { - title: "gpt-4", + title: "OpenAI", class: "OpenAI", args: { model: "gpt-4", @@ -20,7 +23,7 @@ const MODEL_INFO: { title: string; class: string; args: any }[] = [ }, }, { - title: "claude-2", + title: "Anthropic", class: "AnthropicLLM", args: { model: "claude-2", @@ -28,11 +31,6 @@ const MODEL_INFO: { title: string; class: string; args: any }[] = [ }, }, { - title: "GGML", - class: "GGML", - args: {}, - }, - { title: "Ollama", class: "Ollama", args: { @@ -40,6 +38,14 @@ const MODEL_INFO: { title: string; class: string; args: any }[] = [ }, }, { + title: "TogetherAI", + class: "TogetherLLM", + args: { + model: "togethercomputer/CodeLlama-13b-Instruct", + api_key: "<TOGETHER_API_KEY>", + }, + }, + { title: "Replicate", class: "ReplicateLLM", args: { @@ -49,20 +55,19 @@ const MODEL_INFO: { title: string; class: string; args: any }[] = [ }, }, { - title: "TogetherAI", - class: "TogetherLLM", - args: { - model: "togethercomputer/CodeLlama-13b-Instruct", - api_key: "<TOGETHER_API_KEY>", - }, - }, - { title: "llama.cpp", class: "LlamaCpp", args: {}, }, { - title: "gpt-4 (limited free usage)", + title: "Other OpenAI-compatible API", + class: "GGML", + args: { + server_url: "<SERVER_URL>", + }, + }, + { + title: "Continue Free Trial (gpt-4)", class: "MaybeProxyOpenAI", args: { model: "gpt-4", @@ -70,21 +75,56 @@ const MODEL_INFO: { title: string; class: string; args: any }[] = [ }, ]; +const GridDiv = styled.div` + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + border: 0.5px solid ${lightGray}; + border-radius: ${defaultBorderRadius}; + overflow: hidden; +`; + const Select = styled.select` border: none; - width: 25vw; - background-color: ${secondaryDark}; + max-width: 25vw; + background-color: ${vscBackground}; color: ${vscForeground}; - border-radius: ${defaultBorderRadius}; padding: 6px; max-height: 35vh; overflow: scroll; cursor: pointer; - margin-right: auto; &:focus { outline: none; } + &:hover { + background-color: ${secondaryDark}; + } +`; + +const StyledPlusIcon = styled(PlusIcon)` + cursor: pointer; + margin: 0px; + padding-left: 4px; + padding-right: 4px; + height: 100%; + + &:hover { + background-color: ${secondaryDark}; + } + border-left: 0.5px solid ${lightGray}; +`; + +const NewProviderDiv = styled.div` + cursor: pointer; + padding: 8px; + padding-left: 16px; + padding-right: 16px; + border-top: 0.5px solid ${lightGray}; + + &:hover { + background-color: ${secondaryDark}; + } `; function modelSelectTitle(model: any): string { @@ -99,6 +139,7 @@ function modelSelectTitle(model: any): string { } function ModelSelect(props: {}) { + const dispatch = useDispatch(); const client = useContext(GUIClientContext); const defaultModel = useSelector( (state: RootStore) => (state.serverState.config as any)?.models?.default @@ -108,23 +149,20 @@ function ModelSelect(props: {}) { ); return ( - <Select - value={JSON.stringify({ - t: "default", - idx: -1, - })} - defaultValue={0} - onChange={(e) => { - const value = JSON.parse(e.target.value); - if (value.t === "unused") { - client?.setModelForRoleFromIndex("*", value.idx); - } else if (value.t === "new") { - const model = MODEL_INFO[value.idx]; - client?.addModelForRole("*", model.class, model.args); - } - }} - > - <optgroup label="My Saved Models"> + <GridDiv> + <Select + value={JSON.stringify({ + t: "default", + idx: -1, + })} + defaultValue={0} + onChange={(e) => { + const value = JSON.parse(e.target.value); + if (value.t === "unused") { + client?.setModelForRoleFromIndex("*", value.idx); + } + }} + > {defaultModel && ( <option value={JSON.stringify({ @@ -147,22 +185,40 @@ function ModelSelect(props: {}) { </option> ); })} - </optgroup> - <optgroup label="Add New Model"> - {MODEL_INFO.map((model, idx) => { - return ( - <option - value={JSON.stringify({ - t: "new", - idx, - })} - > - {model.title} - </option> + </Select> + + <StyledPlusIcon + width="1.3em" + height="1.3em" + onClick={() => { + dispatch( + setDialogMessage( + <div> + <div className="text-lg font-bold p-2"> + Setup a new model provider + </div> + <br /> + {MODEL_INFO.map((model, idx) => { + return ( + <NewProviderDiv + onClick={() => { + const model = MODEL_INFO[idx]; + client?.addModelForRole("*", model.class, model.args); + dispatch(setShowDialog(false)); + }} + > + {model.title} + </NewProviderDiv> + ); + })} + <br /> + </div> + ) ); - })} - </optgroup> - </Select> + dispatch(setShowDialog(true)); + }} + /> + </GridDiv> ); } diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index 44d25ae6..a9fcbb8f 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -25,12 +25,12 @@ const Dialog = styled.div` color: ${vscForeground}; background-color: ${vscBackground}; border-radius: 8px; - padding: 8px; display: flex; flex-direction: column; box-shadow: 0 0 10px 0 ${vscForeground}; margin: auto; word-wrap: break-word; + overflow: hidden; `; const TextArea = styled.textarea` diff --git a/extension/react-app/src/components/UserInputContainer.tsx b/extension/react-app/src/components/UserInputContainer.tsx index 228c3530..866fef58 100644 --- a/extension/react-app/src/components/UserInputContainer.tsx +++ b/extension/react-app/src/components/UserInputContainer.tsx @@ -2,6 +2,7 @@ import React, { useContext, useEffect, useRef, useState } from "react"; import styled from "styled-components"; import { defaultBorderRadius, + lightGray, secondaryDark, vscBackground, vscForeground, @@ -28,6 +29,9 @@ const StyledDiv = styled.div` padding: 8px; padding-top: 0px; padding-bottom: 0px; + + border-bottom: 0.5px solid ${lightGray}; + border-top: 0.5px solid ${lightGray}; `; const DeleteButtonDiv = styled.div` diff --git a/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx b/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx index f6c7c626..9cd0a95e 100644 --- a/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx +++ b/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx @@ -26,7 +26,7 @@ function AddContextGroupDialog({ }; return ( - <div> + <div className="p-4"> <TextInput defaultValue="My Context Group" type="text" diff --git a/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx b/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx index b8fc2bb7..44095fc0 100644 --- a/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx +++ b/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx @@ -57,7 +57,7 @@ function SelectContextGroupDialog() { const client = useContext(GUIClientContext); return ( - <div className="px-4"> + <div className="p-4"> <h2>Saved Context Groups</h2> {savedContextGroups && Object.keys(savedContextGroups).length > 0 ? ( diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index 99e7cb7b..cb62f7ed 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -277,7 +277,7 @@ function GUI(props: GUIProps) { if (currentCount === 100) { dispatch( setDialogMessage( - <div className="text-center"> + <div className="text-center p-4"> 👋 Thanks for using Continue. We are a beta product and love working closely with our first users. If you're interested in speaking, enter your name and email. We won't use this @@ -293,7 +293,7 @@ function GUI(props: GUIProps) { }); dispatch( setDialogMessage( - <div className="text-center"> + <div className="text-center p-4"> Thanks! We'll be in touch soon. </div> ) |