summaryrefslogtreecommitdiff
path: root/extension/react-app/src
diff options
context:
space:
mode:
Diffstat (limited to 'extension/react-app/src')
-rw-r--r--extension/react-app/src/App.tsx50
-rw-r--r--extension/react-app/src/components/DebugPanel.tsx99
-rw-r--r--extension/react-app/src/components/Layout.tsx28
-rw-r--r--extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts2
-rw-r--r--extension/react-app/src/hooks/ContinueGUIClientProtocol.ts63
-rw-r--r--extension/react-app/src/index.css4
-rw-r--r--extension/react-app/src/main.tsx10
-rw-r--r--extension/react-app/src/pages/gui.tsx14
-rw-r--r--extension/react-app/src/pages/history.tsx55
9 files changed, 201 insertions, 124 deletions
diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx
index aa462171..15b536db 100644
--- a/extension/react-app/src/App.tsx
+++ b/extension/react-app/src/App.tsx
@@ -1,8 +1,26 @@
-import DebugPanel from "./components/DebugPanel";
import GUI from "./pages/gui";
-import { createContext } from "react";
+import History from "./pages/history";
+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,
+ setVscMachineId,
+ setSessionId,
+ setVscMediaUrl,
+ setDataSwitchOn,
+} from "./redux/slices/configSlice";
+import { updateFileSystem } from "./redux/slices/debugContexSlice";
+import { setHighlightedCode } from "./redux/slices/miscSlice";
+import { postVscMessage } from "./vscode";
export const GUIClientContext = createContext<
ContinueGUIClientProtocol | undefined
@@ -11,9 +29,35 @@ export const GUIClientContext = createContext<
function App() {
const client = useContinueGUIProtocol();
+ const dispatch = useDispatch();
+ useEffect(() => {
+ const eventListener = (event: any) => {
+ switch (event.data.type) {
+ case "onLoad":
+ dispatch(setApiUrl(event.data.apiUrl));
+ dispatch(setVscMachineId(event.data.vscMachineId));
+ dispatch(setSessionId(event.data.sessionId));
+ dispatch(setVscMediaUrl(event.data.vscMediaUrl));
+ dispatch(setDataSwitchOn(event.data.dataSwitchOn));
+ break;
+ case "highlightedCode":
+ dispatch(setHighlightedCode(event.data.rangeInFile));
+ dispatch(updateFileSystem(event.data.filesystem));
+ break;
+ }
+ };
+ window.addEventListener("message", eventListener);
+ postVscMessage("onLoad", {});
+ return () => window.removeEventListener("message", eventListener);
+ }, []);
+
return (
<GUIClientContext.Provider value={client}>
- <DebugPanel tabs={[{ element: <GUI />, title: "GUI" }]} />
+ <Routes>
+ <Route path="/" element={<Layout />} />
+ <Route path="/gui" element={<GUI />} />
+ <Route path="/history" element={<History />} />
+ </Routes>
</GUIClientContext.Provider>
);
}
diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx
deleted file mode 100644
index fffb6c6e..00000000
--- a/extension/react-app/src/components/DebugPanel.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import React, { useEffect, useState } from "react";
-import styled from "styled-components";
-import { postVscMessage } from "../vscode";
-import { useDispatch } from "react-redux";
-import {
- setApiUrl,
- setVscMachineId,
- setSessionId,
- setVscMediaUrl,
- setDataSwitchOn,
-} from "../redux/slices/configSlice";
-import { setHighlightedCode } from "../redux/slices/miscSlice";
-import { updateFileSystem } from "../redux/slices/debugContexSlice";
-import { defaultBorderRadius, secondaryDark, vscBackground } from ".";
-interface DebugPanelProps {
- tabs: {
- element: React.ReactElement;
- title: string;
- }[];
-}
-
-const TabBar = styled.div<{ numTabs: number }>`
- display: grid;
- grid-template-columns: repeat(${(props) => props.numTabs}, 1fr);
-`;
-
-const TabsAndBodyDiv = styled.div`
- height: 100%;
- border-radius: ${defaultBorderRadius};
- scrollbar-base-color: transparent;
-`;
-
-function DebugPanel(props: DebugPanelProps) {
- const dispatch = useDispatch();
- useEffect(() => {
- const eventListener = (event: any) => {
- switch (event.data.type) {
- case "onLoad":
- dispatch(setApiUrl(event.data.apiUrl));
- dispatch(setVscMachineId(event.data.vscMachineId));
- dispatch(setSessionId(event.data.sessionId));
- dispatch(setVscMediaUrl(event.data.vscMediaUrl));
- dispatch(setDataSwitchOn(event.data.dataSwitchOn));
- break;
- case "highlightedCode":
- dispatch(setHighlightedCode(event.data.rangeInFile));
- dispatch(updateFileSystem(event.data.filesystem));
- break;
- }
- };
- window.addEventListener("message", eventListener);
- postVscMessage("onLoad", {});
- return () => window.removeEventListener("message", eventListener);
- }, []);
-
- const [currentTab, setCurrentTab] = useState(0);
-
- return (
- <TabsAndBodyDiv>
- {props.tabs.length > 1 && (
- <TabBar numTabs={props.tabs.length}>
- {props.tabs.map((tab, index) => {
- return (
- <div
- key={index}
- className={`p-2 cursor-pointer text-center ${
- index === currentTab
- ? "bg-secondary-dark"
- : "bg-vsc-background"
- }`}
- onClick={() => setCurrentTab(index)}
- >
- {tab.title}
- </div>
- );
- })}
- </TabBar>
- )}
- {props.tabs.map((tab, index) => {
- return (
- <div
- key={index}
- hidden={index !== currentTab}
- style={{
- scrollbarGutter: "stable both-edges",
- minHeight: "100%",
- display: "grid",
- gridTemplateRows: "1fr auto",
- }}
- >
- {tab.element}
- </div>
- );
- })}
- </TabsAndBodyDiv>
- );
-}
-
-export default DebugPanel;
diff --git a/extension/react-app/src/components/Layout.tsx b/extension/react-app/src/components/Layout.tsx
new file mode 100644
index 00000000..d1688299
--- /dev/null
+++ b/extension/react-app/src/components/Layout.tsx
@@ -0,0 +1,28 @@
+import styled from "styled-components";
+import { defaultBorderRadius } from ".";
+import { Outlet } from "react-router-dom";
+
+const LayoutTopDiv = styled.div`
+ height: 100%;
+ border-radius: ${defaultBorderRadius};
+ scrollbar-base-color: transparent;
+ scrollbar-width: thin;
+`;
+const Layout = () => {
+ return (
+ <LayoutTopDiv>
+ <div
+ style={{
+ scrollbarGutter: "stable both-edges",
+ minHeight: "100%",
+ display: "grid",
+ gridTemplateRows: "1fr auto",
+ }}
+ >
+ <Outlet />
+ </div>
+ </LayoutTopDiv>
+ );
+};
+
+export default Layout;
diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
index 8d8b7b7e..168fb156 100644
--- a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
@@ -30,6 +30,8 @@ abstract class AbstractContinueGUIClientProtocol {
abstract showLogsAtIndex(index: number): void;
abstract selectContextItem(id: string, query: string): void;
+
+ abstract onReconnectAtSession(session_id: string): void;
}
export default AbstractContinueGUIClientProtocol;
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index b6dd43d9..830954c5 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -4,15 +4,18 @@ import { Messenger, WebsocketMessenger } from "./messenger";
import { VscodeMessenger } from "./vscodeMessenger";
class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
- messenger: Messenger;
+ messenger?: Messenger;
// Server URL must contain the session ID param
serverUrlWithSessionId: string;
+ useVscodeMessagePassing: boolean;
- constructor(
+ private connectMessenger(
serverUrlWithSessionId: string,
useVscodeMessagePassing: boolean
) {
- super();
+ if (this.messenger) {
+ // this.messenger.close(); TODO
+ }
this.serverUrlWithSessionId = serverUrlWithSessionId;
this.messenger = useVscodeMessagePassing
? new VscodeMessenger(serverUrlWithSessionId)
@@ -24,26 +27,52 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
this.messenger.onError((error) => {
console.log("GUI -> IDE websocket error", error);
});
+
+ this.messenger.onMessageType("reconnect_at_session", (data: any) => {
+ if (data.session_id) {
+ this.onReconnectAtSession(data.session_id);
+ }
+ });
+ }
+
+ constructor(
+ serverUrlWithSessionId: string,
+ useVscodeMessagePassing: boolean
+ ) {
+ super();
+ this.serverUrlWithSessionId = serverUrlWithSessionId;
+ this.useVscodeMessagePassing = useVscodeMessagePassing;
+ this.connectMessenger(serverUrlWithSessionId, useVscodeMessagePassing);
+ }
+
+ onReconnectAtSession(session_id: string): void {
+ this.connectMessenger(
+ this.serverUrlWithSessionId.replace(
+ /\/session\/[a-zA-Z0-9-]+/,
+ `/session/${session_id}`
+ ),
+ this.useVscodeMessagePassing
+ );
}
sendMainInput(input: string) {
- this.messenger.send("main_input", { input });
+ this.messenger?.send("main_input", { input });
}
reverseToIndex(index: number) {
- this.messenger.send("reverse_to_index", { index });
+ this.messenger?.send("reverse_to_index", { index });
}
sendRefinementInput(input: string, index: number) {
- this.messenger.send("refinement_input", { input, index });
+ this.messenger?.send("refinement_input", { input, index });
}
sendStepUserInput(input: string, index: number) {
- this.messenger.send("step_user_input", { input, index });
+ this.messenger?.send("step_user_input", { input, index });
}
onStateUpdate(callback: (state: any) => void) {
- this.messenger.onMessageType("state_update", (data: any) => {
+ this.messenger?.onMessageType("state_update", (data: any) => {
if (data.state) {
callback(data.state);
}
@@ -53,7 +82,7 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
onAvailableSlashCommands(
callback: (commands: { name: string; description: string }[]) => void
) {
- this.messenger.onMessageType("available_slash_commands", (data: any) => {
+ this.messenger?.onMessageType("available_slash_commands", (data: any) => {
if (data.commands) {
callback(data.commands);
}
@@ -61,37 +90,37 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
}
sendClear() {
- this.messenger.send("clear_history", {});
+ this.messenger?.send("clear_history", {});
}
retryAtIndex(index: number) {
- this.messenger.send("retry_at_index", { index });
+ this.messenger?.send("retry_at_index", { index });
}
deleteAtIndex(index: number) {
- this.messenger.send("delete_at_index", { index });
+ this.messenger?.send("delete_at_index", { index });
}
deleteContextWithIds(ids: ContextItemId[]) {
- this.messenger.send("delete_context_with_ids", {
+ this.messenger?.send("delete_context_with_ids", {
ids: ids.map((id) => `${id.provider_title}-${id.item_id}`),
});
}
setEditingAtIds(ids: string[]) {
- this.messenger.send("set_editing_at_ids", { ids });
+ this.messenger?.send("set_editing_at_ids", { ids });
}
toggleAddingHighlightedCode(): void {
- this.messenger.send("toggle_adding_highlighted_code", {});
+ this.messenger?.send("toggle_adding_highlighted_code", {});
}
showLogsAtIndex(index: number): void {
- this.messenger.send("show_logs_at_index", { index });
+ this.messenger?.send("show_logs_at_index", { index });
}
selectContextItem(id: string, query: string): void {
- this.messenger.send("select_context_item", { id, query });
+ this.messenger?.send("select_context_item", { id, query });
}
}
diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css
index 6a46800e..269da69a 100644
--- a/extension/react-app/src/index.css
+++ b/extension/react-app/src/index.css
@@ -9,9 +9,9 @@
--button-color-hover: rgba(113, 28, 59, 0.667);
--def-border-radius: 5px;
- /* --vscode-editor-background: rgb(30, 30, 30);
+ --vscode-editor-background: rgb(30, 30, 30);
--vscode-editor-foreground: rgb(197, 200, 198);
- --vscode-textBlockQuote-background: rgba(255, 255, 255, 0.05); */
+ --vscode-textBlockQuote-background: rgba(255, 255, 255, 0.05);
}
html,
diff --git a/extension/react-app/src/main.tsx b/extension/react-app/src/main.tsx
index 1776490c..1b70e786 100644
--- a/extension/react-app/src/main.tsx
+++ b/extension/react-app/src/main.tsx
@@ -7,6 +7,14 @@ 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");
@@ -19,7 +27,7 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<PostHogProvider client={posthog}>
<Provider store={store}>
- <App />
+ <RouterProvider router={router} />
</Provider>
</PostHogProvider>
</React.StrictMode>
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index d69da57e..247789d6 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -16,11 +16,12 @@ import {
BookOpenIcon,
ChatBubbleOvalLeftEllipsisIcon,
TrashIcon,
+ PlusCircleIcon,
+ FolderIcon,
} from "@heroicons/react/24/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 { useDispatch, useSelector } from "react-redux";
import { RootStore } from "../redux/store";
@@ -589,7 +590,16 @@ If you already have an LLM deployed on your own infrastructure, or would like to
}}
text="Clear"
>
- <TrashIcon width="1.4em" height="1.4em" />
+ <PlusCircleIcon width="1.4em" height="1.4em" />
+ </HeaderButtonWithText>
+ <HeaderButtonWithText
+ onClick={() => {
+ // Go to /history page
+ document.location.href = "/history";
+ }}
+ text="History"
+ >
+ <FolderIcon width="1.4em" height="1.4em" />
</HeaderButtonWithText>
<a
href="https://continue.dev/docs/how-to-use-continue"
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;