diff options
Diffstat (limited to 'extension/react-app/src')
-rw-r--r-- | extension/react-app/src/App.tsx | 50 | ||||
-rw-r--r-- | extension/react-app/src/components/DebugPanel.tsx | 99 | ||||
-rw-r--r-- | extension/react-app/src/components/Layout.tsx | 28 | ||||
-rw-r--r-- | extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts | 2 | ||||
-rw-r--r-- | extension/react-app/src/hooks/ContinueGUIClientProtocol.ts | 63 | ||||
-rw-r--r-- | extension/react-app/src/index.css | 4 | ||||
-rw-r--r-- | extension/react-app/src/main.tsx | 10 | ||||
-rw-r--r-- | extension/react-app/src/pages/gui.tsx | 14 | ||||
-rw-r--r-- | extension/react-app/src/pages/history.tsx | 55 |
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; |