From 93ac6b52c9e5067c9703d096104c99f4f29e8eb5 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Wed, 12 Jul 2023 13:22:40 -0700
Subject: purging unecessary files

---
 extension/react-app/src/pages/gui.tsx | 486 ++++++++++++++++++++++++++++++++++
 1 file changed, 486 insertions(+)
 create mode 100644 extension/react-app/src/pages/gui.tsx

(limited to 'extension/react-app/src/pages/gui.tsx')

diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
new file mode 100644
index 00000000..b6a18dc8
--- /dev/null
+++ b/extension/react-app/src/pages/gui.tsx
@@ -0,0 +1,486 @@
+import styled from "styled-components";
+import { defaultBorderRadius } from "../components";
+import Loader from "../components/Loader";
+import ContinueButton from "../components/ContinueButton";
+import { FullState, HighlightedRangeContext } from "../../../schema/FullState";
+import { useCallback, useEffect, useRef, useState, useContext } from "react";
+import { History } from "../../../schema/History";
+import { HistoryNode } from "../../../schema/HistoryNode";
+import StepContainer from "../components/StepContainer";
+import { GUIClientContext } from "../App";
+import {
+  BookOpen,
+  ChatBubbleOvalLeftEllipsis,
+  Trash,
+} from "@styled-icons/heroicons-outline";
+import ComboBox from "../components/ComboBox";
+import TextDialog from "../components/TextDialog";
+import HeaderButtonWithText from "../components/HeaderButtonWithText";
+import ReactSwitch from "react-switch";
+import { usePostHog } from "posthog-js/react";
+import { useSelector } from "react-redux";
+import { RootStore } from "../redux/store";
+import LoadingCover from "../components/LoadingCover";
+import { postVscMessage } from "../vscode";
+import UserInputContainer from "../components/UserInputContainer";
+import Onboarding from "../components/Onboarding";
+
+const TopGUIDiv = styled.div`
+  overflow: hidden;
+`;
+
+const UserInputQueueItem = styled.div`
+  border-radius: ${defaultBorderRadius};
+  color: gray;
+  padding: 8px;
+  margin: 8px;
+  text-align: center;
+`;
+
+const Footer = styled.footer<{ dataSwitchChecked: boolean }>`
+  display: flex;
+  flex-direction: row;
+  gap: 8px;
+  justify-content: right;
+  padding: 8px;
+  align-items: center;
+  margin-top: 8px;
+  border-top: 0.1px solid gray;
+  background-color: ${(props) =>
+    props.dataSwitchChecked ? "#12887a33" : "transparent"};
+`;
+
+interface GUIProps {
+  firstObservation?: any;
+}
+
+function GUI(props: GUIProps) {
+  const client = useContext(GUIClientContext);
+  const posthog = usePostHog();
+  const vscMachineId = useSelector(
+    (state: RootStore) => state.config.vscMachineId
+  );
+  const [dataSwitchChecked, setDataSwitchChecked] = useState(false);
+  const dataSwitchOn = useSelector(
+    (state: RootStore) => state.config.dataSwitchOn
+  );
+
+  useEffect(() => {
+    if (typeof dataSwitchOn !== "undefined") {
+      setDataSwitchChecked(dataSwitchOn);
+    }
+  }, [dataSwitchOn]);
+
+  const [usingFastModel, setUsingFastModel] = useState(false);
+  const [waitingForSteps, setWaitingForSteps] = useState(false);
+  const [userInputQueue, setUserInputQueue] = useState<string[]>([]);
+  const [highlightedRanges, setHighlightedRanges] = useState<
+    HighlightedRangeContext[]
+  >([]);
+  const [addingHighlightedCode, setAddingHighlightedCode] = useState(false);
+  const [availableSlashCommands, setAvailableSlashCommands] = useState<
+    { name: string; description: string }[]
+  >([]);
+  const [pinned, setPinned] = useState(false);
+  const [showDataSharingInfo, setShowDataSharingInfo] = useState(false);
+  const [stepsOpen, setStepsOpen] = useState<boolean[]>([
+    true,
+    true,
+    true,
+    true,
+  ]);
+  const [history, setHistory] = useState<History | undefined>({
+    timeline: [
+      {
+        step: {
+          name: "Welcome to Continue",
+          hide: false,
+          description: `- Highlight code and ask a question or give instructions
+- Use \`cmd+k\` (Mac) / \`ctrl+k\` (Windows) to open Continue
+- Use \`cmd+shift+e\` / \`ctrl+shift+e\` to open file Explorer
+- Add your own OpenAI API key to VS Code Settings with \`cmd+,\`
+- Use slash commands when you want fine-grained control
+- Past steps are included as part of the context by default`,
+          system_message: null,
+          chat_context: [],
+          manage_own_chat_context: false,
+          message: "",
+        },
+        depth: 0,
+        deleted: false,
+        active: false,
+      },
+    ],
+    current_index: 3,
+  } as any);
+
+  const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
+  const [feedbackDialogMessage, setFeedbackDialogMessage] = useState("");
+
+  const topGuiDivRef = useRef<HTMLDivElement>(null);
+
+  const [scrollTimeout, setScrollTimeout] = useState<NodeJS.Timeout | null>(
+    null
+  );
+  const scrollToBottom = useCallback(() => {
+    if (scrollTimeout) {
+      clearTimeout(scrollTimeout);
+    }
+    // Debounced smooth scroll to bottom of screen
+    if (topGuiDivRef.current) {
+      const timeout = setTimeout(() => {
+        window.scrollTo({
+          top: topGuiDivRef.current!.offsetHeight,
+          behavior: "smooth",
+        });
+      }, 200);
+      setScrollTimeout(timeout);
+    }
+  }, [topGuiDivRef.current, scrollTimeout]);
+
+  useEffect(() => {
+    const listener = (e: any) => {
+      // Cmd + i to toggle fast model
+      if (e.key === "i" && e.metaKey && e.shiftKey) {
+        setUsingFastModel((prev) => !prev);
+        // Cmd + backspace to stop currently running step
+      } else if (
+        e.key === "Backspace" &&
+        e.metaKey &&
+        typeof history?.current_index !== "undefined" &&
+        history.timeline[history.current_index]?.active
+      ) {
+        client?.deleteAtIndex(history.current_index);
+      }
+    };
+    window.addEventListener("keydown", listener);
+
+    return () => {
+      window.removeEventListener("keydown", listener);
+    };
+  }, [client, history]);
+
+  useEffect(() => {
+    client?.onStateUpdate((state: FullState) => {
+      // Scroll only if user is at very bottom of the window.
+      setUsingFastModel(state.default_model === "gpt-3.5-turbo");
+      const shouldScrollToBottom =
+        topGuiDivRef.current &&
+        topGuiDivRef.current?.offsetHeight - window.scrollY < 100;
+
+      const waitingForSteps =
+        state.active &&
+        state.history.current_index < state.history.timeline.length &&
+        state.history.timeline[state.history.current_index] &&
+        state.history.timeline[
+          state.history.current_index
+        ].step.description?.trim() === "";
+
+      setWaitingForSteps(waitingForSteps);
+      setHistory(state.history);
+      setHighlightedRanges(state.highlighted_ranges);
+      setUserInputQueue(state.user_input_queue);
+      setAddingHighlightedCode(state.adding_highlighted_code);
+      setAvailableSlashCommands(
+        state.slash_commands.map((c: any) => {
+          return {
+            name: `/${c.name}`,
+            description: c.description,
+          };
+        })
+      );
+      setStepsOpen((prev) => {
+        const nextStepsOpen = [...prev];
+        for (
+          let i = nextStepsOpen.length;
+          i < state.history.timeline.length;
+          i++
+        ) {
+          nextStepsOpen.push(true);
+        }
+        return nextStepsOpen;
+      });
+
+      if (shouldScrollToBottom) {
+        scrollToBottom();
+      }
+    });
+  }, [client]);
+
+  useEffect(() => {
+    scrollToBottom();
+  }, [waitingForSteps]);
+
+  const mainTextInputRef = useRef<HTMLInputElement>(null);
+
+  const deleteContextItems = useCallback(
+    (indices: number[]) => {
+      client?.deleteContextAtIndices(indices);
+    },
+    [client]
+  );
+
+  const onMainTextInput = (event?: any) => {
+    if (mainTextInputRef.current) {
+      let input = (mainTextInputRef.current as any).inputValue;
+      // cmd+enter to /edit
+      if (event?.metaKey) {
+        input = `/edit ${input}`;
+      }
+      (mainTextInputRef.current as any).setInputValue("");
+      if (!client) return;
+
+      setWaitingForSteps(true);
+
+      if (
+        history &&
+        history.current_index >= 0 &&
+        history.current_index < history.timeline.length
+      ) {
+        if (
+          history.timeline[history.current_index]?.step.name ===
+          "Waiting for user input"
+        ) {
+          if (input.trim() === "") return;
+          onStepUserInput(input, history!.current_index);
+          return;
+        } else if (
+          history.timeline[history.current_index]?.step.name ===
+          "Waiting for user confirmation"
+        ) {
+          onStepUserInput("ok", history!.current_index);
+          return;
+        }
+      }
+      if (input.trim() === "") return;
+
+      client.sendMainInput(input);
+      setUserInputQueue((queue) => {
+        return [...queue, input];
+      });
+    }
+  };
+
+  const onStepUserInput = (input: string, index: number) => {
+    if (!client) return;
+    console.log("Sending step user input", input, index);
+    client.sendStepUserInput(input, index);
+  };
+
+  // const iterations = useSelector(selectIterations);
+  return (
+    <>
+      <Onboarding></Onboarding>
+      <LoadingCover hidden={true} message="Downloading local model..." />
+      <TextDialog
+        showDialog={showFeedbackDialog}
+        onEnter={(text) => {
+          client?.sendMainInput(`/feedback ${text}`);
+          setShowFeedbackDialog(false);
+        }}
+        onClose={() => {
+          setShowFeedbackDialog(false);
+        }}
+        message={feedbackDialogMessage}
+      ></TextDialog>
+
+      <TopGUIDiv
+        ref={topGuiDivRef}
+        onKeyDown={(e) => {
+          if (e.key === "Enter" && e.ctrlKey) {
+            onMainTextInput();
+          }
+        }}
+      >
+        {typeof client === "undefined" && (
+          <>
+            <Loader />
+            <p style={{ textAlign: "center" }}>Loading Continue server...</p>
+          </>
+        )}
+        {history?.timeline.map((node: HistoryNode, index: number) => {
+          return node.step.name === "User Input" ? (
+            node.step.hide || (
+              <UserInputContainer
+                onDelete={() => {
+                  client?.deleteAtIndex(index);
+                }}
+                historyNode={node}
+              >
+                {node.step.description as string}
+              </UserInputContainer>
+            )
+          ) : (
+            <StepContainer
+              isLast={index === history.timeline.length - 1}
+              isFirst={index === 0}
+              open={stepsOpen[index]}
+              onToggle={() => {
+                const nextStepsOpen = [...stepsOpen];
+                nextStepsOpen[index] = !nextStepsOpen[index];
+                setStepsOpen(nextStepsOpen);
+              }}
+              onToggleAll={() => {
+                const shouldOpen = !stepsOpen[index];
+                setStepsOpen((prev) => prev.map(() => shouldOpen));
+              }}
+              key={index}
+              onUserInput={(input: string) => {
+                onStepUserInput(input, index);
+              }}
+              inFuture={index > history?.current_index}
+              historyNode={node}
+              onReverse={() => {
+                client?.reverseToIndex(index);
+              }}
+              onRetry={() => {
+                client?.retryAtIndex(index);
+                setWaitingForSteps(true);
+              }}
+              onDelete={() => {
+                client?.deleteAtIndex(index);
+              }}
+            />
+          );
+        })}
+        {waitingForSteps && <Loader></Loader>}
+
+        <div>
+          {userInputQueue.map((input) => {
+            return <UserInputQueueItem>{input}</UserInputQueueItem>;
+          })}
+        </div>
+
+        <ComboBox
+          ref={mainTextInputRef}
+          onEnter={(e) => {
+            onMainTextInput(e);
+            e.stopPropagation();
+            e.preventDefault();
+          }}
+          onInputValueChange={() => {}}
+          items={availableSlashCommands}
+          highlightedCodeSections={highlightedRanges}
+          deleteContextItems={deleteContextItems}
+          onTogglePin={() => {
+            setPinned((prev: boolean) => !prev);
+          }}
+          onToggleAddContext={() => {
+            client?.toggleAddingHighlightedCode();
+          }}
+          addingHighlightedCode={addingHighlightedCode}
+        />
+        <ContinueButton onClick={onMainTextInput} />
+      </TopGUIDiv>
+      <div
+        style={{
+          position: "fixed",
+          bottom: "50px",
+          backgroundColor: "white",
+          color: "black",
+          borderRadius: defaultBorderRadius,
+          padding: "16px",
+          margin: "16px",
+          zIndex: 100,
+        }}
+        hidden={!showDataSharingInfo}
+      >
+        By turning on this switch, you will begin collecting accepted and
+        rejected suggestions in .continue/suggestions.json. This data is stored
+        locally on your machine and not sent anywhere.
+        <br />
+        <br />
+        <b>
+          {dataSwitchChecked
+            ? "👍 Data is being collected"
+            : "👎 No data is being collected"}
+        </b>
+      </div>
+      <Footer dataSwitchChecked={dataSwitchChecked}>
+        <div
+          style={{
+            display: "flex",
+            gap: "4px",
+            marginRight: "auto",
+            alignItems: "center",
+          }}
+          onMouseEnter={() => {
+            setShowDataSharingInfo(true);
+          }}
+          onMouseLeave={() => {
+            setShowDataSharingInfo(false);
+          }}
+        >
+          <ReactSwitch
+            height={20}
+            handleDiameter={20}
+            width={40}
+            onChange={() => {
+              posthog?.capture("data_switch_toggled", {
+                vscMachineId: vscMachineId,
+                dataSwitchChecked: !dataSwitchChecked,
+              });
+              postVscMessage("toggleDataSwitch", { on: !dataSwitchChecked });
+              setDataSwitchChecked((prev) => !prev);
+            }}
+            onColor="#12887a"
+            checked={dataSwitchChecked}
+          />
+          <span style={{ cursor: "help", fontSize: "14px" }}>Collect Data</span>
+        </div>
+        <HeaderButtonWithText
+          onClick={() => {
+            // client?.changeDefaultModel(
+            //   usingFastModel ? "gpt-4" : "gpt-3.5-turbo"
+            // );
+            if (!usingFastModel) {
+              // Show the dialog
+              setFeedbackDialogMessage(
+                "We don't yet support local models, but we're working on it! If privacy is a concern of yours, please write a short note to let us know."
+              );
+              setShowFeedbackDialog(true);
+            }
+            setUsingFastModel((prev) => !prev);
+          }}
+          text={usingFastModel ? "local" : "gpt-4"}
+        >
+          <div
+            style={{ fontSize: "18px", marginLeft: "2px", marginRight: "2px" }}
+          >
+            {usingFastModel ? "🔒" : "🧠"}
+          </div>
+        </HeaderButtonWithText>
+        <HeaderButtonWithText
+          onClick={() => {
+            client?.sendClear();
+          }}
+          text="Clear"
+        >
+          <Trash size="1.6em" />
+        </HeaderButtonWithText>
+        <a
+          href="https://continue.dev/docs/how-to-use-continue"
+          className="no-underline"
+        >
+          <HeaderButtonWithText text="Docs">
+            <BookOpen size="1.6em" />
+          </HeaderButtonWithText>
+        </a>
+        <HeaderButtonWithText
+          onClick={() => {
+            // Set dialog open
+            setFeedbackDialogMessage(
+              "Having trouble using Continue? Want a new feature? Let us know! This box is anonymous, but we will promptly address your feedback."
+            );
+            setShowFeedbackDialog(true);
+          }}
+          text="Feedback"
+        >
+          <ChatBubbleOvalLeftEllipsis size="1.6em" />
+        </HeaderButtonWithText>
+      </Footer>
+    </>
+  );
+}
+
+export default GUI;
-- 
cgit v1.2.3-70-g09d2


