diff options
| author | Nate Sesti <sestinj@gmail.com> | 2023-06-12 21:30:49 -0700 | 
|---|---|---|
| committer | Nate Sesti <sestinj@gmail.com> | 2023-06-12 21:30:49 -0700 | 
| commit | cc0d73c2c1351a08c95654f7792f56c1d3d0ab54 (patch) | |
| tree | a94ed31d6024ca7faa3fb057ca5fcfd0792b393e | |
| parent | b2cdc7edb0db303fa0e2f82bfa26729102a2f111 (diff) | |
| download | sncontinue-cc0d73c2c1351a08c95654f7792f56c1d3d0ab54.tar.gz sncontinue-cc0d73c2c1351a08c95654f7792f56c1d3d0ab54.tar.bz2 sncontinue-cc0d73c2c1351a08c95654f7792f56c1d3d0ab54.zip | |
slash commands dropdown!
22 files changed, 547 insertions, 242 deletions
| 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 @@ -28,6 +28,10 @@ class AbstractGUIProtocolServer(ABC):          """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<HTMLInputElement>) => 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<HTMLDivElement>(null); +  const ulRef = React.useRef<HTMLUListElement>(null); +  const showAbove = () => { +    return (divRef.current?.getBoundingClientRect().top || 0) > UlMaxHeight; +  }; + +  return ( +    <div className="flex px-2" ref={divRef} hidden={!isOpen}> +      <MainTextInput +        disabled={props.disabled} +        placeholder="Ask anything:" +        {...getInputProps({ +          onKeyDown: (event) => { +            if (event.key === "Enter" && (!isOpen || items.length === 0)) { +              // Prevent Downshift's default 'Enter' behavior. +              (event.nativeEvent as any).preventDownshiftDefault = true; +              if (props.onEnter) props.onEnter(event); +              setInputValue(""); +            } +          }, +          ref: ref as any, +        })} +      /> +      <Ul +        {...getMenuProps({ +          ref: ulRef, +        })} +        showAbove={showAbove()} +        ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0} +      > +        {isOpen && +          items.map((item, index) => ( +            <Li +              key={`${item.name}${index}`} +              {...getItemProps({ item, index })} +              highlighted={highlightedIndex === index} +              selected={selectedItem === item} +            > +              <span> +                {item.name}: {item.description} +              </span> +            </Li> +          ))} +      </Ul> +    </div> +  ); +}); + +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<string[]>([]); -  const [history, setHistory] = useState<History | undefined>({ -    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 <module>\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: "<module>", -                  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 <module>\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 <module>\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: "<module>", -                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 <module>\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<History | undefined>(); +  // { +  //   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 <module>\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: "<module>", +  //                 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 <module>\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 <module>\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: "<module>", +  //               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 <module>\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<HTMLDivElement>(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<HTMLTextAreaElement>(null); +  const mainTextInputRef = useRef<HTMLInputElement>(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) {        }}      >        <TopBar> -        <h3>Continue</h3> +        <a href="https://continue.dev/docs" className="no-underline"> +          <HeaderButton style={{ padding: "3px" }}> +            Continue Docs +            <BookOpen size="1.6em" /> +          </HeaderButton> +        </a>          <HeaderButton style={{ padding: "3px" }}>            Clear History            <Trash @@ -323,7 +342,7 @@ function GUI(props: GUIProps) {          })}        </div> -      <MainTextInput +      <ComboBox          disabled={            history              ? history.timeline[history.current_index].step.name === @@ -331,22 +350,13 @@ function GUI(props: GUIProps) {              : false          }          ref={mainTextInputRef} -        onKeyDown={(e) => { -          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}        />        <ContinueButton onClick={onMainTextInput} />      </TopGUIDiv> diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whlBinary files differ index 42f3d4a3..3d7639a9 100644 --- a/extension/scripts/continuedev-0.1.1-py3-none-any.whl +++ b/extension/scripts/continuedev-0.1.1-py3-none-any.whl | 
