From f19345c652cfcf1bdf13d0a44a2f302e0cd1aa4c Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sun, 6 Aug 2023 09:28:22 -0700
Subject: feat: :construction: Router and new history page

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

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

diff --git a/extension/react-app/src/pages/history.tsx b/extension/react-app/src/pages/history.tsx
new file mode 100644
index 00000000..6539f0f5
--- /dev/null
+++ b/extension/react-app/src/pages/history.tsx
@@ -0,0 +1,55 @@
+import React, { useContext, useEffect, useState } from "react";
+import { SessionInfo } from "../../../schema/SessionInfo";
+import { GUIClientContext } from "../App";
+import fetch from "node-fetch";
+import { useSelector } from "react-redux";
+import { RootStore } from "../redux/store";
+
+function History() {
+  const [sessions, setSessions] = useState<SessionInfo[]>([]);
+  const client = useContext(GUIClientContext);
+  const apiUrl = useSelector((state: RootStore) => state.config.apiUrl);
+
+  useEffect(() => {
+    const fetchSessions = async () => {
+      console.log("fetching sessions");
+      if (!apiUrl) {
+        return;
+      }
+      const response = await fetch(`${apiUrl}/sessions/list`);
+      const json = await response.json();
+      console.log(json);
+      setSessions(json);
+    };
+    fetchSessions();
+  }, [client]);
+
+  return (
+    <div style={{ width: "100%" }}>
+      <table style={{ width: "100%" }}>
+        <tbody>
+          {sessions.map((session, index) => (
+            <tr key={index}>
+              <td>
+                <div
+                  style={{ cursor: "pointer" }}
+                  onClick={() => {
+                    // client?.loadSession(session.id);
+                    // document.location.href = "/gui";
+                  }}
+                >
+                  <div>{session.title}</div>
+                  <div style={{ color: "lightgray" }}>
+                    {session.date_created}
+                  </div>
+                </div>
+              </td>
+            </tr>
+          ))}
+        </tbody>
+      </table>
+    </div>
+  );
+}
+
+export default History;
-- 
cgit v1.2.3-70-g09d2


From 31e7c9828f985eceb16b4c9c749cc5d4d9fd5beb Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sun, 6 Aug 2023 13:28:22 -0700
Subject: feat: :construction: react-router-dom work

---
 .../src/continuedev/server/session_manager.py      |   4 +
 extension/react-app/package-lock.json              | 219 ++++-----------------
 extension/react-app/package.json                   |   2 +-
 extension/react-app/src/App.tsx                    |  36 ++--
 extension/react-app/src/components/Layout.tsx      |   1 +
 extension/react-app/src/main.tsx                   |  10 +-
 extension/react-app/src/pages/error.tsx            |  17 ++
 extension/react-app/src/pages/gui.tsx              |   5 +-
 extension/react-app/src/pages/history.tsx          |  23 ++-
 9 files changed, 108 insertions(+), 209 deletions(-)
 create mode 100644 extension/react-app/src/pages/error.tsx

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

diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index dade9853..f876c9a9 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -159,5 +159,9 @@ session_manager = SessionManager()
 async def list_sessions():
     """List all sessions"""
     sessions_list_file = getSessionsListFilePath()
+    if not os.path.exists(sessions_list_file):
+        print("Returning empty sessions list")
+        return []
     sessions = json.load(open(sessions_list_file, "r"))
+    print("Returning sessions list: ", sessions)
     return sessions
diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json
index b5e32316..b057fe20 100644
--- a/extension/react-app/package-lock.json
+++ b/extension/react-app/package-lock.json
@@ -18,7 +18,7 @@
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
         "react-redux": "^8.0.5",
-        "react-router-dom": "^5.2.0",
+        "react-router-dom": "^6.14.2",
         "react-switch": "^7.0.0",
         "react-syntax-highlighter": "^15.5.0",
         "react-tooltip": "^5.18.0",
@@ -872,6 +872,14 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@remix-run/router": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz",
+      "integrity": "sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==",
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/@swc/core": {
       "version": "1.3.73",
       "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.73.tgz",
@@ -2250,19 +2258,6 @@
         "node": "*"
       }
     },
-    "node_modules/history": {
-      "version": "4.10.1",
-      "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
-      "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
-      "dependencies": {
-        "@babel/runtime": "^7.1.2",
-        "loose-envify": "^1.2.0",
-        "resolve-pathname": "^3.0.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0",
-        "value-equal": "^1.0.1"
-      }
-    },
     "node_modules/hoist-non-react-statics": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -2433,11 +2428,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/isarray": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
-    },
     "node_modules/jiti": {
       "version": "1.19.1",
       "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
@@ -3513,14 +3503,6 @@
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
       "dev": true
     },
-    "node_modules/path-to-regexp": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
-      "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
-      "dependencies": {
-        "isarray": "0.0.1"
-      }
-    },
     "node_modules/picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -3842,61 +3824,35 @@
       }
     },
     "node_modules/react-router": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
-      "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
-      "dependencies": {
-        "@babel/runtime": "^7.1.2",
-        "history": "^4.9.0",
-        "hoist-non-react-statics": "^3.1.0",
-        "loose-envify": "^1.3.1",
-        "mini-create-react-context": "^0.4.0",
-        "path-to-regexp": "^1.7.0",
-        "prop-types": "^15.6.2",
-        "react-is": "^16.6.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0"
+      "version": "6.14.2",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz",
+      "integrity": "sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==",
+      "dependencies": {
+        "@remix-run/router": "1.7.2"
+      },
+      "engines": {
+        "node": ">=14"
       },
       "peerDependencies": {
-        "react": ">=15"
+        "react": ">=16.8"
       }
     },
     "node_modules/react-router-dom": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
-      "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
+      "version": "6.14.2",
+      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.2.tgz",
+      "integrity": "sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==",
       "dependencies": {
-        "@babel/runtime": "^7.1.2",
-        "history": "^4.9.0",
-        "loose-envify": "^1.3.1",
-        "prop-types": "^15.6.2",
-        "react-router": "5.2.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0"
+        "@remix-run/router": "1.7.2",
+        "react-router": "6.14.2"
       },
-      "peerDependencies": {
-        "react": ">=15"
-      }
-    },
-    "node_modules/react-router/node_modules/mini-create-react-context": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
-      "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
-      "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
-      "dependencies": {
-        "@babel/runtime": "^7.12.1",
-        "tiny-warning": "^1.0.3"
+      "engines": {
+        "node": ">=14"
       },
       "peerDependencies": {
-        "prop-types": "^15.0.0",
-        "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+        "react": ">=16.8",
+        "react-dom": ">=16.8"
       }
     },
-    "node_modules/react-router/node_modules/react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
-    },
     "node_modules/react-switch": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/react-switch/-/react-switch-7.0.0.tgz",
@@ -4291,11 +4247,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/resolve-pathname": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
-      "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
-    },
     "node_modules/reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -4536,16 +4487,6 @@
         "node": ">=0.8"
       }
     },
