summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--continuedev/src/continuedev/core/autopilot.py30
-rw-r--r--continuedev/src/continuedev/core/config.py7
-rw-r--r--continuedev/src/continuedev/core/main.py9
-rw-r--r--continuedev/src/continuedev/core/sdk.py19
-rw-r--r--continuedev/src/continuedev/libs/llm/__init__.py9
-rw-r--r--continuedev/src/continuedev/libs/llm/openai.py47
-rw-r--r--continuedev/src/continuedev/libs/llm/proxy_server.py43
-rw-r--r--continuedev/src/continuedev/libs/util/step_name_to_steps.py14
-rw-r--r--continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py10
-rw-r--r--continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py2
-rw-r--r--continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py2
-rw-r--r--continuedev/src/continuedev/server/ide.py20
-rw-r--r--continuedev/src/continuedev/steps/chat.py6
-rw-r--r--continuedev/src/continuedev/steps/chroma.py2
-rw-r--r--continuedev/src/continuedev/steps/core/core.py10
-rw-r--r--continuedev/src/continuedev/steps/draft/migration.py2
-rw-r--r--continuedev/src/continuedev/steps/input/nl_multiselect.py2
-rw-r--r--continuedev/src/continuedev/steps/main.py6
-rw-r--r--continuedev/src/continuedev/steps/on_traceback.py14
-rw-r--r--continuedev/src/continuedev/steps/react.py2
-rw-r--r--continuedev/src/continuedev/steps/search_directory.py2
-rw-r--r--extension/package-lock.json4
-rw-r--r--extension/package.json2
-rw-r--r--extension/react-app/src/components/ComboBox.tsx3
-rw-r--r--extension/react-app/src/components/DebugPanel.tsx85
-rw-r--r--extension/react-app/src/components/HeaderButtonWithText.tsx26
-rw-r--r--extension/react-app/src/components/StepContainer.tsx16
-rw-r--r--extension/react-app/src/index.css3
-rw-r--r--extension/react-app/src/tabs/gui.tsx200
-rw-r--r--extension/scripts/continuedev-0.1.1-py3-none-any.whlbin81620 -> 83961 bytes
-rw-r--r--extension/src/activation/activate.ts32
-rw-r--r--extension/src/continueIdeClient.ts10
-rw-r--r--extension/src/terminal/terminalEmulator.ts50
33 files changed, 422 insertions, 267 deletions
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 703a73af..3ccce89a 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -13,6 +13,7 @@ from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep
from ..libs.util.telemetry import capture_event
from .sdk import ContinueSDK
import asyncio
+from ..libs.util.step_name_to_steps import get_step_from_name
class Autopilot(ContinueBaseModel):
@@ -88,9 +89,15 @@ class Autopilot(ContinueBaseModel):
self._manual_edits_buffer.append(edit)
# TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge.
# self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit)
+ # Note that this is being overriden to do nothing in DemoAgent
- def handle_traceback(self, traceback: str):
- raise NotImplementedError
+ async def handle_command_output(self, output: str):
+ is_traceback = False
+ if is_traceback:
+ for tb_step in self.continue_sdk.config.on_traceback:
+ step = get_step_from_name(tb_step.step_name)(
+ output=output, **tb_step.params)
+ await self._run_singular_step(step)
_step_depth: int = 0
@@ -99,9 +106,19 @@ class Autopilot(ContinueBaseModel):
async def delete_at_index(self, index: int):
self.history.timeline[index].step.hide = True
+ self.history.timeline[index].deleted = True
await self.update_subscribers()
async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]:
+ # If a parent step is deleted/cancelled, don't run this step
+ last_depth = self._step_depth
+ i = self.history.current_index
+ while i >= 0 and self.history.timeline[i].depth > last_depth:
+ if self.history.timeline[i].deleted:
+ return None
+ last_depth = self.history.timeline[i].depth
+ i -= 1
+
capture_event(self.continue_sdk.ide.unique_id, 'step run', {
'step_name': step.name, 'params': step.dict()})
@@ -114,7 +131,7 @@ class Autopilot(ContinueBaseModel):
await self._run_singular_step(manualEditsStep)
# Update history - do this first so we get top-first tree ordering
- self.history.add_node(HistoryNode(
+ index_of_history_node = self.history.add_node(HistoryNode(
step=step, observation=None, depth=self._step_depth))
# Call all subscribed callbacks
@@ -127,6 +144,10 @@ class Autopilot(ContinueBaseModel):
try:
observation = await step(self.continue_sdk)
except Exception as e:
+ if self.history.timeline[index_of_history_node].deleted:
+ # If step was deleted/cancelled, don't show error or allow retry
+ return None
+
caught_error = True
is_continue_custom_exception = issubclass(
@@ -176,8 +197,7 @@ class Autopilot(ContinueBaseModel):
# Add observation to history, unless already attached error observation
if not caught_error:
- self.history.get_last_at_depth(
- self._step_depth, include_current=True).observation = observation
+ self.history.timeline[index_of_history_node].observation = observation
await self.update_subscribers()
# Update its description
diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py
index 23be8133..d8b29f5b 100644
--- a/continuedev/src/continuedev/core/config.py
+++ b/continuedev/src/continuedev/core/config.py
@@ -12,6 +12,11 @@ class SlashCommand(BaseModel):
params: Optional[Dict] = {}
+class OnTracebackSteps(BaseModel):
+ step_name: str
+ params: Optional[Dict] = {}
+
+
class ContinueConfig(BaseModel):
"""
A pydantic class for the continue config file.
@@ -48,6 +53,8 @@ class ContinueConfig(BaseModel):
step_name="FeedbackStep",
)
]
+ on_traceback: Optional[List[OnTracebackSteps]] = [
+ OnTracebackSteps(step_name="DefaultOnTracebackStep")]
def load_config(config_file: str) -> ContinueConfig:
diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py
index f6b26d69..0c7ec67f 100644
--- a/continuedev/src/continuedev/core/main.py
+++ b/continuedev/src/continuedev/core/main.py
@@ -11,6 +11,8 @@ ChatMessageRole = Literal["assistant", "user", "system"]
class ChatMessage(ContinueBaseModel):
role: ChatMessageRole
content: str
+ # A summary for pruning chat context to fit context window. Often the Step name.
+ summary: str
class HistoryNode(ContinueBaseModel):
@@ -18,11 +20,12 @@ class HistoryNode(ContinueBaseModel):
step: "Step"
observation: Union[Observation, None]
depth: int
+ deleted: bool = False
def to_chat_messages(self) -> List[ChatMessage]:
if self.step.description is None:
return self.step.chat_context
- return self.step.chat_context + [ChatMessage(role="assistant", content=self.step.description)]
+ return self.step.chat_context + [ChatMessage(role="assistant", content=self.step.description, summary=self.step.name)]
class History(ContinueBaseModel):
@@ -37,9 +40,11 @@ class History(ContinueBaseModel):
msgs += node.to_chat_messages()
return msgs
- def add_node(self, node: HistoryNode):
+ def add_node(self, node: HistoryNode) -> int:
+ """ Add node and return the index where it was added """
self.timeline.insert(self.current_index + 1, node)
self.current_index += 1
+ return self.current_index
def get_current(self) -> Union[HistoryNode, None]:
if self.current_index < 0:
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index 76f72d01..8aea6b7f 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -165,26 +165,29 @@ class ContinueSDK(AbstractContinueSDK):
def raise_exception(self, message: str, title: str, with_step: Union[Step, None] = None):
raise ContinueCustomException(message, title, with_step)
- def add_chat_context(self, content: str, role: ChatMessageRole = "assistent"):
+ def add_chat_context(self, content: str, summary: Union[str, None] = None, role: ChatMessageRole = "assistent"):
self.history.timeline[self.history.current_index].step.chat_context.append(
- ChatMessage(content=content, role=role))
+ ChatMessage(content=content, role=role, summary=summary))
async def get_chat_context(self) -> List[ChatMessage]:
history_context = self.history.to_chat_history()
highlighted_code = await self.ide.getHighlightedCode()
+
+ preface = "The following code is highlighted"
+
if len(highlighted_code) == 0:
+ preface = "The following file is open"
# Get the full contents of all open files
files = await self.ide.getOpenFiles()
- contents = {}
- for file in files:
- contents[file] = await self.ide.readFile(file)
+ if len(files) > 0:
+ content = await self.ide.readFile(files[0])
+ highlighted_code = [
+ RangeInFile.from_entire_file(files[0], content)]
- highlighted_code = [RangeInFile.from_entire_file(
- filepath, content) for filepath, content in contents.items()]
for rif in highlighted_code:
code = await self.ide.readRangeInFile(rif)
history_context.append(ChatMessage(
- content=f"The following code is highlighted:\n```\n{code}\n```", role="user"))
+ content=f"{preface} ({rif.filepath}):\n```\n{code}\n```", role="user", summary=f"{preface}: {rif.filepath}"))
return history_context
async def update_ui(self):
diff --git a/continuedev/src/continuedev/libs/llm/__init__.py b/continuedev/src/continuedev/libs/llm/__init__.py
index 2986b2c4..108eedf1 100644
--- a/continuedev/src/continuedev/libs/llm/__init__.py
+++ b/continuedev/src/continuedev/libs/llm/__init__.py
@@ -1,5 +1,5 @@
from abc import ABC
-from typing import Any, Dict, Generator, List, Union
+from typing import Any, Coroutine, Dict, Generator, List, Union
from ...core.main import ChatMessage
from ...models.main import AbstractModel
@@ -9,17 +9,14 @@ from pydantic import BaseModel
class LLM(ABC):
system_message: Union[str, None] = None
- def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs):
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
"""Return the completion of the text with the given temperature."""
- raise
+ raise NotImplementedError
def stream_chat(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
"""Yield a stream of chat messages."""
raise NotImplementedError
- def __call__(self, prompt: str, **kwargs):
- return self.complete(prompt, **kwargs)
-
def with_system_message(self, system_message: Union[str, None]):
"""Return a new model with the given system message."""
raise NotImplementedError
diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py
index 180ea5f0..ec285d55 100644
--- a/continuedev/src/continuedev/libs/llm/openai.py
+++ b/continuedev/src/continuedev/libs/llm/openai.py
@@ -1,7 +1,7 @@
import asyncio
from functools import cached_property
import time
-from typing import Any, Dict, Generator, List, Union
+from typing import Any, Coroutine, Dict, Generator, List, Union
from ...core.main import ChatMessage
import openai
import aiohttp
@@ -42,12 +42,37 @@ class OpenAI(LLM):
return len(self.__encoding_for_model.encode(text, disallowed_special=()))
def __prune_chat_history(self, chat_history: List[ChatMessage], max_tokens: int, tokens_for_completion: int):
- tokens = tokens_for_completion
- for i in range(len(chat_history) - 1, -1, -1):
- message = chat_history[i]
- tokens += self.count_tokens(message.content)
- if tokens > max_tokens:
- return chat_history[i + 1:]
+ total_tokens = tokens_for_completion + \
+ sum(self.count_tokens(message.content) for message in chat_history)
+
+ # 1. Replace beyond last 5 messages with summary
+ i = 0
+ while total_tokens > max_tokens and i < len(chat_history) - 5:
+ message = chat_history[0]
+ total_tokens -= self.count_tokens(message.content)
+ total_tokens += self.count_tokens(message.summary)
+ message.content = message.summary
+ i += 1
+
+ # 2. Remove entire messages until the last 5
+ while len(chat_history) > 5 and total_tokens > max_tokens:
+ message = chat_history.pop(0)
+ total_tokens -= self.count_tokens(message.content)
+
+ # 3. Truncate message in the last 5
+ i = 0
+ while total_tokens > max_tokens:
+ message = chat_history[0]
+ total_tokens -= self.count_tokens(message.content)
+ total_tokens += self.count_tokens(message.summary)
+ message.content = message.summary
+ i += 1
+
+ # 4. Remove entire messages in the last 5
+ while total_tokens > max_tokens and len(chat_history) > 0:
+ message = chat_history.pop(0)
+ total_tokens -= self.count_tokens(message.content)
+
return chat_history
def with_system_message(self, system_message: Union[str, None]):
@@ -78,7 +103,7 @@ class OpenAI(LLM):
"role": "system",
"content": self.system_message
})
- history += [msg.dict() for msg in msgs]
+ history += [{"role": msg.role, "content": msg.content} for msg in msgs]
history.append({
"role": "user",
"content": prompt
@@ -107,7 +132,7 @@ class OpenAI(LLM):
for chunk in generator:
yield chunk.choices[0].text
- def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> str:
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
t1 = time.time()
self.completion_count += 1
@@ -115,12 +140,12 @@ class OpenAI(LLM):
"frequency_penalty": 0, "presence_penalty": 0, "stream": False} | kwargs
if args["model"] in CHAT_MODELS:
- resp = openai.ChatCompletion.create(
+ resp = await openai.ChatCompletion.acreate(
messages=self.compile_chat_messages(with_history, prompt),
**args,
).choices[0].message.content
else:
- resp = openai.Completion.create(
+ resp = await openai.Completion.acreate(
prompt=prompt,
**args,
).choices[0].text
diff --git a/continuedev/src/continuedev/libs/llm/proxy_server.py b/continuedev/src/continuedev/libs/llm/proxy_server.py
index 4ff57101..4227042f 100644
--- a/continuedev/src/continuedev/libs/llm/proxy_server.py
+++ b/continuedev/src/continuedev/libs/llm/proxy_server.py
@@ -1,8 +1,9 @@
from functools import cached_property
import json
-from typing import Any, Dict, Generator, List, Literal, Union
+from typing import Any, Coroutine, Dict, Generator, List, Literal, Union
import requests
import tiktoken
+import aiohttp
from ...core.main import ChatMessage
from ..llm import LLM
@@ -39,16 +40,6 @@ class ProxyServer(LLM):
def count_tokens(self, text: str):
return len(self.__encoding_for_model.encode(text, disallowed_special=()))
- def stream_chat(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
- resp = requests.post(f"{SERVER_URL}/stream_complete", json={
- "chat_history": self.compile_chat_messages(with_history, prompt),
- "model": self.default_model,
- "unique_id": self.unique_id,
- }, stream=True)
- for line in resp.iter_lines():
- if line:
- yield line.decode("utf-8")
-
def __prune_chat_history(self, chat_history: List[ChatMessage], max_tokens: int, tokens_for_completion: int):
tokens = tokens_for_completion
for i in range(len(chat_history) - 1, -1, -1):
@@ -67,7 +58,7 @@ class ProxyServer(LLM):
"role": "system",
"content": self.system_message
})
- history += [msg.dict() for msg in msgs]
+ history += [{"role": msg.role, "content": msg.content} for msg in msgs]
history.append({
"role": "user",
"content": prompt
@@ -75,11 +66,25 @@ class ProxyServer(LLM):
return history
- def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> str:
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
+ async with aiohttp.ClientSession() as session:
+ async with session.post(f"{SERVER_URL}/complete", json={
+ "chat_history": self.compile_chat_messages(with_history, prompt),
+ "model": self.default_model,
+ "unique_id": self.unique_id,
+ }) as resp:
+ try:
+ return json.loads(await resp.text())
+ except json.JSONDecodeError:
+ raise Exception(await resp.text())
- resp = requests.post(f"{SERVER_URL}/complete", json={
- "chat_history": self.compile_chat_messages(with_history, prompt),
- "model": self.default_model,
- "unique_id": self.unique_id,
- })
- return json.loads(resp.text)
+ async def stream_chat(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ async with aiohttp.ClientSession() as session:
+ async with session.post(f"{SERVER_URL}/stream_complete", json={
+ "chat_history": self.compile_chat_messages(with_history, prompt),
+ "model": self.default_model,
+ "unique_id": self.unique_id,
+ }) as resp:
+ async for line in resp.content:
+ if line:
+ yield line.decode("utf-8")
diff --git a/continuedev/src/continuedev/libs/util/step_name_to_steps.py b/continuedev/src/continuedev/libs/util/step_name_to_steps.py
index 4dd9c430..2c4474af 100644
--- a/continuedev/src/continuedev/libs/util/step_name_to_steps.py
+++ b/continuedev/src/continuedev/libs/util/step_name_to_steps.py
@@ -6,13 +6,25 @@ from ...steps.main import EditHighlightedCodeStep
from ...steps.chat import SimpleChatStep
from ...steps.comment_code import CommentCodeStep
from ...steps.feedback import FeedbackStep
+from ...recipes.AddTransformRecipe.main import AddTransformRecipe
+from ...recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
+from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe
+from ...recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe
+from ...steps.on_traceback import DefaultOnTracebackStep
+# This mapping is used to convert from string in ContinueConfig json to corresponding Step class.
+# Used for example in slash_commands and steps_on_startup
step_name_to_step_class = {
"UserInputStep": UserInputStep,
"EditHighlightedCodeStep": EditHighlightedCodeStep,
"SimpleChatStep": SimpleChatStep,
"CommentCodeStep": CommentCodeStep,
"FeedbackStep": FeedbackStep,
+ "AddTransformRecipe": AddTransformRecipe,
+ "CreatePipelineRecipe": CreatePipelineRecipe,
+ "DDtoBQRecipe": DDtoBQRecipe,
+ "DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe,
+ "DefaultOnTracebackStep": DefaultOnTracebackStep,
}
@@ -22,4 +34,4 @@ def get_step_from_name(step_name: str, params: Dict) -> Step:
except:
print(
f"Incorrect parameters for step {step_name}. Parameters provided were: {params}")
- raise \ No newline at end of file
+ raise
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
index 096b41c6..3fba1112 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
+++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
@@ -29,8 +29,8 @@ class SetupPipelineStep(Step):
async def run(self, sdk: ContinueSDK):
sdk.context.set("api_description", self.api_description)
- source_name = sdk.models.gpt35.complete(
- f"Write a snake_case name for the data source described by {self.api_description}: ").strip()
+ source_name = (await sdk.models.gpt35.complete(
+ f"Write a snake_case name for the data source described by {self.api_description}: ")).strip()
filename = f'{source_name}.py'
# running commands to get started when creating a new dlt pipeline
@@ -91,7 +91,7 @@ class ValidatePipelineStep(Step):
if "Traceback" in output or "SyntaxError" in output:
output = "Traceback" + output.split("Traceback")[-1]
file_content = await sdk.ide.readFile(os.path.join(workspace_dir, filename))
- suggestion = sdk.models.gpt35.complete(dedent(f"""\
+ suggestion = await sdk.models.gpt35.complete(dedent(f"""\
```python
{file_content}
```
@@ -103,7 +103,7 @@ class ValidatePipelineStep(Step):
This is a brief summary of the error followed by a suggestion on how it can be fixed by editing the resource function:"""))
- api_documentation_url = sdk.models.gpt35.complete(dedent(f"""\
+ api_documentation_url = await sdk.models.gpt35.complete(dedent(f"""\
The API I am trying to call is the '{sdk.context.get('api_description')}'. I tried calling it in the @resource function like this:
```python
{file_content}
@@ -159,7 +159,7 @@ class RunQueryStep(Step):
output = 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", handle_error=False)
if "Traceback" in output or "SyntaxError" in output:
- suggestion = sdk.models.gpt35.complete(dedent(f"""\
+ suggestion = await sdk.models.gpt35.complete(dedent(f"""\
```python
{await sdk.ide.readFile(os.path.join(sdk.ide.workspace_directory, "query.py"))}
```
diff --git a/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py b/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py
index 6db9fd4b..df414e2e 100644
--- a/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py
+++ b/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py
@@ -82,7 +82,7 @@ class LoadDataStep(Step):
docs = f.read()
output = "Traceback" + output.split("Traceback")[-1]
- suggestion = sdk.models.default.complete(dedent(f"""\
+ suggestion = await sdk.models.default.complete(dedent(f"""\
When trying to load data into BigQuery, the following error occurred:
```ascii
diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py
index 688f44c3..6e1244b3 100644
--- a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py
+++ b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py
@@ -41,7 +41,7 @@ class WritePytestsRecipe(Step):
"{self.user_input}"
Here is a complete set of pytest unit tests:""")
- tests = sdk.models.gpt35.complete(prompt)
+ tests = await sdk.models.gpt35.complete(prompt)
await sdk.apply_filesystem_edit(AddFile(filepath=path, content=tests))
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index c53149d8..c66cc142 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -1,5 +1,4 @@
# This is a separate server from server/main.py
-import asyncio
from functools import cached_property
import json
import os
@@ -10,11 +9,13 @@ from uvicorn.main import Server
from ..libs.util.queue import AsyncSubscriptionQueue
from ..models.filesystem import FileSystem, RangeInFile, EditDiff, RealFileSystem
-from ..models.main import Traceback
from ..models.filesystem_edit import AddDirectory, AddFile, DeleteDirectory, DeleteFile, FileSystemEdit, FileEdit, FileEditWithFullContents, RenameDirectory, RenameFile, SequentialFileSystemEdit
from pydantic import BaseModel
from .gui import SessionManager, session_manager
from .ide_protocol import AbstractIdeProtocolServer
+import asyncio
+import nest_asyncio
+nest_asyncio.apply()
router = APIRouter(prefix="/ide", tags=["ide"])
@@ -135,6 +136,9 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
fileEdits = list(
map(lambda d: FileEditWithFullContents.parse_obj(d), data["fileEdits"]))
self.onFileEdits(fileEdits)
+ elif message_type == "commandOutput":
+ output = data["output"]
+ self.onCommandOutput(output)
elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret", "runCommand", "uniqueId"]:
self.sub_queue.post(message_type, data)
else:
@@ -189,11 +193,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
def onAcceptRejectSuggestion(self, suggestionId: str, accepted: bool):
pass
- def onTraceback(self, traceback: Traceback):
- # Same as below, maybe not every autopilot?
- for _, session in self.session_manager.sessions.items():
- session.autopilot.handle_traceback(traceback)
-
def onFileSystemUpdate(self, update: FileSystemEdit):
# Access to Autopilot (so SessionManager)
pass
@@ -211,6 +210,13 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
for _, session in self.session_manager.sessions.items():
session.autopilot.handle_manual_edits(edits)
+ def onCommandOutput(self, output: str):
+ # Send the output to ALL autopilots.
+ # Maybe not ideal behavior
+ for _, session in self.session_manager.sessions.items():
+ asyncio.create_task(
+ session.autopilot.handle_command_output(output))
+
# Request information. Session doesn't matter.
async def getOpenFiles(self) -> List[str]:
resp = await self._send_and_receive_json({}, OpenFilesResponse, "openFiles")
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py
index 7cfe7e0c..499d127f 100644
--- a/continuedev/src/continuedev/steps/chat.py
+++ b/continuedev/src/continuedev/steps/chat.py
@@ -11,9 +11,9 @@ class SimpleChatStep(Step):
async def run(self, sdk: ContinueSDK):
self.description = f"## {self.user_input}\n\n"
- for chunk in sdk.models.default.stream_chat(self.user_input, with_history=await sdk.get_chat_context()):
+ async for chunk in sdk.models.default.stream_chat(self.user_input, with_history=await sdk.get_chat_context()):
self.description += chunk
await sdk.update_ui()
- self.name = sdk.models.gpt35.complete(
- f"Write a short title for the following chat message: {self.description}").strip()
+ self.name = (await sdk.models.gpt35.complete(
+ f"Write a short title for the following chat message: {self.description}")).strip()
diff --git a/continuedev/src/continuedev/steps/chroma.py b/continuedev/src/continuedev/steps/chroma.py
index 058455b2..9d085981 100644
--- a/continuedev/src/continuedev/steps/chroma.py
+++ b/continuedev/src/continuedev/steps/chroma.py
@@ -56,7 +56,7 @@ class AnswerQuestionChroma(Step):
Here is the answer:""")
- answer = sdk.models.gpt35.complete(prompt)
+ answer = await sdk.models.gpt35.complete(prompt)
# Make paths relative to the workspace directory
answer = answer.replace(await sdk.ide.getWorkspaceDirectory(), "")
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index af3c6cc2..7f3a93ba 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -72,7 +72,7 @@ class ShellCommandsStep(Step):
return f"Error when running shell commands:\n```\n{self._err_text}\n```"
cmds_str = "\n".join(self.cmds)
- return models.gpt35.complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:")
+ return await models.gpt35.complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:")
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd
@@ -80,7 +80,7 @@ class ShellCommandsStep(Step):
for cmd in self.cmds:
output = await sdk.ide.runCommand(cmd)
if self.handle_error and output is not None and output_contains_error(output):
- suggestion = sdk.models.gpt35.complete(dedent(f"""\
+ suggestion = await sdk.models.gpt35.complete(dedent(f"""\
While running the command `{cmd}`, the following error occurred:
```ascii
@@ -151,10 +151,8 @@ class DefaultModelEditCodeStep(Step):
_prompt_and_completion: str = ""
async def describe(self, models: Models) -> Coroutine[str, None, None]:
- description = models.gpt35.complete(
+ description = await models.gpt35.complete(
f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points. Be concise and only mention changes made to the commit before, not prefix or suffix:")
- # self.name = models.gpt35.complete(
- # f"Write a short title for this description: {description}")
return description
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
@@ -183,7 +181,7 @@ class DefaultModelEditCodeStep(Step):
prompt = self._prompt.format(
code=rif.contents, user_request=self.user_input, file_prefix=segs[0], file_suffix=segs[1])
- completion = str(sdk.models.default.complete(prompt, with_history=await sdk.get_chat_context()))
+ completion = str(await sdk.models.default.complete(prompt, with_history=await sdk.get_chat_context()))
eot_token = "<|endoftext|>"
completion = completion.removesuffix(eot_token)
diff --git a/continuedev/src/continuedev/steps/draft/migration.py b/continuedev/src/continuedev/steps/draft/migration.py
index 7c4b7eb5..f3b36b5e 100644
--- a/continuedev/src/continuedev/steps/draft/migration.py
+++ b/continuedev/src/continuedev/steps/draft/migration.py
@@ -13,7 +13,7 @@ class MigrationStep(Step):
recent_edits = await sdk.ide.get_recent_edits(self.edited_file)
recent_edits_string = "\n\n".join(
map(lambda x: x.to_string(), recent_edits))
- description = sdk.models.gpt35.complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n")
+ description = await sdk.models.gpt35.complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n")
await sdk.run([
"cd libs",
"poetry run alembic revision --autogenerate -m " + description,
diff --git a/continuedev/src/continuedev/steps/input/nl_multiselect.py b/continuedev/src/continuedev/steps/input/nl_multiselect.py
index 36c489c7..aee22866 100644
--- a/continuedev/src/continuedev/steps/input/nl_multiselect.py
+++ b/continuedev/src/continuedev/steps/input/nl_multiselect.py
@@ -23,6 +23,6 @@ class NLMultiselectStep(Step):
if first_try is not None:
return first_try
- gpt_parsed = sdk.models.gpt35.complete(
+ gpt_parsed = await sdk.models.gpt35.complete(
f"These are the available options are: [{', '.join(self.options)}]. The user requested {user_response}. This is the exact string from the options array that they selected:")
return extract_option(gpt_parsed) or self.options[0]
diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py
index 0e42d8bf..b61aa3fe 100644
--- a/continuedev/src/continuedev/steps/main.py
+++ b/continuedev/src/continuedev/steps/main.py
@@ -145,7 +145,7 @@ class FasterEditHighlightedCodeStep(Step):
for rif in rif_with_contents:
rif_dict[rif.filepath] = rif.contents
- completion = sdk.models.gpt35.complete(prompt)
+ completion = await sdk.models.gpt35.complete(prompt)
# Temporarily doing this to generate description.
self._prompt = prompt
@@ -213,7 +213,7 @@ class StarCoderEditHighlightedCodeStep(Step):
_prompt_and_completion: str = ""
async def describe(self, models: Models) -> Coroutine[str, None, None]:
- return models.gpt35.complete(f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points:")
+ return await models.gpt35.complete(f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points:")
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
range_in_files = await sdk.ide.getHighlightedCode()
@@ -247,7 +247,7 @@ class StarCoderEditHighlightedCodeStep(Step):
segs = full_file_contents.split(rif.contents)
prompt = f"<file_prefix>{segs[0]}<file_suffix>{segs[1]}" + prompt
- completion = str((await sdk.models.starcoder()).complete(prompt))
+ completion = str(await sdk.models.starcoder.complete(prompt))
eot_token = "<|endoftext|>"
completion = completion.removesuffix(eot_token)
diff --git a/continuedev/src/continuedev/steps/on_traceback.py b/continuedev/src/continuedev/steps/on_traceback.py
new file mode 100644
index 00000000..de668775
--- /dev/null
+++ b/continuedev/src/continuedev/steps/on_traceback.py
@@ -0,0 +1,14 @@
+from ..core.main import Step
+from ..core.sdk import ContinueSDK
+from .chat import SimpleChatStep
+
+
+class DefaultOnTracebackStep(Step):
+ output: str
+ name: str = "Help With Traceback"
+ hide: bool = True
+
+ async def run(self, sdk: ContinueSDK):
+ sdk.run_step(SimpleChatStep(
+ name="Help With Traceback",
+ user_input=f"""I got the following error, can you please help explain how to fix it?\n\n{self.output}"""))
diff --git a/continuedev/src/continuedev/steps/react.py b/continuedev/src/continuedev/steps/react.py
index d825d424..4d310fc8 100644
--- a/continuedev/src/continuedev/steps/react.py
+++ b/continuedev/src/continuedev/steps/react.py
@@ -27,7 +27,7 @@ class NLDecisionStep(Step):
Select the step which should be taken next to satisfy the user input. Say only the name of the selected step. You must choose one:""")
- resp = sdk.models.gpt35.complete(prompt).lower()
+ resp = (await sdk.models.gpt35.complete(prompt)).lower()
step_to_run = None
for step in self.steps:
diff --git a/continuedev/src/continuedev/steps/search_directory.py b/continuedev/src/continuedev/steps/search_directory.py
index 9f4594b9..d2966f46 100644
--- a/continuedev/src/continuedev/steps/search_directory.py
+++ b/continuedev/src/continuedev/steps/search_directory.py
@@ -41,7 +41,7 @@ class WriteRegexPatternStep(Step):
async def run(self, sdk: ContinueSDK):
# Ask the user for a regex pattern
- pattern = sdk.models.gpt35.complete(dedent(f"""\
+ pattern = await sdk.models.gpt35.complete(dedent(f"""\
This is the user request:
{self.user_request}
diff --git a/extension/package-lock.json b/extension/package-lock.json
index e41cd2c2..7e8da126 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "continue",
- "version": "0.0.40",
+ "version": "0.0.44",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "continue",
- "version": "0.0.40",
+ "version": "0.0.44",
"license": "Apache-2.0",
"dependencies": {
"@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index 4b199420..8cf50d2a 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.40",
+ "version": "0.0.44",
"publisher": "Continue",
"engines": {
"vscode": "^1.74.0"
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index ace0605e..2b140567 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -113,6 +113,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
(event.nativeEvent as any).preventDownshiftDefault = true;
if (props.onEnter) props.onEnter(event);
setInputValue("");
+ } else if (event.key === "Tab" && items.length > 0) {
+ setInputValue(items[0].name);
+ event.preventDefault();
}
},
ref: ref as any,
diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx
index 11ec2fe2..30f38779 100644
--- a/extension/react-app/src/components/DebugPanel.tsx
+++ b/extension/react-app/src/components/DebugPanel.tsx
@@ -17,39 +17,15 @@ interface DebugPanelProps {
}[];
}
-const GradientContainer = styled.div`
- // Uncomment to get gradient border
- /* 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; */
- border-radius: ${defaultBorderRadius};
-`;
-
-const MainDiv = styled.div`
- height: 100%;
- border-radius: ${defaultBorderRadius};
- scrollbar-base-color: transparent;
- background-color: ${vscBackground};
-`;
-
const TabBar = styled.div<{ numTabs: number }>`
display: grid;
grid-template-columns: repeat(${(props) => props.numTabs}, 1fr);
`;
const TabsAndBodyDiv = styled.div`
- display: grid;
- grid-template-rows: auto 1fr;
height: 100%;
+ border-radius: ${defaultBorderRadius};
+ scrollbar-base-color: transparent;
`;
function DebugPanel(props: DebugPanelProps) {
@@ -76,42 +52,43 @@ function DebugPanel(props: DebugPanelProps) {
const [currentTab, setCurrentTab] = useState(0);
return (
- <GradientContainer>
- <MainDiv>
- <TabsAndBodyDiv>
- {props.tabs.length > 1 && (
- <TabBar numTabs={props.tabs.length}>
- {props.tabs.map((tab, index) => {
- return (
- <div
- key={index}
- className={`p-2 cursor-pointer text-center ${
- index === currentTab
- ? "bg-secondary-dark"
- : "bg-vsc-background"
- }`}
- onClick={() => setCurrentTab(index)}
- >
- {tab.title}
- </div>
- );
- })}
- </TabBar>
- )}
+ <TabsAndBodyDiv>
+ {props.tabs.length > 1 && (
+ <TabBar numTabs={props.tabs.length}>
{props.tabs.map((tab, index) => {
return (
<div
key={index}
- hidden={index !== currentTab}
- style={{ scrollbarGutter: "stable both-edges" }}
+ className={`p-2 cursor-pointer text-center ${
+ index === currentTab
+ ? "bg-secondary-dark"
+ : "bg-vsc-background"
+ }`}
+ onClick={() => setCurrentTab(index)}
>
- {tab.element}
+ {tab.title}
</div>
);
})}
- </TabsAndBodyDiv>
- </MainDiv>
- </GradientContainer>
+ </TabBar>
+ )}
+ {props.tabs.map((tab, index) => {
+ return (
+ <div
+ key={index}
+ hidden={index !== currentTab}
+ style={{
+ scrollbarGutter: "stable both-edges",
+ minHeight: "100%",
+ display: "grid",
+ gridTemplateRows: "1fr auto",
+ }}
+ >
+ {tab.element}
+ </div>
+ );
+ })}
+ </TabsAndBodyDiv>
);
}
diff --git a/extension/react-app/src/components/HeaderButtonWithText.tsx b/extension/react-app/src/components/HeaderButtonWithText.tsx
new file mode 100644
index 00000000..d70a3d70
--- /dev/null
+++ b/extension/react-app/src/components/HeaderButtonWithText.tsx
@@ -0,0 +1,26 @@
+import React, { useState } from "react";
+
+import { HeaderButton } from ".";
+
+interface HeaderButtonWithTextProps {
+ text: string;
+ onClick?: (e: any) => void;
+ children: React.ReactNode;
+}
+
+const HeaderButtonWithText = (props: HeaderButtonWithTextProps) => {
+ const [hover, setHover] = useState(false);
+ return (
+ <HeaderButton
+ style={{ padding: "3px" }}
+ onMouseEnter={() => setHover(true)}
+ onMouseLeave={() => setHover(false)}
+ onClick={props.onClick}
+ >
+ <span hidden={!hover}>{props.text}</span>
+ {props.children}
+ </HeaderButton>
+ );
+};
+
+export default HeaderButtonWithText;
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 48f970d7..480f517f 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -21,9 +21,7 @@ import {
} from "@styled-icons/heroicons-outline";
import { HistoryNode } from "../../../schema/HistoryNode";
import ReactMarkdown from "react-markdown";
-import ContinueButton from "./ContinueButton";
-import InputAndButton from "./InputAndButton";
-import ToggleErrorDiv from "./ToggleErrorDiv";
+import HeaderButtonWithText from "./HeaderButtonWithText";
interface StepContainerProps {
historyNode: HistoryNode;
@@ -152,23 +150,25 @@ function StepContainer(props: StepContainerProps) {
</HeaderButton> */}
<>
- <HeaderButton
+ <HeaderButtonWithText
onClick={(e) => {
e.stopPropagation();
props.onDelete();
}}
+ text="Delete"
>
<XMark size="1.6em" onClick={props.onDelete} />
- </HeaderButton>
+ </HeaderButtonWithText>
{props.historyNode.observation?.error ? (
- <HeaderButton
+ <HeaderButtonWithText
+ text="Retry"
onClick={(e) => {
e.stopPropagation();
props.onRetry();
}}
>
<ArrowPath size="1.6em" onClick={props.onRetry} />
- </HeaderButton>
+ </HeaderButtonWithText>
) : (
<></>
)}
@@ -193,7 +193,7 @@ function StepContainer(props: StepContainerProps) {
) : (
<ReactMarkdown
key={1}
- className="overflow-scroll"
+ className="overflow-x-scroll"
components={
{
// pre: ({ node, ...props }) => {
diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css
index 20599d30..32a92d0e 100644
--- a/extension/react-app/src/index.css
+++ b/extension/react-app/src/index.css
@@ -21,7 +21,7 @@
html,
body,
#root {
- height: calc(100%);
+ height: 100%;
}
body {
@@ -31,4 +31,5 @@ body {
font-family: "Mona Sans", "Arial", sans-serif;
padding: 0px;
margin: 0px;
+ height: 100%;
}
diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx
index 5316f42b..279d052b 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -14,25 +14,18 @@ import StepContainer from "../components/StepContainer";
import useContinueGUIProtocol from "../hooks/useWebsocket";
import {
BookOpen,
- ChatBubbleOvalLeft,
ChatBubbleOvalLeftEllipsis,
Trash,
} from "@styled-icons/heroicons-outline";
import ComboBox from "../components/ComboBox";
import TextDialog from "../components/TextDialog";
+import HeaderButtonWithText from "../components/HeaderButtonWithText";
-const MainDiv = styled.div`
- display: grid;
- grid-template-rows: 1fr auto;
+const TopGUIDiv = styled.div`
+ overflow: hidden;
`;
-let TopGUIDiv = styled.div`
- display: grid;
- grid-template-columns: 1fr;
- background-color: ${vscBackground};
-`;
-
-let UserInputQueueItem = styled.div`
+const UserInputQueueItem = styled.div`
border-radius: ${defaultBorderRadius};
color: gray;
padding: 8px;
@@ -40,7 +33,7 @@ let UserInputQueueItem = styled.div`
text-align: center;
`;
-const TopBar = styled.div`
+const Footer = styled.footer`
display: flex;
flex-direction: row;
gap: 8px;
@@ -303,103 +296,98 @@ function GUI(props: GUIProps) {
setShowFeedbackDialog(false);
}}
></TextDialog>
- <MainDiv>
- <TopGUIDiv
- ref={topGuiDivRef}
- onKeyDown={(e) => {
- if (e.key === "Enter" && e.ctrlKey) {
- onMainTextInput();
- }
- }}
- >
- {typeof client === "undefined" && (
- <>
- <Loader></Loader>
- <p style={{ textAlign: "center" }}>
- Trying to reconnect with server...
- </p>
- </>
- )}
- {history?.timeline.map((node: HistoryNode, index: number) => {
- return (
- <StepContainer
- key={index}
- onUserInput={(input: string) => {
- onStepUserInput(input, index);
- }}
- inFuture={index > history?.current_index}
- historyNode={node}
- onRefinement={(input: string) => {
- client?.sendRefinementInput(input, index);
- }}
- onReverse={() => {
- client?.reverseToIndex(index);
- }}
- onRetry={() => {
- client?.retryAtIndex(index);
- setWaitingForSteps(true);
- }}
- onDelete={() => {
- client?.deleteAtIndex(index);
- }}
- />
- );
- })}
- {waitingForSteps && <Loader></Loader>}
- <div>
- {userInputQueue.map((input) => {
- return <UserInputQueueItem>{input}</UserInputQueueItem>;
- })}
- </div>
-
- <ComboBox
- disabled={
- history?.timeline.length
- ? history.timeline[history.current_index].step.name ===
- "Waiting for user confirmation"
- : false
- }
- ref={mainTextInputRef}
- onEnter={(e) => {
- onMainTextInput();
- e.stopPropagation();
- e.preventDefault();
- }}
- onInputValueChange={() => {}}
- items={availableSlashCommands}
- />
- <ContinueButton onClick={onMainTextInput} />
-
- <TopBar>
- <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" }}
- onClick={() => {
- // Set dialog open
- setShowFeedbackDialog(true);
+ <TopGUIDiv
+ ref={topGuiDivRef}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && e.ctrlKey) {
+ onMainTextInput();
+ }
+ }}
+ >
+ {typeof client === "undefined" && (
+ <>
+ <Loader></Loader>
+ <p style={{ textAlign: "center" }}>
+ Trying to reconnect with server...
+ </p>
+ </>
+ )}
+ {history?.timeline.map((node: HistoryNode, index: number) => {
+ return (
+ <StepContainer
+ key={index}
+ onUserInput={(input: string) => {
+ onStepUserInput(input, index);
+ }}
+ inFuture={index > history?.current_index}
+ historyNode={node}
+ onRefinement={(input: string) => {
+ client?.sendRefinementInput(input, index);
+ }}
+ onReverse={() => {
+ client?.reverseToIndex(index);
}}
- >
- Feedback
- <ChatBubbleOvalLeftEllipsis size="1.6em" />
- </HeaderButton>
- <HeaderButton
- onClick={() => {
- client?.sendClear();
+ onRetry={() => {
+ client?.retryAtIndex(index);
+ setWaitingForSteps(true);
}}
- style={{ padding: "3px" }}
- >
- Clear History
- <Trash size="1.6em" />
- </HeaderButton>
- </TopBar>
- </TopGUIDiv>
- </MainDiv>
+ onDelete={() => {
+ client?.deleteAtIndex(index);
+ }}
+ />
+ );
+ })}
+ {waitingForSteps && <Loader></Loader>}
+
+ <div>
+ {userInputQueue.map((input) => {
+ return <UserInputQueueItem>{input}</UserInputQueueItem>;
+ })}
+ </div>
+
+ <ComboBox
+ disabled={
+ history?.timeline.length
+ ? history.timeline[history.current_index].step.name ===
+ "Waiting for user confirmation"
+ : false
+ }
+ ref={mainTextInputRef}
+ onEnter={(e) => {
+ onMainTextInput();
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ onInputValueChange={() => {}}
+ items={availableSlashCommands}
+ />
+ <ContinueButton onClick={onMainTextInput} />
+ </TopGUIDiv>
+ <Footer>
+ <a href="https://continue.dev/docs" className="no-underline">
+ <HeaderButtonWithText text="Continue Docs">
+ <BookOpen size="1.6em" />
+ </HeaderButtonWithText>
+ </a>
+ <HeaderButtonWithText
+ onClick={() => {
+ // Set dialog open
+ setShowFeedbackDialog(true);
+ }}
+ text="Feedback"
+ >
+ <ChatBubbleOvalLeftEllipsis size="1.6em" />
+ </HeaderButtonWithText>
+ <HeaderButtonWithText
+ onClick={() => {
+ client?.sendClear();
+ }}
+ text="Clear History"
+ >
+ <Trash size="1.6em" />
+ </HeaderButtonWithText>
+ </Footer>
</>
);
}
diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl
index 2f8f1550..614190c7 100644
--- a/extension/scripts/continuedev-0.1.1-py3-none-any.whl
+++ b/extension/scripts/continuedev-0.1.1-py3-none-any.whl
Binary files differ
diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts
index 135a8ec7..77010241 100644
--- a/extension/src/activation/activate.ts
+++ b/extension/src/activation/activate.ts
@@ -8,6 +8,7 @@ import * as path from "path";
import IdeProtocolClient from "../continueIdeClient";
import { getContinueServerUrl } from "../bridge";
import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel";
+import { CapturedTerminal } from "../terminal/terminalEmulator";
export let extensionContext: vscode.ExtensionContext | undefined = undefined;
@@ -47,5 +48,36 @@ export function activateExtension(
);
})();
+ // All opened terminals should be replaced by our own terminal
+ vscode.window.onDidOpenTerminal((terminal) => {
+ if (terminal.name === "Continue") {
+ return;
+ }
+ const options = terminal.creationOptions;
+ const capturedTerminal = new CapturedTerminal({
+ ...options,
+ name: "Continue",
+ });
+ terminal.dispose();
+ });
+
+ // If any terminals are open to start, replace them
+ vscode.window.terminals.forEach((terminal) => {
+ if (terminal.name === "Continue") {
+ return;
+ }
+ const options = terminal.creationOptions;
+ const capturedTerminal = new CapturedTerminal(
+ {
+ ...options,
+ name: "Continue",
+ },
+ (commandOutput: string) => {
+ ideProtocolClient.sendCommandOutput(commandOutput);
+ }
+ );
+ terminal.dispose();
+ });
+
extensionContext = context;
}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index ef9a91c8..a889d3dc 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -326,13 +326,19 @@ class IdeProtocolClient {
private continueTerminal: CapturedTerminal | undefined;
async runCommand(command: string) {
- if (!this.continueTerminal) {
- this.continueTerminal = new CapturedTerminal("Continue");
+ if (!this.continueTerminal || this.continueTerminal.isClosed()) {
+ this.continueTerminal = new CapturedTerminal({
+ name: "Continue",
+ });
}
this.continueTerminal.show();
return await this.continueTerminal.runCommand(command);
}
+
+ sendCommandOutput(output: string) {
+ this.messenger?.send("commandOutput", { output });
+ }
}
export default IdeProtocolClient;
diff --git a/extension/src/terminal/terminalEmulator.ts b/extension/src/terminal/terminalEmulator.ts
index b3031baf..35f02ac0 100644
--- a/extension/src/terminal/terminalEmulator.ts
+++ b/extension/src/terminal/terminalEmulator.ts
@@ -62,21 +62,29 @@ export class CapturedTerminal {
this.terminal.show();
}
+ isClosed(): boolean {
+ return this.terminal.exitStatus !== undefined;
+ }
+
private commandQueue: [string, (output: string) => void][] = [];
private hasRunCommand: boolean = false;
+ private dataEndsInPrompt(strippedData: string): boolean {
+ const lines = this.dataBuffer.split("\n");
+ return (
+ lines.length > 0 &&
+ (lines[lines.length - 1].includes("bash-") ||
+ lines[lines.length - 1].includes(") $ ")) &&
+ lines[lines.length - 1].includes("$")
+ );
+ }
+
private async waitForCommandToFinish() {
return new Promise<string>((resolve, reject) => {
this.onDataListeners.push((data: any) => {
const strippedData = stripAnsi(data);
this.dataBuffer += strippedData;
- const lines = this.dataBuffer.split("\n");
- if (
- lines.length > 0 &&
- (lines[lines.length - 1].includes("bash-") ||
- lines[lines.length - 1].includes(") $ ")) &&
- lines[lines.length - 1].includes("$")
- ) {
+ if (this.dataEndsInPrompt(strippedData)) {
resolve(this.dataBuffer);
this.dataBuffer = "";
this.onDataListeners = [];
@@ -112,8 +120,30 @@ export class CapturedTerminal {
private readonly writeEmitter: vscode.EventEmitter<string>;
- constructor(terminalName: string) {
- this.shellCmd = "bash"; // getDefaultShell();
+ private splitByCommandsBuffer: string = "";
+ private readonly onCommandOutput: ((output: string) => void) | undefined;
+
+ splitByCommandsListener(data: string) {
+ // Split the output by commands so it can be sent to Continue Server
+
+ const strippedData = stripAnsi(data);
+ this.splitByCommandsBuffer += strippedData;
+ if (this.dataEndsInPrompt(strippedData)) {
+ if (this.onCommandOutput) {
+ this.onCommandOutput(this.splitByCommandsBuffer);
+ }
+ this.dataBuffer = "";
+ }
+ }
+
+ constructor(
+ options: { name: string } & Partial<vscode.ExtensionTerminalOptions>,
+ onCommandOutput?: (output: string) => void
+ ) {
+ this.onCommandOutput = onCommandOutput;
+
+ // this.shellCmd = "bash"; // getDefaultShell();
+ this.shellCmd = getDefaultShell();
const env = { ...(process.env as any) };
if (os.platform() !== "win32") {
@@ -154,7 +184,7 @@ export class CapturedTerminal {
// Create and clear the terminal
this.terminal = vscode.window.createTerminal({
- name: terminalName,
+ ...options,
pty: newPty,
});
this.terminal.show();