From f6c71a6cfe746ee4bb97c4cbe86827679a199ade Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Wed, 12 Jul 2023 15:52:18 -0700
Subject: smaller fixes

---
 continuedev/src/continuedev/steps/core/core.py     |  2 +-
 extension/package.json                             |  7 +--
 .../react-app/src/components/LoadingCover.tsx      | 50 ----------------------
 extension/react-app/src/components/Onboarding.tsx  |  4 +-
 extension/react-app/src/pages/gui.tsx              | 24 +++++------
 5 files changed, 14 insertions(+), 73 deletions(-)
 delete mode 100644 extension/react-app/src/components/LoadingCover.tsx

(limited to 'extension/react-app/src/pages/gui.tsx')

diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index 9eddc03f..5ea95104 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -355,7 +355,7 @@ Please output the code to be inserted at the cursor in order to fulfill the user
                 lines_to_display = contents_lines[rewritten_lines:]
 
             new_file_contents = "\n".join(
-                full_prefix_lines) + "\n" + completion + "\n" + "\n".join(lines_to_display) + "\n" + "\n".join(full_suffix_lines)
+                full_prefix_lines) + "\n" + completion + "\n" + ("\n".join(lines_to_display) + "\n" if len(lines_to_display) > 0 else "") + "\n".join(full_suffix_lines)
 
             step_index = sdk.history.current_index
 
