From d342c55ed2b8b229ccd40cc45f7af05cb73e7ad0 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 9 Jul 2023 13:09:34 -0700 Subject: expand max_tokens for large highlighted ranges --- continuedev/src/continuedev/libs/llm/openai.py | 6 +++--- .../src/continuedev/libs/llm/proxy_server.py | 6 +++--- .../src/continuedev/libs/util/count_tokens.py | 4 ++-- continuedev/src/continuedev/steps/core/core.py | 23 ++++++++++++++-------- 4 files changed, 23 insertions(+), 16 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py index c4e4139f..f0877d90 100644 --- a/continuedev/src/continuedev/libs/llm/openai.py +++ b/continuedev/src/continuedev/libs/llm/openai.py @@ -37,7 +37,7 @@ class OpenAI(LLM): if args["model"] in CHAT_MODELS: async for chunk in await openai.ChatCompletion.acreate( messages=compile_chat_messages( - args["model"], with_history, prompt, functions=None), + args["model"], with_history, args["max_tokens"], prompt, functions=None), **args, ): if "content" in chunk.choices[0].delta: @@ -58,7 +58,7 @@ class OpenAI(LLM): async for chunk in await openai.ChatCompletion.acreate( messages=compile_chat_messages( - args["model"], messages, functions=args.get("functions", None)), + args["model"], messages, args["max_tokens"], functions=args.get("functions", None)), **args, ): yield chunk.choices[0].delta @@ -69,7 +69,7 @@ class OpenAI(LLM): if args["model"] in CHAT_MODELS: resp = (await openai.ChatCompletion.acreate( messages=compile_chat_messages( - args["model"], with_history, prompt, functions=None), + args["model"], with_history, args["max_tokens"], prompt, functions=None), **args, )).choices[0].message.content else: diff --git a/continuedev/src/continuedev/libs/llm/proxy_server.py b/continuedev/src/continuedev/libs/llm/proxy_server.py index 05ece394..eab6e441 100644 --- a/continuedev/src/continuedev/libs/llm/proxy_server.py +++ b/continuedev/src/continuedev/libs/llm/proxy_server.py @@ -38,7 +38,7 @@ class ProxyServer(LLM): async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session: async with session.post(f"{SERVER_URL}/complete", json={ - "messages": compile_chat_messages(args["model"], with_history, prompt, functions=None), + "messages": compile_chat_messages(args["model"], with_history, args["max_tokens"], prompt, functions=None), "unique_id": self.unique_id, **args }) as resp: @@ -50,7 +50,7 @@ class ProxyServer(LLM): async def stream_chat(self, messages: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, Generator[Union[Any, List, Dict], None, None]]: args = {**self.default_args, **kwargs} messages = compile_chat_messages( - self.default_model, messages, None, functions=args.get("functions", None)) + self.default_model, messages, args["max_tokens"], None, functions=args.get("functions", None)) async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session: async with session.post(f"{SERVER_URL}/stream_chat", json={ @@ -74,7 +74,7 @@ class ProxyServer(LLM): async def stream_complete(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]: args = {**self.default_args, **kwargs} messages = compile_chat_messages( - self.default_model, with_history, prompt, functions=args.get("functions", None)) + self.default_model, with_history, args["max_tokens"], prompt, functions=args.get("functions", None)) async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session: async with session.post(f"{SERVER_URL}/stream_complete", json={ diff --git a/continuedev/src/continuedev/libs/util/count_tokens.py b/continuedev/src/continuedev/libs/util/count_tokens.py index 8b06fef9..73be0717 100644 --- a/continuedev/src/continuedev/libs/util/count_tokens.py +++ b/continuedev/src/continuedev/libs/util/count_tokens.py @@ -76,14 +76,14 @@ def prune_chat_history(model: str, chat_history: List[ChatMessage], max_tokens: return chat_history -def compile_chat_messages(model: str, msgs: List[ChatMessage], prompt: Union[str, None] = None, functions: Union[List, None] = None, system_message: Union[str, None] = None) -> List[Dict]: +def compile_chat_messages(model: str, msgs: List[ChatMessage], max_tokens: int, prompt: Union[str, None] = None, functions: Union[List, None] = None, system_message: Union[str, None] = None) -> List[Dict]: prompt_tokens = count_tokens(model, prompt) if functions is not None: for function in functions: prompt_tokens += count_tokens(model, json.dumps(function)) msgs = prune_chat_history(model, - msgs, MAX_TOKENS_FOR_MODEL[model], prompt_tokens + DEFAULT_MAX_TOKENS + count_tokens(model, system_message)) + msgs, MAX_TOKENS_FOR_MODEL[model], prompt_tokens + max_tokens + count_tokens(model, system_message)) history = [] if system_message: history.append({ diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 10853828..4b35a758 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -181,15 +181,22 @@ class DefaultModelEditCodeStep(Step): # We care because if this prompt itself goes over the limit, then the entire message will have to be cut from the completion. # Overflow won't happen, but prune_chat_messages in count_tokens.py will cut out this whole thing, instead of us cutting out only as many lines as we need. model_to_use = sdk.models.gpt4 + max_tokens = DEFAULT_MAX_TOKENS - BUFFER_FOR_FUNCTIONS = 400 - total_tokens = model_to_use.count_tokens( - full_file_contents + self._prompt + self.user_input) + BUFFER_FOR_FUNCTIONS + DEFAULT_MAX_TOKENS - - TOKENS_TO_BE_CONSIDERED_LARGE_RANGE = 1000 + TOKENS_TO_BE_CONSIDERED_LARGE_RANGE = 1200 if model_to_use.count_tokens(rif.contents) > TOKENS_TO_BE_CONSIDERED_LARGE_RANGE: self.description += "\n\n**It looks like you've selected a large range to edit, which may take a while to complete. If you'd like to cancel, click the 'X' button above. If you highlight a more specific range, Continue will only edit within it.**" + # At this point, we also increase the max_tokens parameter so it doesn't stop in the middle of generation + # Increase max_tokens to be double the size of the range + # But don't exceed twice default max tokens + max_tokens = int(min(model_to_use.count_tokens( + rif.contents), DEFAULT_MAX_TOKENS) * 2.5) + + BUFFER_FOR_FUNCTIONS = 400 + total_tokens = model_to_use.count_tokens( + full_file_contents + self._prompt + self.user_input) + BUFFER_FOR_FUNCTIONS + max_tokens + # If using 3.5 and overflows, upgrade to 3.5.16k if model_to_use.name == "gpt-3.5-turbo": if total_tokens > MAX_TOKENS_FOR_MODEL["gpt-3.5-turbo"]: @@ -252,7 +259,7 @@ class DefaultModelEditCodeStep(Step): file_suffix = "\n" + file_suffix rif.contents = rif.contents[:-1] - return file_prefix, rif.contents, file_suffix, model_to_use + 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: prompt = self._prompt @@ -289,7 +296,7 @@ class DefaultModelEditCodeStep(Step): await sdk.ide.saveFile(rif.filepath) full_file_contents = await sdk.ide.readFile(rif.filepath) - file_prefix, contents, file_suffix, model_to_use = await self.get_prompt_parts( + file_prefix, contents, file_suffix, model_to_use, max_tokens = await self.get_prompt_parts( rif, sdk, full_file_contents) contents, common_whitespace = dedent_and_get_common_whitespace( contents) @@ -435,7 +442,7 @@ class DefaultModelEditCodeStep(Step): completion_lines_covered = 0 repeating_file_suffix = False line_below_highlighted_range = file_suffix.lstrip().split("\n")[0] - async for chunk in model_to_use.stream_chat(messages, temperature=0): + async for chunk in model_to_use.stream_chat(messages, temperature=0, max_tokens=max_tokens): # Stop early if it is repeating the file_suffix or the step was deleted if repeating_file_suffix: break -- cgit v1.2.3-70-g09d2 From 2146529ff2f73356102f67f5011ef2437f334ad6 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 9 Jul 2023 13:21:02 -0700 Subject: cleaning up context adding logic --- continuedev/src/continuedev/core/autopilot.py | 32 +++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 8b3fb97d..b583d682 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -151,31 +151,29 @@ class Autopilot(ContinueBaseModel): self._highlighted_ranges[0].editing = True async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]): - - # If un-highlighting, then remove the range - if len(self._highlighted_ranges) == 1 and len(range_in_files) <= 1 and (len(range_in_files) == 0 or range_in_files[0].range.start == range_in_files[0].range.end) and not self._adding_highlighted_code: - self._highlighted_ranges = [] - await self.update_subscribers() - return - - # If not toggled to be adding context, only edit or add the first range - if not self._adding_highlighted_code and len(self._highlighted_ranges) > 0: - if len(range_in_files) == 0: - return - if range_in_files[0].range.overlaps_with(self._highlighted_ranges[0].range.range) and range_in_files[0].filepath == self._highlighted_ranges[0].range.filepath: - self._highlighted_ranges = [HighlightedRangeContext( - range=range_in_files[0].range, editing=True, pinned=False)] - await self.update_subscribers() - return - # Filter out rifs from ~/.continue/diffs folder range_in_files = [ rif for rif in range_in_files if not os.path.dirname(rif.filepath) == os.path.expanduser("~/.continue/diffs")] + # Make sure all filepaths are relative to workspace workspace_path = self.continue_sdk.ide.workspace_directory for rif in range_in_files: rif.filepath = os.path.basename(rif.filepath) + # If not adding highlighted code + if not self._adding_highlighted_code: + if len(self._highlighted_ranges) == 1 and len(range_in_files) <= 1 and (len(range_in_files) == 0 or range_in_files[0].range.start == range_in_files[0].range.end): + # If un-highlighting the range to edit, then remove the range + self._highlighted_ranges = [] + await self.update_subscribers() + elif len(range_in_files) > 0: + # Otherwise, replace the current range with the new one + # This is the first range to be highlighted + self._highlighted_ranges = [HighlightedRangeContext( + range=range_in_files[0], editing=True, pinned=False)] + await self.update_subscribers() + return + # If current range overlaps with any others, delete them and only keep the new range new_ranges = [] for i, rif in enumerate(self._highlighted_ranges): -- cgit v1.2.3-70-g09d2 From 5709c9ae003ededb520ee641cae8c8570d4c93af Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 9 Jul 2023 14:12:36 -0700 Subject: sdk.get_code_context --- continuedev/src/continuedev/core/autopilot.py | 6 ++-- continuedev/src/continuedev/core/main.py | 1 + continuedev/src/continuedev/core/sdk.py | 7 ++++- continuedev/src/continuedev/steps/main.py | 38 +++++++++++-------------- extension/react-app/src/components/ComboBox.tsx | 6 ++-- extension/schema/FullState.d.ts | 2 ++ schema/json/FullState.json | 7 ++++- 7 files changed, 36 insertions(+), 31 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index b583d682..5c3baafd 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -157,8 +157,6 @@ class Autopilot(ContinueBaseModel): # Make sure all filepaths are relative to workspace workspace_path = self.continue_sdk.ide.workspace_directory - for rif in range_in_files: - rif.filepath = os.path.basename(rif.filepath) # If not adding highlighted code if not self._adding_highlighted_code: @@ -170,7 +168,7 @@ class Autopilot(ContinueBaseModel): # Otherwise, replace the current range with the new one # This is the first range to be highlighted self._highlighted_ranges = [HighlightedRangeContext( - range=range_in_files[0], editing=True, pinned=False)] + range=range_in_files[0], editing=True, pinned=False, display_name=os.path.basename(range_in_files[0].filepath))] await self.update_subscribers() return @@ -193,7 +191,7 @@ class Autopilot(ContinueBaseModel): new_ranges.append(rif) self._highlighted_ranges = new_ranges + [HighlightedRangeContext( - range=rif, editing=False, pinned=False + range=rif, editing=False, pinned=False, display_name=os.path.basename(rif.filepath) ) for rif in range_in_files] self._make_sure_is_editing_range() diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 4ea17f20..88690c83 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -205,6 +205,7 @@ class HighlightedRangeContext(ContinueBaseModel): range: RangeInFileWithContents editing: bool pinned: bool + display_name: str class FullState(ContinueBaseModel): diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index ed670799..8649cd58 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -13,7 +13,7 @@ from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI from ..libs.llm.openai import OpenAI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer -from .main import Context, ContinueCustomException, History, Step, ChatMessage, ChatMessageRole +from .main import Context, ContinueCustomException, HighlightedRangeContext, History, Step, ChatMessage, ChatMessageRole from ..steps.core.core import * from ..libs.llm.proxy_server import ProxyServer @@ -178,6 +178,11 @@ class ContinueSDK(AbstractContinueSDK): 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 + return [c.range for c in context] + def update_default_model(self, model: str): config = self.config config.default_model = model diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 5ccffbfe..0a20ddd7 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -97,7 +97,7 @@ class FasterEditHighlightedCodeStep(Step): return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - range_in_files = await sdk.ide.getHighlightedCode() + range_in_files = await sdk.get_code_context(only_editing=True) if len(range_in_files) == 0: # Get the full contents of all open files files = await sdk.ide.getOpenFiles() @@ -105,21 +105,16 @@ class FasterEditHighlightedCodeStep(Step): for file in files: contents[file] = await sdk.ide.readFile(file) - range_in_files = [RangeInFile.from_entire_file( + range_in_files = [RangeInFileWithContents.from_entire_file( filepath, content) for filepath, content in contents.items()] - rif_with_contents = [] - for range_in_file in range_in_files: - file_contents = await sdk.ide.readRangeInFile(range_in_file) - rif_with_contents.append( - RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) - enc_dec = MarkdownStyleEncoderDecoder(rif_with_contents) + enc_dec = MarkdownStyleEncoderDecoder(range_in_files) code_string = enc_dec.encode() prompt = self._prompt.format( code=code_string, user_input=self.user_input) rif_dict = {} - for rif in rif_with_contents: + for rif in range_in_files: rif_dict[rif.filepath] = rif.contents completion = await sdk.models.gpt35.complete(prompt) @@ -193,7 +188,7 @@ class StarCoderEditHighlightedCodeStep(Step): 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() + range_in_files = await sdk.get_code_context(only_editing=True) found_highlighted_code = len(range_in_files) > 0 if not found_highlighted_code: # Get the full contents of all open files @@ -202,20 +197,14 @@ class StarCoderEditHighlightedCodeStep(Step): for file in files: contents[file] = await sdk.ide.readFile(file) - range_in_files = [RangeInFile.from_entire_file( + range_in_files = [RangeInFileWithContents.from_entire_file( filepath, content) for filepath, content in contents.items()] - rif_with_contents = [] - for range_in_file in range_in_files: - file_contents = await sdk.ide.readRangeInFile(range_in_file) - rif_with_contents.append( - RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) - rif_dict = {} - for rif in rif_with_contents: + for rif in range_in_files: rif_dict[rif.filepath] = rif.contents - for rif in rif_with_contents: + for rif in range_in_files: prompt = self._prompt.format( code=rif.contents, user_request=self.user_input) @@ -255,7 +244,7 @@ class EditHighlightedCodeStep(Step): return "Editing code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - range_in_files = await sdk.ide.getHighlightedCode() + range_in_files = sdk.get_code_context(only_editing=True) if len(range_in_files) == 0: # Get the full contents of all open files files = await sdk.ide.getOpenFiles() @@ -263,7 +252,7 @@ class EditHighlightedCodeStep(Step): for file in files: contents[file] = await sdk.ide.readFile(file) - range_in_files = [RangeInFile.from_entire_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 @@ -271,7 +260,12 @@ class EditHighlightedCodeStep(Step): # Create a new file new_file_path = "new_file.txt" await sdk.add_file(new_file_path, "") - range_in_files = [RangeInFile.from_entire_file(new_file_path, "")] + range_in_files = [ + RangeInFileWithContents.from_entire_file(new_file_path, "")] + + range_in_files = list(map(lambda x: RangeInFile( + filepath=x.filepath, range=x.range + ), range_in_files)) await sdk.run_step(DefaultModelEditCodeStep(user_input=self.user_input, range_in_files=range_in_files)) diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index e6632360..801c3a03 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -224,8 +224,8 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { editing={section.editing} pinned={section.pinned} index={idx} - key={`${section.filepath}${idx}`} - title={`${section.range.filepath} (${ + key={`${section.display_name}${idx}`} + title={`${section.display_name} (${ section.range.range.start.line + 1 }-${section.range.range.end.line + 1})`} onDelete={() => { @@ -372,7 +372,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { > {highlightedCodeSections.map((section, idx) => ( <> -

{section.range.filepath}

+

{section.display_name}

{section.range.contents} diff --git a/extension/schema/FullState.d.ts b/extension/schema/FullState.d.ts index 981e772e..abb0832d 100644 --- a/extension/schema/FullState.d.ts +++ b/extension/schema/FullState.d.ts @@ -32,6 +32,7 @@ export type Character = number; export type Contents = string; export type Editing = boolean; export type Pinned = boolean; +export type DisplayName = string; export type HighlightedRanges = HighlightedRangeContext[]; export type Name3 = string; export type Description1 = string; @@ -102,6 +103,7 @@ export interface HighlightedRangeContext { range: RangeInFileWithContents; editing: Editing; pinned: Pinned; + display_name: DisplayName; [k: string]: unknown; } /** diff --git a/schema/json/FullState.json b/schema/json/FullState.json index af0f25e1..5a7e9d10 100644 --- a/schema/json/FullState.json +++ b/schema/json/FullState.json @@ -222,12 +222,17 @@ "pinned": { "title": "Pinned", "type": "boolean" + }, + "display_name": { + "title": "Display Name", + "type": "string" } }, "required": [ "range", "editing", - "pinned" + "pinned", + "display_name" ] }, "SlashCommandDescription": { -- cgit v1.2.3-70-g09d2 From 20f4d07eb1d584569752e67c754951b7892e3e6b Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 9 Jul 2023 14:31:54 -0700 Subject: edit at cursor --- continuedev/src/continuedev/steps/main.py | 11 +++++++++++ extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 0a20ddd7..4f543022 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -245,6 +245,17 @@ class EditHighlightedCodeStep(Step): async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: range_in_files = sdk.get_code_context(only_editing=True) + + # If nothing highlighted, insert at the cursor if possible + if len(range_in_files) == 0: + highlighted_code = await sdk.ide.getHighlightedCode() + if highlighted_code is not None: + for rif in highlighted_code: + if rif.range.start == rif.range.end: + range_in_files.append( + RangeInFileWithContents.from_range_in_file(rif, "")) + + # If nothing highlighted, edit the first open file if len(range_in_files) == 0: # Get the full contents of all open files files = await sdk.ide.getOpenFiles() diff --git a/extension/package-lock.json b/extension/package-lock.json index 22f8b492..5733c2dd 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.141", + "version": "0.0.143", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.141", + "version": "0.0.143", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index beb675b3..444372f8 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.141", + "version": "0.0.143", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 13848f99e12f69d9d58c3cb31e9e54e386eb3af0 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 +++++++---- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) (limited to 'continuedev/src') 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) diff --git a/extension/package-lock.json b/extension/package-lock.json index 5733c2dd..a2ac0a04 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.143", + "version": "0.0.145", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.143", + "version": "0.0.145", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 3b23ff19..0464ba55 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.143", + "version": "0.0.145", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 55ff206a9c24e67cd1f32ec86d705206530e668f 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/src') 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 20797dbdcdbcfc75d4eaa7cdfccfd384ecbdecd8 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/src') 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 cec0a7df727255b61d5edb4208d286dc17e81096 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/src') 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 50adabaef68917a65b123dbf6026c410b8c5a006 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/src') 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 32faad17ed525209d51756615cddea74f905076c 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/src') 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 b1a39567addc511ac0b477aaedaae4e10d7f5d31 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/src') 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 53fe4afedb4d9b89dce0c3c3934d69a606fa8535 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/src') 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 7780bbed2ab9dfa6d7152d9b63f140984dfaee0c 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/src') 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 f775ed5678109c6ed8b07722ddf0599d5df0dd25 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/src') 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 31fa2bb97461e8c43377ae574607958e51c507b2 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/src') 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 19e79b9336fd9abe5776655ece25224d22d92ad8 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/src') 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 5593c72720dec57f8b734d442e7792fb30632d90 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/src') 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 f6c71a6cfe746ee4bb97c4cbe86827679a199ade 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/src') 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 ( <> - -