diff options
| author | Nate Sesti <33237525+sestinj@users.noreply.github.com> | 2023-09-23 13:06:00 -0700 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-23 13:06:00 -0700 | 
| commit | e976d60974a7837967d03807605cbf2e7b4f3f9a (patch) | |
| tree | 5ecb19062abb162832530dd953e9d2801026c23c /continuedev/src/continuedev/server | |
| parent | 470711d25b44d1a545c57bc17d40d5e1fd402216 (diff) | |
| download | sncontinue-e976d60974a7837967d03807605cbf2e7b4f3f9a.tar.gz sncontinue-e976d60974a7837967d03807605cbf2e7b4f3f9a.tar.bz2 sncontinue-e976d60974a7837967d03807605cbf2e7b4f3f9a.zip  | |
UI Redesign and fixing many details (#496)
* feat: :lipstick: start of major design upgrade
* feat: :lipstick: model selection page
* feat: :lipstick: use shortcut to add highlighted code as ctx
* feat: :lipstick: better display of errors
* feat: :lipstick: ui for learning keyboard shortcuts, more details
* refactor: :construction: testing slash commands ui
* Truncate continue.log
* refactor: :construction: refactoring client_session, ui, more
* feat: :bug: layout fixes
* refactor: :lipstick: ui to enter OpenAI Key
* refactor: :truck: rename MaybeProxyOpenAI -> OpenAIFreeTrial
* starting help center
* removing old shortcut docs
* fix: :bug: fix model setting logic to avoid overwrites
* feat: :lipstick: tutorial and model descriptions
* refactor: :truck: rename unused -> saved
* refactor: :truck: rename model roles
* feat: :lipstick: edit indicator
* refactor: :lipstick: move +, folder icons
* feat: :lipstick: tab to clear all context
* fix: :bug: context providers ui fixes
* fix: :bug: fix lag when stopping step
* fix: :bug: don't override system message for models
* fix: :bug: fix continue button cursor
* feat: :lipstick: title bar
* fix: :bug: updates to code highlighting logic and more
* fix: :bug: fix renaming of summarize model role
* feat: :lipstick: help page and better session title
* feat: :lipstick: more help page / ui improvements
* feat: :lipstick: set session title
* fix: :bug: small fixes for changing sessions
* fix: :bug: perfecting the highlighting code and ctx interactions
* style: :lipstick: sticky headers for scroll, ollama warming
* fix: :bug: fix toggle bug
---------
Co-authored-by: Ty Dunn <ty@tydunn.com>
Diffstat (limited to 'continuedev/src/continuedev/server')
| -rw-r--r-- | continuedev/src/continuedev/server/gui.py | 88 | ||||
| -rw-r--r-- | continuedev/src/continuedev/server/ide.py | 28 | ||||
| -rw-r--r-- | continuedev/src/continuedev/server/ide_protocol.py | 8 | ||||
| -rw-r--r-- | continuedev/src/continuedev/server/meilisearch_server.py | 2 | 
4 files changed, 88 insertions, 38 deletions
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 770065ac..9d2ea47a 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -82,7 +82,9 @@ class GUIProtocolServer:          return resp_model.parse_obj(resp)      def on_error(self, e: Exception): -        return self.session.autopilot.continue_sdk.run_step(DisplayErrorStep(e=e)) +        return self.session.autopilot.continue_sdk.run_step( +            DisplayErrorStep.from_exception(e) +        )      def handle_json(self, message_type: str, data: Any):          if message_type == "main_input": @@ -97,6 +99,8 @@ class GUIProtocolServer:              self.on_retry_at_index(data["index"])          elif message_type == "clear_history":              self.on_clear_history() +        elif message_type == "set_current_session_title": +            self.set_current_session_title(data["title"])          elif message_type == "delete_at_index":              self.on_delete_at_index(data["index"])          elif message_type == "delete_context_with_ids": @@ -107,6 +111,8 @@ class GUIProtocolServer:              self.on_set_editing_at_ids(data["ids"])          elif message_type == "show_logs_at_index":              self.on_show_logs_at_index(data["index"]) +        elif message_type == "show_context_virtual_file": +            self.show_context_virtual_file()          elif message_type == "select_context_item":              self.select_context_item(data["id"], data["query"])          elif message_type == "load_session": @@ -180,11 +186,9 @@ class GUIProtocolServer:          create_async_task(self.session.autopilot.set_editing_at_ids(ids), self.on_error)      def on_show_logs_at_index(self, index: int): -        name = "continue_logs.txt" +        name = "Continue Context"          logs = "\n\n############################################\n\n".join( -            [ -                "This is a log of the prompt/completion pairs sent/received from the LLM during this step" -            ] +            ["This is the prompt sent to the LLM during this step"]              + self.session.autopilot.continue_sdk.history.timeline[index].logs          )          create_async_task( @@ -192,6 +196,22 @@ class GUIProtocolServer:          )          posthog_logger.capture_event("show_logs_at_index", {}) +    def show_context_virtual_file(self): +        async def async_stuff(): +            msgs = await self.session.autopilot.continue_sdk.get_chat_context() +            ctx = "\n\n-----------------------------------\n\n".join( +                ["This is the exact context that will be passed to the LLM"] +                + list(map(lambda x: x.content, msgs)) +            ) +            await self.session.autopilot.ide.showVirtualFile( +                "Continue - Selected Context", ctx +            ) + +        create_async_task( +            async_stuff(), +            self.on_error, +        ) +      def select_context_item(self, id: str, query: str):          """Called when user selects an item from the dropdown"""          create_async_task( @@ -211,6 +231,9 @@ class GUIProtocolServer:          posthog_logger.capture_event("load_session", {"session_id": session_id}) +    def set_current_session_title(self, title: str): +        self.session.autopilot.set_current_session_title(title) +      def set_system_message(self, message: str):          self.session.autopilot.continue_sdk.config.system_message = message          self.session.autopilot.continue_sdk.models.set_system_message(message) @@ -239,14 +262,14 @@ class GUIProtocolServer:              # Set models in SDK              temp = models.default -            models.default = models.unused[index] -            models.unused[index] = temp +            models.default = models.saved[index] +            models.saved[index] = temp              await self.session.autopilot.continue_sdk.start_model(models.default)              # Set models in config.py              JOINER = ",\n\t\t"              models_args = { -                "unused": f"[{JOINER.join([display_llm_class(llm) for llm in models.unused])}]", +                "saved": f"[{JOINER.join([display_llm_class(llm) for llm in models.saved])}]",                  ("default" if role == "*" else role): display_llm_class(models.default),              } @@ -265,48 +288,59 @@ class GUIProtocolServer:      def add_model_for_role(self, role: str, model_class: str, model: Any):          models = self.session.autopilot.continue_sdk.config.models -        unused_models = models.unused          if role == "*":              async def async_stuff(): -                for role in ALL_MODEL_ROLES: -                    models.__setattr__(role, None) - -                # Set and start the default model if didn't already exist from unused -                models.default = MODEL_CLASSES[model_class](**model) -                await self.session.autopilot.continue_sdk.run_step( -                    SetupModelStep(model_class=model_class) +                # Remove all previous models in roles and place in saved +                saved_models = models.saved +                existing_saved_models = set( +                    [display_llm_class(llm) for llm in saved_models]                  ) - -                await self.session.autopilot.continue_sdk.start_model(models.default) - -                models_args = {} -                  for role in ALL_MODEL_ROLES:                      val = models.__getattribute__(role) -                    if val is None: -                        continue  # no pun intended +                    if ( +                        val is not None +                        and display_llm_class(val) not in existing_saved_models +                    ): +                        saved_models.append(val) +                        existing_saved_models.add(display_llm_class(val)) +                    models.__setattr__(role, None) -                    models_args[role] = display_llm_class(val, True) +                # Set and start the new default model +                new_model = MODEL_CLASSES[model_class](**model) +                models.default = new_model +                await self.session.autopilot.continue_sdk.start_model(models.default) +                # Construct and set the new models object                  JOINER = ",\n\t\t" -                models_args[ -                    "unused" -                ] = f"[{JOINER.join([display_llm_class(llm) for llm in unused_models])}]" +                saved_model_strings = set( +                    [display_llm_class(llm) for llm in saved_models] +                ) +                models_args = { +                    "default": display_llm_class(models.default, True), +                    "saved": f"[{JOINER.join(saved_model_strings)}]", +                }                  await self.session.autopilot.set_config_attr(                      ["models"],                      create_obj_node("Models", models_args),                  ) +                # Add the requisite import to config.py                  add_config_import(                      f"from continuedev.src.continuedev.libs.llm.{MODEL_MODULE_NAMES[model_class]} import {model_class}"                  ) +                # Set all roles (in-memory) to the new default model                  for role in ALL_MODEL_ROLES:                      if role != "default":                          models.__setattr__(role, models.default) +                # Display setup help +                await self.session.autopilot.continue_sdk.run_step( +                    SetupModelStep(model_class=model_class) +                ) +              create_async_task(async_stuff(), self.on_error)          else:              # TODO diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 7396b1db..d4f0690b 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -4,7 +4,7 @@ import json  import os  import traceback  import uuid -from typing import Any, Callable, Coroutine, List, Type, TypeVar, Union +from typing import Any, Callable, Coroutine, List, Optional, Type, TypeVar, Union  import nest_asyncio  from fastapi import APIRouter, WebSocket @@ -232,7 +232,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer):              self.onFileEdits(fileEdits)          elif message_type == "highlightedCodePush":              self.onHighlightedCodeUpdate( -                [RangeInFileWithContents(**rif) for rif in data["highlightedCode"]] +                [RangeInFileWithContents(**rif) for rif in data["highlightedCode"]], +                edit=data.get("edit", None),              )          elif message_type == "commandOutput":              output = data["output"] @@ -243,7 +244,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):          elif message_type == "acceptRejectSuggestion":              self.onAcceptRejectSuggestion(data["accepted"])          elif message_type == "acceptRejectDiff": -            self.onAcceptRejectDiff(data["accepted"]) +            self.onAcceptRejectDiff(data["accepted"], data["stepIndex"])          elif message_type == "mainUserInput":              self.onMainUserInput(data["input"])          elif message_type == "deleteAtIndex": @@ -349,10 +350,17 @@ class IdeProtocolServer(AbstractIdeProtocolServer):          posthog_logger.capture_event("accept_reject_suggestion", {"accepted": accepted})          dev_data_logger.capture("accept_reject_suggestion", {"accepted": accepted}) -    def onAcceptRejectDiff(self, accepted: bool): +    def onAcceptRejectDiff(self, accepted: bool, step_index: int):          posthog_logger.capture_event("accept_reject_diff", {"accepted": accepted})          dev_data_logger.capture("accept_reject_diff", {"accepted": accepted}) +        if not accepted: +            if autopilot := self.__get_autopilot(): +                create_async_task( +                    autopilot.reject_diff(step_index), +                    self.on_error, +                ) +      def onFileSystemUpdate(self, update: FileSystemEdit):          # Access to Autopilot (so SessionManager)          pass @@ -387,10 +395,14 @@ class IdeProtocolServer(AbstractIdeProtocolServer):          if autopilot := self.__get_autopilot():              create_async_task(autopilot.handle_debug_terminal(content), self.on_error) -    def onHighlightedCodeUpdate(self, range_in_files: List[RangeInFileWithContents]): +    def onHighlightedCodeUpdate( +        self, +        range_in_files: List[RangeInFileWithContents], +        edit: Optional[bool] = False, +    ):          if autopilot := self.__get_autopilot():              create_async_task( -                autopilot.handle_highlighted_code(range_in_files), self.on_error +                autopilot.handle_highlighted_code(range_in_files, edit), self.on_error              )      ## Subscriptions ## @@ -456,7 +468,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):          resp = await self._send_and_receive_json(              {"commands": commands}, TerminalContentsResponse, "getTerminalContents"          ) -        return resp.contents +        return resp.contents.strip()      async def getHighlightedCode(self) -> List[RangeInFile]:          resp = await self._send_and_receive_json( @@ -640,7 +652,7 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None):          if session_id is not None and session_id in session_manager.sessions:              await session_manager.sessions[session_id].autopilot.continue_sdk.run_step( -                DisplayErrorStep(e=e) +                DisplayErrorStep.from_exception(e)              )          elif ideProtocolServer is not None:              await ideProtocolServer.showMessage(f"Error in Continue server: {err_msg}") diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 34030047..015da767 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -1,5 +1,5 @@  from abc import ABC, abstractmethod -from typing import Any, Callable, List, Union +from typing import Any, Callable, List, Optional, Union  from fastapi import WebSocket @@ -104,7 +104,11 @@ class AbstractIdeProtocolServer(ABC):          """Run a command"""      @abstractmethod -    def onHighlightedCodeUpdate(self, range_in_files: List[RangeInFileWithContents]): +    def onHighlightedCodeUpdate( +        self, +        range_in_files: List[RangeInFileWithContents], +        edit: Optional[bool] = False, +    ):          """Called when highlighted code is updated"""      @abstractmethod diff --git a/continuedev/src/continuedev/server/meilisearch_server.py b/continuedev/src/continuedev/server/meilisearch_server.py index 40d46b18..5e6cdd53 100644 --- a/continuedev/src/continuedev/server/meilisearch_server.py +++ b/continuedev/src/continuedev/server/meilisearch_server.py @@ -78,7 +78,7 @@ async def ensure_meilisearch_installed() -> bool:                  pass              existing_paths.remove(meilisearchPath) -            await download_meilisearch() +        await download_meilisearch()          # Clear the existing directories          for p in existing_paths:  | 