diff --git a/extension/package.json b/extension/package.json
index 69792ea2..6f64ee60 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -44,18 +44,13 @@
     "configuration": {
       "title": "Continue",
       "properties": {
-        "continue.automode": {
-          "type": "boolean",
-          "default": true,
-          "description": "Automatically find relevant code and suggest a fix whenever a traceback is found."
-        },
         "continue.serverUrl": {
           "type": "string",
           "default": "http://localhost:65432",
           "description": "The URL of the Continue server to use."
         },
         "continue.OPENAI_API_KEY": {
-          "type": "password",
+          "type": "string",
           "default": null,
           "description": "The OpenAI API key to use for code generation."
         },
diff --git a/extension/react-app/src/components/LoadingCover.tsx b/extension/react-app/src/components/LoadingCover.tsx
deleted file mode 100644
index a0f8f7a2..00000000
--- a/extension/react-app/src/components/LoadingCover.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from "react";
-import styled from "styled-components";
-
-const StyledDiv = styled.div`
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100vh;
-  background: linear-gradient(
-    101.79deg,
-    #12887a 0%,
-    #87245c 32%,
-    #e12637 63%,
-    #ffb215 100%
-  );
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: column;
-  z-index: 10;
-`;
-
-const StyledImg = styled.img`
-  /* add your styles here */
-`;
-
-const StyledDiv2 = styled.div`
-  width: 50%;
-  height: 5px;
-  background: white;
-  margin-top: 20px;
-`;
-
-interface LoadingCoverProps {
-  message: string;
-  hidden?: boolean;
-}
-
-const LoadingCover = (props: LoadingCoverProps) => {
-  return (
-    <StyledDiv style={{ display: props.hidden ? "none" : "inherit" }}>
-      <StyledImg src="continue.gif" alt="centered image" width="50%" />
-      <StyledDiv2></StyledDiv2>
-      <p>{props.message}</p>
-    </StyledDiv>
-  );
-};
-
-export default LoadingCover;
diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx
index 776ad460..7772a25e 100644
--- a/extension/react-app/src/components/Onboarding.tsx
+++ b/extension/react-app/src/components/Onboarding.tsx
@@ -26,7 +26,7 @@ const StyledSpan = styled.span`
 
 const Onboarding = () => {
   const [counter, setCounter] = useState(4);
-  const gifs = ["intro", "explain", "edit", "generate"];
+  const gifs = ["intro", "explain", "edit", "generate", "intro"];
   const topMessages = [
     "Welcome to Continue!",
     "Answer coding questions",
@@ -42,7 +42,7 @@ const Onboarding = () => {
 
   useEffect(() => {
     const hasVisited = localStorage.getItem("hasVisited");
-    if (hasVisited && false) {
+    if (hasVisited) {
       setCounter(4);
     } else {
       setCounter(0);
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index b6a18dc8..b9382bd1 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -20,7 +20,6 @@ import ReactSwitch from "react-switch";
 import { usePostHog } from "posthog-js/react";
 import { useSelector } from "react-redux";
 import { RootStore } from "../redux/store";
-import LoadingCover from "../components/LoadingCover";
 import { postVscMessage } from "../vscode";
 import UserInputContainer from "../components/UserInputContainer";
 import Onboarding from "../components/Onboarding";
@@ -270,19 +269,16 @@ function GUI(props: GUIProps) {
   // const iterations = useSelector(selectIterations);
   return (
     <>
-      <Onboarding></Onboarding>
-      <LoadingCover hidden={true} message="Downloading local model..." />
-      <TextDialog
-        showDialog={showFeedbackDialog}
-        onEnter={(text) => {
-          client?.sendMainInput(`/feedback ${text}`);
-          setShowFeedbackDialog(false);
-        }}
-        onClose={() => {
-          setShowFeedbackDialog(false);
-        }}
-        message={feedbackDialogMessage}
-      ></TextDialog>
+      <Onboarding />
+      <TextDialog showDialog={showFeedbackDialog}
+      onEnter={(text) => {
+        client?.sendMainInput(`/feedback ${text}`);
+        setShowFeedbackDialog(false);
+      }}
+      onClose={() => {
+        setShowFeedbackDialog(false);
+      }}
+      message={feedbackDialogMessage} />
 
       <TopGUIDiv
         ref={topGuiDivRef}
-- 
cgit v1.2.3-70-g09d2


From 391764f1371dab06af30a29e10a826a516b69bb3 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Wed, 12 Jul 2023 21:53:06 -0700
Subject: persist state and reconnect automatically

---
 continuedev/src/continuedev/core/autopilot.py      |  17 +++-
 continuedev/src/continuedev/libs/constants/main.py |   6 ++
 continuedev/src/continuedev/libs/util/paths.py     |  17 ++++
 continuedev/src/continuedev/server/gui.py          |  10 +-
 continuedev/src/continuedev/server/ide.py          |  25 +++--
 continuedev/src/continuedev/server/ide_protocol.py |  10 +-
 continuedev/src/continuedev/server/main.py         |  16 ++-
 .../src/continuedev/server/session_manager.py      |  41 ++++++--
 docs/docs/concepts/ide.md                          |   4 +-
 extension/react-app/src/components/ComboBox.tsx    |   1 -
 extension/react-app/src/hooks/messenger.ts         |  10 --
 extension/react-app/src/pages/gui.tsx              |   1 -
 extension/src/activation/activate.ts               |   2 +-
 extension/src/continueIdeClient.ts                 |  62 ++++++++---
 extension/src/debugPanel.ts                        | 113 ++++-----------------
 extension/src/util/messenger.ts                    |  10 ++
 16 files changed, 192 insertions(+), 153 deletions(-)
 create mode 100644 continuedev/src/continuedev/libs/constants/main.py
 create mode 100644 continuedev/src/continuedev/libs/util/paths.py

(limited to 'extension/react-app/src/pages/gui.tsx')

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 1b074435..e1c8a076 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -1,13 +1,13 @@
 from functools import cached_property
 import traceback
 import time
-from typing import Any, Callable, Coroutine, Dict, List
+from typing import Any, Callable, Coroutine, Dict, List, Union
 import os
 from aiohttp import ClientPayloadError
+from pydantic import root_validator
 
 from ..models.filesystem import RangeInFileWithContents
 from ..models.filesystem_edit import FileEditWithFullContents
-from ..libs.llm import LLM
 from .observation import Observation, InternalErrorObservation
 from ..server.ide_protocol import AbstractIdeProtocolServer
 from ..libs.util.queue import AsyncSubscriptionQueue
@@ -16,7 +16,6 @@ from .main import Context, ContinueCustomException, HighlightedRangeContext, Pol
 from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep
 from ..libs.util.telemetry import capture_event
 from .sdk import ContinueSDK
-import asyncio
 from ..libs.util.step_name_to_steps import get_step_from_name
 from ..libs.util.traceback_parsers import get_python_traceback, get_javascript_traceback
 from openai import error as openai_errors
@@ -46,6 +45,7 @@ class Autopilot(ContinueBaseModel):
     ide: AbstractIdeProtocolServer
     history: History = History.from_empty()
     context: Context = Context()
+    full_state: Union[FullState, None] = None
     _on_update_callbacks: List[Callable[[FullState], None]] = []
 
     _active: bool = False
@@ -63,8 +63,15 @@ class Autopilot(ContinueBaseModel):
         arbitrary_types_allowed = True
         keep_untouched = (cached_property,)
 
+    @root_validator(pre=True)
+    def fill_in_values(cls, values):
+        full_state: FullState = values.get('full_state')
+        if full_state is not None:
+            values['history'] = full_state.history
+        return values
+
     def get_full_state(self) -> FullState:
-        return FullState(
+        full_state = FullState(
             history=self.history,
             active=self._active,
             user_input_queue=self._main_user_input_queue,
@@ -73,6 +80,8 @@ class Autopilot(ContinueBaseModel):
             slash_commands=self.get_available_slash_commands(),
             adding_highlighted_code=self._adding_highlighted_code,
         )
+        self.full_state = full_state
+        return full_state
 
     def get_available_slash_commands(self) -> List[Dict]:
         custom_commands = list(map(lambda x: {
diff --git a/continuedev/src/continuedev/libs/constants/main.py b/continuedev/src/continuedev/libs/constants/main.py
new file mode 100644
index 00000000..96eb6e69
--- /dev/null
+++ b/continuedev/src/continuedev/libs/constants/main.py
@@ -0,0 +1,6 @@
+## PATHS ##
+
+CONTINUE_GLOBAL_FOLDER = ".continue"
+CONTINUE_SESSIONS_FOLDER = "sessions"
+CONTINUE_SERVER_FOLDER = "server"
+
diff --git a/continuedev/src/continuedev/libs/util/paths.py b/continuedev/src/continuedev/libs/util/paths.py
new file mode 100644
index 00000000..fddef887
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/paths.py
@@ -0,0 +1,17 @@
+import os
+
+from ..constants.main import CONTINUE_SESSIONS_FOLDER, CONTINUE_GLOBAL_FOLDER, CONTINUE_SERVER_FOLDER
+
+def getGlobalFolderPath():
+    return os.path.join(os.path.expanduser("~"), CONTINUE_GLOBAL_FOLDER)
+
+
+
+def getSessionsFolderPath():
+    return os.path.join(getGlobalFolderPath(), CONTINUE_SESSIONS_FOLDER)
+
+def getServerFolderPath():
+    return os.path.join(getGlobalFolderPath(), CONTINUE_SERVER_FOLDER)
+
+def getSessionFilePath(session_id: str):
+    return os.path.join(getSessionsFolderPath(), f"{session_id}.json")
\ No newline at end of file
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index 21089f30..8f6f68f6 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -31,12 +31,12 @@ class AppStatus:
 Server.handle_exit = AppStatus.handle_exit
 
 
-def session(x_continue_session_id: str = Header("anonymous")) -> Session:
-    return session_manager.get_session(x_continue_session_id)
+async def session(x_continue_session_id: str = Header("anonymous")) -> Session:
+    return await session_manager.get_session(x_continue_session_id)
 
 
-def websocket_session(session_id: str) -> Session:
-    return session_manager.get_session(session_id)
+async def websocket_session(session_id: str) -> Session:
+    return await session_manager.get_session(session_id)
 
 
 T = TypeVar("T", bound=BaseModel)
@@ -199,4 +199,6 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we
         print("Closing gui websocket")
         if websocket.client_state != WebSocketState.DISCONNECTED:
             await websocket.close()
+
+        session_manager.persist_session(session.session_id)
         session_manager.remove_session(session.session_id)
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 4645b49e..12a21f19 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -52,9 +52,11 @@ class FileEditsUpdate(BaseModel):
 class OpenFilesResponse(BaseModel):
     openFiles: List[str]
 
+
 class VisibleFilesResponse(BaseModel):
     visibleFiles: List[str]
 
+
 class HighlightedCodeResponse(BaseModel):
     highlightedCode: List[RangeInFile]
 
@@ -115,6 +117,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
     websocket: WebSocket
     session_manager: SessionManager
     sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue()
+    session_id: Union[str, None] = None
 
     def __init__(self, session_manager: SessionManager, websocket: WebSocket):
         self.websocket = websocket
@@ -132,8 +135,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
                 continue
             message_type = message["messageType"]
             data = message["data"]
-            # if message_type == "openGUI":
-            #     await self.openGUI()
             if message_type == "workspaceDirectory":
                 self.workspace_directory = data["workspaceDirectory"]
                 break
@@ -158,8 +159,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
         return resp_model.parse_obj(resp)
 
     async def handle_json(self, message_type: str, data: Any):
-        if message_type == "openGUI":
-            await self.openGUI()
+        if message_type == "getSessionId":
+            await self.getSessionId()
         elif message_type == "setFileOpen":
             await self.setFileOpen(data["filepath"], data["open"])
         elif message_type == "setSuggestionsLocked":
@@ -217,9 +218,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
             "locked": locked
         })
 
-    async def openGUI(self):
-        session_id = self.session_manager.new_session(self)
-        await self._send_json("openGUI", {
+    async def getSessionId(self):
+        session_id = self.session_manager.new_session(
+            self, self.session_id).session_id
+        await self._send_json("getSessionId", {
             "sessionId": session_id
         })
 
@@ -304,7 +306,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
     async def getOpenFiles(self) -> List[str]:
         resp = await self._send_and_receive_json({}, OpenFilesResponse, "openFiles")
         return resp.openFiles
-    
+
     async def getVisibleFiles(self) -> List[str]:
         resp = await self._send_and_receive_json({}, VisibleFilesResponse, "visibleFiles")
         return resp.visibleFiles
@@ -416,7 +418,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
 
 
 @router.websocket("/ws")
-async def websocket_endpoint(websocket: WebSocket):
+async def websocket_endpoint(websocket: WebSocket, session_id: str = None):
     try:
         await websocket.accept()
         print("Accepted websocket connection from, ", websocket.client)
@@ -434,6 +436,9 @@ async def websocket_endpoint(websocket: WebSocket):
                 ideProtocolServer.handle_json(message_type, data))
 
         ideProtocolServer = IdeProtocolServer(session_manager, websocket)
+        ideProtocolServer.session_id = session_id
+        if session_id is not None:
+            session_manager.registered_ides[session_id] = ideProtocolServer
         other_msgs = await ideProtocolServer.initialize()
 
         for other_msg in other_msgs:
@@ -454,3 +459,5 @@ async def websocket_endpoint(websocket: WebSocket):
     finally:
         if websocket.client_state != WebSocketState.DISCONNECTED:
             await websocket.close()
+
+        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 2783dc61..2f78cf0e 100644
--- a/continuedev/src/continuedev/server/ide_protocol.py
+++ b/continuedev/src/continuedev/server/ide_protocol.py
@@ -1,5 +1,6 @@
-from typing import Any, List
+from typing import Any, List, Union
 from abc import ABC, abstractmethod, abstractproperty
+from fastapi import WebSocket
 
 from ..models.main import Traceback
 from ..models.filesystem_edit import FileEdit, FileSystemEdit, EditDiff
@@ -7,6 +8,9 @@ from ..models.filesystem import RangeInFile, RangeInFileWithContents
 
 
 class AbstractIdeProtocolServer(ABC):
+    websocket: WebSocket
+    session_id: Union[str, None]
+
     @abstractmethod
     async def handle_json(self, data: Any):
         """Handle a json message"""
@@ -24,8 +28,8 @@ class AbstractIdeProtocolServer(ABC):
         """Set whether suggestions are locked"""
 
     @abstractmethod
-    async def openGUI(self):
-        """Open a GUI"""
+    async def getSessionId(self):
+        """Get a new session ID"""
 
     @abstractmethod
     async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool:
diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py
index f4d82903..aa093853 100644
--- a/continuedev/src/continuedev/server/main.py
+++ b/continuedev/src/continuedev/server/main.py
@@ -4,7 +4,8 @@ from fastapi import FastAPI
 from fastapi.middleware.cors import CORSMiddleware
 from .ide import router as ide_router
 from .gui import router as gui_router
-import logging
+from .session_manager import session_manager
+import atexit
 import uvicorn
 import argparse
 
@@ -44,5 +45,16 @@ def run_server():
     uvicorn.run(app, host="0.0.0.0", port=args.port)
 
 
+def cleanup():
+    print("Cleaning up sessions")
+    for session_id in session_manager.sessions:
+        session_manager.persist_session(session_id)
+
+
+atexit.register(cleanup)
 if __name__ == "__main__":
-    run_server()
+    try:
+        run_server()
+    except Exception as e:
+        cleanup()
+        raise e
diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index 7147dcfa..fb8ac386 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -1,9 +1,12 @@
-from asyncio import BaseEventLoop
+import os
 from fastapi import WebSocket
 from typing import Any, Dict, List, Union
 from uuid import uuid4
+import json
 
+from ..libs.util.paths import getSessionFilePath, getSessionsFolderPath
 from ..models.filesystem_edit import FileEditWithFullContents
+from ..libs.constants.main import CONTINUE_SESSIONS_FOLDER
 from ..core.policy import DemoPolicy
 from ..core.main import FullState
 from ..core.autopilot import Autopilot
@@ -39,17 +42,35 @@ class DemoAutopilot(Autopilot):
 
 class SessionManager:
     sessions: Dict[str, Session] = {}
+    # Mapping of session_id to IDE, where the IDE is still alive
+    registered_ides: Dict[str, AbstractIdeProtocolServer] = {}
 
-    def get_session(self, session_id: str) -> Session:
+    async def get_session(self, session_id: str) -> Session:
         if session_id not in self.sessions:
+            # Check then whether it is persisted by listing all files in the sessions folder
+            # And only if the IDE is still alive
+            sessions_folder = getSessionsFolderPath()
+            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)
+
             raise KeyError("Session ID not recognized", session_id)
         return self.sessions[session_id]
 
-    def new_session(self, ide: AbstractIdeProtocolServer) -> str:
-        autopilot = DemoAutopilot(policy=DemoPolicy(), ide=ide)
-        session_id = str(uuid4())
+    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(
+            policy=DemoPolicy(), ide=ide, full_state=full_state)
+        session_id = session_id or str(uuid4())
+        ide.session_id = session_id
         session = Session(session_id=session_id, autopilot=autopilot)
         self.sessions[session_id] = session
+        self.registered_ides[session_id] = ide
 
         async def on_update(state: FullState):
             await session_manager.send_ws_data(session_id, "state_update", {
@@ -58,11 +79,19 @@ class SessionManager:
 
         autopilot.on_update(on_update)
         create_async_task(autopilot.run_policy())
-        return session_id
+        return session
 
     def remove_session(self, session_id: str):
         del self.sessions[session_id]
 
+    def persist_session(self, session_id: str):
+        """Save the session's FullState as a json file"""
+        full_state = self.sessions[session_id].autopilot.get_full_state()
+        if not os.path.exists(getSessionsFolderPath()):
+            os.mkdir(getSessionsFolderPath())
+        with open(getSessionFilePath(session_id), "w") as f:
+            json.dump(full_state.dict(), f)
+
     def register_websocket(self, session_id: str, ws: WebSocket):
         self.sessions[session_id].ws = ws
         print("Registered websocket for session", session_id)
diff --git a/docs/docs/concepts/ide.md b/docs/docs/concepts/ide.md
index dc7b9e23..bd31481b 100644
--- a/docs/docs/concepts/ide.md
+++ b/docs/docs/concepts/ide.md
@@ -41,9 +41,9 @@ Get the workspace directory
 
 Set whether a file is open
 
-### openGUI
+### getSessionId
 
-Open a gui
+Get a new session ID
 
 ### showSuggestionsAndWait
 
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index ac994b0a..7d6541c7 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -331,7 +331,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
               ) {
                 (event.nativeEvent as any).preventDownshiftDefault = true;
               } else if (event.key === "ArrowUp") {
-                console.log("OWJFOIJO");
                 if (positionInHistory == 0) return;
                 else if (
                   positionInHistory == history.length &&
diff --git a/extension/react-app/src/hooks/messenger.ts b/extension/react-app/src/hooks/messenger.ts
index e2a0bab8..00ce1fbb 100644
--- a/extension/react-app/src/hooks/messenger.ts
+++ b/extension/react-app/src/hooks/messenger.ts
@@ -1,6 +1,3 @@
-// console.log("Websocket import");
-// const WebSocket = require("ws");
-
 export abstract class Messenger {
   abstract send(messageType: string, data: object): void;
 
@@ -28,13 +25,6 @@ export class WebsocketMessenger extends Messenger {
   private serverUrl: string;
 
   _newWebsocket(): WebSocket {
-    // // Dynamic import, because WebSocket is builtin with browser, but not with node. And can't use require in browser.
-    // if (typeof process === "object") {
-    //   console.log("Using node");
-    //   // process is only available in Node
-    //   var WebSocket = require("ws");
-    // }
-
     const newWebsocket = new WebSocket(this.serverUrl);
     for (const listener of this.onOpenListeners) {
       this.onOpen(listener);
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index b9382bd1..4ff260fa 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -262,7 +262,6 @@ function GUI(props: GUIProps) {
 
   const onStepUserInput = (input: string, index: number) => {
     if (!client) return;
-    console.log("Sending step user input", input, index);
     client.sendStepUserInput(input, index);
   };
 
diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts
index 559caf44..cd885b12 100644
--- a/extension/src/activation/activate.ts
+++ b/extension/src/activation/activate.ts
@@ -56,7 +56,7 @@ export async function activateExtension(context: vscode.ExtensionContext) {
   registerAllCodeLensProviders(context);
   registerAllCommands(context);
 
-  // Initialize IDE Protocol Client, then call "openGUI"
+  // Initialize IDE Protocol Client
   const serverUrl = getContinueServerUrl();
   ideProtocolClient = new IdeProtocolClient(
     `${serverUrl.replace("http", "ws")}/ide/ws`,
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index b728833f..4c1fdf1e 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -1,10 +1,9 @@
-// import { ShowSuggestionRequest } from "../schema/ShowSuggestionRequest";
 import {
   editorSuggestionsLocked,
   showSuggestion as showSuggestionInEditor,
   SuggestionRanges,
 } from "./suggestions";
-import { openEditorAndRevealRange, getRightViewColumn } from "./util/vscode";
+import { openEditorAndRevealRange } from "./util/vscode";
 import { FileEdit } from "../schema/FileEdit";
 import { RangeInFile } from "../schema/RangeInFile";
 import * as vscode from "vscode";
@@ -15,8 +14,6 @@ import {
 import { FileEditWithFullContents } from "../schema/FileEditWithFullContents";
 import fs = require("fs");
 import { WebsocketMessenger } from "./util/messenger";
-import * as path from "path";
-import * as os from "os";
 import { diffManager } from "./diffs";
 
 class IdeProtocolClient {
@@ -27,17 +24,54 @@ class IdeProtocolClient {
 
   private _highlightDebounce: NodeJS.Timeout | null = null;
 
-  constructor(serverUrl: string, context: vscode.ExtensionContext) {
-    this.context = context;
+  private _lastReloadTime: number = 16;
+  private _reconnectionTimeouts: NodeJS.Timeout[] = [];
+
+  private _sessionId: string | null = null;
+  private _serverUrl: string;
 
-    let messenger = new WebsocketMessenger(serverUrl);
+  private _newWebsocketMessenger() {
+    const requestUrl =
+      this._serverUrl +
+      (this._sessionId ? `?session_id=${this._sessionId}` : "");
+    const messenger = new WebsocketMessenger(requestUrl);
     this.messenger = messenger;
-    messenger.onClose(() => {
+
+    const reconnect = () => {
+      console.log("Trying to reconnect IDE protocol websocket...");
       this.messenger = null;
+
+      // Exponential backoff to reconnect
+      this._reconnectionTimeouts.forEach((to) => clearTimeout(to));
+
+      const timeout = setTimeout(() => {
+        if (this.messenger?.websocket?.readyState === 1) {
+          return;
+        }
+        this._newWebsocketMessenger();
+      }, this._lastReloadTime);
+
+      this._reconnectionTimeouts.push(timeout);
+      this._lastReloadTime = Math.min(2 * this._lastReloadTime, 5000);
+    };
+    messenger.onOpen(() => {
+      this._reconnectionTimeouts.forEach((to) => clearTimeout(to));
+    });
+    messenger.onClose(() => {
+      reconnect();
+    });
+    messenger.onError(() => {
+      reconnect();
     });
     messenger.onMessage((messageType, data, messenger) => {
       this.handleMessage(messageType, data, messenger);
     });
+  }
+
+  constructor(serverUrl: string, context: vscode.ExtensionContext) {
+    this.context = context;
+    this._serverUrl = serverUrl;
+    this._newWebsocketMessenger();
 
     // Setup listeners for any file changes in open editors
     // vscode.workspace.onDidChangeTextDocument((event) => {
@@ -171,7 +205,7 @@ class IdeProtocolClient {
       case "showDiff":
         this.showDiff(data.filepath, data.replacement, data.step_index);
         break;
-      case "openGUI":
+      case "getSessionId":
       case "connected":
         break;
       default:
@@ -284,10 +318,6 @@ class IdeProtocolClient {
   // ------------------------------------ //
   // Initiate Request
 
-  async openGUI(asRightWebviewPanel: boolean = false) {
-    // Open the webview panel
-  }
-
   async getSessionId(): Promise<string> {
     await new Promise((resolve, reject) => {
       // Repeatedly try to connect to the server
@@ -303,10 +333,10 @@ class IdeProtocolClient {
         }
       }, 1000);
     });
-    const resp = await this.messenger?.sendAndReceive("openGUI", {});
-    const sessionId = resp.sessionId;
+    const resp = await this.messenger?.sendAndReceive("getSessionId", {});
     // console.log("New Continue session with ID: ", sessionId);
-    return sessionId;
+    this._sessionId = resp.sessionId;
+    return resp.sessionId;
   }
 
   acceptRejectSuggestion(accept: boolean, key: SuggestionRanges) {
diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts
index 487bbedf..5e1689d1 100644
--- a/extension/src/debugPanel.ts
+++ b/extension/src/debugPanel.ts
@@ -8,76 +8,6 @@ import {
 import { RangeInFile } from "./client";
 const WebSocket = require("ws");
 
-class StreamManager {
-  private _fullText: string = "";
-  private _insertionPoint: vscode.Position | undefined;
-
-  private _addToEditor(update: string) {
-    let editor =
-      vscode.window.activeTextEditor || vscode.window.visibleTextEditors[0];
-
-    if (typeof this._insertionPoint === "undefined") {
-      if (editor?.selection.isEmpty) {
-        this._insertionPoint = editor?.selection.active;
-      } else {
-        this._insertionPoint = editor?.selection.end;
-      }
-    }
-    editor?.edit((editBuilder) => {
-      if (this._insertionPoint) {
-        editBuilder.insert(this._insertionPoint, update);
-        this._insertionPoint = this._insertionPoint.translate(
-          Array.from(update.matchAll(/\n/g)).length,
-          update.length
-        );
-      }
-    });
-  }
-
-  public closeStream() {
-    this._fullText = "";
-    this._insertionPoint = undefined;
-    this._codeBlockStatus = "closed";
-    this._pendingBackticks = 0;
-  }
-
-  private _codeBlockStatus: "open" | "closed" | "language-descriptor" =
-    "closed";
-  private _pendingBackticks: number = 0;
-  public onStreamUpdate(update: string) {
-    let textToInsert = "";
-    for (let i = 0; i < update.length; i++) {
-      switch (this._codeBlockStatus) {
-        case "closed":
-          if (update[i] === "`" && this._fullText.endsWith("``")) {
-            this._codeBlockStatus = "language-descriptor";
-          }
-          break;
-        case "language-descriptor":
-          if (update[i] === " " || update[i] === "\n") {
-            this._codeBlockStatus = "open";
-          }
-          break;
-        case "open":
-          if (update[i] === "`") {
-            if (this._fullText.endsWith("``")) {
-              this._codeBlockStatus = "closed";
-              this._pendingBackticks = 0;
-            } else {
-              this._pendingBackticks += 1;
-            }
-          } else {
-            textToInsert += "`".repeat(this._pendingBackticks) + update[i];
-            this._pendingBackticks = 0;
-          }
-          break;
-      }
-      this._fullText += update[i];
-    }
-    this._addToEditor(textToInsert);
-  }
-}
-
 let websocketConnections: { [url: string]: WebsocketConnection | undefined } =
   {};
 
