From 0d10108db66a6b97caedd342fe330b2db1771206 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Fri, 14 Jul 2023 01:45:24 -0700
Subject: copy while streaming!

---
 .../react-app/src/components/StepContainer.tsx     | 71 ++++++++++------------
 1 file changed, 32 insertions(+), 39 deletions(-)

(limited to 'extension/react-app/src/components/StepContainer.tsx')

diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index d1a8a46a..6fa4ba13 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState } from "react";
 import styled, { keyframes } from "styled-components";
 import {
   appear,
@@ -15,9 +15,8 @@ import {
 } from "@styled-icons/heroicons-outline";
 import { StopCircle } from "@styled-icons/heroicons-solid";
 import { HistoryNode } from "../../../schema/HistoryNode";
-import ReactMarkdown from "react-markdown";
 import HeaderButtonWithText from "./HeaderButtonWithText";
-import CodeBlock from "./CodeBlock";
+import MarkdownPreview from "@uiw/react-markdown-preview";
 
 interface StepContainerProps {
   historyNode: HistoryNode;
@@ -72,19 +71,6 @@ const ContentDiv = styled.div<{ isUserInput: boolean }>`
   font-size: 13px;
 `;
 
-const MarkdownPre = styled.pre`
-  background-color: ${secondaryDark};
-  padding: 10px;
-  border-radius: ${defaultBorderRadius};
-  border: 0.5px solid white;
-`;
-
-const StyledCode = styled.code`
-  word-wrap: break-word;
-  color: #f69292;
-  background: transparent;
-`;
-
 const gradient = keyframes`
   0% {
     background-position: 0px 0;
@@ -124,6 +110,31 @@ const GradientBorder = styled.div<{
   background-size: 200% 200%;
 `;
 
+const StyledMarkdownPreview = styled(MarkdownPreview)`
+  pre {
+    background-color: ${secondaryDark};
+    padding: 1px;
+    border-radius: ${defaultBorderRadius};
+    border: 0.5px solid white;
+  }
+
+  code {
+    color: #f69292;
+    word-wrap: break-word;
+  }
+
+  pre > code {
+    background-color: ${secondaryDark};
+    color: white;
+  }
+
+  background-color: ${vscBackground};
+  font-family: "Lexend", sans-serif;
+  font-size: 13px;
+  padding: 8px;
+  color: white;
+`;
+
 // #endregion
 
 function StepContainer(props: StepContainerProps) {
@@ -158,7 +169,7 @@ function StepContainer(props: StepContainerProps) {
     >
       <StepContainerDiv open={props.open}>
         <GradientBorder
-          loading={props.historyNode.active as boolean || false}
+          loading={(props.historyNode.active as boolean) || false}
           isFirst={props.isFirst}
           isLast={props.isLast}
           borderColor={
@@ -178,7 +189,7 @@ function StepContainer(props: StepContainerProps) {
           }}
         >
           <HeaderDiv
-            loading={props.historyNode.active as boolean || false}
+            loading={(props.historyNode.active as boolean) || false}
             error={props.historyNode.observation?.error ? true : false}
           >
             <div className="m-2">
@@ -246,27 +257,9 @@ function StepContainer(props: StepContainerProps) {
               {props.historyNode.observation.error as string}
             </pre>
           ) : (
-            <ReactMarkdown
-              key={1}
-              className="overflow-x-scroll"
-              components={{
-                pre: ({ node, ...props }) => {
-                  return (
-                    <CodeBlock
-                      children={(props.children[0] as any).props.children[0]}
-                    />
-                  );
-                },
-                code: ({ node, ...props }) => {
-                  return <StyledCode children={props.children[0] as any} />;
-                },
-                ul: ({ node, ...props }) => {
-                  return <ul className="ml-0" {...props} />;
-                },
-              }}
-            >
-              {props.historyNode.step.description as any}
-            </ReactMarkdown>
+            <StyledMarkdownPreview
+              source={props.historyNode.step.description || ""}
+            />
           )}
         </ContentDiv>
       </StepContainerDiv>
-- 
cgit v1.2.3-70-g09d2


From f0b2597895920b7d714b53f2d70a3a5858f89d42 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Fri, 14 Jul 2023 13:45:10 -0700
Subject: insidious client_state vs application_state err

---
 continuedev/src/continuedev/core/autopilot.py        | 2 ++
 continuedev/src/continuedev/server/gui.py            | 2 +-
 continuedev/src/continuedev/server/ide.py            | 2 +-
 continuedev/src/continuedev/steps/core/core.py       | 9 ++++++++-
 extension/react-app/src/components/ComboBox.tsx      | 9 ++++++++-
 extension/react-app/src/components/StepContainer.tsx | 9 ++++++---
 extension/src/continueIdeClient.ts                   | 8 ++++++--
 extension/src/diffs.ts                               | 2 +-
 extension/src/util/messenger.ts                      | 2 +-
 9 files changed, 34 insertions(+), 11 deletions(-)

(limited to 'extension/react-app/src/components/StepContainer.tsx')

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index e1c8a076..82439f49 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -37,6 +37,8 @@ def get_error_title(e: Exception) -> str:
         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 ""'
+    elif e.__str__().startswith("Cannot connect to host"):
+        return "The request failed. Please check your internet connection and try again."
     return e.__str__() or e.__repr__()
 
 
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index 238273b2..9a411fbe 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -53,7 +53,7 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
         self.session = session
 
     async def _send_json(self, message_type: str, data: Any):
-        if self.websocket.client_state == WebSocketState.DISCONNECTED:
+        if self.websocket.application_state == WebSocketState.DISCONNECTED:
             return
         await self.websocket.send_json({
             "messageType": message_type,
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 73cce201..7875c94d 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -149,7 +149,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
         return other_msgs
 
     async def _send_json(self, message_type: str, data: Any):
-        if self.websocket.client_state == WebSocketState.DISCONNECTED:
+        if self.websocket.application_state == WebSocketState.DISCONNECTED:
             return
         await self.websocket.send_json({
             "messageType": message_type,
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index 787da316..75f8e460 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -9,7 +9,7 @@ from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
 from ...models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit
 from ...models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents
 from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation
-from ...core.main import ChatMessage, Step, SequentialStep
+from ...core.main import ChatMessage, ContinueCustomException, Step, SequentialStep
 from ...libs.util.count_tokens import MAX_TOKENS_FOR_MODEL, DEFAULT_MAX_TOKENS
 from ...libs.util.dedent import dedent_and_get_common_whitespace
 import difflib
@@ -608,6 +608,13 @@ Please output the code to be inserted at the cursor in order to fulfill the user
             rif_dict[rif.filepath] = rif.contents
 
         for rif in rif_with_contents:
+            # If the file doesn't exist, ask them to save it first
+            if not os.path.exists(rif.filepath):
+                message = f"The file {rif.filepath} does not exist. Please save it first."
+                raise ContinueCustomException(
+                    title=message, message=message
+                )
+
             await sdk.ide.setFileOpen(rif.filepath)
             await sdk.ide.setSuggestionsLocked(rif.filepath, True)
             await self.stream_rif(rif, sdk)
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 5d9b5109..754c9445 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -169,6 +169,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
   useImperativeHandle(ref, () => downshiftProps, [downshiftProps]);
 
   const [metaKeyPressed, setMetaKeyPressed] = useState(false);
+  const [focused, setFocused] = useState(false);
   useEffect(() => {
     const handleKeyDown = (e: KeyboardEvent) => {
       if (e.key === "Meta") {
@@ -298,7 +299,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
 
               // setShowContextDropdown(target.value.endsWith("@"));
             },
+            onFocus: (e) => {
+              setFocused(true);
+            },
             onBlur: (e) => {
+              setFocused(false);
               postVscMessage("blurContinueInput", {});
             },
             onKeyDown: (event) => {
@@ -374,7 +379,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
       </div>
       {highlightedCodeSections.length === 0 &&
         (downshiftProps.inputValue?.startsWith("/edit") ||
-          (metaKeyPressed && downshiftProps.inputValue?.length > 0)) && (
+          (focused &&
+            metaKeyPressed &&
+            downshiftProps.inputValue?.length > 0)) && (
           <div className="text-trueGray-400 pr-4 text-xs text-right">
             Inserting at cursor
           </div>
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 6fa4ba13..14e9b854 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -253,9 +253,12 @@ function StepContainer(props: StepContainerProps) {
           )}
 
           {props.historyNode.observation?.error ? (
-            <pre className="overflow-x-scroll">
-              {props.historyNode.observation.error as string}
-            </pre>
+            <details>
+              <summary>View Traceback</summary>
+              <pre className="overflow-x-scroll">
+                {props.historyNode.observation.error as string}
+              </pre>
+            </details>
           ) : (
             <StyledMarkdownPreview
               source={props.historyNode.step.description || ""}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index 4c1fdf1e..6dd117d3 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -386,8 +386,12 @@ class IdeProtocolClient {
         contents = editor.document.getText();
       }
     });
-    if (!contents) {
-      contents = fs.readFileSync(filepath, "utf-8");
+    if (typeof contents === "undefined") {
+      if (fs.existsSync(filepath)) {
+        contents = fs.readFileSync(filepath, "utf-8");
+      } else {
+        contents = "";
+      }
     }
     return contents;
   }
diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts
index 37943de4..d04f9bdb 100644
--- a/extension/src/diffs.ts
+++ b/extension/src/diffs.ts
@@ -129,7 +129,7 @@ class DiffManager {
     // Open the diff editor if this is a new diff
     if (!this.diffs.has(newFilepath)) {
       // Figure out the first line that is different
-      const oldContent = fs.readFileSync(originalFilepath).toString("utf-8");
+      const oldContent = ideProtocolClient.readFile(originalFilepath);
       const line = this._findFirstDifferentLine(oldContent, newContent);
 
       const diffInfo: DiffInfo = {
diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts
index 7fd71ddd..3044898e 100644
--- a/extension/src/util/messenger.ts
+++ b/extension/src/util/messenger.ts
@@ -15,7 +15,7 @@ export abstract class Messenger {
   abstract onOpen(callback: () => void): void;
 
   abstract onClose(callback: () => void): void;
-  
+
   abstract onError(callback: () => void): void;
 
   abstract sendAndReceive(messageType: string, data: any): Promise<any>;
-- 
cgit v1.2.3-70-g09d2


From 925c3e0ef45d9eb01a8f6c1efd239fa011492bd2 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sat, 15 Jul 2023 14:30:11 -0700
Subject: ctrl shortcuts on windows, load models immediately

---
 continuedev/src/continuedev/core/autopilot.py      | 10 ++--
 continuedev/src/continuedev/core/sdk.py            | 59 +++++++++++++++-------
 .../src/continuedev/libs/llm/hf_inference_api.py   |  6 ++-
 continuedev/src/continuedev/server/ide.py          |  4 +-
 .../src/continuedev/server/session_manager.py      |  6 +--
 .../react-app/src/components/StepContainer.tsx     |  2 +-
 extension/react-app/src/components/TextDialog.tsx  |  6 ++-
 extension/react-app/src/pages/gui.tsx              |  6 +--
 extension/react-app/src/util/index.ts              | 30 +++++++++++
 9 files changed, 98 insertions(+), 31 deletions(-)
 create mode 100644 extension/react-app/src/util/index.ts

(limited to 'extension/react-app/src/components/StepContainer.tsx')

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 82439f49..0696c360 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -50,6 +50,8 @@ class Autopilot(ContinueBaseModel):
     full_state: Union[FullState, None] = None
     _on_update_callbacks: List[Callable[[FullState], None]] = []
 
+    continue_sdk: ContinueSDK = None
+
     _active: bool = False
     _should_halt: bool = False
     _main_user_input_queue: List[str] = []
@@ -57,9 +59,11 @@ class Autopilot(ContinueBaseModel):
     _user_input_queue = AsyncSubscriptionQueue()
     _retry_queue = AsyncSubscriptionQueue()
 
-    @cached_property
-    def continue_sdk(self) -> ContinueSDK:
-        return ContinueSDK(self)
+    @classmethod
+    async def create(cls, policy: Policy, ide: AbstractIdeProtocolServer, full_state: FullState) -> "Autopilot":
+        autopilot = cls(ide=ide, policy=policy)
+        autopilot.continue_sdk = await ContinueSDK.create(autopilot)
+        return autopilot
 
     class Config:
         arbitrary_types_allowed = True
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index aa2d8892..d73561d2 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -1,6 +1,6 @@
 import asyncio
 from functools import cached_property
-from typing import Coroutine, Union
+from typing import Coroutine, Dict, Union
 import os
 
 from ..steps.core.core import DefaultModelEditCodeStep
@@ -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, HighlightedRangeContext, History, Step, ChatMessage, ChatMessageRole
+from .main import Context, ContinueCustomException, History, Step, ChatMessage
 from ..steps.core.core import *
 from ..libs.llm.proxy_server import ProxyServer
 
@@ -22,26 +22,46 @@ class Autopilot:
     pass
 
 
+ModelProvider = Literal["openai", "hf_inference_api", "ggml", "anthropic"]
+MODEL_PROVIDER_TO_ENV_VAR = {
+    "openai": "OPENAI_API_KEY",
+    "hf_inference_api": "HUGGING_FACE_TOKEN",
+    "anthropic": "ANTHROPIC_API_KEY"
+}
+
+
 class Models:
-    def __init__(self, sdk: "ContinueSDK"):
+    provider_keys: Dict[ModelProvider, str] = {}
+    model_providers: List[ModelProvider]
+
+    def __init__(self, sdk: "ContinueSDK", model_providers: List[ModelProvider]):
         self.sdk = sdk
+        self.model_providers = model_providers
+
+    @classmethod
+    async def create(cls, sdk: "ContinueSDK", with_providers: List[ModelProvider] = ["openai"]) -> "Models":
+        models = Models(sdk, with_providers)
+        for provider in with_providers:
+            if provider in MODEL_PROVIDER_TO_ENV_VAR:
+                env_var = MODEL_PROVIDER_TO_ENV_VAR[provider]
+                models.provider_keys[provider] = await sdk.get_user_secret(
+                    env_var, f'Please add your {env_var} to the .env file')
+
+        return models
 
     def __load_openai_model(self, model: str) -> OpenAI:
-        async def load_openai_model():
-            api_key = await self.sdk.get_user_secret(
-                'OPENAI_API_KEY', 'Enter your OpenAI API key or press enter to try for free')
-            if api_key == "":
-                return ProxyServer(self.sdk.ide.unique_id, model)
-            return OpenAI(api_key=api_key, default_model=model)
-        return asyncio.get_event_loop().run_until_complete(load_openai_model())
+        api_key = self.provider_keys["openai"]
+        if api_key == "":
+            return ProxyServer(self.sdk.ide.unique_id, model)
+        return OpenAI(api_key=api_key, default_model=model)
+
+    def __load_hf_inference_api_model(self, model: str) -> HuggingFaceInferenceAPI:
+        api_key = self.provider_keys["hf_inference_api"]
+        return HuggingFaceInferenceAPI(api_key=api_key, model=model)
 
     @cached_property
     def starcoder(self):
-        async def load_starcoder():
-            api_key = await self.sdk.get_user_secret(
-                'HUGGING_FACE_TOKEN', 'Please add your Hugging Face token to the .env file')
-            return HuggingFaceInferenceAPI(api_key=api_key)
-        return asyncio.get_event_loop().run_until_complete(load_starcoder())
+        return self.__load_hf_inference_api_model("bigcode/starcoder")
 
     @cached_property
     def gpt35(self):
@@ -74,7 +94,7 @@ class Models:
     @property
     def default(self):
         default_model = self.sdk.config.default_model
-        return self.__model_from_name(default_model) if default_model is not None else self.gpt35
+        return self.__model_from_name(default_model) if default_model is not None else self.gpt4
 
 
 class ContinueSDK(AbstractContinueSDK):
@@ -87,10 +107,15 @@ class ContinueSDK(AbstractContinueSDK):
     def __init__(self, autopilot: Autopilot):
         self.ide = autopilot.ide
         self.__autopilot = autopilot
-        self.models = Models(self)
         self.context = autopilot.context
         self.config = self._load_config()
 
+    @classmethod
+    async def create(cls, autopilot: Autopilot) -> "ContinueSDK":
+        sdk = ContinueSDK(autopilot)
+        sdk.models = await Models.create(sdk)
+        return sdk
+
     config: ContinueConfig
 
     def _load_config(self) -> ContinueConfig:
diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py
index 1586c620..803ba122 100644
--- a/continuedev/src/continuedev/libs/llm/hf_inference_api.py
+++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py
@@ -9,7 +9,11 @@ DEFAULT_MAX_TIME = 120.
 
 class HuggingFaceInferenceAPI(LLM):
     api_key: str
-    model: str = "bigcode/starcoder"
+    model: str
+
+    def __init__(self, api_key: str, model: str):
+        self.api_key = api_key
+        self.model = model
 
     def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs):
         """Return the completion of the text with the given temperature."""
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 7875c94d..77b13483 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -227,8 +227,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
         })
 
     async def getSessionId(self):
-        session_id = self.session_manager.new_session(
-            self, self.session_id).session_id
+        session_id = (await self.session_manager.new_session(
+            self, self.session_id)).session_id
         await self._send_json("getSessionId", {
             "sessionId": session_id
         })
diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index fb8ac386..6d109ca6 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -53,18 +53,18 @@ class SessionManager:
             session_files = os.listdir(sessions_folder)
             if f"{session_id}.json" in session_files and session_id in self.registered_ides:
                 if self.registered_ides[session_id].session_id is not None:
-                    return self.new_session(self.registered_ides[session_id], session_id=session_id)
+                    return await self.new_session(self.registered_ides[session_id], session_id=session_id)
 
             raise KeyError("Session ID not recognized", session_id)
         return self.sessions[session_id]
 
-    def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session:
+    async def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session:
         full_state = None
         if session_id is not None and os.path.exists(getSessionFilePath(session_id)):
             with open(getSessionFilePath(session_id), "r") as f:
                 full_state = FullState(**json.load(f))
 
-        autopilot = DemoAutopilot(
+        autopilot = await DemoAutopilot.create(
             policy=DemoPolicy(), ide=ide, full_state=full_state)
         session_id = session_id or str(uuid4())
         ide.session_id = session_id
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 14e9b854..7f23e333 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -181,7 +181,7 @@ function StepContainer(props: StepContainerProps) {
           }
           className="overflow-hidden cursor-pointer"
           onClick={(e) => {
-            if (e.metaKey) {
+            if (isMetaEquivalentKeyPressed(e)) {
               props.onToggleAll();
             } else {
               props.onToggle();
diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx
index ea5727f0..c724697d 100644
--- a/extension/react-app/src/components/TextDialog.tsx
+++ b/extension/react-app/src/components/TextDialog.tsx
@@ -81,7 +81,11 @@ const TextDialog = (props: {
             rows={10}
             ref={textAreaRef}
             onKeyDown={(e) => {
-              if (e.key === "Enter" && e.metaKey && textAreaRef.current) {
+              if (
+                e.key === "Enter" &&
+                isMetaEquivalentKeyPressed(e) &&
+                textAreaRef.current
+              ) {
                 props.onEnter(textAreaRef.current.value);
                 setText("");
               } else if (e.key === "Escape") {
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index 57cebac3..cb0404ab 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -137,12 +137,12 @@ function GUI(props: GUIProps) {
   useEffect(() => {
     const listener = (e: any) => {
       // Cmd + i to toggle fast model
-      if (e.key === "i" && e.metaKey && e.shiftKey) {
+      if (e.key === "i" && isMetaEquivalentKeyPressed(e) && e.shiftKey) {
         setUsingFastModel((prev) => !prev);
         // Cmd + backspace to stop currently running step
       } else if (
         e.key === "Backspace" &&
-        e.metaKey &&
+        isMetaEquivalentKeyPressed(e) &&
         typeof history?.current_index !== "undefined" &&
         history.timeline[history.current_index]?.active
       ) {
@@ -220,7 +220,7 @@ function GUI(props: GUIProps) {
     if (mainTextInputRef.current) {
       let input = (mainTextInputRef.current as any).inputValue;
       // cmd+enter to /edit
-      if (event?.metaKey) {
+      if (isMetaEquivalentKeyPressed(event)) {
         input = `/edit ${input}`;
       }
       (mainTextInputRef.current as any).setInputValue("");
diff --git a/extension/react-app/src/util/index.ts b/extension/react-app/src/util/index.ts
new file mode 100644
index 00000000..ad711321
--- /dev/null
+++ b/extension/react-app/src/util/index.ts
@@ -0,0 +1,30 @@
+type Platform = "mac" | "linux" | "windows" | "unknown";
+
+function getPlatform(): Platform {
+  const platform = window.navigator.platform.toUpperCase();
+  if (platform.indexOf("MAC") >= 0) {
+    return "mac";
+  } else if (platform.indexOf("LINUX") >= 0) {
+    return "linux";
+  } else if (platform.indexOf("WIN") >= 0) {
+    return "windows";
+  } else {
+    return "unknown";
+  }
+}
+
+function isMetaEquivalentKeyPressed(event: {
+  metaKey: boolean;
+  ctrlKey: boolean;
+}): boolean {
+  const platform = getPlatform();
+  switch (platform) {
+    case "mac":
+      return event.metaKey;
+    case "linux":
+    case "windows":
+      return event.ctrlKey;
+    default:
+      return event.metaKey;
+  }
+}
-- 
cgit v1.2.3-70-g09d2


From 46883738a287a5eb1cfae71ab1f6127450f7554f Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sat, 15 Jul 2023 15:06:32 -0700
Subject: use correct label for meta key

---
 extension/react-app/src/components/ComboBox.tsx    |  3 ++-
 .../react-app/src/components/StepContainer.tsx     |  7 +++++-
 extension/react-app/src/util/index.ts              | 17 +++++++++++--
 extension/src/diffs.ts                             |  3 ++-
 extension/src/lang-server/codeLens.ts              |  7 +++---
 extension/src/util/util.ts                         | 29 ++++++++++++++++++++++
 6 files changed, 58 insertions(+), 8 deletions(-)

(limited to 'extension/react-app/src/components/StepContainer.tsx')

diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 754c9445..f11e07af 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -13,6 +13,7 @@ import HeaderButtonWithText from "./HeaderButtonWithText";
 import { DocumentPlus } from "@styled-icons/heroicons-outline";
 import { HighlightedRangeContext } from "../../../schema/FullState";
 import { postVscMessage } from "../vscode";
+import { getMetaKeyLabel } from "../util";
 
 // #region styled components
 const mainInputFontSize = 13;
@@ -286,7 +287,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
       <div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}>
         <MainTextInput
           disabled={props.disabled}
-          placeholder="Ask a question, give instructions, or type '/' to see slash commands. ⌘⏎ to edit."
+          placeholder={`Ask a question, give instructions, or type '/' to see slash commands. ${getMetaKeyLabel()}⏎ to edit.`}
           {...getInputProps({
             onChange: (e) => {
               const target = e.target as HTMLTextAreaElement;
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 7f23e333..93bdbc89 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -17,6 +17,7 @@ import { StopCircle } from "@styled-icons/heroicons-solid";
 import { HistoryNode } from "../../../schema/HistoryNode";
 import HeaderButtonWithText from "./HeaderButtonWithText";
 import MarkdownPreview from "@uiw/react-markdown-preview";
+import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util";
 
 interface StepContainerProps {
   historyNode: HistoryNode;
@@ -217,7 +218,11 @@ function StepContainer(props: StepContainerProps) {
                   e.stopPropagation();
                   props.onDelete();
                 }}
-                text={props.historyNode.active ? "Stop (⌘⌫)" : "Delete"}
+                text={
+                  props.historyNode.active
+                    ? `Stop (${getMetaKeyLabel()}⌫)`
+                    : "Delete"
+                }
               >
                 {props.historyNode.active ? (
                   <StopCircle size="1.6em" onClick={props.onDelete} />
diff --git a/extension/react-app/src/util/index.ts b/extension/react-app/src/util/index.ts
index ad711321..c4168e13 100644
--- a/extension/react-app/src/util/index.ts
+++ b/extension/react-app/src/util/index.ts
@@ -1,6 +1,6 @@
 type Platform = "mac" | "linux" | "windows" | "unknown";
 
-function getPlatform(): Platform {
+export function getPlatform(): Platform {
   const platform = window.navigator.platform.toUpperCase();
   if (platform.indexOf("MAC") >= 0) {
     return "mac";
@@ -13,7 +13,7 @@ function getPlatform(): Platform {
   }
 }
 
-function isMetaEquivalentKeyPressed(event: {
+export function isMetaEquivalentKeyPressed(event: {
   metaKey: boolean;
   ctrlKey: boolean;
 }): boolean {
@@ -28,3 +28,16 @@ function isMetaEquivalentKeyPressed(event: {
       return event.metaKey;
   }
 }
+
+export function getMetaKeyLabel(): string {
+  const platform = getPlatform();
+  switch (platform) {
+    case "mac":
+      return "⌘";
+    case "linux":
+    case "windows":
+      return "^";
+    default:
+      return "⌘";
+  }
+}
diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts
index d04f9bdb..9d0c9fe7 100644
--- a/extension/src/diffs.ts
+++ b/extension/src/diffs.ts
@@ -3,6 +3,7 @@ import * as path from "path";
 import * as fs from "fs";
 import * as vscode from "vscode";
 import { extensionContext, ideProtocolClient } from "./activation/activate";
+import { getMetaKeyLabel } from "./util/util";
 
 interface DiffInfo {
   originalFilepath: string;
@@ -86,7 +87,7 @@ class DiffManager {
     ) {
       vscode.window
         .showInformationMessage(
-          "Accept (⌘⇧↩) or reject (⌘⇧⌫) at the top of the file.",
+          `Accept (${getMetaKeyLabel()}⇧↩) or reject (${getMetaKeyLabel()}⇧⌫) at the top of the file.`,
           "Got it",
           "Don't show again"
         )
diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts
index 1cfef5d5..ba80e557 100644
--- a/extension/src/lang-server/codeLens.ts
+++ b/extension/src/lang-server/codeLens.ts
@@ -3,6 +3,7 @@ import { editorToSuggestions, editorSuggestionsLocked } from "../suggestions";
 import * as path from "path";
 import * as os from "os";
 import { DIFF_DIRECTORY, diffManager } from "../diffs";
+import { getMetaKeyLabel } from "../util/util";
 class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
   public provideCodeLenses(
     document: vscode.TextDocument,
@@ -35,7 +36,7 @@ class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
       if (codeLenses.length === 2) {
         codeLenses.push(
           new vscode.CodeLens(range, {
-            title: "(⌘⇧↩/⌘⇧⌫ to accept/reject all)",
+            title: `(${getMetaKeyLabel()}⇧↩/${getMetaKeyLabel()}⇧⌫ to accept/reject all)`,
             command: "",
           })
         );
@@ -60,12 +61,12 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
       }
       codeLenses.push(
         new vscode.CodeLens(range, {
-          title: "Accept All ✅ (⌘⇧↩)",
+          title: `Accept All ✅ (${getMetaKeyLabel()}⇧↩)`,
           command: "continue.acceptDiff",
           arguments: [document.uri.fsPath],
         }),
         new vscode.CodeLens(range, {
-          title: "Reject All ❌ (⌘⇧⌫)",
+          title: `Reject All ❌ (${getMetaKeyLabel()}⇧⌫)`,
           command: "continue.rejectDiff",
           arguments: [document.uri.fsPath],
         })
diff --git a/extension/src/util/util.ts b/extension/src/util/util.ts
index d33593e1..dfc10c90 100644
--- a/extension/src/util/util.ts
+++ b/extension/src/util/util.ts
@@ -1,5 +1,6 @@
 import { RangeInFile, SerializedDebugContext } from "../client";
 import * as fs from "fs";
+const os = require("os");
 
 function charIsEscapedAtIndex(index: number, str: string): boolean {
   if (index === 0) return false;
@@ -113,3 +114,31 @@ export function debounced(delay: number, fn: Function) {
     }, delay);
   };
 }
+
+type Platform = "mac" | "linux" | "windows" | "unknown";
+
+function getPlatform(): Platform {
+  const platform = os.platform();
+  if (platform === "darwin") {
+    return "mac";
+  } else if (platform === "linux") {
+    return "linux";
+  } else if (platform === "win32") {
+    return "windows";
+  } else {
+    return "unknown";
+  }
+}
+
+export function getMetaKeyLabel() {
+  const platform = getPlatform();
+  switch (platform) {
+    case "mac":
+      return "⌘";
+    case "linux":
+    case "windows":
+      return "^";
+    default:
+      return "⌘";
+  }
+}
-- 
cgit v1.2.3-70-g09d2


From 79b4da7e0afe773d161d2e50688dd31791efa1e1 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 17 Jul 2023 12:05:03 -0700
Subject: match vscode color theme

---
 continuedev/src/continuedev/libs/util/dedent.py    | 25 -----------
 continuedev/src/continuedev/libs/util/strings.py   | 49 ++++++++++++++++++++++
 continuedev/src/continuedev/steps/chat.py          |  8 ++--
 continuedev/src/continuedev/steps/core/core.py     | 17 ++------
 extension/package-lock.json                        |  4 +-
 extension/package.json                             |  2 +-
 extension/react-app/src/components/ComboBox.tsx    | 10 +++--
 .../react-app/src/components/InputAndButton.tsx    | 10 ++---
 extension/react-app/src/components/PillButton.tsx  |  9 +++-
 .../react-app/src/components/StepContainer.tsx     | 12 ++++--
 extension/react-app/src/components/TextDialog.tsx  | 14 ++++---
 extension/react-app/src/components/index.ts        | 23 ++++++----
 extension/react-app/src/index.css                  |  4 +-
 extension/react-app/src/pages/gui.tsx              | 11 +++--
 14 files changed, 119 insertions(+), 79 deletions(-)
 delete mode 100644 continuedev/src/continuedev/libs/util/dedent.py
 create mode 100644 continuedev/src/continuedev/libs/util/strings.py

(limited to 'extension/react-app/src/components/StepContainer.tsx')

diff --git a/continuedev/src/continuedev/libs/util/dedent.py b/continuedev/src/continuedev/libs/util/dedent.py
deleted file mode 100644
index e59c2e97..00000000
--- a/continuedev/src/continuedev/libs/util/dedent.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from typing import Tuple
-
-
-def dedent_and_get_common_whitespace(s: str) -> Tuple[str, str]:
-    lines = s.splitlines()
-    if len(lines) == 0:
-        return "", ""
-
-    # Longest common whitespace prefix
-    lcp = lines[0].split(lines[0].strip())[0]
-    # Iterate through the lines
-    for i in range(1, len(lines)):
-        # Empty lines are wildcards
-        if lines[i].strip() == "":
-            continue
-        # Iterate through the leading whitespace characters of the current line
-        for j in range(0, len(lcp)):
-            # If it doesn't have the same whitespace as lcp, then update lcp
-            if j >= len(lines[i]) or lcp[j] != lines[i][j]:
-                lcp = lcp[:j]
-                if lcp == "":
-                    return s, ""
-                break
-
-    return "\n".join(map(lambda x: x.lstrip(lcp), lines)), lcp
diff --git a/continuedev/src/continuedev/libs/util/strings.py b/continuedev/src/continuedev/libs/util/strings.py
new file mode 100644
index 00000000..f1fb8d0b
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/strings.py
@@ -0,0 +1,49 @@
+from typing import Tuple
+
+
+def dedent_and_get_common_whitespace(s: str) -> Tuple[str, str]:
+    lines = s.splitlines()
+    if len(lines) == 0:
+        return "", ""
+
+    # Longest common whitespace prefix
+    lcp = lines[0].split(lines[0].strip())[0]
+    # Iterate through the lines
+    for i in range(1, len(lines)):
+        # Empty lines are wildcards
+        if lines[i].strip() == "":
+            continue
+        # Iterate through the leading whitespace characters of the current line
+        for j in range(0, len(lcp)):
+            # If it doesn't have the same whitespace as lcp, then update lcp
+            if j >= len(lines[i]) or lcp[j] != lines[i][j]:
+                lcp = lcp[:j]
+                if lcp == "":
+                    return s, ""
+                break
+
+    return "\n".join(map(lambda x: x.lstrip(lcp), lines)), lcp
+
+
+def remove_quotes_and_escapes(output: str) -> str:
+    """
+    Clean up the output of the completion API, removing unnecessary escapes and quotes
+    """
+    output = output.strip()
+
+    # Replace smart quotes
+    output = output.replace("“", '"')
+    output = output.replace("”", '"')
+    output = output.replace("‘", "'")
+    output = output.replace("’", "'")
+
+    # Remove escapes
+    output = output.replace('\\"', '"')
+    output = output.replace("\\'", "'")
+    output = output.replace("\\n", "\n")
+    output = output.replace("\\t", "\t")
+    output = output.replace("\\\\", "\\")
+    if (output.startswith('"') and output.endswith('"')) or (output.startswith("'") and output.endswith("'")):
+        output = output[1:-1]
+
+    return output
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py
index 3751dec2..7c6b42db 100644
--- a/continuedev/src/continuedev/steps/chat.py
+++ b/continuedev/src/continuedev/steps/chat.py
@@ -3,6 +3,7 @@ from typing import Any, Coroutine, List
 
 from pydantic import Field
 
+from ..libs.util.strings import remove_quotes_and_escapes
 from .main import EditHighlightedCodeStep
 from .core.core import MessageStep
 from ..core.main import FunctionCall, Models
@@ -43,11 +44,8 @@ class SimpleChatStep(Step):
         finally:
             await generator.aclose()
 
-        self.name = (await sdk.models.gpt35.complete(
-            f"Write a short title for the following chat message: {self.description}")).strip()
-
-        if self.name.startswith('"') and self.name.endswith('"'):
-            self.name = self.name[1:-1]
+        self.name = remove_quotes_and_escapes(await sdk.models.gpt35.complete(
+            f"Write a short title for the following chat message: {self.description}"))
 
         self.chat_context.append(ChatMessage(
             role="assistant",
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index d5a7cd9a..5b9b9fd5 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -12,7 +12,7 @@ from ...models.filesystem import FileSystem, RangeInFile, RangeInFileWithContent
 from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation
 from ...core.main import ChatMessage, ContinueCustomException, Step, SequentialStep
 from ...libs.util.count_tokens import MAX_TOKENS_FOR_MODEL, DEFAULT_MAX_TOKENS
-from ...libs.util.dedent import dedent_and_get_common_whitespace
+from ...libs.util.strings import dedent_and_get_common_whitespace, remove_quotes_and_escapes
 import difflib
 
 
@@ -157,17 +157,6 @@ class DefaultModelEditCodeStep(Step):
     _new_contents: str = ""
     _prompt_and_completion: str = ""
 
-    def _cleanup_output(self, output: str) -> str:
-        output = output.replace('\\"', '"')
-        output = output.replace("\\'", "'")
-        output = output.replace("\\n", "\n")
-        output = output.replace("\\t", "\t")
-        output = output.replace("\\\\", "\\")
-        if output.startswith('"') and output.endswith('"'):
-            output = output[1:-1]
-
-        return output
-
     async def describe(self, models: Models) -> Coroutine[str, None, None]:
         if self._previous_contents.strip() == self._new_contents.strip():
             description = "No edits were made"
@@ -183,9 +172,9 @@ class DefaultModelEditCodeStep(Step):
 
                 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)
+        self.name = remove_quotes_and_escapes(name)
 
-        return f"{self._cleanup_output(description)}"
+        return f"{remove_quotes_and_escapes(description)}"
 
     async def get_prompt_parts(self, rif: RangeInFileWithContents, sdk: ContinueSDK, full_file_contents: str):
         # We don't know here all of the functions being passed in.
diff --git a/extension/package-lock.json b/extension/package-lock.json
index 33f81dec..0e0125b0 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.0.176",
+  "version": "0.0.177",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.0.176",
+      "version": "0.0.177",
       "license": "Apache-2.0",
       "dependencies": {
         "@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index e515ed36..8462bf68 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.176",
+  "version": "0.0.177",
   "publisher": "Continue",
   "engines": {
     "vscode": "^1.67.0"
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index dbebd534..0ea8a3e1 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -6,6 +6,7 @@ import {
   lightGray,
   secondaryDark,
   vscBackground,
+  vscForeground,
 } from ".";
 import CodeBlock from "./CodeBlock";
 import PillButton from "./PillButton";
@@ -48,7 +49,7 @@ const MainTextInput = styled.textarea`
   height: auto;
   width: 100%;
   background-color: ${secondaryDark};
-  color: white;
+  color: ${vscForeground};
   z-index: 1;
   border: 1px solid transparent;
 
@@ -71,7 +72,7 @@ const Ul = styled.ul<{
   position: absolute;
   background: ${vscBackground};
   background-color: ${secondaryDark};
-  color: white;
+  color: ${vscForeground};
   max-height: ${UlMaxHeight}px;
   width: calc(100% - 16px);
   overflow-y: scroll;
@@ -95,6 +96,7 @@ const Li = styled.li<{
   selected: boolean;
   isLastItem: boolean;
 }>`
+  background-color: ${secondaryDark};
   ${({ highlighted }) => highlighted && "background: #ff000066;"}
   ${({ selected }) => selected && "font-weight: bold;"}
     padding: 0.5rem 0.75rem;
@@ -218,7 +220,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
                 ? "Editing such a large range may be slow"
                 : undefined
             }
-            onlyShowDelete={highlightedCodeSections.length <= 1}
+            onlyShowDelete={
+              highlightedCodeSections.length <= 1 || section.editing
+            }
             editing={section.editing}
             pinned={section.pinned}
             index={idx}
diff --git a/extension/react-app/src/components/InputAndButton.tsx b/extension/react-app/src/components/InputAndButton.tsx
index 0a8592f2..8019d014 100644
--- a/extension/react-app/src/components/InputAndButton.tsx
+++ b/extension/react-app/src/components/InputAndButton.tsx
@@ -1,6 +1,6 @@
 import React, { useRef } from "react";
 import styled from "styled-components";
-import { vscBackground } from ".";
+import { vscBackground, vscForeground } from ".";
 
 interface InputAndButtonProps {
   onUserInput: (input: string) => void;
@@ -16,7 +16,7 @@ const Input = styled.input`
   padding: 0.5rem;
   border: 1px solid white;
   background-color: ${vscBackground};
-  color: white;
+  color: ${vscForeground};
   border-radius: 4px;
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
@@ -27,7 +27,7 @@ const Button = styled.button`
   padding: 0.5rem;
   border: 1px solid white;
   background-color: ${vscBackground};
-  color: white;
+  color: ${vscForeground};
   border-radius: 4px;
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
@@ -35,8 +35,8 @@ const Button = styled.button`
   cursor: pointer;
 
   &:hover {
-    background-color: white;
-    color: black;
+    background-color: ${vscForeground};
+    color: ${vscBackground};
   }
 `;
 
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
index 5a16516e..eba5cf8f 100644
--- a/extension/react-app/src/components/PillButton.tsx
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -1,6 +1,11 @@
 import { useContext, useState } from "react";
 import styled from "styled-components";
-import { StyledTooltip, defaultBorderRadius, secondaryDark } from ".";
+import {
+  StyledTooltip,
+  defaultBorderRadius,
+  secondaryDark,
+  vscForeground,
+} from ".";
 import {
   Trash,
   PaintBrush,
@@ -10,7 +15,7 @@ import { GUIClientContext } from "../App";
 
 const Button = styled.button`
   border: none;
-  color: white;
+  color: ${vscForeground};
   background-color: ${secondaryDark};
   border-radius: ${defaultBorderRadius};
   padding: 8px;
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 93bdbc89..26bc8e33 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -6,6 +6,7 @@ import {
   secondaryDark,
   vscBackground,
   vscBackgroundTransparent,
+  vscForeground,
 } from ".";
 import {
   ChevronDown,
@@ -120,20 +121,22 @@ const StyledMarkdownPreview = styled(MarkdownPreview)`
   }
 
   code {
-    color: #f69292;
+    color: #f78383;
     word-wrap: break-word;
+    border-radius: ${defaultBorderRadius};
+    background-color: ${secondaryDark};
   }
 
   pre > code {
     background-color: ${secondaryDark};
-    color: white;
+    color: ${vscForeground};
   }
 
   background-color: ${vscBackground};
   font-family: "Lexend", sans-serif;
   font-size: 13px;
   padding: 8px;
-  color: white;
+  color: ${vscForeground};
 `;
 
 // #endregion
@@ -267,6 +270,9 @@ function StepContainer(props: StepContainerProps) {
           ) : (
             <StyledMarkdownPreview
               source={props.historyNode.step.description || ""}
+              wrapperElement={{
+                "data-color-mode": "dark",
+              }}
             />
           )}
         </ContentDiv>
diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx
index 646d6846..cba3852d 100644
--- a/extension/react-app/src/components/TextDialog.tsx
+++ b/extension/react-app/src/components/TextDialog.tsx
@@ -1,7 +1,7 @@
 // Write a component that displays a dialog box with a text field and a button.
 import React, { useEffect, useState } from "react";
 import styled from "styled-components";
-import { Button, buttonColor, secondaryDark, vscBackground } from ".";
+import { Button, secondaryDark, vscBackground, vscForeground } from ".";
 import { isMetaEquivalentKeyPressed } from "../util";
 
 const ScreenCover = styled.div`
@@ -21,13 +21,13 @@ const DialogContainer = styled.div`
 `;
 
 const Dialog = styled.div`
-  background-color: white;
+  color: ${vscForeground};
+  background-color: ${vscBackground};
   border-radius: 8px;
   padding: 8px;
   display: flex;
   flex-direction: column;
-  /* box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.5); */
-  border: 2px solid ${buttonColor};
+  box-shadow: 0 0 10px 0 ${vscForeground};
   width: fit-content;
   margin: auto;
 `;
@@ -38,14 +38,16 @@ const TextArea = styled.textarea`
   padding: 8px;
   outline: 1px solid black;
   resize: none;
+  background-color: ${secondaryDark};
+  color: ${vscForeground};
 
   &:focus {
-    outline: 1px solid ${buttonColor};
+    outline: 1px solid ${vscForeground};
   }
 `;
 
 const P = styled.p`
-  color: black;
+  color: ${vscForeground};
   margin: 8px auto;
 `;
 
diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts
index 9ae0f097..cb5e7915 100644
--- a/extension/react-app/src/components/index.ts
+++ b/extension/react-app/src/components/index.ts
@@ -3,12 +3,16 @@ import styled, { keyframes } from "styled-components";
 
 export const defaultBorderRadius = "5px";
 export const lightGray = "rgb(100 100 100)";
-export const secondaryDark = "rgb(45 45 45)";
-export const vscBackground = "rgb(30 30 30)";
+// export const secondaryDark = "rgb(45 45 45)";
+// export const vscBackground = "rgb(30 30 30)";
 export const vscBackgroundTransparent = "#1e1e1ede";
 export const buttonColor = "rgb(113 28 59)";
 export const buttonColorHover = "rgb(113 28 59 0.67)";
 
+export const secondaryDark = "var(--vscode-textBlockQuote-background)";
+export const vscBackground = "var(--vscode-editor-background)";
+export const vscForeground = "var(--vscode-editor-foreground)";
+
 export const Button = styled.button`
   padding: 10px 12px;
   margin: 8px 0;
@@ -46,8 +50,8 @@ export const TextArea = styled.textarea`
   resize: vertical;
 
   padding: 4px;
-  caret-color: white;
-  color: white;
+  caret-color: ${vscForeground};
+  color: #{vscForeground};
 
   &:focus {
     outline: 1px solid ${buttonColor};
@@ -120,7 +124,7 @@ export const MainTextInput = styled.textarea`
   border: 1px solid #ccc;
   margin: 8px 8px;
   background-color: ${vscBackground};
-  color: white;
+  color: ${vscForeground};
   outline: 1px solid orange;
   resize: none;
 `;
@@ -137,8 +141,9 @@ export const appear = keyframes`
 `;
 
 export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`
-  background-color: ${({ inverted }) => (inverted ? "white" : "transparent")};
-  color: ${({ inverted }) => (inverted ? "black" : "white")};
+  background-color: ${({ inverted }) =>
+    inverted ? vscForeground : "transparent"};
+  color: ${({ inverted }) => (inverted ? vscBackground : vscForeground)};
 
   border: none;
   border-radius: ${defaultBorderRadius};
@@ -146,7 +151,9 @@ export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`
 
   &:hover {
     background-color: ${({ inverted }) =>
-      typeof inverted === "undefined" || inverted ? lightGray : "transparent"};
+      typeof inverted === "undefined" || inverted
+        ? secondaryDark
+        : "transparent"};
   }
   display: flex;
   align-items: center;
diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css
index 6e33c89c..bac7fe97 100644
--- a/extension/react-app/src/index.css
+++ b/extension/react-app/src/index.css
@@ -14,13 +14,13 @@ html,
 body,
 #root {
   height: 100%;
-  background-color: var(--vsc-background);
+  background-color: var(--vscode-editor-background);
   font-family: "Lexend", sans-serif;
 }
 
 body {
   padding: 0;
-  color: white;
+  color: var(--vscode-editor-foreground);
   padding: 0px;
   margin: 0px;
   height: 100%;
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index 64207487..c35cf21b 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -1,5 +1,9 @@
 import styled from "styled-components";
-import { defaultBorderRadius } from "../components";
+import {
+  defaultBorderRadius,
+  vscBackground,
+  vscForeground,
+} from "../components";
 import Loader from "../components/Loader";
 import ContinueButton from "../components/ContinueButton";
 import { FullState, HighlightedRangeContext } from "../../../schema/FullState";
@@ -371,12 +375,13 @@ function GUI(props: GUIProps) {
         style={{
           position: "fixed",
           bottom: "50px",
-          backgroundColor: "white",
-          color: "black",
+          backgroundColor: vscBackground,
+          color: vscForeground,
           borderRadius: defaultBorderRadius,
           padding: "16px",
           margin: "16px",
           zIndex: 100,
+          boxShadow: `0px 0px 10px 0px ${vscForeground}`,
         }}
         hidden={!showDataSharingInfo}
       >
-- 
cgit v1.2.3-70-g09d2


From e65a15f50d226acd802ffc580306e1477394ffe0 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 17 Jul 2023 12:24:22 -0700
Subject: disambiguate highlighted ranges with dirname

---
 continuedev/src/continuedev/core/autopilot.py        | 17 +++++++++++++++++
 extension/react-app/src/components/StepContainer.tsx |  7 +------
 2 files changed, 18 insertions(+), 6 deletions(-)

(limited to 'extension/react-app/src/components/StepContainer.tsx')

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 0696c360..fb8da2e8 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -166,6 +166,22 @@ class Autopilot(ContinueBaseModel):
         if not any(map(lambda x: x.editing, self._highlighted_ranges)):
             self._highlighted_ranges[0].editing = True
 
+    def _disambiguate_highlighted_ranges(self):
+        """If any files have the same name, also display their folder name"""
+        name_counts = {}
+        for rif in self._highlighted_ranges:
+            if rif.display_name in name_counts:
+                name_counts[rif.display_name] += 1
+            else:
+                name_counts[rif.display_name] = 1
+
+        for rif in self._highlighted_ranges:
+            if name_counts[rif.display_name] > 1:
+                rif.display_name = os.path.join(
+                    os.path.basename(os.path.dirname(rif.range.filepath)), rif.display_name)
+            else:
+                rif.display_name = os.path.basename(rif.range.filepath)
+
     async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]):
         # Filter out rifs from ~/.continue/diffs folder
         range_in_files = [
@@ -211,6 +227,7 @@ class Autopilot(ContinueBaseModel):
         ) for rif in range_in_files]
 
         self._make_sure_is_editing_range()
+        self._disambiguate_highlighted_ranges()
 
         await self.update_subscribers()
 
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 26bc8e33..9ab7430c 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -53,12 +53,7 @@ const StepContainerDiv = styled.div<{ open: boolean }>`
 `;
 
 const HeaderDiv = styled.div<{ error: boolean; loading: boolean }>`
-  background-color: ${(props) =>
-    props.error
-      ? "#522"
-      : props.loading
-      ? vscBackgroundTransparent
-      : vscBackground};
+  background-color: ${(props) => (props.error ? "#522" : vscBackground)};
   display: grid;
   grid-template-columns: 1fr auto auto;
   grid-gap: 8px;
-- 
cgit v1.2.3-70-g09d2


From a0d5cf94c8bef5726dd23711ddbef58813f42fc2 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 17 Jul 2023 14:54:36 -0700
Subject: anthropic support

---
 continuedev/src/continuedev/core/sdk.py            |  6 +--
 continuedev/src/continuedev/libs/llm/anthropic.py  | 50 ++++++++++++++--------
 .../src/continuedev/libs/util/count_tokens.py      |  4 +-
 .../react-app/src/components/StepContainer.tsx     |  1 -
 4 files changed, 39 insertions(+), 22 deletions(-)

(limited to 'extension/react-app/src/components/StepContainer.tsx')

diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index d3501f08..280fefa8 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -11,7 +11,7 @@ from ..models.filesystem_edit import FileEdit, FileSystemEdit, AddFile, DeleteFi
 from ..models.filesystem import RangeInFile
 from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI
 from ..libs.llm.openai import OpenAI
-from ..libs.llm.anthropic import Anthropic
+from ..libs.llm.anthropic import AnthropicLLM
 from ..libs.llm.ggml import GGML
 from .observation import Observation
 from ..server.ide_protocol import AbstractIdeProtocolServer
@@ -66,9 +66,9 @@ class Models:
         api_key = self.provider_keys["hf_inference_api"]
         return HuggingFaceInferenceAPI(api_key=api_key, model=model, system_message=self.system_message)
 
-    def __load_anthropic_model(self, model: str) -> Anthropic:
+    def __load_anthropic_model(self, model: str) -> AnthropicLLM:
         api_key = self.provider_keys["anthropic"]
-        return Anthropic(api_key=api_key, model=model)
+        return AnthropicLLM(api_key, model, self.system_message)
 
     @cached_property
     def claude2(self):
diff --git a/continuedev/src/continuedev/libs/llm/anthropic.py b/continuedev/src/continuedev/libs/llm/anthropic.py
index 2b8831f0..566f7150 100644
--- a/continuedev/src/continuedev/libs/llm/anthropic.py
+++ b/continuedev/src/continuedev/libs/llm/anthropic.py
@@ -3,7 +3,7 @@ from functools import cached_property
 import time
 from typing import Any, Coroutine, Dict, Generator, List, Union
 from ...core.main import ChatMessage
-from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
+from anthropic import HUMAN_PROMPT, AI_PROMPT, AsyncAnthropic
 from ..llm import LLM
 from ..util.count_tokens import DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, DEFAULT_ARGS, count_tokens, prune_raw_prompt_from_top
 
@@ -11,14 +11,14 @@ from ..util.count_tokens import DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_
 class AnthropicLLM(LLM):
     api_key: str
     default_model: str
-    anthropic: Anthropic
+    async_client: AsyncAnthropic
 
     def __init__(self, api_key: str, default_model: str, system_message: str = None):
         self.api_key = api_key
         self.default_model = default_model
         self.system_message = system_message
 
-        self.anthropic = Anthropic(api_key)
+        self.async_client = AsyncAnthropic(api_key=api_key)
 
     @cached_property
     def name(self):
@@ -28,24 +28,39 @@ class AnthropicLLM(LLM):
     def default_args(self):
         return {**DEFAULT_ARGS, "model": self.default_model}
 
+    def _transform_args(self, args: Dict[str, Any]) -> Dict[str, Any]:
+        args = args.copy()
+        if "max_tokens" in args:
+            args["max_tokens_to_sample"] = args["max_tokens"]
+            del args["max_tokens"]
+        if "frequency_penalty" in args:
+            del args["frequency_penalty"]
+        if "presence_penalty" in args:
+            del args["presence_penalty"]
+        return args
+
     def count_tokens(self, text: str):
         return count_tokens(self.default_model, text)
 
-    def __messages_to_prompt(self, messages: List[ChatMessage]) -> str:
+    def __messages_to_prompt(self, messages: List[Dict[str, str]]) -> str:
         prompt = ""
+
+        # Anthropic prompt must start with a Human turn
+        if len(messages) > 0 and messages[0]["role"] != "user" and messages[0]["role"] != "system":
+            prompt += f"{HUMAN_PROMPT} Hello."
         for msg in messages:
-            prompt += f"{HUMAN_PROMPT if msg.role == 'user' else AI_PROMPT} {msg.content} "
+            prompt += f"{HUMAN_PROMPT if (msg['role'] == 'user' or msg['role'] == 'system') else AI_PROMPT} {msg['content']} "
 
+        prompt += AI_PROMPT
         return prompt
 
     async def stream_complete(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
         args = self.default_args.copy()
         args.update(kwargs)
         args["stream"] = True
+        args = self._transform_args(args)
 
-        async for chunk in await self.anthropic.completions.create(
-            model=args["model"],
-            max_tokens_to_sample=args["max_tokens"],
+        async for chunk in await self.async_client.completions.create(
             prompt=f"{HUMAN_PROMPT} {prompt} {AI_PROMPT}",
             **args
         ):
@@ -55,25 +70,26 @@ class AnthropicLLM(LLM):
         args = self.default_args.copy()
         args.update(kwargs)
         args["stream"] = True
+        args = self._transform_args(args)
 
         messages = compile_chat_messages(
-            args["model"], messages, args["max_tokens"], functions=args.get("functions", None))
-        async for chunk in await self.anthropic.completions.create(
-            model=args["model"],
-            max_tokens_to_sample=args["max_tokens"],
+            args["model"], messages, args["max_tokens_to_sample"], functions=args.get("functions", None))
+        async for chunk in await self.async_client.completions.create(
             prompt=self.__messages_to_prompt(messages),
             **args
         ):
-            yield chunk.completion
+            yield {
+                "role": "assistant",
+                "content": chunk.completion
+            }
 
     async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
         args = {**self.default_args, **kwargs}
+        args = self._transform_args(args)
 
         messages = compile_chat_messages(
-            args["model"], with_history, args["max_tokens"], prompt, functions=None)
-        resp = (await self.anthropic.completions.create(
-            model=args["model"],
-            max_tokens_to_sample=args["max_tokens"],
+            args["model"], with_history, args["max_tokens_to_sample"], prompt, functions=None)
+        resp = (await self.async_client.completions.create(
             prompt=self.__messages_to_prompt(messages),
             **args
         )).completion
diff --git a/continuedev/src/continuedev/libs/util/count_tokens.py b/continuedev/src/continuedev/libs/util/count_tokens.py
index 1ca98fe6..1d5d6729 100644
--- a/continuedev/src/continuedev/libs/util/count_tokens.py
+++ b/continuedev/src/continuedev/libs/util/count_tokens.py
@@ -6,6 +6,7 @@ import tiktoken
 
 aliases = {
     "ggml": "gpt-3.5-turbo",
+    "claude-2": "gpt-3.5-turbo",
 }
 DEFAULT_MAX_TOKENS = 2048
 MAX_TOKENS_FOR_MODEL = {
@@ -13,7 +14,8 @@ MAX_TOKENS_FOR_MODEL = {
     "gpt-3.5-turbo-0613": 4096,
     "gpt-3.5-turbo-16k": 16384,
     "gpt-4": 8192,
-    "ggml": 2048
+    "ggml": 2048,
+    "claude-2": 100000
 }
 CHAT_MODELS = {
     "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4", "gpt-3.5-turbo-0613"
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 9ab7430c..93b90f0d 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -39,7 +39,6 @@ interface StepContainerProps {
 const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>`
   opacity: ${(props) => (props.inFuture ? 0.3 : 1)};
   animation: ${appear} 0.3s ease-in-out;
-  /* padding-left: ${(props) => props.stepDepth * 20}px; */
   overflow: hidden;
   margin-left: 0px;
   margin-right: 0px;
-- 
cgit v1.2.3-70-g09d2


From bd230674bd78425dc0931e6725547faea73c1ee5 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 17 Jul 2023 21:09:30 -0700
Subject: show exact prompt/completion logs

---
 continuedev/src/continuedev/core/main.py           |  1 +
 continuedev/src/continuedev/core/sdk.py            | 28 +++++++++++--
 continuedev/src/continuedev/libs/llm/openai.py     | 47 +++++++++++++++++-----
 .../src/continuedev/libs/llm/proxy_server.py       | 33 +++++++++++----
 .../src/continuedev/libs/util/count_tokens.py      |  7 ++++
 continuedev/src/continuedev/server/gui.py          |  9 +++++
 continuedev/src/continuedev/server/ide.py          | 14 ++++++-
 continuedev/src/continuedev/server/ide_protocol.py |  4 ++
 .../src/continuedev/server/session_manager.py      |  2 +-
 extension/package-lock.json                        |  4 +-
 extension/package.json                             |  2 +-
 .../react-app/src/components/StepContainer.tsx     | 17 +++++++-
 .../src/hooks/ContinueGUIClientProtocol.ts         |  2 +
 .../react-app/src/hooks/useContinueGUIProtocol.ts  |  4 ++
 extension/react-app/src/pages/gui.tsx              |  1 +
 extension/src/continueIdeClient.ts                 | 38 +++++++++++++++++
 16 files changed, 185 insertions(+), 28 deletions(-)

(limited to 'extension/react-app/src/components/StepContainer.tsx')

diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py
index 88690c83..5931d978 100644
--- a/continuedev/src/continuedev/core/main.py
+++ b/continuedev/src/continuedev/core/main.py
@@ -102,6 +102,7 @@ class HistoryNode(ContinueBaseModel):
     depth: int
     deleted: bool = False
     active: bool = True
+    logs: List[str] = []
 
     def to_chat_messages(self) -> List[ChatMessage]:
         if self.step.description is None or self.step.manage_own_chat_context:
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index 280fefa8..53214384 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -37,6 +37,25 @@ class Models:
     model_providers: List[ModelProvider]
     system_message: str
 
+    """
+    Better to have sdk.llm.stream_chat(messages, model="claude-2").
+    Then you also don't care that it' async.
+    And it's easier to add more models.
+    And intermediate shared code is easier to add.
+    And you can make constants like ContinueModels.GPT35 = "gpt-3.5-turbo"
+    PromptTransformer would be a good concept: You pass a prompt or list of messages and a model, then it outputs the prompt for that model.
+    Easy to reason about, can place anywhere.
+    And you can even pass a Prompt object to sdk.llm.stream_chat maybe, and it'll automatically be transformed for the given model.
+    This can all happen inside of Models?
+
+    class Prompt:
+        def __init__(self, ...info):
+            '''take whatever info is needed to describe the prompt'''
+
+        def to_string(self, model: str) -> str:
+            '''depending on the model, return the single prompt string'''
+    """
+
     def __init__(self, sdk: "ContinueSDK", model_providers: List[ModelProvider]):
         self.sdk = sdk
         self.model_providers = model_providers
@@ -59,8 +78,8 @@ class Models:
     def __load_openai_model(self, model: str) -> OpenAI:
         api_key = self.provider_keys["openai"]
         if api_key == "":
-            return ProxyServer(self.sdk.ide.unique_id, model, system_message=self.system_message)
-        return OpenAI(api_key=api_key, default_model=model, system_message=self.system_message, azure_info=self.sdk.config.azure_openai_info)
+            return ProxyServer(self.sdk.ide.unique_id, model, system_message=self.system_message, write_log=self.sdk.write_log)
+        return OpenAI(api_key=api_key, default_model=model, system_message=self.system_message, azure_info=self.sdk.config.azure_openai_info, write_log=self.sdk.write_log)
 
     def __load_hf_inference_api_model(self, model: str) -> HuggingFaceInferenceAPI:
         api_key = self.provider_keys["hf_inference_api"]
@@ -156,6 +175,9 @@ class ContinueSDK(AbstractContinueSDK):
     def history(self) -> History:
         return self.__autopilot.history
 
+    def write_log(self, message: str):
+        self.history.timeline[self.history.current_index].logs.append(message)
+
     async def _ensure_absolute_path(self, path: str) -> str:
         if os.path.isabs(path):
             return path
@@ -263,7 +285,7 @@ class ContinueSDK(AbstractContinueSDK):
 
         for rif in highlighted_code:
             msg = ChatMessage(content=f"{preface} ({rif.filepath}):\n```\n{rif.contents}\n```",
-                              role="system", summary=f"{preface}: {rif.filepath}")
+                              role="user", summary=f"{preface}: {rif.filepath}")
 
             # Don't insert after latest user message or function call
             i = -1
diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py
index 33d10985..64bb39a2 100644
--- a/continuedev/src/continuedev/libs/llm/openai.py
+++ b/continuedev/src/continuedev/libs/llm/openai.py
@@ -1,10 +1,11 @@
 from functools import cached_property
-from typing import Any, Coroutine, Dict, Generator, List, Union
+import json
+from typing import Any, Callable, Coroutine, Dict, Generator, List, Union
 
 from ...core.main import ChatMessage
 import openai
 from ..llm import LLM
-from ..util.count_tokens import compile_chat_messages, CHAT_MODELS, DEFAULT_ARGS, count_tokens, prune_raw_prompt_from_top
+from ..util.count_tokens import compile_chat_messages, CHAT_MODELS, DEFAULT_ARGS, count_tokens, format_chat_messages, prune_raw_prompt_from_top
 from ...core.config import AzureInfo
 
 
@@ -12,11 +13,12 @@ class OpenAI(LLM):
     api_key: str
     default_model: str
 
-    def __init__(self, api_key: str, default_model: str, system_message: str = None, azure_info: AzureInfo = None):
+    def __init__(self, api_key: str, default_model: str, system_message: str = None, azure_info: AzureInfo = None, write_log: Callable[[str], None] = None):
         self.api_key = api_key
         self.default_model = default_model
         self.system_message = system_message
         self.azure_info = azure_info
+        self.write_log = write_log
 
         openai.api_key = api_key
 
@@ -46,18 +48,29 @@ class OpenAI(LLM):
         args["stream"] = True
 
         if args["model"] in CHAT_MODELS:
+            messages = compile_chat_messages(
+                args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message)
+            self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
+            completion = ""
             async for chunk in await openai.ChatCompletion.acreate(
-                messages=compile_chat_messages(
-                    args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message),
+                messages=messages,
                 **args,
             ):
                 if "content" in chunk.choices[0].delta:
                     yield chunk.choices[0].delta.content
+                    completion += chunk.choices[0].delta.content
                 else:
                     continue
+
+            self.write_log(f"Completion: \n\n{completion}")
         else:
+            self.write_log(f"Prompt:\n\n{prompt}")
+            completion = ""
             async for chunk in await openai.Completion.acreate(prompt=prompt, **args):
                 yield chunk.choices[0].text
+                completion += chunk.choices[0].text
+
+            self.write_log(f"Completion:\n\n{completion}")
 
     async def stream_chat(self, messages: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
         args = self.default_args.copy()
@@ -67,27 +80,39 @@ class OpenAI(LLM):
         if not args["model"].endswith("0613") and "functions" in args:
             del args["functions"]
 
+        messages = compile_chat_messages(
+            args["model"], messages, args["max_tokens"], functions=args.get("functions", None), system_message=self.system_message)
+        self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
+        completion = ""
         async for chunk in await openai.ChatCompletion.acreate(
-            messages=compile_chat_messages(
-                args["model"], messages, args["max_tokens"], functions=args.get("functions", None), system_message=self.system_message),
+            messages=messages,
             **args,
         ):
             yield chunk.choices[0].delta
+            if "content" in chunk.choices[0].delta:
+                completion += chunk.choices[0].delta.content
+        self.write_log(f"Completion: \n\n{completion}")
 
     async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
         args = {**self.default_args, **kwargs}
 
         if args["model"] in CHAT_MODELS:
+            messages = compile_chat_messages(
+                args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message)
+            self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
             resp = (await openai.ChatCompletion.acreate(
-                messages=compile_chat_messages(
-                    args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message),
+                messages=messages,
                 **args,
             )).choices[0].message.content
+            self.write_log(f"Completion: \n\n{resp}")
         else:
+            prompt = prune_raw_prompt_from_top(
+                args["model"], prompt, args["max_tokens"])
+            self.write_log(f"Prompt:\n\n{prompt}")
             resp = (await openai.Completion.acreate(
-                prompt=prune_raw_prompt_from_top(
-                    args["model"], prompt, args["max_tokens"]),
+                prompt=prompt,
                 **args,
             )).choices[0].text
+            self.write_log(f"Completion:\n\n{resp}")
 
         return resp
diff --git a/continuedev/src/continuedev/libs/llm/proxy_server.py b/continuedev/src/continuedev/libs/llm/proxy_server.py
index 3ec492f3..91b5842a 100644
--- a/continuedev/src/continuedev/libs/llm/proxy_server.py
+++ b/continuedev/src/continuedev/libs/llm/proxy_server.py
@@ -1,10 +1,11 @@
+
 from functools import cached_property
 import json
-from typing import Any, Coroutine, Dict, Generator, List, Literal, Union
+from typing import Any, Callable, Coroutine, Dict, Generator, List, Literal, Union
 import aiohttp
 from ...core.main import ChatMessage
 from ..llm import LLM
-from ..util.count_tokens import DEFAULT_ARGS, DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, count_tokens
+from ..util.count_tokens import DEFAULT_ARGS, DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, count_tokens, format_chat_messages
 import certifi
 import ssl
 
@@ -19,12 +20,14 @@ class ProxyServer(LLM):
     unique_id: str
     name: str
     default_model: Literal["gpt-3.5-turbo", "gpt-4"]
+    write_log: Callable[[str], None]
 
-    def __init__(self, unique_id: str, default_model: Literal["gpt-3.5-turbo", "gpt-4"], system_message: str = None):
+    def __init__(self, unique_id: str, default_model: Literal["gpt-3.5-turbo", "gpt-4"], system_message: str = None, write_log: Callable[[str], None] = None):
         self.unique_id = unique_id
         self.default_model = default_model
         self.system_message = system_message
         self.name = default_model
+        self.write_log = write_log
 
     @property
     def default_args(self):
@@ -36,14 +39,19 @@ class ProxyServer(LLM):
     async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
         args = {**self.default_args, **kwargs}
 
+        messages = compile_chat_messages(
+            args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message)
+        self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
         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, args["max_tokens"], prompt, functions=None, system_message=self.system_message),
+                "messages": messages,
                 "unique_id": self.unique_id,
                 **args
             }) as resp:
                 try:
-                    return await resp.text()
+                    response_text = await resp.text()
+                    self.write_log(f"Completion: \n\n{response_text}")
+                    return response_text
                 except:
                     raise Exception(await resp.text())
 
@@ -51,6 +59,7 @@ class ProxyServer(LLM):
         args = {**self.default_args, **kwargs}
         messages = compile_chat_messages(
             self.default_model, messages, args["max_tokens"], None, functions=args.get("functions", None), system_message=self.system_message)
+        self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
 
         async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session:
             async with session.post(f"{SERVER_URL}/stream_chat", json={
@@ -59,6 +68,7 @@ class ProxyServer(LLM):
                 **args
             }) as resp:
                 # This is streaming application/json instaed of text/event-stream
+                completion = ""
                 async for line in resp.content.iter_chunks():
                     if line[1]:
                         try:
@@ -67,14 +77,19 @@ class ProxyServer(LLM):
                             chunks = json_chunk.split("\n")
                             for chunk in chunks:
                                 if chunk.strip() != "":
-                                    yield json.loads(chunk)
+                                    loaded_chunk = json.loads(chunk)
+                                    yield loaded_chunk
+                                    if "content" in loaded_chunk:
+                                        completion += loaded_chunk["content"]
                         except:
                             raise Exception(str(line[0]))
+                self.write_log(f"Completion: \n\n{completion}")
 
     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, args["max_tokens"], prompt, functions=args.get("functions", None), system_message=self.system_message)
+        self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
 
         async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session:
             async with session.post(f"{SERVER_URL}/stream_complete", json={
@@ -82,9 +97,13 @@ class ProxyServer(LLM):
                 "unique_id": self.unique_id,
                 **args
             }) as resp:
+                completion = ""
                 async for line in resp.content.iter_any():
                     if line:
                         try:
-                            yield line.decode("utf-8")
+                            decoded_line = line.decode("utf-8")
+                            yield decoded_line
+                            completion += decoded_line
                         except:
                             raise Exception(str(line))
+                self.write_log(f"Completion: \n\n{completion}")
diff --git a/continuedev/src/continuedev/libs/util/count_tokens.py b/continuedev/src/continuedev/libs/util/count_tokens.py
index 1d5d6729..13de7990 100644
--- a/continuedev/src/continuedev/libs/util/count_tokens.py
+++ b/continuedev/src/continuedev/libs/util/count_tokens.py
@@ -107,3 +107,10 @@ def compile_chat_messages(model: str, msgs: List[ChatMessage], max_tokens: int,
         })
 
     return history
+
+
+def format_chat_messages(messages: List[ChatMessage]) -> str:
+    formatted = ""
+    for msg in messages:
+        formatted += f"<{msg['role'].capitalize()}>\n{msg['content']}\n\n"
+    return formatted
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index 4201353e..ae57c0b6 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -99,6 +99,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
                 self.on_set_editing_at_indices(data["indices"])
             elif message_type == "set_pinned_at_indices":
                 self.on_set_pinned_at_indices(data["indices"])
+            elif message_type == "show_logs_at_index":
+                self.on_show_logs_at_index(data["index"])
         except Exception as e:
             print(e)
 
@@ -166,6 +168,13 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
                 indices), self.session.autopilot.continue_sdk.ide.unique_id
         )
 
+    def on_show_logs_at_index(self, index: int):
+        name = f"continue_logs.txt"
+        logs = "\n\n############################################\n\n".join(
+            ["This is a log of the exact prompt/completion pairs sent/received from the LLM during this step"] + self.session.autopilot.continue_sdk.history.timeline[index].logs)
+        create_async_task(
+            self.session.autopilot.ide.showVirtualFile(name, logs))
+
 
 @router.websocket("/ws")
 async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)):
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 43538407..aeff5623 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -224,6 +224,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
             "open": open
         })
 
+    async def showVirtualFile(self, name: str, contents: str):
+        await self._send_json("showVirtualFile", {
+            "name": name,
+            "contents": contents
+        })
+
     async def setSuggestionsLocked(self, filepath: str, locked: bool = True):
         # Lock suggestions in the file so they don't ruin the offset before others are inserted
         await self._send_json("setSuggestionsLocked", {
@@ -288,6 +294,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
         pass
 
     def __get_autopilot(self):
+        if self.session_id not in self.session_manager.sessions:
+            return None
         return self.session_manager.sessions[self.session_id].autopilot
 
     def onFileEdits(self, edits: List[FileEditWithFullContents]):
@@ -442,7 +450,8 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None):
         if session_id is not None:
             session_manager.registered_ides[session_id] = ideProtocolServer
         other_msgs = await ideProtocolServer.initialize(session_id)
-        capture_event(ideProtocolServer.unique_id, "session_started", { "session_id": ideProtocolServer.session_id })
+        capture_event(ideProtocolServer.unique_id, "session_started", {
+                      "session_id": ideProtocolServer.session_id})
 
         for other_msg in other_msgs:
             handle_msg(other_msg)
@@ -463,5 +472,6 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None):
         if websocket.client_state != WebSocketState.DISCONNECTED:
             await websocket.close()
 
-        capture_event(ideProtocolServer.unique_id, "session_ended", { "session_id": ideProtocolServer.session_id })
+        capture_event(ideProtocolServer.unique_id, "session_ended", {
+                      "session_id": ideProtocolServer.session_id})
         session_manager.registered_ides.pop(ideProtocolServer.session_id)
diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py
index d0fb0bf8..0ae7e7fa 100644
--- a/continuedev/src/continuedev/server/ide_protocol.py
+++ b/continuedev/src/continuedev/server/ide_protocol.py
@@ -23,6 +23,10 @@ class AbstractIdeProtocolServer(ABC):
     async def setFileOpen(self, filepath: str, open: bool = True):
         """Set whether a file is open"""
 
+    @abstractmethod
+    async def showVirtualFile(self, name: str, contents: str):
+        """Show a virtual file"""
+
     @abstractmethod
     async def setSuggestionsLocked(self, filepath: str, locked: bool = True):
         """Set whether suggestions are locked"""
diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index 6d109ca6..90172a4e 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -100,7 +100,7 @@ class SessionManager:
         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")
+            # print(f"Session {session_id} has no websocket")
             return
 
         await self.sessions[session_id].ws.send_json({
diff --git a/extension/package-lock.json b/extension/package-lock.json
index e67fa950..107a7001 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.0.178",
+  "version": "0.0.179",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.0.178",
+      "version": "0.0.179",
       "license": "Apache-2.0",
       "dependencies": {
         "@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index 121423ed..89c6daf5 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.178",
+  "version": "0.0.179",
   "publisher": "Continue",
   "engines": {
     "vscode": "^1.67.0"
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 93b90f0d..bc8665fd 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from "react";
+import { useContext, useEffect, useRef, useState } from "react";
 import styled, { keyframes } from "styled-components";
 import {
   appear,
@@ -13,12 +13,14 @@ import {
   ChevronRight,
   ArrowPath,
   XMark,
+  MagnifyingGlass,
 } from "@styled-icons/heroicons-outline";
 import { StopCircle } from "@styled-icons/heroicons-solid";
 import { HistoryNode } from "../../../schema/HistoryNode";
 import HeaderButtonWithText from "./HeaderButtonWithText";
 import MarkdownPreview from "@uiw/react-markdown-preview";
 import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util";
+import { GUIClientContext } from "../App";
 
 interface StepContainerProps {
   historyNode: HistoryNode;
@@ -32,6 +34,7 @@ interface StepContainerProps {
   onToggle: () => void;
   isFirst: boolean;
   isLast: boolean;
+  index: number;
 }
 
 // #region styled components
@@ -140,6 +143,7 @@ function StepContainer(props: StepContainerProps) {
   const naturalLanguageInputRef = useRef<HTMLTextAreaElement>(null);
   const userInputRef = useRef<HTMLInputElement>(null);
   const isUserInput = props.historyNode.step.name === "UserInputStep";
+  const client = useContext(GUIClientContext);
 
   useEffect(() => {
     if (userInputRef?.current) {
@@ -210,6 +214,17 @@ function StepContainer(props: StepContainerProps) {
             </HeaderButton> */}
 
             <>
+              {(props.historyNode.logs as any)?.length > 0 && (
+                <HeaderButtonWithText
+                  text="Logs"
+                  onClick={(e) => {
+                    e.stopPropagation();
+                    client?.showLogsAtIndex(props.index);
+                  }}
+                >
+                  <MagnifyingGlass size="1.4em" />
+                </HeaderButtonWithText>
+              )}
               <HeaderButtonWithText
                 onClick={(e) => {
                   e.stopPropagation();
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index a179c2bf..6c0df8fc 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -28,6 +28,8 @@ abstract class AbstractContinueGUIClientProtocol {
   abstract setPinnedAtIndices(indices: number[]): void;
 
   abstract toggleAddingHighlightedCode(): void;
+
+  abstract showLogsAtIndex(index: number): void;
 }
 
 export default AbstractContinueGUIClientProtocol;
diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
index 2060dd7f..fef5b2e1 100644
--- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts
+++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
@@ -86,6 +86,10 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
   toggleAddingHighlightedCode(): void {
     this.messenger.send("toggle_adding_highlighted_code", {});
   }
+
+  showLogsAtIndex(index: number): void {
+    this.messenger.send("show_logs_at_index", { index });
+  }
 }
 
 export default ContinueGUIClientProtocol;
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index c35cf21b..fccc9b4b 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -311,6 +311,7 @@ function GUI(props: GUIProps) {
             )
           ) : (
             <StepContainer
+              index={index}
               isLast={index === history.timeline.length - 1}
               isFirst={index === 0}
               open={stepsOpen[index]}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index fac0a227..14a8df72 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -17,6 +17,8 @@ import { WebsocketMessenger } from "./util/messenger";
 import { diffManager } from "./diffs";
 import path = require("path");
 
+const continueVirtualDocumentScheme = "continue";
+
 class IdeProtocolClient {
   private messenger: WebsocketMessenger | null = null;
   private readonly context: vscode.ExtensionContext;
@@ -136,6 +138,25 @@ class IdeProtocolClient {
         this.sendHighlightedCode(highlightedCode);
       }, 100);
     });
+
+    // Register a content provider for the readonly virtual documents
+    const documentContentProvider = new (class
+      implements vscode.TextDocumentContentProvider
+    {
+      // emitter and its event
+      onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
+      onDidChange = this.onDidChangeEmitter.event;
+
+      provideTextDocumentContent(uri: vscode.Uri): string {
+        return uri.query;
+      }
+    })();
+    context.subscriptions.push(
+      vscode.workspace.registerTextDocumentContentProvider(
+        continueVirtualDocumentScheme,
+        documentContentProvider
+      )
+    );
   }
 
   async handleMessage(
@@ -200,6 +221,9 @@ class IdeProtocolClient {
         this.openFile(data.filepath);
         // TODO: Close file if False
         break;
+      case "showVirtualFile":
+        this.showVirtualFile(data.name, data.contents);
+        break;
       case "setSuggestionsLocked":
         this.setSuggestionsLocked(data.filepath, data.locked);
         break;
@@ -295,6 +319,20 @@ class IdeProtocolClient {
     openEditorAndRevealRange(filepath, undefined, vscode.ViewColumn.One);
   }
 
+  showVirtualFile(name: string, contents: string) {
+    vscode.workspace
+      .openTextDocument(
+        vscode.Uri.parse(
+          `${continueVirtualDocumentScheme}:${name}?${encodeURIComponent(
+            contents
+          )}`
+        )
+      )
+      .then((doc) => {
+        vscode.window.showTextDocument(doc, { preview: false });
+      });
+  }
+
   setSuggestionsLocked(filepath: string, locked: boolean) {
     editorSuggestionsLocked.set(filepath, locked);
     // TODO: Rerender?
-- 
cgit v1.2.3-70-g09d2