-    "node_modules/tiny-invariant": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
-      "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
-    },
-    "node_modules/tiny-warning": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
-      "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
-    },
     "node_modules/to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -4781,11 +4722,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/value-equal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
-      "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
-    },
     "node_modules/vfile": {
       "version": "5.3.7",
       "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz",
@@ -5470,6 +5406,11 @@
         "fastq": "^1.6.0"
       }
     },
+    "@remix-run/router": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz",
+      "integrity": "sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A=="
+    },
     "@swc/core": {
       "version": "1.3.73",
       "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.73.tgz",
@@ -6446,19 +6387,6 @@
       "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
       "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="
     },
-    "history": {
-      "version": "4.10.1",
-      "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
-      "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
-      "requires": {
-        "@babel/runtime": "^7.1.2",
-        "loose-envify": "^1.2.0",
-        "resolve-pathname": "^3.0.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0",
-        "value-equal": "^1.0.1"
-      }
-    },
     "hoist-non-react-statics": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -6573,11 +6501,6 @@
       "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
       "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="
     },
-    "isarray": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
-    },
     "jiti": {
       "version": "1.19.1",
       "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
@@ -7268,14 +7191,6 @@
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
       "dev": true
     },
-    "path-to-regexp": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
-      "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
-      "requires": {
-        "isarray": "0.0.1"
-      }
-    },
     "picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -7462,50 +7377,20 @@
       }
     },
     "react-router": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
-      "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
-      "requires": {
-        "@babel/runtime": "^7.1.2",
-        "history": "^4.9.0",
-        "hoist-non-react-statics": "^3.1.0",
-        "loose-envify": "^1.3.1",
-        "mini-create-react-context": "^0.4.0",
-        "path-to-regexp": "^1.7.0",
-        "prop-types": "^15.6.2",
-        "react-is": "^16.6.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0"
-      },
-      "dependencies": {
-        "mini-create-react-context": {
-          "version": "0.4.1",
-          "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
-          "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
-          "requires": {
-            "@babel/runtime": "^7.12.1",
-            "tiny-warning": "^1.0.3"
-          }
-        },
-        "react-is": {
-          "version": "16.13.1",
-          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
-        }
+      "version": "6.14.2",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz",
+      "integrity": "sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==",
+      "requires": {
+        "@remix-run/router": "1.7.2"
       }
     },
     "react-router-dom": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
-      "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
+      "version": "6.14.2",
+      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.2.tgz",
+      "integrity": "sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==",
       "requires": {
-        "@babel/runtime": "^7.1.2",
-        "history": "^4.9.0",
-        "loose-envify": "^1.3.1",
-        "prop-types": "^15.6.2",
-        "react-router": "5.2.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0"
+        "@remix-run/router": "1.7.2",
+        "react-router": "6.14.2"
       }
     },
     "react-switch": {
@@ -7798,11 +7683,6 @@
         "supports-preserve-symlinks-flag": "^1.0.0"
       }
     },
-    "resolve-pathname": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
-      "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
-    },
     "reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -7967,16 +7847,6 @@
         "thenify": ">= 3.1.0 < 4"
       }
     },
-    "tiny-invariant": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
-      "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
-    },
-    "tiny-warning": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
-      "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
-    },
     "to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -8132,11 +8002,6 @@
         "sade": "^1.7.3"
       }
     },