@@ -127,8 +57,6 @@ class WebsocketConnection {
   }
 }
 
-let streamManager = new StreamManager();
-
 export let debugPanelWebview: vscode.Webview | undefined;
 export function setupDebugPanel(
   panel: vscode.WebviewPanel | vscode.WebviewView,
@@ -147,10 +75,7 @@ export function setupDebugPanel(
     .toString();
 
   const isProduction = true; // context?.extensionMode === vscode.ExtensionMode.Development;
-  if (!isProduction) {
-    scriptUri = "http://localhost:5173/src/main.tsx";
-    styleMainUri = "http://localhost:5173/src/main.css";
-  } else {
+  if (isProduction) {
     scriptUri = debugPanelWebview
       .asWebviewUri(
         vscode.Uri.joinPath(extensionUri, "react-app/dist/assets/index.js")
@@ -161,6 +86,9 @@ export function setupDebugPanel(
         vscode.Uri.joinPath(extensionUri, "react-app/dist/assets/index.css")
       )
       .toString();
+  } else {
+    scriptUri = "http://localhost:5173/src/main.tsx";
+    styleMainUri = "http://localhost:5173/src/main.css";
   }
 
   panel.webview.options = {
@@ -175,11 +103,11 @@ export function setupDebugPanel(
       return;
     }
 
-    let rangeInFile: RangeInFile = {
+    const rangeInFile: RangeInFile = {
       range: e.selections[0],
       filepath: e.textEditor.document.fileName,
     };
-    let filesystem = {
+    const filesystem = {
       [rangeInFile.filepath]: e.textEditor.document.getText(),
     };
     panel.webview.postMessage({
@@ -217,13 +145,19 @@ export function setupDebugPanel(
           url,
         });
       };
-      const connection = new WebsocketConnection(
-        url,
-        onMessage,
-        onOpen,
-        onClose
-      );
-      websocketConnections[url] = connection;
+      try {
+        const connection = new WebsocketConnection(
+          url,
+          onMessage,
+          onOpen,
+          onClose
+        );
+        websocketConnections[url] = connection;
+        resolve(null);
+      } catch (e) {
+        console.log("Caught it!: ", e);
+        reject(e);
+      }
     });
   }
 
@@ -292,15 +226,6 @@ export function setupDebugPanel(
         openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One);
         break;
       }
-      case "streamUpdate": {
-        // Write code at the position of the cursor
-        streamManager.onStreamUpdate(data.update);
-        break;
-      }
-      case "closeStream": {
-        streamManager.closeStream();
-        break;
-      }
       case "withProgress": {
         // This message allows withProgress to be used in the webview
         if (data.done) {
diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts
index b1df161b..7fd71ddd 100644
--- a/extension/src/util/messenger.ts
+++ b/extension/src/util/messenger.ts
@@ -15,6 +15,8 @@ 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>;
 }
@@ -26,6 +28,7 @@ export class WebsocketMessenger extends Messenger {
   } = {};
   private onOpenListeners: (() => void)[] = [];
   private onCloseListeners: (() => void)[] = [];
+  private onErrorListeners: (() => void)[] = [];
   private serverUrl: string;
 
   _newWebsocket(): WebSocket {
@@ -43,6 +46,9 @@ export class WebsocketMessenger extends Messenger {
     for (const listener of this.onCloseListeners) {
       this.onClose(listener);
     }
+    for (const listener of this.onErrorListeners) {
+      this.onError(listener);
+    }
     for (const messageType in this.onMessageListeners) {
       for (const listener of this.onMessageListeners[messageType]) {
         this.onMessageType(messageType, listener);
@@ -151,4 +157,8 @@ export class WebsocketMessenger extends Messenger {
   onClose(callback: () => void): void {
     this.websocket.addEventListener("close", callback);
   }
+
+  onError(callback: () => void): void {
+    this.websocket.addEventListener("error", callback);
+  }
 }
-- 
cgit v1.2.3-70-g09d2


From 39cd2ef27d6ed439b00a9edec4a487343ff1c2c9 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Fri, 14 Jul 2023 03:24:46 -0700
Subject: warn of large highlighted ranges, cmd+k->m

---
 continuedev/src/continuedev/core/policy.py        |   2 +-
 continuedev/src/continuedev/steps/help.py         |   2 +-
 extension/package-lock.json                       |   4 +-
 extension/package.json                            |   6 +-
 extension/react-app/src/components/ComboBox.tsx   |   5 +
 extension/react-app/src/components/Onboarding.tsx |   1 +
 extension/react-app/src/components/PillButton.tsx | 167 +++++++++++++---------
 extension/react-app/src/pages/gui.tsx             |  27 ++--
 extension/src/commands.ts                         |  17 ++-
 extension/src/lang-server/codeLens.ts             |   4 +-
 10 files changed, 141 insertions(+), 94 deletions(-)

(limited to 'extension/react-app/src/pages/gui.tsx')

diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index 59ea78b1..bc897357 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -59,7 +59,7 @@ class DemoPolicy(Policy):
             return (
                 MessageStep(name="Welcome to Continue", message=dedent("""\
                     - Highlight code and ask a question or give instructions
-                    - Use `cmd+k` (Mac) / `ctrl+k` (Windows) to open Continue
+                    - Use `cmd+m` (Mac) / `ctrl+m` (Windows) to open Continue
                     - Use `/help` to ask questions about how to use Continue""")) >>
                 WelcomeStep() >>
                 # SetupContinueWorkspaceStep() >>
diff --git a/continuedev/src/continuedev/steps/help.py b/continuedev/src/continuedev/steps/help.py
index 2dc3647c..ba1e6087 100644
--- a/continuedev/src/continuedev/steps/help.py
+++ b/continuedev/src/continuedev/steps/help.py
@@ -19,7 +19,7 @@ help = dedent("""\
 
         Continue passes all of the sections of code you highlight, the code above and below the to-be edited highlighted code section, and all previous steps above input box as context to the LLM.
 
-        You can use cmd+k (Mac) / ctrl+k (Windows) to open Continue. You can use cmd+shift+e / ctrl+shift+e to open file Explorer. You can add your own OpenAI API key to VS Code Settings with `cmd+,`
+        You can use cmd+m (Mac) / ctrl+m (Windows) to open Continue. You can use cmd+shift+e / ctrl+shift+e to open file Explorer. You can add your own OpenAI API key to VS Code Settings with `cmd+,`
 
         If Continue is stuck loading, try using `cmd+shift+p` to open the command palette, search "Reload Window", and then select it. This will reload VS Code and Continue and often fixes issues.
 
diff --git a/extension/package-lock.json b/extension/package-lock.json
index 9d5c73e1..a79dd6b4 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.0.163",
+  "version": "0.0.164",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.0.163",
+      "version": "0.0.164",
       "license": "Apache-2.0",
       "dependencies": {
         "@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index 2b0f6b94..de1f395d 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.163",
+  "version": "0.0.164",
   "publisher": "Continue",
   "engines": {
     "vscode": "^1.67.0"
@@ -111,8 +111,8 @@
     "keybindings": [
       {
         "command": "continue.focusContinueInput",
-        "mac": "cmd+k",
-        "key": "ctrl+k"
+        "mac": "cmd+m",
+        "key": "ctrl+m"
       },
       {
         "command": "continue.suggestionDown",
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 73db33ca..bd0d59b5 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -228,6 +228,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
         )} */}
         {highlightedCodeSections.map((section, idx) => (
           <PillButton
+            warning={
+              section.range.contents.length > 4000 && section.editing
+                ? "Editing such a large range may be slow"
+                : undefined
+            }
             editing={section.editing}
             pinned={section.pinned}
             index={idx}
diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx
index 6bfb0ccd..231c1e93 100644
--- a/extension/react-app/src/components/Onboarding.tsx
+++ b/extension/react-app/src/components/Onboarding.tsx
@@ -109,6 +109,7 @@ const Onboarding = () => {
             paddingBottom: "50px",
             textAlign: "center",
             cursor: "pointer",
+            whiteSpace: "nowrap",
           }}
         >
           <StyledSpan
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
index 31d98c0f..d9d779d1 100644
--- a/extension/react-app/src/components/PillButton.tsx
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -1,12 +1,11 @@
 import { useContext, useState } from "react";
 import styled from "styled-components";
+import { StyledTooltip, defaultBorderRadius, secondaryDark } from ".";
 import {
-  StyledTooltip,
-  defaultBorderRadius,
-  lightGray,
-  secondaryDark,
-} from ".";
-import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline";
+  Trash,
+  PaintBrush,
+  ExclamationTriangle,
+} from "@styled-icons/heroicons-outline";
 import { GUIClientContext } from "../App";
 
 const Button = styled.button`
@@ -31,7 +30,6 @@ const GridDiv = styled.div`
   grid-template-columns: 1fr 1fr;
   align-items: center;
   border-radius: ${defaultBorderRadius};
-  overflow: hidden;
 
   background-color: ${secondaryDark};
 `;
@@ -48,6 +46,21 @@ const ButtonDiv = styled.div<{ backgroundColor: string }>`
   }
 `;
 
+const CircleDiv = styled.div`
+  position: absolute;
+  top: -10px;
+  right: -10px;
+  width: 20px;
+  height: 20px;
+  border-radius: 50%;
+  background-color: red;
+  color: white;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 2px;
+`;
+
 interface PillButtonProps {
   onHover?: (arg0: boolean) => void;
   onDelete?: () => void;
@@ -55,6 +68,7 @@ interface PillButtonProps {
   index: number;
   editing: boolean;
   pinned: boolean;
+  warning?: string;
 }
 
 const PillButton = (props: PillButtonProps) => {
@@ -63,75 +77,96 @@ const PillButton = (props: PillButtonProps) => {
 
   return (
     <>
-      <Button
-        style={{
-          position: "relative",
-          borderColor: props.editing
-            ? "#8800aa"
-            : props.pinned
-            ? "#ffff0099"
-            : "transparent",
-          borderWidth: "1px",
-          borderStyle: "solid",
-        }}
-        onMouseEnter={() => {
-          setIsHovered(true);
-          if (props.onHover) {
-            props.onHover(true);
-          }
-        }}
-        onMouseLeave={() => {
-          setIsHovered(false);
-          if (props.onHover) {
-            props.onHover(false);
-          }
-        }}
-      >
-        {isHovered && (
-          <GridDiv>
-            <ButtonDiv
-              data-tooltip-id={`edit-${props.index}`}
-              backgroundColor={"#8800aa55"}
-              onClick={() => {
-                client?.setEditingAtIndices([props.index]);
-              }}
-            >
-              <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush>
-            </ButtonDiv>
+      <div style={{ position: "relative" }}>
+        <Button
+          style={{
+            position: "relative",
+            borderColor: props.warning
+              ? "red"
+              : props.editing
+              ? "#8800aa"
+              : props.pinned
+              ? "#ffff0099"
+              : "transparent",
+            borderWidth: "1px",
+            borderStyle: "solid",
+          }}
+          onMouseEnter={() => {
+            setIsHovered(true);
+            if (props.onHover) {
+              props.onHover(true);
+            }
+          }}
+          onMouseLeave={() => {
+            setIsHovered(false);
+            if (props.onHover) {
+              props.onHover(false);
+            }
+          }}
+        >
+          {isHovered && (
+            <GridDiv>
+              <ButtonDiv
+                data-tooltip-id={`edit-${props.index}`}
+                backgroundColor={"#8800aa55"}
+                onClick={() => {
+                  client?.setEditingAtIndices([props.index]);
+                }}
+              >
+                <PaintBrush
+                  style={{ margin: "auto" }}
+                  width="1.6em"
+                ></PaintBrush>
+              </ButtonDiv>
 
-            {/* <ButtonDiv
+              {/* <ButtonDiv
             data-tooltip-id={`pin-${props.index}`}
             backgroundColor={"#ffff0055"}
             onClick={() => {
               client?.setPinnedAtIndices([props.index]);
             }}
-          >
+            >
             <MapPin style={{ margin: "auto" }} width="1.6em"></MapPin>
           </ButtonDiv> */}
-            <StyledTooltip id={`pin-${props.index}`}>
-              Edit this range
+              <StyledTooltip id={`pin-${props.index}`}>
+                Edit this range
+              </StyledTooltip>
+              <ButtonDiv
+                data-tooltip-id={`delete-${props.index}`}
+                backgroundColor={"#cc000055"}
+                onClick={() => {
+                  if (props.onDelete) {
+                    props.onDelete();
+                  }
+                }}
+              >
+                <Trash style={{ margin: "auto" }} width="1.6em"></Trash>
+              </ButtonDiv>
+            </GridDiv>
+          )}
+          {props.title}
+        </Button>
+        <StyledTooltip id={`edit-${props.index}`}>
+          {props.editing
+            ? "Editing this range (with rest of file as context)"
+            : "Edit this range"}
+        </StyledTooltip>
+        <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+        {props.warning && (
+          <>
+            <CircleDiv data-tooltip-id={`circle-div-${props.title}`}>
+              <ExclamationTriangle
+                style={{ margin: "auto" }}
+                width="1.0em"
+                strokeWidth={2}
+              />
+            </CircleDiv>
+            <StyledTooltip id={`circle-div-${props.title}`}>
+              {props.warning}
             </StyledTooltip>
-            <ButtonDiv
-              data-tooltip-id={`delete-${props.index}`}
-              backgroundColor={"#cc000055"}
-              onClick={() => {
-                if (props.onDelete) {
-                  props.onDelete();
-                }
-              }}
-            >
-              <Trash style={{ margin: "auto" }} width="1.6em"></Trash>
-            </ButtonDiv>
-          </GridDiv>
+          </>
         )}
-        {props.title}
-      </Button>
-      <StyledTooltip id={`edit-${props.index}`}>
-        {props.editing
-          ? "Editing this range (with rest of file as context)"
-          : "Edit this range"}
-      </StyledTooltip>
-      <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+      </div>
     </>
   );
 };
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index 4ff260fa..57cebac3 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -95,11 +95,8 @@ function GUI(props: GUIProps) {
           name: "Welcome to Continue",
           hide: false,
           description: `- Highlight code and ask a question or give instructions
-- Use \`cmd+k\` (Mac) / \`ctrl+k\` (Windows) to open Continue
-- Use \`cmd+shift+e\` / \`ctrl+shift+e\` to open file Explorer
-- Add your own OpenAI API key to VS Code Settings with \`cmd+,\`
-- Use slash commands when you want fine-grained control
-- Past steps are included as part of the context by default`,
+          - Use \`cmd+m\` (Mac) / \`ctrl+m\` (Windows) to open Continue
+          - Use \`/help\` to ask questions about how to use Continue`,
           system_message: null,
           chat_context: [],
           manage_own_chat_context: false,
@@ -269,15 +266,17 @@ function GUI(props: GUIProps) {
   return (
     <>
       <Onboarding />
-      <TextDialog showDialog={showFeedbackDialog}
-      onEnter={(text) => {
-        client?.sendMainInput(`/feedback ${text}`);
-        setShowFeedbackDialog(false);
-      }}
-      onClose={() => {
-        setShowFeedbackDialog(false);
-      }}
-      message={feedbackDialogMessage} />
+      <TextDialog
+        showDialog={showFeedbackDialog}
+        onEnter={(text) => {
+          client?.sendMainInput(`/feedback ${text}`);
+          setShowFeedbackDialog(false);
+        }}
+        onClose={() => {
+          setShowFeedbackDialog(false);
+        }}
+        message={feedbackDialogMessage}
+      />
 
       <TopGUIDiv
         ref={topGuiDivRef}
diff --git a/extension/src/commands.ts b/extension/src/commands.ts
index 0b002549..0025340a 100644
--- a/extension/src/commands.ts
+++ b/extension/src/commands.ts
@@ -19,6 +19,8 @@ import { debugPanelWebview } from "./debugPanel";
 import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";
 import { ideProtocolClient } from "./activation/activate";
 
+let focusedOnContinueInput = false;
+
 // Copy everything over from extension.ts
 const commandsMap: { [command: string]: (...args: any) => any } = {
   "continue.suggestionDown": suggestionDownCommand,
@@ -30,10 +32,15 @@ const commandsMap: { [command: string]: (...args: any) => any } = {
   "continue.acceptAllSuggestions": acceptAllSuggestionsCommand,
   "continue.rejectAllSuggestions": rejectAllSuggestionsCommand,
   "continue.focusContinueInput": async () => {
-    vscode.commands.executeCommand("continue.continueGUIView.focus");
-    debugPanelWebview?.postMessage({
-      type: "focusContinueInput",
-    });
+    if (focusedOnContinueInput) {
+      vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup");
+    } else {
+      vscode.commands.executeCommand("continue.continueGUIView.focus");
+      debugPanelWebview?.postMessage({
+        type: "focusContinueInput",
+      });
+    }
+    focusedOnContinueInput = !focusedOnContinueInput;
   },
   "continue.quickTextEntry": async () => {
     const text = await vscode.window.showInputBox({
@@ -53,4 +60,4 @@ export function registerAllCommands(context: vscode.ExtensionContext) {
       vscode.commands.registerCommand(command, callback)
     );
   }
-}
\ No newline at end of file
+}
diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts
index 5800a00e..1cfef5d5 100644
--- a/extension/src/lang-server/codeLens.ts
+++ b/extension/src/lang-server/codeLens.ts
@@ -60,12 +60,12 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
       }
       codeLenses.push(
         new vscode.CodeLens(range, {
-          title: "Accept ✅ (⌘⇧↩)",
+          title: "Accept All ✅ (⌘⇧↩)",
           command: "continue.acceptDiff",
           arguments: [document.uri.fsPath],
         }),
         new vscode.CodeLens(range, {
-          title: "Reject ❌ (⌘⇧⌫)",
+          title: "Reject All ❌ (⌘⇧⌫)",
           command: "continue.rejectDiff",
           arguments: [document.uri.fsPath],
         })
-- 
cgit v1.2.3-70-g09d2


From 48e5c8001e897eb37493357087410ee8f98217fa 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/pages/gui.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 3a39b979c55b005d9bb18b88b43ca7293ee5410d Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sat, 15 Jul 2023 16:32:56 -0700
Subject: patch

---
 extension/package-lock.json                       |  4 ++--
 extension/package.json                            |  2 +-
 extension/react-app/src/components/TextDialog.tsx |  1 +
 extension/react-app/src/pages/gui.tsx             |  1 +
 extension/src/activation/environmentSetup.ts      | 19 ++++++++-----------
 5 files changed, 13 insertions(+), 14 deletions(-)

(limited to 'extension/react-app/src/pages/gui.tsx')

diff --git a/extension/package-lock.json b/extension/package-lock.json
index 0edd4885..e77bfac2 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.0.166",
+  "version": "0.0.167",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.0.166",
+      "version": "0.0.167",
       "license": "Apache-2.0",
       "dependencies": {
         "@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index 7cd7b793..bbd18b12 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.166",
+  "version": "0.0.167",
   "publisher": "Continue",
   "engines": {
     "vscode": "^1.67.0"
diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx
index c724697d..646d6846 100644
--- a/extension/react-app/src/components/TextDialog.tsx
+++ b/extension/react-app/src/components/TextDialog.tsx
@@ -2,6 +2,7 @@
 import React, { useEffect, useState } from "react";
 import styled from "styled-components";
 import { Button, buttonColor, secondaryDark, vscBackground } from ".";
+import { isMetaEquivalentKeyPressed } from "../util";
 
 const ScreenCover = styled.div`
   position: absolute;
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index cb0404ab..64207487 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -23,6 +23,7 @@ import { RootStore } from "../redux/store";
 import { postVscMessage } from "../vscode";
 import UserInputContainer from "../components/UserInputContainer";
 import Onboarding from "../components/Onboarding";
+import { isMetaEquivalentKeyPressed } from "../util";
 
 const TopGUIDiv = styled.div`
   overflow: hidden;
diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts
index 374c38c0..6a66532e 100644
--- a/extension/src/activation/environmentSetup.ts
+++ b/extension/src/activation/environmentSetup.ts
@@ -11,7 +11,8 @@ import * as os from "os";
 import fkill from "fkill";
 import { sendTelemetryEvent, TelemetryEvent } from "../telemetry";
 
-const WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR = "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. Please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again.";
+const WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR =
+  "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. In order to use Continue, please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again.";
 
 const MAX_RETRIES = 3;
 async function retryThenFail(
@@ -26,7 +27,8 @@ async function retryThenFail(
     }
 
     // Show corresponding error message depending on the platform
-    let msg = "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!";
+    let msg =
+      "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!";
     try {
       switch (process.platform) {
         case "win32":
@@ -35,14 +37,14 @@ async function retryThenFail(
         case "darwin":
           break;
         case "linux":
-          const [pythonCmd,] = await getPythonPipCommands();
+          const [pythonCmd] = await getPythonPipCommands();
           msg = await getLinuxAptInstallError(pythonCmd);
           break;
       }
     } finally {
       vscode.window.showErrorMessage(msg);
     }
-    
+
     sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, {
       error: e.message,
     });
@@ -216,10 +218,7 @@ async function getLinuxAptInstallError(pythonCmd: string) {
   const version = stdout.split(" ")[1].split(".")[1];
   const installVenvCommand = `apt-get install python3.${version}-venv`;
   await runCommand("apt-get update");
-  // Ask the user to run the command to install python3-venv (requires sudo, so we can't)
-  // First, get the python version
-  const msg = `[Important] Continue needs to create a Python virtual environment, but python3.${version}-venv is not installed. Please run this command in your terminal: \`${installVenvCommand}\`, reload VS Code, and then try again.`;
-  return msg;
+  return `[Important] Continue needs to create a Python virtual environment, but python3.${version}-venv is not installed. Please run this command in your terminal: \`${installVenvCommand}\`, reload VS Code, and then try again.`;
 }
 
 async function setupPythonEnv() {
@@ -246,9 +245,7 @@ async function setupPythonEnv() {
       stderr &&
       stderr.includes("running scripts is disabled on this system")
     ) {
-      await vscode.window.showErrorMessage(
-        WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR
-      );
+      await vscode.window.showErrorMessage(WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR);
       throw new Error(stderr);
     } else if (
       stderr?.includes("On Debian/Ubuntu systems") ||
-- 
cgit v1.2.3-70-g09d2

