summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNate Sesti <sestinj@gmail.com>2023-06-06 09:26:54 -0400
committerNate Sesti <sestinj@gmail.com>2023-06-06 09:26:54 -0400
commit5cb69176f731d6e93802e29eb37c5c5d83003be2 (patch)
tree5cdd3b786e0391648a265be117c53f9162f64f18
parent9f33cac01eef7cbe15dafb4bd51666195f120d69 (diff)
parent60eaf08df63b77ce31ce8afaa77fdd6b357c8a8a (diff)
downloadsncontinue-5cb69176f731d6e93802e29eb37c5c5d83003be2.tar.gz
sncontinue-5cb69176f731d6e93802e29eb37c5c5d83003be2.tar.bz2
sncontinue-5cb69176f731d6e93802e29eb37c5c5d83003be2.zip
Merge branch 'design'
-rw-r--r--continuedev/src/continuedev/core/autopilot.py34
-rw-r--r--continuedev/src/continuedev/core/main.py30
-rw-r--r--continuedev/src/continuedev/core/policy.py7
-rw-r--r--continuedev/src/continuedev/core/sdk.py21
-rw-r--r--continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py2
-rw-r--r--continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py62
-rw-r--r--continuedev/src/continuedev/server/ide.py11
-rw-r--r--continuedev/src/continuedev/server/ide_protocol.py8
-rw-r--r--continuedev/src/continuedev/steps/core/core.py27
-rw-r--r--extension/package.json4
-rw-r--r--extension/react-app/src/components/DebugPanel.tsx15
-rw-r--r--extension/react-app/src/components/StepContainer.tsx52
-rw-r--r--extension/react-app/src/components/index.ts11
-rw-r--r--extension/react-app/src/tabs/chat/MessageDiv.tsx1
-rw-r--r--extension/react-app/src/tabs/gui.tsx74
-rw-r--r--extension/src/continueIdeClient.ts39
16 files changed, 286 insertions, 112 deletions
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index db06c975..d55e521b 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -1,13 +1,13 @@
import traceback
import time
-from typing import Callable, Coroutine, List
+from typing import Any, Callable, Coroutine, Dict, List
from ..models.filesystem_edit import FileEditWithFullContents
from ..libs.llm import LLM
from .observation import Observation, InternalErrorObservation
from ..server.ide_protocol import AbstractIdeProtocolServer
from ..libs.util.queue import AsyncSubscriptionQueue
from ..models.main import ContinueBaseModel
-from .main import Policy, History, FullState, Step, HistoryNode
+from .main import Context, ContinueCustomException, Policy, History, FullState, Step, HistoryNode
from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep
from ..libs.util.telemetry import capture_event
from .sdk import ContinueSDK
@@ -18,6 +18,7 @@ class Autopilot(ContinueBaseModel):
policy: Policy
ide: AbstractIdeProtocolServer
history: History = History.from_empty()
+ context: Context = Context()
_on_update_callbacks: List[Callable[[FullState], None]] = []
_active: bool = False
@@ -26,6 +27,10 @@ class Autopilot(ContinueBaseModel):
_user_input_queue = AsyncSubscriptionQueue()
+ @property
+ def continue_sdk(self) -> ContinueSDK:
+ return ContinueSDK(self)
+
class Config:
arbitrary_types_allowed = True
@@ -60,7 +65,7 @@ class Autopilot(ContinueBaseModel):
current_step = self.history.get_current().step
self.history.step_back()
if issubclass(current_step.__class__, ReversibleStep):
- await current_step.reverse(ContinueSDK(self))
+ await current_step.reverse(self.continue_sdk)
await self.update_subscribers()
except Exception as e:
@@ -105,7 +110,16 @@ class Autopilot(ContinueBaseModel):
self._step_depth += 1
try:
- observation = await step(ContinueSDK(self))
+ observation = await step(self.continue_sdk)
+ except ContinueCustomException as e:
+ # Attach an InternalErrorObservation to the step and unhide it.
+ error_string = e.message
+ print(
+ f"\n{error_string}\n{e}")
+
+ observation = InternalErrorObservation(
+ error=error_string)
+ step.hide = False
except Exception as e:
# Attach an InternalErrorObservation to the step and unhide it.
error_string = '\n\n'.join(
@@ -125,11 +139,13 @@ class Autopilot(ContinueBaseModel):
await self.update_subscribers()
# Update its description
- async def update_description():
- step._set_description(await step.describe(ContinueSDK(self).models))
- # Update subscribers with new description
- await self.update_subscribers()
- asyncio.create_task(update_description())
+ if step.description is None:
+ async def update_description():
+ step.description = await step.describe(self.continue_sdk.models)
+ # Update subscribers with new description
+ await self.update_subscribers()
+
+ asyncio.create_task(update_description())
return observation
diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py
index b2b97bae..17b30e96 100644
--- a/continuedev/src/continuedev/core/main.py
+++ b/continuedev/src/continuedev/core/main.py
@@ -105,7 +105,7 @@ class Policy(ContinueBaseModel):
class Step(ContinueBaseModel):
name: str = None
hide: bool = False
- _description: Union[str, None] = None
+ description: Union[str, None] = None
system_message: Union[str, None] = None
@@ -113,17 +113,14 @@ class Step(ContinueBaseModel):
copy_on_model_validation = False
async def describe(self, models: Models) -> Coroutine[str, None, None]:
- if self._description is not None:
- return self._description
+ if self.description is not None:
+ return self.description
return "Running step: " + self.name
- def _set_description(self, description: str):
- self._description = description
-
def dict(self, *args, **kwargs):
d = super().dict(*args, **kwargs)
- if self._description is not None:
- d["description"] = self._description
+ if self.description is not None:
+ d["description"] = self.description
else:
d["description"] = "`Description loading...`"
return d
@@ -173,4 +170,21 @@ class Validator(Step):
raise NotImplementedError
+class Context:
+ key_value: Dict[str, str] = {}
+
+ def set(self, key: str, value: str):
+ self.key_value[key] = value
+
+ def get(self, key: str) -> str:
+ return self.key_value[key]
+
+
+class ContinueCustomException(Exception):
+ message: str
+
+ def __init__(self, message: str):
+ self.message = message
+
+
HistoryNode.update_forward_refs()
diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index e65b6c9d..4934497d 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -17,9 +17,10 @@ class DemoPolicy(Policy):
def next(self, history: History) -> Step:
# At the very start, run initial Steps spcecified in the config
if history.get_current() is None:
- return (MessageStep(message="Welcome to Continue!") >>
- SetupContinueWorkspaceStep() >>
- CreateCodebaseIndexChroma() >>
+ return (
+ MessageStep(name="Welcome to Continue!", message="") >>
+ # SetupContinueWorkspaceStep() >>
+ # CreateCodebaseIndexChroma() >>
StepsOnStartupStep())
observation = history.get_current().observation
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index 8317a3d1..690949f1 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -11,7 +11,7 @@ from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI
from ..libs.llm.openai import OpenAI
from .observation import Observation
from ..server.ide_protocol import AbstractIdeProtocolServer
-from .main import History, Step
+from .main import Context, ContinueCustomException, History, Step
from ..steps.core.core import *
@@ -44,6 +44,7 @@ class ContinueSDK(AbstractContinueSDK):
ide: AbstractIdeProtocolServer
steps: ContinueSDKSteps
models: Models
+ context: Context
__autopilot: Autopilot
def __init__(self, autopilot: Autopilot):
@@ -51,6 +52,7 @@ class ContinueSDK(AbstractContinueSDK):
self.__autopilot = autopilot
self.steps = ContinueSDKSteps(self)
self.models = Models(self)
+ self.context = autopilot.context
@property
def history(self) -> History:
@@ -64,8 +66,8 @@ class ContinueSDK(AbstractContinueSDK):
async def run_step(self, step: Step) -> Coroutine[Observation, None, None]:
return await self.__autopilot._run_singular_step(step)
- async def apply_filesystem_edit(self, edit: FileSystemEdit):
- return await self.run_step(FileSystemEditStep(edit=edit))
+ async def apply_filesystem_edit(self, edit: FileSystemEdit, name: str = None, description: str = None):
+ return await self.run_step(FileSystemEditStep(edit=edit, description=description, **({'name': name} if name else {})))
async def wait_for_user_input(self) -> str:
return await self.__autopilot.wait_for_user_input()
@@ -73,18 +75,20 @@ class ContinueSDK(AbstractContinueSDK):
async def wait_for_user_confirmation(self, prompt: str):
return await self.run_step(WaitForUserConfirmationStep(prompt=prompt))
- async def run(self, commands: Union[List[str], str], cwd: str = None):
+ async def run(self, commands: Union[List[str], str], cwd: str = None, name: str = None, description: str = None):
commands = commands if isinstance(commands, List) else [commands]
- return await self.run_step(ShellCommandsStep(cmds=commands, cwd=cwd))
+ return await self.run_step(ShellCommandsStep(cmds=commands, cwd=cwd, description=description, **({'name': name} if name else {})))
- async def edit_file(self, filename: str, prompt: str):
+ async def edit_file(self, filename: str, prompt: str, name: str = None, description: str = None):
filepath = await self._ensure_absolute_path(filename)
await self.ide.setFileOpen(filepath)
contents = await self.ide.readFile(filepath)
await self.run_step(EditCodeStep(
range_in_files=[RangeInFile.from_entire_file(filepath, contents)],
- prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n'
+ prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n',
+ description=description,
+ **({'name': name} if name else {})
))
async def append_to_file(self, filename: str, content: str):
@@ -126,3 +130,6 @@ class ContinueSDK(AbstractContinueSDK):
def set_loading_message(self, message: str):
# self.__autopilot.set_loading_message(message)
raise NotImplementedError()
+
+ def raise_exception(self, message: str):
+ raise ContinueCustomException(message)
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
index 55c25da4..1206db0e 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
+++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
@@ -12,7 +12,7 @@ class CreatePipelineRecipe(Step):
async def run(self, sdk: ContinueSDK):
text_observation = await sdk.run_step(
- MessageStep(message=dedent("""\
+ MessageStep(name="Building your first dlt pipeline", message=dedent("""\
This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will:
- Create a Python virtual environment with dlt installed
- Run `dlt init` to generate a pipeline template
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
index ef5e3b43..3c8277c0 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
+++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
@@ -1,8 +1,12 @@
+import os
+import subprocess
from textwrap import dedent
+from ...models.main import Range
+from ...models.filesystem import RangeInFile
from ...steps.main import MessageStep
from ...core.sdk import Models
-from ...core.observation import DictObservation
+from ...core.observation import DictObservation, InternalErrorObservation
from ...models.filesystem_edit import AddFile
from ...core.main import Step
from ...core.sdk import ContinueSDK
@@ -33,45 +37,69 @@ class SetupPipelineStep(Step):
f'dlt init {source_name} duckdb',
'Y',
'pip install -r requirements.txt'
- ])
-
+ ], description=dedent(f"""\
+ Running the following commands:
+ - `python3 -m venv env`: Create a Python virtual environment
+ - `source env/bin/activate`: Activate the virtual environment
+ - `pip install dlt`: Install dlt
+ - `dlt init {source_name} duckdb`: Create a new dlt pipeline called {source_name} that loads data into a local DuckDB instance
+ - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline"""))
+
+ await sdk.wait_for_user_confirmation("Wait for the commands to finish running, then press `Continue`")
# editing the resource function to call the requested API
+ await sdk.ide.highlightCode(RangeInFile(filepath=os.path.join(await sdk.ide.getWorkspaceDirectory(), filename), range=Range.from_shorthand(15, 0, 30, 0)), "#00ff0022")
+
await sdk.edit_file(
filename=filename,
- prompt=f'Edit the resource function to call the API described by this: {self.api_description}'
+ prompt=f'Edit the resource function to call the API described by this: {self.api_description}',
+ name="Edit the resource function to call the API"
)
# wait for user to put API key in secrets.toml
await sdk.ide.setFileOpen(await sdk.ide.getWorkspaceDirectory() + "/.dlt/secrets.toml")
await sdk.wait_for_user_confirmation("If this service requires an API key, please add it to the `secrets.toml` file and then press `Continue`")
- return DictObservation(values={"source_name": source_name})
+
+ sdk.context.set("source_name", source_name)
class ValidatePipelineStep(Step):
hide: bool = True
async def run(self, sdk: ContinueSDK):
- source_name = sdk.history.last_observation().values["source_name"]
+ workspace_dir = await sdk.ide.getWorkspaceDirectory()
+ source_name = sdk.context.get("source_name")
filename = f'{source_name}.py'
- await sdk.run_step(MessageStep(message=dedent("""\
- This step will validate that your dlt pipeline is working as expected:
- - Test that the API call works
- - Load the data into a local DuckDB instance
- - Write a query to view the data
- """)))
+ # await sdk.run_step(MessageStep(name="Validate the pipeline", message=dedent("""\
+ # Next, we will validate that your dlt pipeline is working as expected:
+ # - Test that the API call works
+ # - Load the data into a local DuckDB instance
+ # - Write a query to view the data
+ # """)))
# test that the API call works
- await sdk.run(f'python3 {filename}')
+
+ p = subprocess.run(
+ ['python3', f'{filename}'], capture_output=True, text=True, cwd=workspace_dir)
+ err = p.stderr
+
+ # If it fails, return the error
+ if err is not None and err != "":
+ sdk.raise_exception(
+ f"Error while running pipeline. Fix the resource function in {filename} and rerun this step: \n\n" + err)
+
+ await sdk.run(f'python3 {filename}', name="Test the pipeline", description=f"Running python3 {filename} to test loading data from the API")
# remove exit() from the main main function
await sdk.edit_file(
filename=filename,
- prompt='Remove exit() from the main function'
+ prompt='Remove exit() from the main function',
+ name="Remove early exit() from main function",
+ description="Remove the `exit()` call from the main function in the pipeline file so that the data is loaded into DuckDB"
)
# load the data into the DuckDB instance
- await sdk.run(f'python3 {filename}')
+ await sdk.run(f'python3 {filename}', name="Load data into DuckDB", description=f"Running python3 {filename} to load data into DuckDB")
table_name = f"{source_name}.{source_name}_resource"
tables_query_code = dedent(f'''\
@@ -89,5 +117,5 @@ class ValidatePipelineStep(Step):
''')
query_filename = (await sdk.ide.getWorkspaceDirectory()) + "/query.py"
- await sdk.apply_filesystem_edit(AddFile(filepath=query_filename, content=tables_query_code))
- await sdk.run('env/bin/python3 query.py')
+ await sdk.apply_filesystem_edit(AddFile(filepath=query_filename, content=tables_query_code), name="Add query.py file", description="Adding a file called `query.py` to the workspace that will run a test query on the DuckDB instance")
+ await sdk.run('env/bin/python3 query.py', name="Run test query", description="Running `env/bin/python3 query.py` to test that the data was loaded into DuckDB as expected")
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index eec5b607..073e1dba 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -133,6 +133,17 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
"sessionId": session_id
})
+ async def highlightCode(self, range_in_file: RangeInFile, color: str):
+ await self._send_json("highlightCode", {
+ "rangeInFile": range_in_file.dict(),
+ "color": color
+ })
+
+ async def runCommand(self, command: str):
+ await self._send_json("runCommand", {
+ "command": command
+ })
+
async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool:
ids = [str(uuid.uuid4()) for _ in suggestions]
for i in range(len(suggestions)):
diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py
index 8f155415..f42de68f 100644
--- a/continuedev/src/continuedev/server/ide_protocol.py
+++ b/continuedev/src/continuedev/server/ide_protocol.py
@@ -82,3 +82,11 @@ class AbstractIdeProtocolServer(ABC):
@abstractmethod
async def getUserSecret(self, key: str):
"""Get a user secret"""
+
+ @abstractmethod
+ async def highlightCode(self, range_in_file: RangeInFile, color: str):
+ """Highlight code"""
+
+ @abstractmethod
+ async def runCommand(self, command: str):
+ """Run a command"""
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index ad468595..99786b00 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -55,18 +55,21 @@ class ShellCommandsStep(Step):
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd
- process = subprocess.Popen(
- '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd)
+ for cmd in self.cmds:
+ await sdk.ide.runCommand(cmd)
- stdin_input = "\n".join(self.cmds)
- out, err = process.communicate(stdin_input.encode())
+ # process = subprocess.Popen(
+ # '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd)
- # If it fails, return the error
- if err is not None and err != "":
- self._err_text = err
- return TextObservation(text=err)
+ # stdin_input = "\n".join(self.cmds)
+ # out, err = process.communicate(stdin_input.encode())
- return None
+ # # If it fails, return the error
+ # if err is not None and err != "":
+ # self._err_text = err
+ # return TextObservation(text=err)
+
+ # return None
class EditCodeStep(Step):
@@ -197,10 +200,10 @@ class WaitForUserInputStep(Step):
if self._response is None:
return self.prompt
else:
- return self.prompt + "\n\n" + self._response
+ return f"{self.prompt}\n\n`{self._response}`"
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- self._description = self.prompt
+ self.description = self.prompt
resp = await sdk.wait_for_user_input()
self._response = resp
return TextObservation(text=resp)
@@ -214,7 +217,7 @@ class WaitForUserConfirmationStep(Step):
return self.prompt
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- self._description = self.prompt
+ self.description = self.prompt
resp = await sdk.wait_for_user_input()
self.hide = True
return TextObservation(text=resp)
diff --git a/extension/package.json b/extension/package.json
index 7bd48f98..c834f402 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -42,12 +42,12 @@
"description": "The URL of the Continue server to use."
},
"continue.OPENAI_API_KEY": {
- "type": "string",
+ "type": "password",
"default": "",
"description": "The OpenAI API key to use for code generation."
},
"continue.HUGGING_FACE_TOKEN": {
- "type": "string",
+ "type": "password",
"default": "",
"description": "The Hugging Face API token to use for code generation."
}
diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx
index 9dacc624..11ec2fe2 100644
--- a/extension/react-app/src/components/DebugPanel.tsx
+++ b/extension/react-app/src/components/DebugPanel.tsx
@@ -9,7 +9,7 @@ import {
} from "../redux/slices/configSlice";
import { setHighlightedCode } from "../redux/slices/miscSlice";
import { updateFileSystem } from "../redux/slices/debugContexSlice";
-import { buttonColor, defaultBorderRadius, vscBackground } from ".";
+import { defaultBorderRadius, secondaryDark, vscBackground } from ".";
interface DebugPanelProps {
tabs: {
element: React.ReactElement;
@@ -19,14 +19,15 @@ interface DebugPanelProps {
const GradientContainer = styled.div`
// Uncomment to get gradient border
- background: linear-gradient(
+ /* background: linear-gradient(
101.79deg,
#12887a 0%,
#87245c 37.64%,
#e12637 65.98%,
#ffb215 110.45%
- );
+ ); */
/* padding: 10px; */
+ background-color: ${secondaryDark};
margin: 0;
height: 100%;
/* border: 1px solid white; */
@@ -36,11 +37,8 @@ const GradientContainer = styled.div`
const MainDiv = styled.div`
height: 100%;
border-radius: ${defaultBorderRadius};
- overflow-y: scroll;
- scrollbar-gutter: stable both-edges;
scrollbar-base-color: transparent;
- /* background: ${vscBackground}; */
- background-color: #1e1e1ede;
+ background-color: ${vscBackground};
`;
const TabBar = styled.div<{ numTabs: number }>`
@@ -105,9 +103,6 @@ function DebugPanel(props: DebugPanelProps) {
<div
key={index}
hidden={index !== currentTab}
- className={
- tab.title === "Chat" ? "overflow-hidden" : "overflow-scroll"
- }
style={{ scrollbarGutter: "stable both-edges" }}
>
{tab.element}
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index f54d4d75..dab5a752 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -2,13 +2,11 @@ import { useCallback, useEffect, useRef, useState } from "react";
import styled, { keyframes } from "styled-components";
import {
appear,
- buttonColor,
defaultBorderRadius,
- MainContainerWithBorder,
- MainTextInput,
secondaryDark,
vscBackground,
GradientBorder,
+ vscBackgroundTransparent,
} from ".";
import { RangeInFile, FileEdit } from "../../../src/client";
import CodeBlock from "./CodeBlock";
@@ -32,6 +30,7 @@ interface StepContainerProps {
onRefinement: (input: string) => void;
onUserInput: (input: string) => void;
onRetry: () => void;
+ open?: boolean;
}
const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>`
@@ -44,17 +43,25 @@ const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>`
`;
const StepContainerDiv = styled.div<{ open: boolean }>`
- background-color: ${(props) => (props.open ? vscBackground : secondaryDark)};
- border-radius: ${defaultBorderRadius};
- padding: 8px;
+ /* background-color: ${(props) =>
+ props.open ? vscBackground : secondaryDark}; */
+ /* border-radius: ${defaultBorderRadius}; */
+ /* padding: 8px; */
`;
const HeaderDiv = styled.div`
+ background-color: ${vscBackgroundTransparent};
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
`;
+const ContentDiv = styled.div`
+ padding: 8px;
+ padding-left: 16px;
+ background-color: ${vscBackground};
+`;
+
const HeaderButton = styled.button`
background-color: transparent;
border: 1px solid white;
@@ -75,12 +82,10 @@ const OnHoverDiv = styled.div`
animation: ${appear} 0.3s ease-in-out;
`;
-const NaturalLanguageInput = styled(MainTextInput)`
- width: 80%;
-`;
-
function StepContainer(props: StepContainerProps) {
- const [open, setOpen] = useState(false);
+ const [open, setOpen] = useState(
+ typeof props.open === "undefined" ? true : props.open
+ );
const [isHovered, setIsHovered] = useState(false);
const naturalLanguageInputRef = useRef<HTMLTextAreaElement>(null);
const userInputRef = useRef<HTMLInputElement>(null);
@@ -116,11 +121,11 @@ function StepContainer(props: StepContainerProps) {
}}
hidden={props.historyNode.step.hide as any}
>
- <GradientBorder
- className="m-2 overflow-hidden cursor-pointer"
- onClick={() => setOpen((prev) => !prev)}
- >
- <StepContainerDiv open={open}>
+ <StepContainerDiv open={open}>
+ <GradientBorder
+ className="overflow-hidden cursor-pointer"
+ onClick={() => setOpen((prev) => !prev)}
+ >
<HeaderDiv>
<h4 className="m-2">
{open ? (
@@ -128,7 +133,7 @@ function StepContainer(props: StepContainerProps) {
) : (
<ChevronRight size="1.4em" />
)}
- {props.historyNode.step.name as any}:
+ {props.historyNode.step.name as any}
</h4>
{/* <HeaderButton
onClick={(e) => {
@@ -152,8 +157,9 @@ function StepContainer(props: StepContainerProps) {
<></>
)}
</HeaderDiv>
-
- {open && (
+ </GradientBorder>
+ <ContentDiv hidden={!open}>
+ {open && false && (
<>
<pre className="overflow-scroll">
Step Details:
@@ -177,7 +183,7 @@ function StepContainer(props: StepContainerProps) {
</ReactMarkdown>
)}
- {props.historyNode.step.name === "Waiting for user input" && (
+ {/* {props.historyNode.step.name === "Waiting for user input" && (
<InputAndButton
onUserInput={(value) => {
props.onUserInput(value);
@@ -202,9 +208,9 @@ function StepContainer(props: StepContainerProps) {
value="Confirm"
/>
</>
- )}
- </StepContainerDiv>
- </GradientBorder>
+ )} */}
+ </ContentDiv>
+ </StepContainerDiv>
{/* <OnHoverDiv hidden={!open}>
<NaturalLanguageInput
diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts
index 7ba60467..ac5faa41 100644
--- a/extension/react-app/src/components/index.ts
+++ b/extension/react-app/src/components/index.ts
@@ -3,6 +3,7 @@ import styled, { keyframes } from "styled-components";
export const defaultBorderRadius = "5px";
export const secondaryDark = "rgb(37 37 38)";
export const vscBackground = "rgb(30 30 30)";
+export const vscBackgroundTransparent = "#1e1e1ede";
export const buttonColor = "rgb(113 28 59)";
export const buttonColorHover = "rgb(113 28 59 0.67)";
@@ -94,9 +95,13 @@ export const Loader = styled.div`
margin: auto;
`;
-export const GradientBorder = styled.div<{ borderWidth?: string }>`
- border-radius: ${defaultBorderRadius};
- padding: ${(props) => props.borderWidth || "1px"};
+export const GradientBorder = styled.div<{
+ borderWidth?: string;
+ borderRadius?: string;
+}>`
+ border-radius: ${(props) => props.borderRadius || "0"};
+ padding-top: ${(props) => props.borderWidth || "1px"};
+ padding-bottom: ${(props) => props.borderWidth || "1px"};
background: linear-gradient(
101.79deg,
#12887a 0%,
diff --git a/extension/react-app/src/tabs/chat/MessageDiv.tsx b/extension/react-app/src/tabs/chat/MessageDiv.tsx
index ad81f5e9..1d7bb5f5 100644
--- a/extension/react-app/src/tabs/chat/MessageDiv.tsx
+++ b/extension/react-app/src/tabs/chat/MessageDiv.tsx
@@ -21,7 +21,6 @@ const Container = styled.div`
width: fit-content;
max-width: 75%;
overflow-y: scroll;
- scrollbar-gutter: stable both-edges;
word-wrap: break-word;
-ms-word-wrap: break-word;
height: fit-content;
diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx
index 7dd30acb..c66172a9 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -1,25 +1,21 @@
import styled from "styled-components";
import {
- Button,
defaultBorderRadius,
vscBackground,
- MainTextInput,
Loader,
+ MainTextInput,
} from "../components";
import ContinueButton from "../components/ContinueButton";
import { useCallback, useEffect, useRef, useState } from "react";
import { History } from "../../../schema/History";
import { HistoryNode } from "../../../schema/HistoryNode";
import StepContainer from "../components/StepContainer";
-import { useSelector } from "react-redux";
-import { RootStore } from "../redux/store";
-import useContinueWebsocket from "../hooks/useWebsocket";
import useContinueGUIProtocol from "../hooks/useWebsocket";
let TopGUIDiv = styled.div`
display: grid;
grid-template-columns: 1fr;
- overflow: scroll;
+ background-color: ${vscBackground};
`;
let UserInputQueueItem = styled.div`
@@ -156,8 +152,20 @@ function GUI(props: GUIProps) {
// current_index: 0,
// } as any);
+ const topGuiDivRef = useRef<HTMLDivElement>(null);
const client = useContinueGUIProtocol();
+ const scrollToBottom = useCallback(() => {
+ if (topGuiDivRef.current) {
+ setTimeout(() => {
+ window.scrollTo({
+ top: window.outerHeight,
+ behavior: "smooth",
+ });
+ }, 100);
+ }
+ }, [topGuiDivRef.current]);
+
useEffect(() => {
console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate);
client?.onStateUpdate((state) => {
@@ -165,9 +173,15 @@ function GUI(props: GUIProps) {
setWaitingForSteps(state.active);
setHistory(state.history);
setUserInputQueue(state.user_input_queue);
+
+ scrollToBottom();
});
}, [client]);
+ useEffect(() => {
+ scrollToBottom();
+ }, [waitingForSteps]);
+
const mainTextInputRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
@@ -189,16 +203,33 @@ function GUI(props: GUIProps) {
if (mainTextInputRef.current) {
if (!client) return;
let input = mainTextInputRef.current.value;
- if (input.trim() === "") return;
- setWaitingForSteps(true);
- client.sendMainInput(input);
- setUserInputQueue((queue) => {
- return [...queue, input];
- });
- mainTextInputRef.current.value = "";
- mainTextInputRef.current.style.height = "";
+ if (
+ history &&
+ history.timeline[history.current_index].step.name ===
+ "Waiting for user input"
+ ) {
+ if (input.trim() === "") return;
+ onStepUserInput(input, history!.current_index);
+ } else if (
+ history &&
+ history.timeline[history.current_index].step.name ===
+ "Waiting for user confirmation"
+ ) {
+ onStepUserInput("ok", history!.current_index);
+ } else {
+ if (input.trim() === "") return;
+
+ client.sendMainInput(input);
+ setUserInputQueue((queue) => {
+ return [...queue, input];
+ });
+ mainTextInputRef.current.value = "";
+ mainTextInputRef.current.style.height = "";
+ }
}
+
+ setWaitingForSteps(true);
};
const onStepUserInput = (input: string, index: number) => {
@@ -209,7 +240,14 @@ function GUI(props: GUIProps) {
// const iterations = useSelector(selectIterations);
return (
- <TopGUIDiv>
+ <TopGUIDiv
+ ref={topGuiDivRef}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && e.ctrlKey) {
+ onMainTextInput();
+ }
+ }}
+ >
{typeof client === "undefined" && (
<>
<Loader></Loader>
@@ -249,6 +287,12 @@ function GUI(props: GUIProps) {
</div>
<MainTextInput
+ disabled={
+ history
+ ? history.timeline[history.current_index].step.name ===
+ "Waiting for user confirmation"
+ : false
+ }
ref={mainTextInputRef}
onKeyDown={(e) => {
if (e.key === "Enter") {
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index a5a1c5dc..25287d32 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -96,6 +96,12 @@ class IdeProtocolClient {
fileEdit,
});
break;
+ case "highlightCode":
+ this.highlightCode(data.rangeInFile, data.color);
+ break;
+ case "runCommand":
+ this.runCommand(data.command);
+ break;
case "saveFile":
this.saveFile(data.filepath);
break;
@@ -117,6 +123,28 @@ class IdeProtocolClient {
// ------------------------------------ //
// On message handlers
+ async highlightCode(rangeInFile: RangeInFile, color: string) {
+ const range = new vscode.Range(
+ rangeInFile.range.start.line,
+ rangeInFile.range.start.character,
+ rangeInFile.range.end.line,
+ rangeInFile.range.end.character
+ );
+ const editor = await openEditorAndRevealRange(
+ rangeInFile.filepath,
+ range,
+ vscode.ViewColumn.One
+ );
+ if (editor) {
+ editor.setDecorations(
+ vscode.window.createTextEditorDecorationType({
+ backgroundColor: color,
+ }),
+ [range]
+ );
+ }
+ }
+
showSuggestion(edit: FileEdit) {
// showSuggestion already exists
showSuggestion(
@@ -289,7 +317,16 @@ class IdeProtocolClient {
}
runCommand(command: string) {
- vscode.window.terminals[0].sendText(command, true);
+ if (vscode.window.terminals.length === 0) {
+ const terminal = vscode.window.createTerminal();
+ terminal.show();
+ terminal.sendText("bash", true);
+ terminal.sendText(command, true);
+ return;
+ }
+ const terminal = vscode.window.terminals[0];
+ terminal.show();
+ terminal.sendText(command, true);
// But need to know when it's done executing...
}
}