From c84fdf356dccad9bb41140572b5704ee53807021 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 9 Jul 2023 17:43:37 -0700 Subject: say "No edits were made" if true --- continuedev/src/continuedev/steps/core/core.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 4b35a758..b0d9d719 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -167,10 +167,13 @@ class DefaultModelEditCodeStep(Step): return output async def describe(self, models: Models) -> Coroutine[str, None, None]: - description = await models.gpt3516k.complete(dedent(f"""\ - {self._prompt_and_completion} - - Please 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:""")) + if self._prompt_and_completion == "": + description = "No edits were made" + else: + description = await models.gpt3516k.complete(dedent(f"""\ + {self._prompt_and_completion} + + Please 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:""")) name = await models.gpt3516k.complete(f"Write a very short title to describe this requested change (no quotes): '{self.user_input}'. This is the title:") self.name = self._cleanup_output(name) -- cgit v1.2.3-70-g09d2 From ab8c7c7d6ab79b2498521b5d64fc30909934271e Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 10 Jul 2023 19:31:48 -0700 Subject: catch errors during asyncio tasks --- continuedev/src/continuedev/core/autopilot.py | 4 +- .../src/continuedev/libs/util/create_async_task.py | 23 +++++++++ continuedev/src/continuedev/server/gui.py | 54 +++++++++++++--------- continuedev/src/continuedev/server/ide.py | 20 ++++---- .../src/continuedev/server/session_manager.py | 9 ++-- .../src/continuedev/steps/search_directory.py | 5 +- extension/src/continueIdeClient.ts | 2 +- 7 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 continuedev/src/continuedev/libs/util/create_async_task.py (limited to 'continuedev') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 5c3baafd..615e7657 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -20,6 +20,7 @@ import asyncio from ..libs.util.step_name_to_steps import get_step_from_name from ..libs.util.traceback_parsers import get_python_traceback, get_javascript_traceback from openai import error as openai_errors +from ..libs.util.create_async_task import create_async_task def get_error_title(e: Exception) -> str: @@ -341,7 +342,8 @@ class Autopilot(ContinueBaseModel): # Update subscribers with new description await self.update_subscribers() - asyncio.create_task(update_description()) + create_async_task(update_description(), + self.continue_sdk.ide.unique_id) return observation diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py new file mode 100644 index 00000000..89b84868 --- /dev/null +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -0,0 +1,23 @@ +from typing import Coroutine, Union +import traceback +from .telemetry import capture_event +import asyncio +import nest_asyncio +nest_asyncio.apply() + + +def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): + """asyncio.create_task and log errors by adding a callback""" + task = asyncio.create_task(coro) + + def callback(future: asyncio.Future): + try: + future.result() + except Exception as e: + print("Exception caught from async task: ", e) + capture_event(unique_id or "None", "async_task_error", { + "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format(e.__traceback__) + }) + + task.add_done_callback(callback) + return task diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 8e9b1fb9..8daad73a 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -7,9 +7,8 @@ from uvicorn.main import Server from .session_manager import SessionManager, session_manager, Session from .gui_protocol import AbstractGUIProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue -import asyncio -import nest_asyncio -nest_asyncio.apply() +from ..libs.util.telemetry import capture_event +from ..libs.util.create_async_task import create_async_task router = APIRouter(prefix="/gui", tags=["gui"]) @@ -102,51 +101,60 @@ class GUIProtocolServer(AbstractGUIProtocolServer): def on_main_input(self, input: str): # Do something with user input - asyncio.create_task(self.session.autopilot.accept_user_input(input)) + create_async_task(self.session.autopilot.accept_user_input( + input), self.session.autopilot.continue_sdk.ide.unique_id) def on_reverse_to_index(self, index: int): # Reverse the history to the given index - asyncio.create_task(self.session.autopilot.reverse_to_index(index)) + create_async_task(self.session.autopilot.reverse_to_index( + index), self.session.autopilot.continue_sdk.ide.unique_id) def on_step_user_input(self, input: str, index: int): - asyncio.create_task( - self.session.autopilot.give_user_input(input, index)) + create_async_task( + self.session.autopilot.give_user_input(input, index), self.session.autopilot.continue_sdk.ide.unique_id) def on_refinement_input(self, input: str, index: int): - asyncio.create_task( - self.session.autopilot.accept_refinement_input(input, index)) + create_async_task( + self.session.autopilot.accept_refinement_input(input, index), self.session.autopilot.continue_sdk.ide.unique_id) def on_retry_at_index(self, index: int): - asyncio.create_task( - self.session.autopilot.retry_at_index(index)) + create_async_task( + self.session.autopilot.retry_at_index(index), self.session.autopilot.continue_sdk.ide.unique_id) def on_change_default_model(self, model: str): - asyncio.create_task(self.session.autopilot.change_default_model(model)) + create_async_task(self.session.autopilot.change_default_model( + model), self.session.autopilot.continue_sdk.ide.unique_id) def on_clear_history(self): - asyncio.create_task(self.session.autopilot.clear_history()) + create_async_task(self.session.autopilot.clear_history( + ), self.session.autopilot.continue_sdk.ide.unique_id) def on_delete_at_index(self, index: int): - asyncio.create_task(self.session.autopilot.delete_at_index(index)) + create_async_task(self.session.autopilot.delete_at_index( + index), self.session.autopilot.continue_sdk.ide.unique_id) def on_delete_context_at_indices(self, indices: List[int]): - asyncio.create_task( - self.session.autopilot.delete_context_at_indices(indices) + create_async_task( + self.session.autopilot.delete_context_at_indices( + indices), self.session.autopilot.continue_sdk.ide.unique_id ) def on_toggle_adding_highlighted_code(self): - asyncio.create_task( - self.session.autopilot.toggle_adding_highlighted_code() + create_async_task( + self.session.autopilot.toggle_adding_highlighted_code( + ), self.session.autopilot.continue_sdk.ide.unique_id ) def on_set_editing_at_indices(self, indices: List[int]): - asyncio.create_task( - self.session.autopilot.set_editing_at_indices(indices) + create_async_task( + self.session.autopilot.set_editing_at_indices( + indices), self.session.autopilot.continue_sdk.ide.unique_id ) def on_set_pinned_at_indices(self, indices: List[int]): - asyncio.create_task( - self.session.autopilot.set_pinned_at_indices(indices) + create_async_task( + self.session.autopilot.set_pinned_at_indices( + indices), self.session.autopilot.continue_sdk.ide.unique_id ) @@ -179,6 +187,8 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we except Exception as e: print("ERROR in gui websocket: ", e) + capture_event(session.autopilot.continue_sdk.ide.unique_id, "gui_error", { + "error_title": e.__str__() or e.__repr__(), "error_message": e.__traceback__}) raise e finally: print("Closing gui websocket") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index e4a6266a..f84f3de2 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -15,8 +15,7 @@ from pydantic import BaseModel from .gui import SessionManager, session_manager from .ide_protocol import AbstractIdeProtocolServer import asyncio -import nest_asyncio -nest_asyncio.apply() +from ..libs.util.create_async_task import create_async_task router = APIRouter(prefix="/ide", tags=["ide"]) @@ -250,24 +249,25 @@ class IdeProtocolServer(AbstractIdeProtocolServer): def onDeleteAtIndex(self, index: int): for _, session in self.session_manager.sessions.items(): - asyncio.create_task(session.autopilot.delete_at_index(index)) + create_async_task( + session.autopilot.delete_at_index(index), self.unique_id) 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)) + create_async_task( + session.autopilot.handle_command_output(output), self.unique_id) def onHighlightedCodeUpdate(self, range_in_files: List[RangeInFileWithContents]): for _, session in self.session_manager.sessions.items(): - asyncio.create_task( - session.autopilot.handle_highlighted_code(range_in_files)) + create_async_task( + session.autopilot.handle_highlighted_code(range_in_files), self.unique_id) def onMainUserInput(self, input: str): for _, session in self.session_manager.sessions.items(): - asyncio.create_task( - session.autopilot.accept_user_input(input)) + create_async_task( + session.autopilot.accept_user_input(input), self.unique_id) # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: @@ -412,5 +412,7 @@ async def websocket_endpoint(websocket: WebSocket): await websocket.close() except Exception as e: print("Error in ide websocket: ", e) + capture_event(ideProtocolServer.unique_id, "gui_error", { + "error_title": e.__str__() or e.__repr__(), "error_message": e.__traceback__}) await websocket.close() raise e diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 99a38146..873a379e 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -1,3 +1,4 @@ +from asyncio import BaseEventLoop from fastapi import WebSocket from typing import Any, Dict, List, Union from uuid import uuid4 @@ -7,9 +8,7 @@ from ..core.policy import DemoPolicy from ..core.main import FullState from ..core.autopilot import Autopilot from .ide_protocol import AbstractIdeProtocolServer -import asyncio -import nest_asyncio -nest_asyncio.apply() +from ..libs.util.create_async_task import create_async_task class Session: @@ -38,7 +37,7 @@ class DemoAutopilot(Autopilot): class SessionManager: sessions: Dict[str, Session] = {} - _event_loop: Union[asyncio.BaseEventLoop, None] = None + _event_loop: Union[BaseEventLoop, None] = None def get_session(self, session_id: str) -> Session: if session_id not in self.sessions: @@ -57,7 +56,7 @@ class SessionManager: }) autopilot.on_update(on_update) - asyncio.create_task(autopilot.run_policy()) + create_async_task(autopilot.run_policy()) return session_id def remove_session(self, session_id: str): diff --git a/continuedev/src/continuedev/steps/search_directory.py b/continuedev/src/continuedev/steps/search_directory.py index 2eecc99c..bfb97630 100644 --- a/continuedev/src/continuedev/steps/search_directory.py +++ b/continuedev/src/continuedev/steps/search_directory.py @@ -6,6 +6,7 @@ from ..models.filesystem import RangeInFile from ..models.main import Range from ..core.main import Step from ..core.sdk import ContinueSDK +from ..libs.util.create_async_task import create_async_task import os import re @@ -60,9 +61,9 @@ class EditAllMatchesStep(Step): # Search all files for a given string range_in_files = find_all_matches_in_dir(self.pattern, self.directory or await sdk.ide.getWorkspaceDirectory()) - tasks = [asyncio.create_task(sdk.edit_file( + tasks = [create_async_task(sdk.edit_file( range=range_in_file.range, filename=range_in_file.filepath, prompt=self.user_request - )) for range_in_file in range_in_files] + ), sdk.ide.unique_id) for range_in_file in range_in_files] await asyncio.gather(*tasks) diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 679d94ba..304c592b 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -300,7 +300,7 @@ class IdeProtocolClient { }); const resp = await this.messenger?.sendAndReceive("openGUI", {}); const sessionId = resp.sessionId; - console.log("New Continue session with ID: ", sessionId); + // console.log("New Continue session with ID: ", sessionId); return sessionId; } -- cgit v1.2.3-70-g09d2 From b7ef7ccf7580f989d2dbf8450f3f2e79129ba80e Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 10 Jul 2023 23:12:21 -0700 Subject: traceback.format( -> format_tb --- continuedev/src/continuedev/libs/util/create_async_task.py | 2 +- continuedev/src/continuedev/server/gui.py | 3 ++- continuedev/src/continuedev/server/ide.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py index 89b84868..608d4977 100644 --- a/continuedev/src/continuedev/libs/util/create_async_task.py +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -16,7 +16,7 @@ def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): except Exception as e: print("Exception caught from async task: ", e) capture_event(unique_id or "None", "async_task_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format(e.__traceback__) + "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__) }) task.add_done_callback(callback) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 8daad73a..ae53be00 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -2,6 +2,7 @@ import json from fastapi import Depends, Header, WebSocket, APIRouter from typing import Any, List, Type, TypeVar, Union from pydantic import BaseModel +import traceback from uvicorn.main import Server from .session_manager import SessionManager, session_manager, Session @@ -188,7 +189,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we except Exception as e: print("ERROR in gui websocket: ", e) capture_event(session.autopilot.continue_sdk.ide.unique_id, "gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": e.__traceback__}) + "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__)}) raise e finally: print("Closing gui websocket") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index f84f3de2..93996edd 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -6,6 +6,7 @@ from typing import Any, Dict, List, Type, TypeVar, Union import uuid from fastapi import WebSocket, Body, APIRouter from uvicorn.main import Server +import traceback from ..libs.util.telemetry import capture_event from ..libs.util.queue import AsyncSubscriptionQueue @@ -413,6 +414,6 @@ async def websocket_endpoint(websocket: WebSocket): except Exception as e: print("Error in ide websocket: ", e) capture_event(ideProtocolServer.unique_id, "gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": e.__traceback__}) + "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__)}) await websocket.close() raise e -- cgit v1.2.3-70-g09d2 From bb02a83bf2f41ac2e14582aa16f8dfbe1e2ec915 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 00:04:05 -0700 Subject: better explanations for /config --- continuedev/src/continuedev/core/config.py | 4 ++-- continuedev/src/continuedev/steps/open_config.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index 55f5bc60..f6167638 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -131,7 +131,7 @@ def load_global_config() -> ContinueConfig: config_path = os.path.join(global_dir, 'config.json') if not os.path.exists(config_path): with open(config_path, 'w') as f: - json.dump(ContinueConfig().dict(), f) + json.dump(ContinueConfig().dict(), f, indent=4) with open(config_path, 'r') as f: try: config_dict = json.load(f) @@ -151,7 +151,7 @@ def update_global_config(config: ContinueConfig): yaml_path = os.path.join(global_dir, 'config.yaml') if os.path.exists(yaml_path): with open(config_path, 'w') as f: - yaml.dump(config.dict(), f) + yaml.dump(config.dict(), f, indent=4) else: config_path = os.path.join(global_dir, 'config.json') with open(config_path, 'w') as f: diff --git a/continuedev/src/continuedev/steps/open_config.py b/continuedev/src/continuedev/steps/open_config.py index 441cb0e7..87f03e9f 100644 --- a/continuedev/src/continuedev/steps/open_config.py +++ b/continuedev/src/continuedev/steps/open_config.py @@ -18,7 +18,10 @@ class OpenConfigStep(Step): "prompt": "Write a comprehensive set of unit tests for the selected code. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated." } ], - ```""") + ``` + `"name"` is the command you will type. + `"description"` is the description displayed in the slash command menu. + `"prompt"` is the instruction given to the model. The overall prompt becomes "Task: {prompt}, Additional info: {user_input}". For example, if you entered "/test exactly 5 assertions", the overall prompt would become "Task: Write a comprehensive...and sophisticated, Additional info: exactly 5 assertions".""") async def run(self, sdk: ContinueSDK): global_dir = os.path.expanduser('~/.continue') -- cgit v1.2.3-70-g09d2 From 4b215f5fa19ebd0a4e49dec1e7b2bc3f376ddd15 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 00:51:42 -0700 Subject: don't allow /edit in diff editor, save diff editor --- continuedev/src/continuedev/steps/main.py | 10 +++++++++- extension/src/diffs.ts | 17 ++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 4f543022..2a8cd250 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -10,7 +10,7 @@ from ..models.filesystem import RangeInFile, RangeInFileWithContents from ..core.observation import Observation, TextObservation, TracebackObservation from ..libs.llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent -from ..core.main import Step +from ..core.main import ContinueCustomException, Step from ..core.sdk import ContinueSDK, Models from ..core.observation import Observation import subprocess @@ -251,6 +251,9 @@ class EditHighlightedCodeStep(Step): highlighted_code = await sdk.ide.getHighlightedCode() if highlighted_code is not None: for rif in highlighted_code: + if os.path.dirname(rif.filepath) == os.path.expanduser(os.path.join("~", ".continue", "diffs")): + raise ContinueCustomException( + message="Please accept or reject the change before making another edit in this file.", title="Accept/Reject First") if rif.range.start == rif.range.end: range_in_files.append( RangeInFileWithContents.from_range_in_file(rif, "")) @@ -278,6 +281,11 @@ class EditHighlightedCodeStep(Step): filepath=x.filepath, range=x.range ), range_in_files)) + for range_in_file in range_in_files: + if os.path.dirname(range_in_file.filepath) == os.path.expanduser(os.path.join("~", ".continue", "diffs")): + self.description = "Please accept or reject the change before making another edit in this file." + return + await sdk.run_step(DefaultModelEditCodeStep(user_input=self.user_input, range_in_files=range_in_files)) diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index b9ef8384..1dc292e1 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -132,11 +132,18 @@ class DiffManager { console.log("No corresponding diffInfo found for newFilepath"); return; } - fs.writeFileSync( - diffInfo.originalFilepath, - fs.readFileSync(diffInfo.newFilepath) - ); - this.cleanUpDiff(diffInfo); + + // Save the right-side file, then copy over to original + vscode.workspace.textDocuments + .find((doc) => doc.uri.fsPath === newFilepath) + ?.save() + .then(() => { + fs.writeFileSync( + diffInfo.originalFilepath, + fs.readFileSync(diffInfo.newFilepath) + ); + this.cleanUpDiff(diffInfo); + }); } rejectDiff(newFilepath?: string) { -- cgit v1.2.3-70-g09d2 From bbda840cb7d79679a4b4a0de136708181d6c8de6 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 00:58:39 -0700 Subject: raise error if no code selected to edit --- continuedev/src/continuedev/steps/main.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 2a8cd250..e6ef9281 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -258,24 +258,10 @@ class EditHighlightedCodeStep(Step): range_in_files.append( RangeInFileWithContents.from_range_in_file(rif, "")) - # If nothing highlighted, edit the first open file + # If still no highlighted code, raise error if len(range_in_files) == 0: - # Get the full contents of all open files - files = await sdk.ide.getOpenFiles() - contents = {} - for file in files: - contents[file] = await sdk.ide.readFile(file) - - range_in_files = [RangeInFileWithContents.from_entire_file( - filepath, content) for filepath, content in contents.items()] - - # If still no highlighted code, create a new file and edit there - if len(range_in_files) == 0: - # Create a new file - new_file_path = "new_file.txt" - await sdk.add_file(new_file_path, "") - range_in_files = [ - RangeInFileWithContents.from_entire_file(new_file_path, "")] + raise ContinueCustomException( + message="Please highlight some code and try again.", title="No Code Selected") range_in_files = list(map(lambda x: RangeInFile( filepath=x.filepath, range=x.range -- cgit v1.2.3-70-g09d2 From 721735d65a2b6f3811514396891c90b950686323 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 11:31:41 -0700 Subject: explain insert at cursor, better diff streaming --- continuedev/src/continuedev/steps/core/core.py | 44 ++++++++++++++++++++++--- extension/react-app/src/components/ComboBox.tsx | 32 +++++++++++++++--- extension/src/diffs.ts | 7 +++- 3 files changed, 72 insertions(+), 11 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index b0d9d719..d4d067ba 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -265,6 +265,23 @@ class DefaultModelEditCodeStep(Step): return file_prefix, rif.contents, file_suffix, model_to_use, max_tokens def compile_prompt(self, file_prefix: str, contents: str, file_suffix: str, sdk: ContinueSDK) -> str: + if contents.strip() == "": + # Seperate prompt for insertion at the cursor, the other tends to cause it to repeat whole file + prompt = dedent(f"""\ + +{file_prefix} + + + +{file_suffix} + + +{self.user_input} + + +Please output the code to be inserted at the cursor in order to fulfill the user_request. Do NOT preface your answer or write anything other than code. You should not write any tags, just the code. Make sure to correctly indent the code:""") + return prompt + prompt = self._prompt if file_prefix.strip() != "": prompt += dedent(f""" @@ -306,15 +323,32 @@ class DefaultModelEditCodeStep(Step): prompt = self.compile_prompt(file_prefix, contents, file_suffix, sdk) full_file_contents_lines = full_file_contents.split("\n") - async def sendDiffUpdate(lines: List[str], sdk: ContinueSDK): - nonlocal full_file_contents_lines, rif + lines_to_display = [] + + async def sendDiffUpdate(lines: List[str], sdk: ContinueSDK, final: bool = False): + nonlocal full_file_contents_lines, rif, lines_to_display completion = "\n".join(lines) full_prefix_lines = full_file_contents_lines[:rif.range.start.line] full_suffix_lines = full_file_contents_lines[rif.range.end.line:] + + # Don't do this at the very end, just show the inserted code + if final: + lines_to_display = [] + # Only recalculate at every new-line, because this is sort of expensive + elif completion.endswith("\n"): + contents_lines = rif.contents.split("\n") + rewritten_lines = 0 + for line in lines: + for i in range(rewritten_lines, len(contents_lines)): + if difflib.SequenceMatcher(None, line, contents_lines[i]).ratio() > 0.7 and contents_lines[i].strip() != "": + rewritten_lines = i + 1 + break + lines_to_display = contents_lines[rewritten_lines:] + new_file_contents = "\n".join( - full_prefix_lines) + "\n" + completion + "\n" + "\n".join(full_suffix_lines) + full_prefix_lines) + "\n" + completion + "\n" + "\n".join(lines_to_display) + "\n" + "\n".join(full_suffix_lines) step_index = sdk.history.current_index @@ -495,7 +529,7 @@ class DefaultModelEditCodeStep(Step): completion_lines_covered += 1 current_line_in_file += 1 - await sendDiffUpdate(lines + [common_whitespace + unfinished_line], sdk) + await sendDiffUpdate(lines + [common_whitespace if unfinished_line.startswith("<") else (common_whitespace + unfinished_line)], sdk) # Add the unfinished line if unfinished_line != "" and not self.line_to_be_ignored(unfinished_line, completion_lines_covered == 0) and not self.is_end_line(unfinished_line): @@ -505,7 +539,7 @@ class DefaultModelEditCodeStep(Step): completion_lines_covered += 1 current_line_in_file += 1 - await sendDiffUpdate(lines, sdk) + await sendDiffUpdate(lines, sdk, final=True) if False: # If the current block isn't empty, add that suggestion diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 801c3a03..585a0584 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -180,6 +180,26 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { useImperativeHandle(ref, () => downshiftProps, [downshiftProps]); + const [metaKeyPressed, setMetaKeyPressed] = useState(false); + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Meta") { + setMetaKeyPressed(true); + } + }; + const handleKeyUp = (e: KeyboardEvent) => { + if (e.key === "Meta") { + setMetaKeyPressed(false); + } + }; + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + }; + }); + useEffect(() => { if (!inputRef.current) { return; @@ -272,7 +292,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { - {/* - Highlight code to include as context. Currently open file included by - default. {highlightedCodeSections.length === 0 && ""} - */} + {highlightedCodeSections.length === 0 && + (downshiftProps.inputValue?.startsWith("/edit") || metaKeyPressed) && ( +
+ Inserting at cursor +
+ )} { setHoveringContextDropdown(true); diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index 1dc292e1..3ea6b4f8 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -164,7 +164,12 @@ class DiffManager { // Stop the step at step_index in case it is still streaming ideProtocolClient.deleteAtIndex(diffInfo.step_index); - this.cleanUpDiff(diffInfo); + vscode.workspace.textDocuments + .find((doc) => doc.uri.fsPath === newFilepath) + ?.save() + .then(() => { + this.cleanUpDiff(diffInfo); + }); } } -- cgit v1.2.3-70-g09d2 From ab717248fac98050dada5f9a7f08d844e98009ce Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 11:40:09 -0700 Subject: don't hide chat when deleted --- continuedev/src/continuedev/steps/chat.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py index a10319d8..e1e041d0 100644 --- a/continuedev/src/continuedev/steps/chat.py +++ b/continuedev/src/continuedev/steps/chat.py @@ -29,6 +29,8 @@ class SimpleChatStep(Step): messages = self.messages or await sdk.get_chat_context() async for chunk in sdk.models.gpt4.stream_chat(messages, temperature=0.5): if sdk.current_step_was_deleted(): + # So that the message doesn't disappear + self.hide = False return if "content" in chunk: -- cgit v1.2.3-70-g09d2 From c388282d44b16e29a7b7cfe9474960a959bb8e85 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 15:02:45 -0700 Subject: correctly format traceback when logging errors --- continuedev/src/continuedev/core/autopilot.py | 2 +- continuedev/src/continuedev/libs/util/create_async_task.py | 2 +- continuedev/src/continuedev/server/gui.py | 2 +- continuedev/src/continuedev/server/ide.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 615e7657..11fc9cb1 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -285,7 +285,7 @@ class Autopilot(ContinueBaseModel): e.__class__, ContinueCustomException) error_string = e.message if is_continue_custom_exception else '\n'.join( - traceback.format_tb(e.__traceback__)) + f"\n\n{e.__repr__()}" + traceback.format_exception(e)) error_title = e.title if is_continue_custom_exception else get_error_title( e) diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py index 608d4977..f41f642e 100644 --- a/continuedev/src/continuedev/libs/util/create_async_task.py +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -16,7 +16,7 @@ def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): except Exception as e: print("Exception caught from async task: ", e) capture_event(unique_id or "None", "async_task_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__) + "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_exception(e) }) task.add_done_callback(callback) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index ae53be00..1321c27f 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -189,7 +189,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we except Exception as e: print("ERROR in gui websocket: ", e) capture_event(session.autopilot.continue_sdk.ide.unique_id, "gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__)}) + "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_exception(e)}) raise e finally: print("Closing gui websocket") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 93996edd..fd9faec1 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -414,6 +414,6 @@ async def websocket_endpoint(websocket: WebSocket): except Exception as e: print("Error in ide websocket: ", e) capture_event(ideProtocolServer.unique_id, "gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__)}) + "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_exception(e)}) await websocket.close() raise e -- cgit v1.2.3-70-g09d2 From aa40e9c5e3717cef7552973c495095be96ab6d1c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 15:04:20 -0700 Subject: logging as string, not array of frames --- continuedev/src/continuedev/libs/util/create_async_task.py | 2 +- continuedev/src/continuedev/server/gui.py | 2 +- continuedev/src/continuedev/server/ide.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py index f41f642e..62ff30ec 100644 --- a/continuedev/src/continuedev/libs/util/create_async_task.py +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -16,7 +16,7 @@ def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): except Exception as e: print("Exception caught from async task: ", e) capture_event(unique_id or "None", "async_task_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_exception(e) + "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e)) }) task.add_done_callback(callback) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 1321c27f..dbc063c8 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -189,7 +189,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we except Exception as e: print("ERROR in gui websocket: ", e) capture_event(session.autopilot.continue_sdk.ide.unique_id, "gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_exception(e)}) + "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) raise e finally: print("Closing gui websocket") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index fd9faec1..782c2ba6 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -414,6 +414,6 @@ async def websocket_endpoint(websocket: WebSocket): except Exception as e: print("Error in ide websocket: ", e) capture_event(ideProtocolServer.unique_id, "gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_exception(e)}) + "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) await websocket.close() raise e -- cgit v1.2.3-70-g09d2 From 4ae20d0ded02f49f6a848dee933c54cb24df641f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 15:41:20 -0700 Subject: if ssl prob with personal key, tell to use our key --- continuedev/src/continuedev/core/autopilot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 11fc9cb1..3f07e270 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -35,7 +35,7 @@ def get_error_title(e: Exception) -> str: elif isinstance(e, ClientPayloadError): return "The request to OpenAI failed. Please try again." elif isinstance(e, openai_errors.APIConnectionError): - return "The request failed. Please check your internet connection and try again." + return "The request failed. Please check your internet connection and try again. If this issue persists, you can use our API key for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to \"\"" elif isinstance(e, openai_errors.InvalidRequestError): return 'Your API key does not have access to GPT-4. You can use ours for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to ""' return e.__str__() or e.__repr__() -- cgit v1.2.3-70-g09d2 From 72ade108029a1f3a1f5964501ff4670e655efa6b Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 16:40:11 -0700 Subject: explain accept/reject, better edit summaries --- continuedev/src/continuedev/steps/core/core.py | 19 ++++++++++++----- extension/src/diffs.ts | 29 ++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 7 deletions(-) (limited to 'continuedev') diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index d4d067ba..9eddc03f 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -152,7 +152,8 @@ class DefaultModelEditCodeStep(Step): Main task: """) - + _previous_contents: str = "" + _new_contents: str = "" _prompt_and_completion: str = "" def _cleanup_output(self, output: str) -> str: @@ -167,13 +168,19 @@ class DefaultModelEditCodeStep(Step): return output async def describe(self, models: Models) -> Coroutine[str, None, None]: - if self._prompt_and_completion == "": + if self._previous_contents.strip() == self._new_contents.strip(): description = "No edits were made" else: description = await models.gpt3516k.complete(dedent(f"""\ - {self._prompt_and_completion} - - Please 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:""")) + ```original + {self._previous_contents} + ``` + + ```new + {self._new_contents} + ``` + + Please give brief a description of the changes made above using markdown bullet points. Be concise:""")) name = await models.gpt3516k.complete(f"Write a very short title to describe this requested change (no quotes): '{self.user_input}'. This is the title:") self.name = self._cleanup_output(name) @@ -573,6 +580,8 @@ Please output the code to be inserted at the cursor in order to fulfill the user # Record the completion completion = "\n".join(lines) + self._previous_contents = "\n".join(original_lines) + self._new_contents = completion self._prompt_and_completion += prompt + completion async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index 3ea6b4f8..28089fc6 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -2,7 +2,7 @@ import * as os from "os"; import * as path from "path"; import * as fs from "fs"; import * as vscode from "vscode"; -import { ideProtocolClient } from "./activation/activate"; +import { extensionContext, ideProtocolClient } from "./activation/activate"; interface DiffInfo { originalFilepath: string; @@ -70,6 +70,28 @@ class DiffManager { .getConfiguration("diffEditor", editor.document.uri) .update("codeLens", true, vscode.ConfigurationTarget.Global); + if ( + extensionContext?.globalState.get( + "continue.showDiffInfoMessage" + ) !== false + ) { + vscode.window + .showInformationMessage( + "Accept (⌘⇧↩) or reject (⌘⇧⌫) at the top of the file.", + "Got it", + "Don't show again" + ) + .then((selection) => { + if (selection === "Don't show again") { + // Get the global state + extensionContext?.globalState.update( + "continue.showDiffInfoMessage", + false + ); + } + }); + } + return editor; } @@ -152,7 +174,10 @@ class DiffManager { newFilepath = Array.from(this.diffs.keys())[0]; } if (!newFilepath) { - console.log("No newFilepath provided to reject the diff"); + console.log( + "No newFilepath provided to reject the diff, diffs.size was", + this.diffs.size + ); return; } const diffInfo = this.diffs.get(newFilepath); -- cgit v1.2.3-70-g09d2 From a6ac1fdae55ba79e2ffbd00f88fc39971ed47f58 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 12 Jul 2023 00:32:51 -0700 Subject: finally found the snag --- continuedev/src/continuedev/core/abstract_sdk.py | 4 +- continuedev/src/continuedev/core/autopilot.py | 5 +- continuedev/src/continuedev/core/sdk.py | 26 +++++---- .../src/continuedev/libs/util/create_async_task.py | 3 +- continuedev/src/continuedev/libs/util/errors.py | 2 + continuedev/src/continuedev/server/gui.py | 11 +++- continuedev/src/continuedev/server/ide.py | 67 ++++++++++++++++------ continuedev/src/continuedev/server/ide_protocol.py | 8 +-- .../src/continuedev/server/session_manager.py | 5 +- .../src/continuedev/server/state_manager.py | 21 ------- continuedev/src/continuedev/steps/chat.py | 25 ++++---- 11 files changed, 99 insertions(+), 78 deletions(-) create mode 100644 continuedev/src/continuedev/libs/util/errors.py delete mode 100644 continuedev/src/continuedev/server/state_manager.py (limited to 'continuedev') diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py index 7bd3da6c..94d7be10 100644 --- a/continuedev/src/continuedev/core/abstract_sdk.py +++ b/continuedev/src/continuedev/core/abstract_sdk.py @@ -76,9 +76,7 @@ class AbstractContinueSDK(ABC): async def get_user_secret(self, env_var: str, prompt: str) -> str: pass - @abstractproperty - def config(self) -> ContinueConfig: - pass + config: ContinueConfig @abstractmethod def set_loading_message(self, message: str): diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 3f07e270..ac00e4f0 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -251,7 +251,7 @@ class Autopilot(ContinueBaseModel): # i -= 1 capture_event(self.continue_sdk.ide.unique_id, 'step run', { - 'step_name': step.name, 'params': step.dict()}) + 'step_name': step.name, 'params': step.dict()}) if not is_future_step: # Check manual edits buffer, clear out if needed by creating a ManualEditStep @@ -290,7 +290,8 @@ class Autopilot(ContinueBaseModel): e) # Attach an InternalErrorObservation to the step and unhide it. - print(f"Error while running step: \n{error_string}\n{error_title}") + print( + f"Error while running step: \n{error_string}\n{error_title}") capture_event(self.continue_sdk.ide.unique_id, 'step error', { 'error_message': error_string, 'error_title': error_title, 'step_name': step.name, 'params': step.dict()}) diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 8649cd58..a3441ad9 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -89,6 +89,20 @@ class ContinueSDK(AbstractContinueSDK): self.__autopilot = autopilot self.models = Models(self) self.context = autopilot.context + self.config = self._load_config() + + config: ContinueConfig + + def _load_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): + return load_config(yaml_path) + elif os.path.exists(json_path): + return load_config(json_path) + else: + return load_global_config() @property def history(self) -> History: @@ -166,18 +180,6 @@ class ContinueSDK(AbstractContinueSDK): async def get_user_secret(self, env_var: str, prompt: str) -> str: return await self.ide.getUserSecret(env_var) - @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): - return load_config(yaml_path) - elif os.path.exists(json_path): - return load_config(json_path) - else: - return load_global_config() - def get_code_context(self, only_editing: bool = False) -> List[RangeInFileWithContents]: context = list(filter(lambda x: x.editing, self.__autopilot._highlighted_ranges) ) if only_editing else self.__autopilot._highlighted_ranges diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py index 62ff30ec..354cea82 100644 --- a/continuedev/src/continuedev/libs/util/create_async_task.py +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -14,7 +14,8 @@ def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): try: future.result() except Exception as e: - print("Exception caught from async task: ", e) + print("Exception caught from async task: ", + '\n'.join(traceback.format_exception(e))) capture_event(unique_id or "None", "async_task_error", { "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e)) }) diff --git a/continuedev/src/continuedev/libs/util/errors.py b/continuedev/src/continuedev/libs/util/errors.py new file mode 100644 index 00000000..46074cfc --- /dev/null +++ b/continuedev/src/continuedev/libs/util/errors.py @@ -0,0 +1,2 @@ +class SessionNotFound(Exception): + pass diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index dbc063c8..21089f30 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -1,5 +1,6 @@ import json from fastapi import Depends, Header, WebSocket, APIRouter +from starlette.websockets import WebSocketState, WebSocketDisconnect from typing import Any, List, Type, TypeVar, Union from pydantic import BaseModel import traceback @@ -52,6 +53,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer): self.session = session async def _send_json(self, message_type: str, data: Any): + if self.websocket.client_state == WebSocketState.DISCONNECTED: + return await self.websocket.send_json({ "messageType": message_type, "data": data @@ -171,7 +174,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_state_update() + # await protocol.send_state_update() while AppStatus.should_exit is False: message = await websocket.receive_text() @@ -185,7 +188,8 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we data = message["data"] protocol.handle_json(message_type, data) - + except WebSocketDisconnect as e: + print("GUI websocket disconnected") except Exception as e: print("ERROR in gui websocket: ", e) capture_event(session.autopilot.continue_sdk.ide.unique_id, "gui_error", { @@ -193,5 +197,6 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we raise e finally: print("Closing gui websocket") - await websocket.close() + if websocket.client_state != WebSocketState.DISCONNECTED: + await websocket.close() session_manager.remove_session(session.session_id) diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 782c2ba6..400ad740 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -5,6 +5,7 @@ import os from typing import Any, Dict, List, Type, TypeVar, Union import uuid from fastapi import WebSocket, Body, APIRouter +from starlette.websockets import WebSocketState, WebSocketDisconnect from uvicorn.main import Server import traceback @@ -17,6 +18,8 @@ from .gui import SessionManager, session_manager from .ide_protocol import AbstractIdeProtocolServer import asyncio from ..libs.util.create_async_task import create_async_task +import nest_asyncio +nest_asyncio.apply() router = APIRouter(prefix="/ide", tags=["ide"]) @@ -115,7 +118,30 @@ class IdeProtocolServer(AbstractIdeProtocolServer): self.websocket = websocket self.session_manager = session_manager + workspace_directory: str + + async def initialize(self) -> List[str]: + await self._send_json("workspaceDirectory", {}) + other_msgs = [] + while True: + msg_string = await self.websocket.receive_text() + message = json.loads(msg_string) + if "messageType" not in message or "data" not in message: + continue + message_type = message["messageType"] + data = message["data"] + # if message_type == "openGUI": + # await self.openGUI() + if message_type == "workspaceDirectory": + self.workspace_directory = data["workspaceDirectory"] + break + else: + other_msgs.append(msg_string) + return other_msgs + async def _send_json(self, message_type: str, data: Any): + if self.websocket.client_state == WebSocketState.DISCONNECTED: + return await self.websocket.send_json({ "messageType": message_type, "data": data @@ -154,8 +180,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer): self.onMainUserInput(data["input"]) elif message_type == "deleteAtIndex": self.onDeleteAtIndex(data["index"]) - elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret", "runCommand", "uniqueId"]: + elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "getUserSecret", "runCommand", "uniqueId"]: self.sub_queue.post(message_type, data) + elif message_type == "workspaceDirectory": + self.workspace_directory = data["workspaceDirectory"] else: raise ValueError("Unknown message type", message_type) @@ -275,18 +303,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer): resp = await self._send_and_receive_json({}, OpenFilesResponse, "openFiles") return resp.openFiles - async def getWorkspaceDirectory(self) -> str: - resp = await self._send_and_receive_json({}, WorkspaceDirectoryResponse, "workspaceDirectory") - return resp.workspaceDirectory - async def get_unique_id(self) -> str: resp = await self._send_and_receive_json({}, UniqueIdResponse, "uniqueId") return resp.uniqueId - @property - def workspace_directory(self) -> str: - return asyncio.run(self.getWorkspaceDirectory()) - @cached_property_no_none def unique_id(self) -> str: return asyncio.run(self.get_unique_id()) @@ -396,24 +416,35 @@ async def websocket_endpoint(websocket: WebSocket): print("Accepted websocket connection from, ", websocket.client) await websocket.send_json({"messageType": "connected", "data": {}}) - ideProtocolServer = IdeProtocolServer(session_manager, websocket) - - while AppStatus.should_exit is False: - message = await websocket.receive_text() - message = json.loads(message) + def handle_msg(msg): + message = json.loads(msg) if "messageType" not in message or "data" not in message: - continue + return message_type = message["messageType"] data = message["data"] - await ideProtocolServer.handle_json(message_type, data) + create_async_task( + ideProtocolServer.handle_json(message_type, data)) + + ideProtocolServer = IdeProtocolServer(session_manager, websocket) + other_msgs = await ideProtocolServer.initialize() + + for other_msg in other_msgs: + handle_msg(other_msg) + + while AppStatus.should_exit is False: + message = await websocket.receive_text() + handle_msg(message) print("Closing ide websocket") - await websocket.close() + except WebSocketDisconnect as e: + print("IDE wbsocket disconnected") except Exception as e: print("Error in ide websocket: ", e) capture_event(ideProtocolServer.unique_id, "gui_error", { "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) - await websocket.close() raise e + finally: + if websocket.client_state != WebSocketState.DISCONNECTED: + await websocket.close() diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index dfdca504..69cb6c10 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -15,10 +15,6 @@ class AbstractIdeProtocolServer(ABC): def showSuggestion(self, file_edit: FileEdit): """Show a suggestion to the user""" - @abstractmethod - async def getWorkspaceDirectory(self): - """Get the workspace directory""" - @abstractmethod async def setFileOpen(self, filepath: str, open: bool = True): """Set whether a file is open""" @@ -103,9 +99,7 @@ class AbstractIdeProtocolServer(ABC): async def showDiff(self, filepath: str, replacement: str, step_index: int): """Show a diff""" - @abstractproperty - def workspace_directory(self) -> str: - """Get the workspace directory""" + workspace_directory: str @abstractproperty def unique_id(self) -> str: diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 873a379e..7147dcfa 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -9,11 +9,13 @@ from ..core.main import FullState from ..core.autopilot import Autopilot from .ide_protocol import AbstractIdeProtocolServer from ..libs.util.create_async_task import create_async_task +from ..libs.util.errors import SessionNotFound class Session: session_id: str autopilot: Autopilot + # The GUI websocket for the session ws: Union[WebSocket, None] def __init__(self, session_id: str, autopilot: Autopilot): @@ -37,7 +39,6 @@ class DemoAutopilot(Autopilot): class SessionManager: sessions: Dict[str, Session] = {} - _event_loop: Union[BaseEventLoop, None] = None def get_session(self, session_id: str) -> Session: if session_id not in self.sessions: @@ -67,6 +68,8 @@ class SessionManager: print("Registered websocket for session", session_id) async def send_ws_data(self, session_id: str, message_type: str, data: Any): + if session_id not in self.sessions: + raise SessionNotFound(f"Session {session_id} not found") if self.sessions[session_id].ws is None: print(f"Session {session_id} has no websocket") return diff --git a/continuedev/src/continuedev/server/state_manager.py b/continuedev/src/continuedev/server/state_manager.py deleted file mode 100644 index c9bd760b..00000000 --- a/continuedev/src/continuedev/server/state_manager.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any, List, Tuple, Union -from fastapi import WebSocket -from pydantic import BaseModel -from ..core.main import FullState - -# State updates represented as (path, replacement) pairs -StateUpdate = Tuple[List[Union[str, int]], Any] - - -class StateManager: - """ - A class that acts as the source of truth for state, ingesting changes to the entire object and streaming only the updated portions to client. - """ - - def __init__(self, ws: WebSocket): - self.ws = ws - - def _send_update(self, updates: List[StateUpdate]): - self.ws.send_json( - [update.dict() for update in updates] - ) diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py index e1e041d0..14a1cd41 100644 --- a/continuedev/src/continuedev/steps/chat.py +++ b/continuedev/src/continuedev/steps/chat.py @@ -27,16 +27,21 @@ class SimpleChatStep(Step): async def run(self, sdk: ContinueSDK): completion = "" messages = self.messages or await sdk.get_chat_context() - async for chunk in sdk.models.gpt4.stream_chat(messages, temperature=0.5): - if sdk.current_step_was_deleted(): - # So that the message doesn't disappear - self.hide = False - return - - if "content" in chunk: - self.description += chunk["content"] - completion += chunk["content"] - await sdk.update_ui() + + generator = sdk.models.gpt4.stream_chat(messages, temperature=0.5) + try: + async for chunk in generator: + if sdk.current_step_was_deleted(): + # So that the message doesn't disappear + self.hide = False + return + + if "content" in chunk: + self.description += chunk["content"] + completion += chunk["content"] + await sdk.update_ui() + finally: + await generator.aclose() self.name = (await sdk.models.gpt35.complete( f"Write a short title for the following chat message: {self.description}")).strip() -- cgit v1.2.3-70-g09d2 From 4ed1d4f6c707dfd80ff60abe4b5ea1ce88583b67 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 12 Jul 2023 15:52:18 -0700 Subject: smaller fixes --- continuedev/src/continuedev/steps/core/core.py | 2 +- extension/package.json | 7 +-- .../react-app/src/components/LoadingCover.tsx | 50 ---------------------- extension/react-app/src/components/Onboarding.tsx | 4 +- extension/react-app/src/pages/gui.tsx | 24 +++++------ 5 files changed, 14 insertions(+), 73 deletions(-) delete mode 100644 extension/react-app/src/components/LoadingCover.tsx (limited to 'continuedev') diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 9eddc03f..5ea95104 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -355,7 +355,7 @@ Please output the code to be inserted at the cursor in order to fulfill the user lines_to_display = contents_lines[rewritten_lines:] new_file_contents = "\n".join( - full_prefix_lines) + "\n" + completion + "\n" + "\n".join(lines_to_display) + "\n" + "\n".join(full_suffix_lines) + full_prefix_lines) + "\n" + completion + "\n" + ("\n".join(lines_to_display) + "\n" if len(lines_to_display) > 0 else "") + "\n".join(full_suffix_lines) step_index = sdk.history.current_index diff --git a/extension/package.json b/extension/package.json index 69792ea2..6f64ee60 100644 --- a/extension/package.json +++ b/extension/package.json @@ -44,18 +44,13 @@ "configuration": { "title": "Continue", "properties": { - "continue.automode": { - "type": "boolean", - "default": true, - "description": "Automatically find relevant code and suggest a fix whenever a traceback is found." - }, "continue.serverUrl": { "type": "string", "default": "http://localhost:65432", "description": "The URL of the Continue server to use." }, "continue.OPENAI_API_KEY": { - "type": "password", + "type": "string", "default": null, "description": "The OpenAI API key to use for code generation." }, diff --git a/extension/react-app/src/components/LoadingCover.tsx b/extension/react-app/src/components/LoadingCover.tsx deleted file mode 100644 index a0f8f7a2..00000000 --- a/extension/react-app/src/components/LoadingCover.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const StyledDiv = styled.div` - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100vh; - background: linear-gradient( - 101.79deg, - #12887a 0%, - #87245c 32%, - #e12637 63%, - #ffb215 100% - ); - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - z-index: 10; -`; - -const StyledImg = styled.img` - /* add your styles here */ -`; - -const StyledDiv2 = styled.div` - width: 50%; - height: 5px; - background: white; - margin-top: 20px; -`; - -interface LoadingCoverProps { - message: string; - hidden?: boolean; -} - -const LoadingCover = (props: LoadingCoverProps) => { - return ( - - - -

{props.message}

-
- ); -}; - -export default LoadingCover; diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx index 776ad460..7772a25e 100644 --- a/extension/react-app/src/components/Onboarding.tsx +++ b/extension/react-app/src/components/Onboarding.tsx @@ -26,7 +26,7 @@ const StyledSpan = styled.span` const Onboarding = () => { const [counter, setCounter] = useState(4); - const gifs = ["intro", "explain", "edit", "generate"]; + const gifs = ["intro", "explain", "edit", "generate", "intro"]; const topMessages = [ "Welcome to Continue!", "Answer coding questions", @@ -42,7 +42,7 @@ const Onboarding = () => { useEffect(() => { const hasVisited = localStorage.getItem("hasVisited"); - if (hasVisited && false) { + if (hasVisited) { setCounter(4); } else { setCounter(0); diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index b6a18dc8..b9382bd1 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -20,7 +20,6 @@ import ReactSwitch from "react-switch"; import { usePostHog } from "posthog-js/react"; import { useSelector } from "react-redux"; import { RootStore } from "../redux/store"; -import LoadingCover from "../components/LoadingCover"; import { postVscMessage } from "../vscode"; import UserInputContainer from "../components/UserInputContainer"; import Onboarding from "../components/Onboarding"; @@ -270,19 +269,16 @@ function GUI(props: GUIProps) { // const iterations = useSelector(selectIterations); return ( <> - -