-    "value-equal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
-      "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
-    },
     "vfile": {
       "version": "5.3.7",
       "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz",
diff --git a/extension/react-app/package.json b/extension/react-app/package.json
index 5eb58c8a..b4762990 100644
--- a/extension/react-app/package.json
+++ b/extension/react-app/package.json
@@ -19,7 +19,7 @@
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-redux": "^8.0.5",
-    "react-router-dom": "^5.2.0",
+    "react-router-dom": "^6.14.2",
     "react-switch": "^7.0.0",
     "react-syntax-highlighter": "^15.5.0",
     "react-tooltip": "^5.18.0",
diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx
index 15b536db..48ecebaa 100644
--- a/extension/react-app/src/App.tsx
+++ b/extension/react-app/src/App.tsx
@@ -4,12 +4,6 @@ import Layout from "./components/Layout";
 import { createContext, useEffect } from "react";
 import useContinueGUIProtocol from "./hooks/useWebsocket";
 import ContinueGUIClientProtocol from "./hooks/ContinueGUIClientProtocol";
-import {
-  BrowserRouter as Router,
-  Route,
-  Routes,
-  BrowserRouter,
-} from "react-router-dom";
 import { useDispatch } from "react-redux";
 import {
   setApiUrl,
@@ -21,6 +15,30 @@ import {
 import { updateFileSystem } from "./redux/slices/debugContexSlice";
 import { setHighlightedCode } from "./redux/slices/miscSlice";
 import { postVscMessage } from "./vscode";
+import { createBrowserRouter, RouterProvider } from "react-router-dom";
+import ErrorPage from "./pages/error";
+
+const router = createBrowserRouter([
+  {
+    path: "/",
+    element: <Layout />,
+    errorElement: <ErrorPage />,
+    children: [
+      {
+        path: "/index.html",
+        element: <GUI />,
+      },
+      {
+        path: "/",
+        element: <GUI />,
+      },
+      {
+        path: "/history",
+        element: <History />,
+      },
+    ],
+  },
+]);
 
 export const GUIClientContext = createContext<
   ContinueGUIClientProtocol | undefined
@@ -53,11 +71,7 @@ function App() {
 
   return (
     <GUIClientContext.Provider value={client}>
-      <Routes>
-        <Route path="/" element={<Layout />} />
-        <Route path="/gui" element={<GUI />} />
-        <Route path="/history" element={<History />} />
-      </Routes>
+      <RouterProvider router={router} />
     </GUIClientContext.Provider>
   );
 }
diff --git a/extension/react-app/src/components/Layout.tsx b/extension/react-app/src/components/Layout.tsx
index d1688299..47ed9cc3 100644
--- a/extension/react-app/src/components/Layout.tsx
+++ b/extension/react-app/src/components/Layout.tsx
@@ -8,6 +8,7 @@ const LayoutTopDiv = styled.div`
   scrollbar-base-color: transparent;
   scrollbar-width: thin;
 `;
+
 const Layout = () => {
   return (
     <LayoutTopDiv>
diff --git a/extension/react-app/src/main.tsx b/extension/react-app/src/main.tsx
index 1b70e786..1776490c 100644
--- a/extension/react-app/src/main.tsx
+++ b/extension/react-app/src/main.tsx
@@ -7,14 +7,6 @@ import "./index.css";
 
 import posthog from "posthog-js";
 import { PostHogProvider } from "posthog-js/react";
-import { createBrowserRouter, RouterProvider } from "react-router-dom";
-
-const router = createBrowserRouter([
-  {
-    path: "/",
-    element: <App />,
-  },
-]);
 
 console.log("Starting React");
 
@@ -27,7 +19,7 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
   <React.StrictMode>
     <PostHogProvider client={posthog}>
       <Provider store={store}>
-        <RouterProvider router={router} />
+        <App />
       </Provider>
     </PostHogProvider>
   </React.StrictMode>
diff --git a/extension/react-app/src/pages/error.tsx b/extension/react-app/src/pages/error.tsx
new file mode 100644
index 00000000..5267c405
--- /dev/null
+++ b/extension/react-app/src/pages/error.tsx
@@ -0,0 +1,17 @@
+import { useRouteError } from "react-router-dom";
+
+export default function ErrorPage() {
+  const error: any = useRouteError();
+  console.error(error);
+
+  return (
+    <div id="error-page" className="text-center">
+      <h1>Error in Continue React App</h1>
+      <p>
+        <i>{error.statusText || error.message}</i>
+      </p>
+      <br />
+      <pre className="text-left m-4">{error.stack}</pre>
+    </div>
+  );
+}
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index 247789d6..d565e64f 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -34,6 +34,7 @@ import {
   setBottomMessageCloseTimeout,
 } from "../redux/slices/uiStateSlice";
 import RingLoader from "../components/RingLoader";
+import { useNavigate } from "react-router-dom";
 
 const TopGUIDiv = styled.div`
   overflow: hidden;
@@ -83,6 +84,8 @@ interface GUIProps {
 }
 
 function GUI(props: GUIProps) {
+  const navigate = useNavigate();
+
   const client = useContext(GUIClientContext);
   const posthog = usePostHog();
 
@@ -595,7 +598,7 @@ If you already have an LLM deployed on your own infrastructure, or would like to
         <HeaderButtonWithText
           onClick={() => {
             // Go to /history page
-            document.location.href = "/history";
+            navigate("/history");
           }}
           text="History"
         >
diff --git a/extension/react-app/src/pages/history.tsx b/extension/react-app/src/pages/history.tsx
index 6539f0f5..052fe5be 100644
--- a/extension/react-app/src/pages/history.tsx
+++ b/extension/react-app/src/pages/history.tsx
@@ -1,22 +1,26 @@
 import React, { useContext, useEffect, useState } from "react";
 import { SessionInfo } from "../../../schema/SessionInfo";
 import { GUIClientContext } from "../App";
-import fetch from "node-fetch";
 import { useSelector } from "react-redux";
 import { RootStore } from "../redux/store";
+import { useNavigate } from "react-router-dom";
 
 function History() {
+  const navigate = useNavigate();
   const [sessions, setSessions] = useState<SessionInfo[]>([]);
   const client = useContext(GUIClientContext);
   const apiUrl = useSelector((state: RootStore) => state.config.apiUrl);
 
   useEffect(() => {
     const fetchSessions = async () => {
-      console.log("fetching sessions");
+      console.log("fetching sessions from: ", apiUrl);
       if (!apiUrl) {
         return;
       }
       const response = await fetch(`${apiUrl}/sessions/list`);
+      if (!response.ok) {
+        throw new Error(`HTTP error! status: ${response.status}`);
+      }
       const json = await response.json();
       console.log(json);
       setSessions(json);
@@ -25,23 +29,22 @@ function History() {
   }, [client]);
 
   return (
-    <div style={{ width: "100%" }}>
-      <table style={{ width: "100%" }}>
+    <div className="w-full">
+      <h1 className="text-2xl font-bold">History</h1>
+      <table className="w-full">
         <tbody>
           {sessions.map((session, index) => (
             <tr key={index}>
               <td>
                 <div
-                  style={{ cursor: "pointer" }}
+                  className="cursor-pointer"
                   onClick={() => {
                     // client?.loadSession(session.id);
-                    // document.location.href = "/gui";
+                    navigate("/");
                   }}
                 >
-                  <div>{session.title}</div>
-                  <div style={{ color: "lightgray" }}>
-                    {session.date_created}
-                  </div>
+                  <div className="text-lg">{session.title}</div>
+                  <div className="text-gray-400">{session.date_created}</div>
                 </div>
               </td>
             </tr>
-- 
cgit v1.2.3-70-g09d2


From c25527926ad1d1f861dbed01df577e962e08d746 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sun, 6 Aug 2023 15:24:13 -0700
Subject: feat: :construction: successfully loading past sessions

---
 continuedev/src/continuedev/core/autopilot.py      | 21 ++++++++++++---
 continuedev/src/continuedev/core/main.py           | 15 ++++++-----
 continuedev/src/continuedev/libs/util/paths.py     |  3 +++
 continuedev/src/continuedev/libs/util/telemetry.py |  1 -
 continuedev/src/continuedev/server/gui.py          | 11 ++++++--
 .../src/continuedev/server/session_manager.py      | 27 ++++++++-----------
 .../src/hooks/AbstractContinueGUIClientProtocol.ts |  2 ++
 .../src/hooks/ContinueGUIClientProtocol.ts         | 13 ++++++----
 extension/react-app/src/hooks/messenger.ts         |  6 +++++
 extension/react-app/src/hooks/vscodeMessenger.ts   |  4 +++
 extension/react-app/src/pages/history.tsx          | 30 +++++++++++++++++-----
 .../react-app/src/redux/slices/configSlice.ts      |  4 +--
 extension/src/debugPanel.ts                        |  9 +++++++
 extension/src/util/messenger.ts                    |  6 +++++
 14 files changed, 108 insertions(+), 44 deletions(-)

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

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 7e7ce5d8..6dd30db1 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -15,7 +15,7 @@ from ..plugins.context_providers.highlighted_code import HighlightedCodeContextP
 from ..server.ide_protocol import AbstractIdeProtocolServer
 from ..libs.util.queue import AsyncSubscriptionQueue
 from ..models.main import ContinueBaseModel
-from .main import Context, ContinueCustomException, Policy, History, FullState, Step, HistoryNode
+from .main import Context, ContinueCustomException, Policy, History, FullState, SessionInfo, Step, HistoryNode
 from ..plugins.steps.core.core import DisplayErrorStep, ReversibleStep, ManualEditStep, UserInputStep
 from .sdk import ContinueSDK
 from ..libs.util.traceback_parsers import get_python_traceback, get_javascript_traceback
@@ -53,7 +53,8 @@ class Autopilot(ContinueBaseModel):
     policy: Policy = DefaultPolicy()
     history: History = History.from_empty()
     context: Context = Context()
-    full_state: Union[FullState, None] = None
+    full_state: Optional[FullState] = None
+    session_info: Optional[SessionInfo] = None
     context_manager: ContextManager = ContextManager()
     continue_sdk: ContinueSDK = None
 
@@ -88,7 +89,6 @@ class Autopilot(ContinueBaseModel):
         if full_state is not None:
             self.history = full_state.history
             self.context_manager.context_providers["code"].adding_highlighted_code = full_state.adding_highlighted_code
-            await self.context_manager.set_selected_items(full_state.selected_context_items)
 
         self.started = True
 
@@ -112,6 +112,7 @@ class Autopilot(ContinueBaseModel):
             adding_highlighted_code=self.context_manager.context_providers[
                 "code"].adding_highlighted_code if "code" in self.context_manager.context_providers else False,
             selected_context_items=await self.context_manager.get_selected_items() if self.context_manager is not None else [],
+            session_info=self.session_info
         )
         self.full_state = full_state
         return full_state
@@ -375,6 +376,20 @@ class Autopilot(ContinueBaseModel):
         self._main_user_input_queue.append(user_input)
         await self.update_subscribers()
 
+        # Use the first input to create title for session info, and make the session saveable
+        if self.session_info is None:
+            async def create_title():
+                title = await self.continue_sdk.models.medium.complete(f"Give a short title to describe the current chat session. Do not put quotes around the title. The first message was: \"{user_input}\". The title is: ")
+                self.session_info = SessionInfo(
+                    title=title,
+                    session_id=self.ide.session_id,
+                    date_created=time.strftime(
+                        "%Y-%m-%d %H:%M:%S", time.gmtime())
+                )
+
+            create_async_task(create_title(), on_error=lambda e: self.continue_sdk.run_step(
+                DisplayErrorStep(e=e)))
+
         if len(self._main_user_input_queue) > 1:
             return
 
diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py
index 9a06f2e1..a33d777e 100644
--- a/continuedev/src/continuedev/core/main.py
+++ b/continuedev/src/continuedev/core/main.py
@@ -1,5 +1,5 @@
 import json
-from typing import Coroutine, Dict, List, Literal, Union
+from typing import Coroutine, Dict, List, Literal, Optional, Union
 from pydantic.schema import schema
 
 
@@ -253,6 +253,12 @@ class ContextItem(BaseModel):
     editable: bool = False
 
 
+class SessionInfo(ContinueBaseModel):
+    session_id: str
+    title: str
+    date_created: str
+
+
 class FullState(ContinueBaseModel):
     """A full state of the program, including the history"""
     history: History
@@ -261,12 +267,7 @@ class FullState(ContinueBaseModel):
     slash_commands: List[SlashCommandDescription]
     adding_highlighted_code: bool
     selected_context_items: List[ContextItem]
-
-
-class SessionInfo(ContinueBaseModel):
-    session_id: str
-    title: str
-    date_created: str
+    session_info: Optional[SessionInfo] = None
 
 
 class ContinueSDK:
diff --git a/continuedev/src/continuedev/libs/util/paths.py b/continuedev/src/continuedev/libs/util/paths.py
index 66c921f7..01b594cf 100644
--- a/continuedev/src/continuedev/libs/util/paths.py
+++ b/continuedev/src/continuedev/libs/util/paths.py
@@ -35,6 +35,9 @@ def getSessionFilePath(session_id: str):
 def getSessionsListFilePath():
     path = os.path.join(getSessionsFolderPath(), "sessions.json")
     os.makedirs(os.path.dirname(path), exist_ok=True)
+    if not os.path.exists(path):
+        with open(path, 'w') as f:
+            f.write("[]")
     return path
 
 
diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py
index 60c910bb..0f66ad8d 100644
--- a/continuedev/src/continuedev/libs/util/telemetry.py
+++ b/continuedev/src/continuedev/libs/util/telemetry.py
@@ -23,7 +23,6 @@ class PostHogLogger:
         self.posthog = Posthog(self.api_key, host='https://app.posthog.com')
 
     def setup(self, unique_id: str, allow_anonymous_telemetry: bool):
-        logger.debug(f"Setting unique_id as {unique_id}")
         self.unique_id = unique_id or "NO_UNIQUE_ID"
         self.allow_anonymous_telemetry = allow_anonymous_telemetry or True
 
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index b6f7b141..661e1787 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -99,6 +99,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
             self.on_show_logs_at_index(data["index"])
         elif message_type == "select_context_item":
             self.select_context_item(data["id"], data["query"])
+        elif message_type == "load_session":
+            self.load_session(data["session_id"])
 
     def on_main_input(self, input: str):
         # Do something with user input
@@ -154,8 +156,13 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
         create_async_task(
             self.session.autopilot.select_context_item(id, query), self.on_error)
 
-    async def reconnect_at_session(self, session_id: str):
-        await self._send_json("reconnect_at_session", {"session_id": session_id})
+    def load_session(self, session_id: str):
+        async def load_and_tell_to_reconnect():
+            await session_manager.load_session(self.session.session_id, session_id)
+            await self._send_json("reconnect_at_session", {"session_id": session_id})
+
+        create_async_task(
+            load_and_tell_to_reconnect(), self.on_error)
 
 
 @router.websocket("/ws")
diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index f876c9a9..062f9527 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -78,18 +78,7 @@ class SessionManager:
         try:
             await autopilot.start(full_state=full_state)
         except Exception as e:
-            # Have to manually add to history because autopilot isn't started
-            formatted_err = '\n'.join(traceback.format_exception(e))
-            msg_step = MessageStep(
-                name="Error starting Autopilot", message=formatted_err)
-            msg_step.description = f"```\n{formatted_err}\n```"
-            autopilot.history.add_node(HistoryNode(
-                step=msg_step,
-                observation=None,
-                depth=0,
-                active=False
-            ))
-            logger.warning(f"Error starting Autopilot: {e}")
+            await self.on_error(e)
 
         def on_error(e: Exception) -> Coroutine:
             err_msg = '\n'.join(traceback.format_exception(e))
@@ -101,7 +90,7 @@ class SessionManager:
     async def remove_session(self, session_id: str):
         logger.debug(f"Removing session: {session_id}")
         if session_id in self.sessions:
-            if session_id in self.registered_ides:
+            if session_id in self.registered_ides and self.registered_ides[session_id] is not None:
                 ws_to_close = self.registered_ides[session_id].websocket
                 if ws_to_close is not None and ws_to_close.client_state != WebSocketState.DISCONNECTED:
                     await self.sessions[session_id].autopilot.ide.websocket.close()
@@ -111,6 +100,9 @@ class SessionManager:
     async def persist_session(self, session_id: str):
         """Save the session's FullState as a json file"""
         full_state = await self.sessions[session_id].autopilot.get_full_state()
+        if full_state.session_info is None:
+            return
+
         with open(getSessionFilePath(session_id), "w") as f:
             json.dump(full_state.dict(), f)
 
@@ -118,9 +110,10 @@ class SessionManager:
         with open(getSessionsListFilePath(), "r") as f:
             sessions_list = json.load(f)
 
-        sessions_list.append(SessionInfo(
-            session_info=full_state.session_info
-        ))
+        sessions_list.append(full_state.session_info.dict())
+
+        with open(getSessionsListFilePath(), "w") as f:
+            json.dump(sessions_list, f)
 
     async def load_session(self, old_session_id: str, new_session_id: str):
         """Load the session's FullState from a json file"""
@@ -130,7 +123,7 @@ class SessionManager:
 
         # Delete the old session, but keep the IDE
         ide = self.registered_ides[old_session_id]
-        self.registered_ides[old_session_id] = None
+        del self.registered_ides[old_session_id]
 
         # Start the new session
         await self.new_session(ide, session_id=new_session_id)
diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
index 168fb156..139c9d05 100644
--- a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
@@ -31,6 +31,8 @@ abstract class AbstractContinueGUIClientProtocol {
 
   abstract selectContextItem(id: string, query: string): void;
 
+  abstract loadSession(session_id: string): void;
+
   abstract onReconnectAtSession(session_id: string): void;
 }
 
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index 830954c5..6cfbf66a 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -14,9 +14,11 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
     useVscodeMessagePassing: boolean
   ) {
     if (this.messenger) {
-      // this.messenger.close(); TODO
+      console.log("Closing session: ", this.serverUrlWithSessionId);
+      this.messenger.close();
     }
     this.serverUrlWithSessionId = serverUrlWithSessionId;
+    this.useVscodeMessagePassing = useVscodeMessagePassing;
     this.messenger = useVscodeMessagePassing
       ? new VscodeMessenger(serverUrlWithSessionId)
       : new WebsocketMessenger(serverUrlWithSessionId);
@@ -45,12 +47,13 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
     this.connectMessenger(serverUrlWithSessionId, useVscodeMessagePassing);
   }
 
+  loadSession(session_id: string): void {
+    this.messenger?.send("load_session", { session_id });
+  }
+
   onReconnectAtSession(session_id: string): void {
     this.connectMessenger(
-      this.serverUrlWithSessionId.replace(
-        /\/session\/[a-zA-Z0-9-]+/,
-        `/session/${session_id}`
-      ),
+      `${this.serverUrlWithSessionId.split("?")[0]}?session_id=${session_id}`,
       this.useVscodeMessagePassing
     );
   }
diff --git a/extension/react-app/src/hooks/messenger.ts b/extension/react-app/src/hooks/messenger.ts
index ecf646c7..0bfbe00c 100644
--- a/extension/react-app/src/hooks/messenger.ts
+++ b/extension/react-app/src/hooks/messenger.ts
@@ -15,6 +15,8 @@ export abstract class Messenger {
   abstract sendAndReceive(messageType: string, data: any): Promise<any>;
 
   abstract onError(callback: (error: any) => void): void;
+
+  abstract close(): void;
 }
 
 export class WebsocketMessenger extends Messenger {
@@ -105,4 +107,8 @@ export class WebsocketMessenger extends Messenger {
   onError(callback: (error: any) => void): void {
     this.websocket.addEventListener("error", callback);
   }
+
+  close(): void {
+    this.websocket.close();
+  }
 }
diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts
index 13f5092b..cf626721 100644
--- a/extension/react-app/src/hooks/vscodeMessenger.ts
+++ b/extension/react-app/src/hooks/vscodeMessenger.ts
@@ -76,4 +76,8 @@ export class VscodeMessenger extends Messenger {
       }
     });
   }
+
+  close(): void {
+    postVscMessage("websocketForwardingClose", { url: this.serverUrl });
+  }
 }
diff --git a/extension/react-app/src/pages/history.tsx b/extension/react-app/src/pages/history.tsx
index 052fe5be..0142836f 100644
--- a/extension/react-app/src/pages/history.tsx
+++ b/extension/react-app/src/pages/history.tsx
@@ -4,6 +4,23 @@ import { GUIClientContext } from "../App";
 import { useSelector } from "react-redux";
 import { RootStore } from "../redux/store";
 import { useNavigate } from "react-router-dom";
+import { secondaryDark, vscBackground } from "../components";
+import styled from "styled-components";
+
+const Tr = styled.tr`
+  &:hover {
+    background-color: ${secondaryDark};
+  }
+`;
+
+const TdDiv = styled.div`
+  cursor: pointer;
+  padding-left: 1rem;
+  padding-right: 1rem;
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+  border-bottom: 1px solid ${secondaryDark};
+`;
 
 function History() {
   const navigate = useNavigate();
@@ -30,24 +47,23 @@ function History() {
 
   return (
     <div className="w-full">
-      <h1 className="text-2xl font-bold">History</h1>
+      <h1 className="text-2xl font-bold m-4">History</h1>
       <table className="w-full">
         <tbody>
           {sessions.map((session, index) => (
-            <tr key={index}>
+            <Tr key={index}>
               <td>
-                <div
-                  className="cursor-pointer"
+                <TdDiv
                   onClick={() => {
-                    // client?.loadSession(session.id);
+                    client?.loadSession(session.session_id);
                     navigate("/");
                   }}
                 >
                   <div className="text-lg">{session.title}</div>
                   <div className="text-gray-400">{session.date_created}</div>
-                </div>
+                </TdDiv>
               </td>
-            </tr>
+            </Tr>
           ))}
         </tbody>
       </table>
diff --git a/extension/react-app/src/redux/slices/configSlice.ts b/extension/react-app/src/redux/slices/configSlice.ts
index 57c4f860..59c76066 100644
--- a/extension/react-app/src/redux/slices/configSlice.ts
+++ b/extension/react-app/src/redux/slices/configSlice.ts
@@ -50,7 +50,7 @@ export const configSlice = createSlice({
     ) => ({
       ...state,
       dataSwitchOn: action.payload,
-    })
+    }),
   },
 });
 
@@ -60,6 +60,6 @@ export const {
   setWorkspacePath,
   setSessionId,
   setVscMediaUrl,
-  setDataSwitchOn
+  setDataSwitchOn,
 } = configSlice.actions;
 export default configSlice.reducer;
diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts
index b687c3e4..d133080b 100644
--- a/extension/src/debugPanel.ts
+++ b/extension/src/debugPanel.ts
@@ -221,6 +221,15 @@ export function setupDebugPanel(
         }
         break;
       }
+      case "websocketForwardingClose": {
+        let url = data.url;
+        let connection = websocketConnections[url];
+        if (typeof connection !== "undefined") {
+          connection.close();
+          websocketConnections[url] = undefined;
+        }
+        break;
+      }
       case "websocketForwardingMessage": {
         let url = data.url;
         let connection = websocketConnections[url];
diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts
index bcc88fe1..152d4a1f 100644
--- a/extension/src/util/messenger.ts
+++ b/extension/src/util/messenger.ts
@@ -18,6 +18,8 @@ export abstract class Messenger {
   abstract onError(callback: () => void): void;
 
   abstract sendAndReceive(messageType: string, data: any): Promise<any>;
+
+  abstract close(): void;
 }
 
 export class WebsocketMessenger extends Messenger {
@@ -160,4 +162,8 @@ export class WebsocketMessenger extends Messenger {
   onError(callback: () => void): void {
     this.websocket.addEventListener("error", callback);
   }
+
+  close(): void {
+    this.websocket.close();
+  }
 }
-- 
cgit v1.2.3-70-g09d2


From aafa5d5ec91a533f70d644e4d3fadd6f388c3e4b Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sun, 6 Aug 2023 16:23:19 -0700
Subject: feat: :sparkles: back button on history page

---
 extension/react-app/src/pages/history.tsx | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

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

diff --git a/extension/react-app/src/pages/history.tsx b/extension/react-app/src/pages/history.tsx
index 0142836f..f925d52c 100644
--- a/extension/react-app/src/pages/history.tsx
+++ b/extension/react-app/src/pages/history.tsx
@@ -6,6 +6,7 @@ import { RootStore } from "../redux/store";
 import { useNavigate } from "react-router-dom";
 import { secondaryDark, vscBackground } from "../components";
 import styled from "styled-components";
+import { ArrowLeftIcon } from "@heroicons/react/24/outline";
 
 const Tr = styled.tr`
   &:hover {
@@ -47,7 +48,15 @@ function History() {
 
   return (
     <div className="w-full">
-      <h1 className="text-2xl font-bold m-4">History</h1>
+      <div className="items-center flex">
+        <ArrowLeftIcon
+          width="1.4em"
+          height="1.4em"
+          onClick={() => navigate("/")}
+          className="inline-block ml-4 cursor-pointer"
+        />
+        <h1 className="text-2xl font-bold m-4 inline-block">History</h1>
+      </div>
       <table className="w-full">
         <tbody>
           {sessions.map((session, index) => (
-- 
cgit v1.2.3-70-g09d2


From 6bce4b2802d06818f33b260ca4353331ea4b2df9 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sun, 6 Aug 2023 22:51:10 -0700
Subject: refactor: :recycle: hoist FullState up to redux

---
 continuedev/src/continuedev/core/autopilot.py      |   5 +-
 extension/react-app/src/App.tsx                    |   2 -
 extension/react-app/src/pages/gui.tsx              |  75 +++++------
 extension/react-app/src/pages/history.tsx          |  13 +-
 extension/react-app/src/redux/hooks.ts             |  21 ---
 .../src/redux/selectors/debugContextSelectors.ts   |  29 ----
 .../react-app/src/redux/slices/debugContexSlice.ts | 149 ---------------------
 .../src/redux/slices/serverStateReducer.ts         |  53 ++++++++
 extension/react-app/src/redux/store.ts             |  12 +-
 9 files changed, 105 insertions(+), 254 deletions(-)
 delete mode 100644 extension/react-app/src/redux/hooks.ts
 delete mode 100644 extension/react-app/src/redux/selectors/debugContextSelectors.ts
 delete mode 100644 extension/react-app/src/redux/slices/debugContexSlice.ts
 create mode 100644 extension/react-app/src/redux/slices/serverStateReducer.ts

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

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index ee29dc88..256f3439 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -5,6 +5,7 @@ from typing import Callable, Coroutine, Dict, List, Optional, Union
 from aiohttp import ClientPayloadError
 from pydantic import root_validator
 
+from ..libs.util.strings import remove_quotes_and_escapes
 from ..models.filesystem import RangeInFileWithContents
 from ..models.filesystem_edit import FileEditWithFullContents
 from .observation import Observation, InternalErrorObservation
@@ -381,11 +382,11 @@ class Autopilot(ContinueBaseModel):
         if self.session_info is None:
             async def create_title():
                 title = await self.continue_sdk.models.medium.complete(f"Give a short title to describe the current chat session. Do not put quotes around the title. The first message was: \"{user_input}\". The title is: ")
+                title = remove_quotes_and_escapes(title)
                 self.session_info = SessionInfo(
                     title=title,
                     session_id=self.ide.session_id,
-                    date_created=time.strftime(
-                        "%Y-%m-%d %H:%M:%S", time.gmtime())
+                    date_created=str(time.time())
                 )
 
             create_async_task(create_title(), on_error=lambda e: self.continue_sdk.run_step(
diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx
index 48ecebaa..879373a0 100644
--- a/extension/react-app/src/App.tsx
+++ b/extension/react-app/src/App.tsx
@@ -12,7 +12,6 @@ import {
   setVscMediaUrl,
   setDataSwitchOn,
 } from "./redux/slices/configSlice";
-import { updateFileSystem } from "./redux/slices/debugContexSlice";
 import { setHighlightedCode } from "./redux/slices/miscSlice";
 import { postVscMessage } from "./vscode";
 import { createBrowserRouter, RouterProvider } from "react-router-dom";
@@ -60,7 +59,6 @@ function App() {
           break;
         case "highlightedCode":
           dispatch(setHighlightedCode(event.data.rangeInFile));
-          dispatch(updateFileSystem(event.data.filesystem));
           break;
       }
     };
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index e2e3431a..e96ffe65 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -2,9 +2,8 @@ import styled from "styled-components";
 import { defaultBorderRadius } from "../components";
 import Loader from "../components/Loader";
 import ContinueButton from "../components/ContinueButton";
-import { ContextItem, FullState } from "../../../schema/FullState";
+import { FullState } 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";
@@ -23,6 +22,10 @@ import {
   setShowDialog,
 } from "../redux/slices/uiStateSlice";
 import RingLoader from "../components/RingLoader";
+import {
+  setServerState,
+  temporarilySetUserInputQueue,
+} from "../redux/slices/serverStateReducer";
 
 const UserInputQueueItem = styled.div`
   border-radius: ${defaultBorderRadius};
@@ -44,13 +47,18 @@ function GUI(props: GUIProps) {
 
   // #endregion
 
+  // #region Selectors
+  const {
+    history,
+    user_input_queue,
+    adding_highlighted_code,
+    selected_context_items,
+  } = useSelector((state: RootStore) => state.serverState);
+
+  // #endregion
+
   // #region State
   const [waitingForSteps, setWaitingForSteps] = useState(false);
-  const [userInputQueue, setUserInputQueue] = useState<string[]>([]);
-  const [addingHighlightedCode, setAddingHighlightedCode] = useState(false);
-  const [selectedContextItems, setSelectedContextItems] = useState<
-    ContextItem[]
-  >([]);
   const [availableSlashCommands, setAvailableSlashCommands] = useState<
     { name: string; description: string }[]
   >([]);
@@ -60,27 +68,6 @@ function GUI(props: GUIProps) {
     true,
     true,
   ]);
-  const [history, setHistory] = useState<History | undefined>({
-    timeline: [
-      {
-        step: {
-          name: "Welcome to Continue",
-          hide: false,
-          description: `- Highlight code section and ask a question or give instructions
-- 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,
-          message: "",
-        },
-        depth: 0,
-        deleted: false,
-        active: false,
-      },
-    ],
-    current_index: 3,
-  } as any);
   const [waitingForClient, setWaitingForClient] = useState(true);
   const [showLoading, setShowLoading] = useState(false);
 
@@ -163,11 +150,9 @@ function GUI(props: GUIProps) {
           state.history.current_index
         ].step.description?.trim() === "";
 
+      dispatch(setServerState(state));
+
       setWaitingForSteps(waitingForSteps);
-      setHistory(state.history);
-      setSelectedContextItems(state.selected_context_items || []);
-      setUserInputQueue(state.user_input_queue);
-      setAddingHighlightedCode(state.adding_highlighted_code);
       setAvailableSlashCommands(
         state.slash_commands.map((c: any) => {
           return {
@@ -203,11 +188,11 @@ function GUI(props: GUIProps) {
   useEffect(() => {
     if (client && waitingForClient) {
       setWaitingForClient(false);
-      for (const input of userInputQueue) {
+      for (const input of user_input_queue) {
         client.sendMainInput(input);
       }
     }
-  }, [client, userInputQueue, waitingForClient]);
+  }, [client, user_input_queue, waitingForClient]);
 
   const onMainTextInput = (event?: any) => {
     if (mainTextInputRef.current) {
@@ -218,9 +203,11 @@ function GUI(props: GUIProps) {
       }
       (mainTextInputRef.current as any).setInputValue("");
       if (!client) {
-        setUserInputQueue((queue) => {
-          return [...queue, input];
-        });
+        dispatch(
+          temporarilySetUserInputQueue((queue: string[]) => {
+            return [...queue, input];
+          })
+        );
         return;
       }
 
@@ -249,9 +236,11 @@ function GUI(props: GUIProps) {
       if (input.trim() === "") return;
 
       client.sendMainInput(input);
-      setUserInputQueue((queue) => {
-        return [...queue, input];
-      });
+      dispatch(
+        temporarilySetUserInputQueue((queue: string[]) => {
+          return [...queue, input];
+        })
+      );
 
       // Increment localstorage counter
       const counter = localStorage.getItem("mainTextEntryCounter");
@@ -435,7 +424,7 @@ function GUI(props: GUIProps) {
       {waitingForSteps && <Loader />}
 
       <div>
-        {userInputQueue.map((input) => {
+        {user_input_queue.map((input) => {
           return <UserInputQueueItem>{input}</UserInputQueueItem>;
         })}
       </div>
@@ -450,11 +439,11 @@ function GUI(props: GUIProps) {
         }}
         onInputValueChange={() => {}}
         items={availableSlashCommands}
-        selectedContextItems={selectedContextItems}
+        selectedContextItems={selected_context_items}
         onToggleAddContext={() => {
           client?.toggleAddingHighlightedCode();
         }}
-        addingHighlightedCode={addingHighlightedCode}
+        addingHighlightedCode={adding_highlighted_code}
       />
       <ContinueButton onClick={onMainTextInput} />
     </div>
diff --git a/extension/react-app/src/pages/history.tsx b/extension/react-app/src/pages/history.tsx
index f925d52c..a553c6ae 100644
--- a/extension/react-app/src/pages/history.tsx
+++ b/extension/react-app/src/pages/history.tsx
@@ -69,7 +69,18 @@ function History() {
                   }}
                 >
                   <div className="text-lg">{session.title}</div>
-                  <div className="text-gray-400">{session.date_created}</div>
+                  <div className="text-gray-400">
+                    {new Date(
+                      parseInt(session.date_created) * 1000
+                    ).toLocaleString("en-US", {
+                      weekday: "short",
+                      year: "numeric",
+                      month: "long",
+                      day: "numeric",
+                      hour: "numeric",
+                      minute: "numeric",
+                    })}
+                  </div>
                 </TdDiv>
               </td>
             </Tr>
diff --git a/extension/react-app/src/redux/hooks.ts b/extension/react-app/src/redux/hooks.ts
deleted file mode 100644
index a6aef869..00000000
--- a/extension/react-app/src/redux/hooks.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useCallback } from "react";
-import { useDispatch, useSelector } from "react-redux";
-import { RootStore } from "./store";
-import { selectDebugContextValue } from "./selectors/debugContextSelectors";
-import { updateValue } from "./slices/debugContexSlice";
-import { SerializedDebugContext } from "../../../src/client";
-
-export function useDebugContextValue(
-  key: keyof SerializedDebugContext,
-  defaultValue: any
-): [any, (value: any) => void] {
-  const dispatch = useDispatch();
-  const state =
-    useSelector((state: RootStore) => selectDebugContextValue(state, key)) ||
-    defaultValue;
-  const boundAction = useCallback(
-    (value: any) => dispatch(updateValue({ key, value })),
-    [dispatch, key]
-  );
-  return [state, boundAction];
-}
diff --git a/extension/react-app/src/redux/selectors/debugContextSelectors.ts b/extension/react-app/src/redux/selectors/debugContextSelectors.ts
deleted file mode 100644
index 89201bb7..00000000
--- a/extension/react-app/src/redux/selectors/debugContextSelectors.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { RootStore } from "../store";
-
-const selectDebugContext = (state: RootStore) => {
-  return {
-    ...state.debugState.debugContext,
-    rangesInFiles: state.debugState.debugContext.rangesInFiles.filter(
-      (_, index) => state.debugState.rangesMask[index]
-    ),
-  };
-};
-
-const selectAllRangesInFiles = (state: RootStore) => {
-  return state.debugState.debugContext.rangesInFiles;
-};
-
-const selectRangesMask = (state: RootStore) => {
-  return state.debugState.rangesMask;
-};
-
-const selectDebugContextValue = (state: RootStore, key: string) => {
-  return (state.debugState.debugContext as any)[key];
-};
-
-export {
-  selectDebugContext,
-  selectDebugContextValue,
-  selectAllRangesInFiles,
-  selectRangesMask,
-};
diff --git a/extension/react-app/src/redux/slices/debugContexSlice.ts b/extension/react-app/src/redux/slices/debugContexSlice.ts
deleted file mode 100644
index 647440d5..00000000
--- a/extension/react-app/src/redux/slices/debugContexSlice.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { createSlice } from "@reduxjs/toolkit";
-import { RangeInFile, SerializedDebugContext } from "../../../../src/client";
-import { RootStore } from "../store";
-
-export const debugStateSlice = createSlice({
-  name: "debugState",
-  initialState: {
-    debugContext: {
-      rangesInFiles: [],
-      filesystem: {},
-      traceback: undefined,
-      description: undefined,
-    },
-    rangesMask: [],
-  } as RootStore["debugState"],
-  reducers: {
-    updateValue: (
-      state: RootStore["debugState"],
-      action: {
-        type: string;
-        payload: { key: keyof SerializedDebugContext; value: any };
-      }
-    ) => {
-      return {
-        ...state,
-        debugContext: {
-          ...state.debugContext,
-          [action.payload.key]: action.payload.value,
-        },
-      };
-    },
-    addRangeInFile: (
-      state: RootStore["debugState"],
-      action: {
-        type: string;
-        payload: {
-          rangeInFile: RangeInFile;
-          canUpdateLast: boolean;
-        };
-      }
-    ) => {
-      let rangesInFiles = state.debugContext.rangesInFiles;
-      // If identical to existing range, don't add. Ideally you check for overlap of ranges.
-      for (let range of rangesInFiles) {
-        if (
-          range.filepath === action.payload.rangeInFile.filepath &&
-          range.range.start.line ===
-            action.payload.rangeInFile.range.start.line &&
-          range.range.end.line === action.payload.rangeInFile.range.end.line
-        ) {
-          return state;
-        }
-      }
-
-      if (
-        action.payload.canUpdateLast &&
-        rangesInFiles.length > 0 &&
-        rangesInFiles[rangesInFiles.length - 1].filepath ===
-          action.payload.rangeInFile.filepath
-      ) {
-        return {
-          ...state,
-          debugContext: {
-            ...state.debugContext,
-            rangesInFiles: [
-              ...rangesInFiles.slice(0, rangesInFiles.length - 1),
-              action.payload.rangeInFile,
-            ],
-          },
-        };
-      } else {
-        return {
-          ...state,
-          debugContext: {
-            ...state.debugContext,
-            rangesInFiles: [
-              ...state.debugContext.rangesInFiles,
-              action.payload.rangeInFile,
-            ],
-          },
-          rangesMask: [...state.rangesMask, true],
-        };
-      }
-    },
-    deleteRangeInFileAt: (
-      state: RootStore["debugState"],
-      action: {
-        type: string;
-        payload: number;
-      }
-    ) => {
-      return {
-        ...state,
-        debugContext: {
-          ...state.debugContext,
-          rangesInFiles: state.debugContext.rangesInFiles.filter(
-            (_, index) => index !== action.payload
-          ),
-        },
-        rangesMask: state.rangesMask.filter(
-          (_, index) => index !== action.payload
-        ),
-      };
-    },
-    toggleSelectionAt: (
-      state: RootStore["debugState"],
-      action: {
-        type: string;
-        payload: number;
-      }
-    ) => {
-      return {
-        ...state,
-        rangesMask: state.rangesMask.map((_, index) =>
-          index === action.payload
-            ? !state.rangesMask[index]
-            : state.rangesMask[index]
-        ),
-      };
-    },
-    updateFileSystem: (
-      state: RootStore["debugState"],
-      action: {
-        type: string;
-        payload: { [filepath: string]: string };
-      }
-    ) => {
-      return {
-        ...state,
-        debugContext: {
-          ...state.debugContext,
-          filesystem: {
-            ...state.debugContext.filesystem,
-            ...action.payload,
-          },
-        },
-      };
-    },
-  },
-});
-
-export const {
-  updateValue,
-  updateFileSystem,
-  addRangeInFile,
-  deleteRangeInFileAt,
-  toggleSelectionAt,
-} = debugStateSlice.actions;
-export default debugStateSlice.reducer;
diff --git a/extension/react-app/src/redux/slices/serverStateReducer.ts b/extension/react-app/src/redux/slices/serverStateReducer.ts
new file mode 100644
index 00000000..4d9dc326
--- /dev/null
+++ b/extension/react-app/src/redux/slices/serverStateReducer.ts
@@ -0,0 +1,53 @@
+import { createSlice } from "@reduxjs/toolkit";
+import { FullState } from "../../../../schema/FullState";
+
+const initialState: FullState = {
+  history: {
+    timeline: [
+      {
+        step: {
+          name: "Welcome to Continue",
+          hide: false,
+          description: `- Highlight code section and ask a question or give instructions
+    - 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,
+          message: "",
+        },
+        depth: 0,
+        deleted: false,
+        active: false,
+      },
+    ],
+    current_index: 3,
+  } as any,
+  user_input_queue: [],
+  active: false,
+  slash_commands: [],
+  adding_highlighted_code: false,
+  selected_context_items: [],
+};
+
+export const serverStateSlice = createSlice({
+  name: "serverState",
+  initialState,
+  reducers: {
+    setServerState: (state, action) => {
+      return {
+        ...action.payload,
+        selected_context_items: action.payload.selected_context_items || [],
+        user_input_queue: action.payload.user_input_queue || [],
+        slash_commands: action.payload.slash_commands || [],
+      };
+    },
+    temporarilySetUserInputQueue: (state, action) => {
+      state.user_input_queue = action.payload;
+    },
+  },
+});
+
+export const { setServerState, temporarilySetUserInputQueue } =
+  serverStateSlice.actions;
+export default serverStateSlice.reducer;
diff --git a/extension/react-app/src/redux/store.ts b/extension/react-app/src/redux/store.ts
index aa8f5e7b..59339060 100644
--- a/extension/react-app/src/redux/store.ts
+++ b/extension/react-app/src/redux/store.ts
@@ -1,10 +1,11 @@
 import { configureStore } from "@reduxjs/toolkit";
-import debugStateReducer from "./slices/debugContexSlice";
 import chatReducer from "./slices/chatSlice";
 import configReducer from "./slices/configSlice";
 import miscReducer from "./slices/miscSlice";
 import uiStateReducer from "./slices/uiStateSlice";
-import { RangeInFile, SerializedDebugContext } from "../../../src/client";
+import { RangeInFile } from "../../../src/client";
+import { FullState } from "../../../schema/FullState";
+import serverStateReducer from "./slices/serverStateReducer";
 
 export interface ChatMessage {
   role: "system" | "user" | "assistant";
@@ -12,10 +13,6 @@ export interface ChatMessage {
 }
 
 export interface RootStore {
-  debugState: {
-    debugContext: SerializedDebugContext;
-    rangesMask: boolean[];
-  };
   config: {
     workspacePath: string | undefined;
     apiUrl: string;
@@ -40,15 +37,16 @@ export interface RootStore {
     dialogMessage: string | JSX.Element;
     dialogEntryOn: boolean;
   };
+  serverState: FullState;
 }
 
 const store = configureStore({
   reducer: {
-    debugState: debugStateReducer,
     chat: chatReducer,
     config: configReducer,
     misc: miscReducer,
     uiState: uiStateReducer,
+    serverState: serverStateReducer,
   },
 });
 
-- 
cgit v1.2.3-70-g09d2


From 8ada89b0f66f9e746394ee64591359537fe0c7f0 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sun, 6 Aug 2023 22:54:31 -0700
Subject: feat: :memo: note about where session data is stored

---
 extension/react-app/src/pages/history.tsx | 4 ++++
 1 file changed, 4 insertions(+)

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

diff --git a/extension/react-app/src/pages/history.tsx b/extension/react-app/src/pages/history.tsx
index a553c6ae..c3240a66 100644
--- a/extension/react-app/src/pages/history.tsx
+++ b/extension/react-app/src/pages/history.tsx
@@ -87,6 +87,10 @@ function History() {
           ))}
         </tbody>
       </table>
+      <br />
+      <i className="text-sm ml-4">
+        All session data is saved in ~/.continue/sessions
+      </i>
     </div>
   );
 }
-- 
cgit v1.2.3-70-g09d2

