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.tsx62
-rw-r--r--extension/react-app/src/components/DebugPanel.tsx99
-rw-r--r--extension/react-app/src/components/Layout.tsx210
-rw-r--r--extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts4
-rw-r--r--extension/react-app/src/hooks/ContinueGUIClientProtocol.ts66
-rw-r--r--extension/react-app/src/hooks/messenger.ts6
-rw-r--r--extension/react-app/src/hooks/vscodeMessenger.ts4
-rw-r--r--extension/react-app/src/index.css4
-rw-r--r--extension/react-app/src/pages/error.tsx17
-rw-r--r--extension/react-app/src/pages/gui.tsx609
-rw-r--r--extension/react-app/src/pages/history.tsx98
-rw-r--r--extension/react-app/src/redux/hooks.ts21
-rw-r--r--extension/react-app/src/redux/selectors/debugContextSelectors.ts29
-rw-r--r--extension/react-app/src/redux/slices/configSlice.ts4
-rw-r--r--extension/react-app/src/redux/slices/debugContexSlice.ts149
-rw-r--r--extension/react-app/src/redux/slices/serverStateReducer.ts53
-rw-r--r--extension/react-app/src/redux/slices/uiStateSlice.ts26
-rw-r--r--extension/react-app/src/redux/store.ts16
18 files changed, 758 insertions, 719 deletions
diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx
index aa462171..879373a0 100644
--- a/extension/react-app/src/App.tsx
+++ b/extension/react-app/src/App.tsx
@@ -1,8 +1,43 @@
-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 { useDispatch } from "react-redux";
+import {
+ setApiUrl,
+ setVscMachineId,
+ setSessionId,
+ setVscMediaUrl,
+ setDataSwitchOn,
+} from "./redux/slices/configSlice";
+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
@@ -11,9 +46,30 @@ 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));
+ break;
+ }
+ };
+ window.addEventListener("message", eventListener);
+ postVscMessage("onLoad", {});
+ return () => window.removeEventListener("message", eventListener);
+ }, []);
+
return (
<GUIClientContext.Provider value={client}>
- <DebugPanel tabs={[{ element: <GUI />, title: "GUI" }]} />
+ <RouterProvider router={router} />
</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..30cb5df2
--- /dev/null
+++ b/extension/react-app/src/components/Layout.tsx
@@ -0,0 +1,210 @@
+import styled from "styled-components";
+import { defaultBorderRadius, secondaryDark, vscForeground } from ".";
+import { Outlet } from "react-router-dom";
+import Onboarding from "./Onboarding";
+import TextDialog from "./TextDialog";
+import { useContext } from "react";
+import { GUIClientContext } from "../App";
+import { useDispatch, useSelector } from "react-redux";
+import { RootStore } from "../redux/store";
+import {
+ setBottomMessage,
+ setBottomMessageCloseTimeout,
+ setDialogEntryOn,
+ setDialogMessage,
+ setShowDialog,
+} from "../redux/slices/uiStateSlice";
+import {
+ PlusIcon,
+ FolderIcon,
+ BookOpenIcon,
+ ChatBubbleOvalLeftEllipsisIcon,
+} from "@heroicons/react/24/outline";
+import HeaderButtonWithText from "./HeaderButtonWithText";
+import { useNavigate } from "react-router-dom";
+
+// #region Styled Components
+
+const LayoutTopDiv = styled.div`
+ height: 100%;
+ border-radius: ${defaultBorderRadius};
+ scrollbar-base-color: transparent;
+ scrollbar-width: thin;
+`;
+
+const BottomMessageDiv = styled.div<{ displayOnBottom: boolean }>`
+ position: fixed;
+ bottom: ${(props) => (props.displayOnBottom ? "50px" : undefined)};
+ top: ${(props) => (props.displayOnBottom ? undefined : "50px")};
+ left: 0;
+ right: 0;
+ margin: 8px;
+ margin-top: 0;
+ background-color: ${secondaryDark};
+ color: ${vscForeground};
+ border-radius: ${defaultBorderRadius};
+ padding: 12px;
+ z-index: 100;
+ box-shadow: 0px 0px 2px 0px ${vscForeground};
+ max-height: 50vh;
+ overflow: scroll;
+`;
+
+const Footer = styled.footer`
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ justify-content: right;
+ padding: 8px;
+ align-items: center;
+ margin-top: 8px;
+ border-top: 0.1px solid gray;
+`;
+
+// #endregion
+
+const Layout = () => {
+ const navigate = useNavigate();
+ const client = useContext(GUIClientContext);
+ const dispatch = useDispatch();
+ const { showDialog, dialogEntryOn, dialogMessage } = useSelector(
+ (state: RootStore) => state.uiState
+ );
+
+ // #region Selectors
+ const vscMediaUrl = useSelector(
+ (state: RootStore) => state.config.vscMediaUrl
+ );
+
+ const { bottomMessage, displayBottomMessageOnBottom } = useSelector(
+ (state: RootStore) => state.uiState
+ );
+
+ // #endregion
+
+ return (
+ <LayoutTopDiv>
+ <div
+ style={{
+ scrollbarGutter: "stable both-edges",
+ minHeight: "100%",
+ display: "grid",
+ gridTemplateRows: "1fr auto",
+ }}
+ >
+ <Onboarding />
+ <TextDialog
+ showDialog={showDialog}
+ onEnter={(text) => {
+ client?.sendMainInput(`/feedback ${text}`);
+ dispatch(setShowDialog(false));
+ }}
+ onClose={() => {
+ dispatch(setShowDialog(false));
+ }}
+ message={dialogMessage}
+ entryOn={dialogEntryOn}
+ />
+ <Outlet />
+
+ <BottomMessageDiv
+ displayOnBottom={displayBottomMessageOnBottom}
+ onMouseEnter={() => {
+ dispatch(setBottomMessageCloseTimeout(undefined));
+ }}
+ onMouseLeave={(e) => {
+ if (!e.buttons) {
+ dispatch(setBottomMessage(undefined));
+ }
+ }}
+ hidden={!bottomMessage}
+ >
+ {bottomMessage}
+ </BottomMessageDiv>
+ <Footer>
+ {vscMediaUrl && (
+ <a
+ href="https://github.com/continuedev/continue"
+ style={{ marginRight: "auto" }}
+ >
+ <img
+ src={`${vscMediaUrl}/continue-dev-square.png`}
+ width="22px"
+ style={{ backgroundColor: "black", color: "red" }}
+ />
+ </a>
+ )}
+ <HeaderButtonWithText
+ onClick={() => {
+ // Show the dialog
+ dispatch(
+ setDialogMessage(`Continue uses GPT-4 by default, but works with any model. If you'd like to keep your code completely private, there are few options:
+
+ Run a local model with ggml: [5 minute quickstart](https://github.com/continuedev/ggml-server-example)
+
+ Use Azure OpenAI service, which is GDPR and HIPAA compliant: [Tutorial](https://continue.dev/docs/customization#azure-openai-service)
+
+ If you already have an LLM deployed on your own infrastructure, or would like to do so, please contact us at hi@continue.dev.
+ `)
+ );
+ dispatch(setDialogEntryOn(false));
+ dispatch(setShowDialog(true));
+ }}
+ text={"Use Private Model"}
+ >
+ <div
+ style={{
+ fontSize: "18px",
+ marginLeft: "2px",
+ marginRight: "2px",
+ }}
+ >
+ 🔒
+ </div>
+ </HeaderButtonWithText>
+ <HeaderButtonWithText
+ onClick={() => {
+ client?.loadSession(undefined);
+ }}
+ text="New Session"
+ >
+ <PlusIcon width="1.4em" height="1.4em" />
+ </HeaderButtonWithText>
+ <HeaderButtonWithText
+ onClick={() => {
+ navigate("/history");
+ }}
+ text="History"
+ >
+ <FolderIcon width="1.4em" height="1.4em" />
+ </HeaderButtonWithText>
+ <a
+ href="https://continue.dev/docs/how-to-use-continue"
+ className="no-underline"
+ >
+ <HeaderButtonWithText text="Docs">
+ <BookOpenIcon width="1.4em" height="1.4em" />
+ </HeaderButtonWithText>
+ </a>
+ <HeaderButtonWithText
+ onClick={() => {
+ // Set dialog open
+ dispatch(
+ setDialogMessage(
+ "Having trouble using Continue? Want a new feature? Let us know! This box is anonymous, but we will promptly address your feedback."
+ )
+ );
+ dispatch(setDialogEntryOn(true));
+ dispatch(setShowDialog(true));
+ }}
+ text="Feedback"
+ >
+ <ChatBubbleOvalLeftEllipsisIcon width="1.4em" height="1.4em" />
+ </HeaderButtonWithText>
+ </Footer>
+ </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..e018c03c 100644
--- a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
@@ -30,6 +30,10 @@ abstract class AbstractContinueGUIClientProtocol {
abstract showLogsAtIndex(index: number): void;
abstract selectContextItem(id: string, query: string): void;
+
+ abstract loadSession(session_id?: 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..c2285f6d 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -4,16 +4,21 @@ 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) {
+ console.log("Closing session: ", this.serverUrlWithSessionId);
+ this.messenger.close();
+ }
this.serverUrlWithSessionId = serverUrlWithSessionId;
+ this.useVscodeMessagePassing = useVscodeMessagePassing;
this.messenger = useVscodeMessagePassing
? new VscodeMessenger(serverUrlWithSessionId)
: new WebsocketMessenger(serverUrlWithSessionId);
@@ -24,26 +29,53 @@ 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);
+ }
+
+ loadSession(session_id?: string): void {
+ this.messenger?.send("load_session", { session_id });
+ }
+
+ onReconnectAtSession(session_id: string): void {
+ this.connectMessenger(
+ `${this.serverUrlWithSessionId.split("?")[0]}?session_id=${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 +85,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 +93,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/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/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/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 d69da57e..e96ffe65 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -1,60 +1,31 @@
import styled from "styled-components";
-import {
- defaultBorderRadius,
- secondaryDark,
- vscForeground,
-} from "../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";
-import {
- BookOpenIcon,
- ChatBubbleOvalLeftEllipsisIcon,
- TrashIcon,
-} 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";
import { postVscMessage } from "../vscode";
import UserInputContainer from "../components/UserInputContainer";
-import Onboarding from "../components/Onboarding";
import { isMetaEquivalentKeyPressed } from "../util";
import {
setBottomMessage,
- setBottomMessageCloseTimeout,
+ setDialogEntryOn,
+ setDialogMessage,
+ setDisplayBottomMessageOnBottom,
+ setShowDialog,
} from "../redux/slices/uiStateSlice";
import RingLoader from "../components/RingLoader";
-
-const TopGUIDiv = styled.div`
- overflow: hidden;
-`;
-
-const BottomMessageDiv = styled.div<{ displayOnBottom: boolean }>`
- position: fixed;
- bottom: ${(props) => (props.displayOnBottom ? "50px" : undefined)};
- top: ${(props) => (props.displayOnBottom ? undefined : "50px")};
- left: 0;
- right: 0;
- margin: 8px;
- margin-top: 0;
- background-color: ${secondaryDark};
- color: ${vscForeground};
- border-radius: ${defaultBorderRadius};
- padding: 12px;
- z-index: 100;
- box-shadow: 0px 0px 2px 0px ${vscForeground};
- max-height: 50vh;
- overflow: scroll;
-`;
+import {
+ setServerState,
+ temporarilySetUserInputQueue,
+} from "../redux/slices/serverStateReducer";
const UserInputQueueItem = styled.div`
border-radius: ${defaultBorderRadius};
@@ -64,47 +35,30 @@ const UserInputQueueItem = styled.div`
text-align: center;
`;
-const Footer = styled.footer<{ dataSwitchChecked: boolean }>`
- display: flex;
- flex-direction: row;
- gap: 8px;
- justify-content: right;
- padding: 8px;
- align-items: center;
- margin-top: 8px;
- border-top: 0.1px solid gray;
- background-color: ${(props) =>
- props.dataSwitchChecked ? "#12887a33" : "transparent"};
-`;
-
interface GUIProps {
firstObservation?: any;
}
function GUI(props: GUIProps) {
+ // #region Hooks
const client = useContext(GUIClientContext);
const posthog = usePostHog();
+ const dispatch = useDispatch();
- const vscMachineId = useSelector(
- (state: RootStore) => state.config.vscMachineId
- );
- const [dataSwitchChecked, setDataSwitchChecked] = useState(false);
- const dataSwitchOn = useSelector(
- (state: RootStore) => state.config.dataSwitchOn
- );
+ // #endregion
- useEffect(() => {
- if (typeof dataSwitchOn !== "undefined") {
- setDataSwitchChecked(dataSwitchOn);
- }
- }, [dataSwitchOn]);
+ // #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 }[]
>([]);
@@ -114,62 +68,33 @@ 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 vscMediaUrl = useSelector(
- (state: RootStore) => state.config.vscMediaUrl
- );
- const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
- const [feedbackDialogMessage, setFeedbackDialogMessage] = useState<
- string | JSX.Element
- >("");
- const [feedbackEntryOn, setFeedbackEntryOn] = useState(true);
+ const [waitingForClient, setWaitingForClient] = useState(true);
+ const [showLoading, setShowLoading] = useState(false);
- const dispatch = useDispatch();
- const bottomMessage = useSelector(
- (state: RootStore) => state.uiState.bottomMessage
- );
+ // #endregion
- const [displayBottomMessageOnBottom, setDisplayBottomMessageOnBottom] =
- useState<boolean>(true);
+ // #region Refs
const mainTextInputRef = useRef<HTMLInputElement>(null);
+ const topGuiDivRef = useRef<HTMLDivElement>(null);
+ // #endregion
- const aboveComboBoxDivRef = useRef<HTMLDivElement>(null);
+ // #region Effects
+ // Set displayBottomMessageOnBottom
+ const aboveComboBoxDivRef = useRef<HTMLDivElement>(null);
+ const bottomMessage = useSelector(
+ (state: RootStore) => state.uiState.bottomMessage
+ );
useEffect(() => {
if (!aboveComboBoxDivRef.current) return;
- if (
- aboveComboBoxDivRef.current.getBoundingClientRect().top >
- window.innerHeight / 2
- ) {
- setDisplayBottomMessageOnBottom(false);
- } else {
- setDisplayBottomMessageOnBottom(true);
- }
+ dispatch(
+ setDisplayBottomMessageOnBottom(
+ aboveComboBoxDivRef.current.getBoundingClientRect().top <
+ window.innerHeight / 2
+ )
+ );
}, [bottomMessage, aboveComboBoxDivRef.current]);
- const topGuiDivRef = useRef<HTMLDivElement>(null);
-
const [scrollTimeout, setScrollTimeout] = useState<NodeJS.Timeout | null>(
null
);
@@ -225,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 {
@@ -260,15 +183,16 @@ function GUI(props: GUIProps) {
scrollToBottom();
}, [waitingForSteps]);
- const [waitingForClient, setWaitingForClient] = useState(true);
+ // #endregion
+
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) {
@@ -279,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;
}
@@ -310,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");
@@ -323,62 +251,66 @@ function GUI(props: GUIProps) {
(currentCount + 1).toString()
);
if (currentCount === 25) {
- setFeedbackDialogMessage(
- <div className="text-center">
- 👋 Thanks for using Continue. We are a beta product and love
- working closely with our first users. If you're interested in
- speaking, enter your name and email. We won't use this information
- for anything other than reaching out.
- <br />
- <br />
- <form
- onSubmit={(e: any) => {
- e.preventDefault();
- posthog?.capture("user_interest_form", {
- name: e.target.elements[0].value,
- email: e.target.elements[1].value,
- });
- setFeedbackDialogMessage(
- <div className="text-center">
- Thanks! We'll be in touch soon.
- </div>
- );
- }}
- style={{
- display: "flex",
- flexDirection: "column",
- gap: "10px",
- }}
- >
- <input
- style={{ padding: "10px", borderRadius: "5px" }}
- type="text"
- name="name"
- placeholder="Name"
- required
- />
- <input
- style={{ padding: "10px", borderRadius: "5px" }}
- type="email"
- name="email"
- placeholder="Email"
- required
- />
- <button
+ dispatch(
+ setDialogMessage(
+ <div className="text-center">
+ 👋 Thanks for using Continue. We are a beta product and love
+ working closely with our first users. If you're interested in
+ speaking, enter your name and email. We won't use this
+ information for anything other than reaching out.
+ <br />
+ <br />
+ <form
+ onSubmit={(e: any) => {
+ e.preventDefault();
+ posthog?.capture("user_interest_form", {
+ name: e.target.elements[0].value,
+ email: e.target.elements[1].value,
+ });
+ dispatch(
+ setDialogMessage(
+ <div className="text-center">
+ Thanks! We'll be in touch soon.
+ </div>
+ )
+ );
+ }}
style={{
- padding: "10px",
- borderRadius: "5px",
- cursor: "pointer",
+ display: "flex",
+ flexDirection: "column",
+ gap: "10px",
}}
- type="submit"
>
- Submit
- </button>
- </form>
- </div>
+ <input
+ style={{ padding: "10px", borderRadius: "5px" }}
+ type="text"
+ name="name"
+ placeholder="Name"
+ required
+ />
+ <input
+ style={{ padding: "10px", borderRadius: "5px" }}
+ type="email"
+ name="email"
+ placeholder="Email"
+ required
+ />
+ <button
+ style={{
+ padding: "10px",
+ borderRadius: "5px",
+ cursor: "pointer",
+ }}
+ type="submit"
+ >
+ Submit
+ </button>
+ </form>
+ </div>
+ )
);
- setFeedbackEntryOn(false);
- setShowFeedbackDialog(true);
+ dispatch(setDialogEntryOn(false));
+ dispatch(setShowDialog(true));
}
} else {
localStorage.setItem("mainTextEntryCounter", "1");
@@ -391,7 +323,6 @@ function GUI(props: GUIProps) {
client.sendStepUserInput(input, index);
};
- const [showLoading, setShowLoading] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
setShowLoading(true);
@@ -401,219 +332,121 @@ function GUI(props: GUIProps) {
clearTimeout(timeout);
};
}, []);
-
- // const iterations = useSelector(selectIterations);
return (
- <>
- <Onboarding />
- <TextDialog
- showDialog={showFeedbackDialog}
- onEnter={(text) => {
- client?.sendMainInput(`/feedback ${text}`);
- setShowFeedbackDialog(false);
- }}
- onClose={() => {
- setShowFeedbackDialog(false);
- }}
- message={feedbackDialogMessage}
- entryOn={feedbackEntryOn}
- />
-
- <TopGUIDiv
- ref={topGuiDivRef}
- onKeyDown={(e) => {
- if (e.key === "Enter" && e.ctrlKey) {
- onMainTextInput();
- }
- }}
- >
- {showLoading && typeof client === "undefined" && (
- <>
- <RingLoader />
- <p
- style={{
- textAlign: "center",
- margin: "0px",
- fontSize: "14px",
- }}
- >
- Continue Server Starting
- </p>
- <p
- style={{
- margin: "auto",
- textAlign: "center",
- marginTop: "4px",
- fontSize: "12px",
- cursor: "pointer",
- opacity: 0.7,
- }}
- onClick={() => {
- postVscMessage("toggleDevTools", {});
- }}
- >
- <u>Click to view logs</u>
- </p>
- <div className="w-3/4 m-auto text-center text-xs">
- Tip: Drag the Continue logo from the far left of the window to the
- right, then toggle Continue using option/alt+command+m.
- </div>
- </>
- )}
- {history?.timeline.map((node: HistoryNode, index: number) => {
- return node.step.name === "User Input" ? (
- node.step.hide || (
- <UserInputContainer
- onDelete={() => {
- client?.deleteAtIndex(index);
- }}
- historyNode={node}
- >
- {node.step.description as string}
- </UserInputContainer>
- )
- ) : (
- <StepContainer
- index={index}
- isLast={index === history.timeline.length - 1}
- isFirst={index === 0}
- open={stepsOpen[index]}
- onToggle={() => {
- const nextStepsOpen = [...stepsOpen];
- nextStepsOpen[index] = !nextStepsOpen[index];
- setStepsOpen(nextStepsOpen);
- }}
- onToggleAll={() => {
- const shouldOpen = !stepsOpen[index];
- setStepsOpen((prev) => prev.map(() => shouldOpen));
- }}
- key={index}
- onUserInput={(input: string) => {
- onStepUserInput(input, index);
- }}
- inFuture={index > history?.current_index}
- historyNode={node}
- onReverse={() => {
- client?.reverseToIndex(index);
- }}
- onRetry={() => {
- client?.retryAtIndex(index);
- setWaitingForSteps(true);
- }}
+ <div
+ className="overflow-hidden"
+ ref={topGuiDivRef}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && e.ctrlKey) {
+ onMainTextInput();
+ }
+ }}
+ >
+ {showLoading && typeof client === "undefined" && (
+ <>
+ <RingLoader />
+ <p
+ style={{
+ textAlign: "center",
+ margin: "0px",
+ fontSize: "14px",
+ }}
+ >
+ Continue Server Starting
+ </p>
+ <p
+ style={{
+ margin: "auto",
+ textAlign: "center",
+ marginTop: "4px",
+ fontSize: "12px",
+ cursor: "pointer",
+ opacity: 0.7,
+ }}
+ onClick={() => {
+ postVscMessage("toggleDevTools", {});
+ }}
+ >
+ <u>Click to view logs</u>
+ </p>
+ <div className="w-3/4 m-auto text-center text-xs">
+ Tip: Drag the Continue logo from the far left of the window to the
+ right, then toggle Continue using option/alt+command+m.
+ </div>
+ </>
+ )}
+ {history?.timeline.map((node: HistoryNode, index: number) => {
+ return node.step.name === "User Input" ? (
+ node.step.hide || (
+ <UserInputContainer
onDelete={() => {
client?.deleteAtIndex(index);
}}
- />
- );
+ historyNode={node}
+ >
+ {node.step.description as string}
+ </UserInputContainer>
+ )
+ ) : (
+ <StepContainer
+ index={index}
+ isLast={index === history.timeline.length - 1}
+ isFirst={index === 0}
+ open={stepsOpen[index]}
+ onToggle={() => {
+ const nextStepsOpen = [...stepsOpen];
+ nextStepsOpen[index] = !nextStepsOpen[index];
+ setStepsOpen(nextStepsOpen);
+ }}
+ onToggleAll={() => {
+ const shouldOpen = !stepsOpen[index];
+ setStepsOpen((prev) => prev.map(() => shouldOpen));
+ }}
+ key={index}
+ onUserInput={(input: string) => {
+ onStepUserInput(input, index);
+ }}
+ inFuture={index > history?.current_index}
+ historyNode={node}
+ onReverse={() => {
+ client?.reverseToIndex(index);
+ }}
+ onRetry={() => {
+ client?.retryAtIndex(index);
+ setWaitingForSteps(true);
+ }}
+ onDelete={() => {
+ client?.deleteAtIndex(index);
+ }}
+ />
+ );
+ })}
+ {waitingForSteps && <Loader />}
+
+ <div>
+ {user_input_queue.map((input) => {
+ return <UserInputQueueItem>{input}</UserInputQueueItem>;
})}
- {waitingForSteps && <Loader></Loader>}
-
- <div>
- {userInputQueue.map((input) => {
- return <UserInputQueueItem>{input}</UserInputQueueItem>;
- })}
- </div>
-
- <div ref={aboveComboBoxDivRef} />
- <ComboBox
- ref={mainTextInputRef}
- onEnter={(e) => {
- onMainTextInput(e);
- e.stopPropagation();
- e.preventDefault();
- }}
- onInputValueChange={() => {}}
- items={availableSlashCommands}
- selectedContextItems={selectedContextItems}
- onToggleAddContext={() => {
- client?.toggleAddingHighlightedCode();
- }}
- addingHighlightedCode={addingHighlightedCode}
- />
- <ContinueButton onClick={onMainTextInput} />
- </TopGUIDiv>
- <BottomMessageDiv
- displayOnBottom={displayBottomMessageOnBottom}
- onMouseEnter={() => {
- dispatch(setBottomMessageCloseTimeout(undefined));
+ </div>
+
+ <div ref={aboveComboBoxDivRef} />
+ <ComboBox
+ ref={mainTextInputRef}
+ onEnter={(e) => {
+ onMainTextInput(e);
+ e.stopPropagation();
+ e.preventDefault();
}}
- onMouseLeave={(e) => {
- if (!e.buttons) {
- dispatch(setBottomMessage(undefined));
- }
+ onInputValueChange={() => {}}
+ items={availableSlashCommands}
+ selectedContextItems={selected_context_items}
+ onToggleAddContext={() => {
+ client?.toggleAddingHighlightedCode();
}}
- hidden={!bottomMessage}
- >
- {bottomMessage}
- </BottomMessageDiv>
- <Footer dataSwitchChecked={dataSwitchChecked}>
- {vscMediaUrl && (
- <a
- href="https://github.com/continuedev/continue"
- style={{ marginRight: "auto" }}
- >
- <img
- src={`${vscMediaUrl}/continue-dev-square.png`}
- width="22px"
- style={{ backgroundColor: "black", color: "red" }}
- />
- </a>
- )}
- <HeaderButtonWithText
- onClick={() => {
- // Show the dialog
- setFeedbackDialogMessage(
- `Continue uses GPT-4 by default, but works with any model. If you'd like to keep your code completely private, there are few options:
-
-Run a local model with ggml: [5 minute quickstart](https://github.com/continuedev/ggml-server-example)
-
-Use Azure OpenAI service, which is GDPR and HIPAA compliant: [Tutorial](https://continue.dev/docs/customization#azure-openai-service)
-
-If you already have an LLM deployed on your own infrastructure, or would like to do so, please contact us at hi@continue.dev.
- `
- );
- setFeedbackEntryOn(false);
- setShowFeedbackDialog(true);
- }}
- text={"Use Private Model"}
- >
- <div
- style={{ fontSize: "18px", marginLeft: "2px", marginRight: "2px" }}
- >
- 🔒
- </div>
- </HeaderButtonWithText>
- <HeaderButtonWithText
- onClick={() => {
- client?.sendClear();
- }}
- text="Clear"
- >
- <TrashIcon width="1.4em" height="1.4em" />
- </HeaderButtonWithText>
- <a
- href="https://continue.dev/docs/how-to-use-continue"
- className="no-underline"
- >
- <HeaderButtonWithText text="Docs">
- <BookOpenIcon width="1.4em" height="1.4em" />
- </HeaderButtonWithText>
- </a>
- <HeaderButtonWithText
- onClick={() => {
- // Set dialog open
- setFeedbackDialogMessage(
- "Having trouble using Continue? Want a new feature? Let us know! This box is anonymous, but we will promptly address your feedback."
- );
- setFeedbackEntryOn(true);
- setShowFeedbackDialog(true);
- }}
- text="Feedback"
- >
- <ChatBubbleOvalLeftEllipsisIcon width="1.4em" height="1.4em" />
- </HeaderButtonWithText>
- </Footer>
- </>
+ 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
new file mode 100644
index 00000000..c3240a66
--- /dev/null
+++ b/extension/react-app/src/pages/history.tsx
@@ -0,0 +1,98 @@
+import React, { useContext, useEffect, useState } from "react";
+import { SessionInfo } from "../../../schema/SessionInfo";
+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";
+import { ArrowLeftIcon } from "@heroicons/react/24/outline";
+
+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();
+ 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 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);
+ };
+ fetchSessions();
+ }, [client]);
+
+ return (
+ <div className="w-full">
+ <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) => (
+ <Tr key={index}>
+ <td>
+ <TdDiv
+ onClick={() => {
+ client?.loadSession(session.session_id);
+ navigate("/");
+ }}
+ >
+ <div className="text-lg">{session.title}</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>
+ ))}
+ </tbody>
+ </table>
+ <br />
+ <i className="text-sm ml-4">
+ All session data is saved in ~/.continue/sessions
+ </i>
+ </div>
+ );
+}
+
+export default History;
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/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/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/slices/uiStateSlice.ts b/extension/react-app/src/redux/slices/uiStateSlice.ts
index 837d19e9..d34596c9 100644
--- a/extension/react-app/src/redux/slices/uiStateSlice.ts
+++ b/extension/react-app/src/redux/slices/uiStateSlice.ts
@@ -5,6 +5,10 @@ export const uiStateSlice = createSlice({
initialState: {
bottomMessage: undefined,
bottomMessageCloseTimeout: undefined,
+ showDialog: false,
+ dialogMessage: "",
+ dialogEntryOn: false,
+ displayBottomMessageOnBottom: true,
},
reducers: {
setBottomMessage: (state, action) => {
@@ -16,9 +20,27 @@ export const uiStateSlice = createSlice({
}
state.bottomMessageCloseTimeout = action.payload;
},
+ setDialogMessage: (state, action) => {
+ state.dialogMessage = action.payload;
+ },
+ setDialogEntryOn: (state, action) => {
+ state.dialogEntryOn = action.payload;
+ },
+ setShowDialog: (state, action) => {
+ state.showDialog = action.payload;
+ },
+ setDisplayBottomMessageOnBottom: (state, action) => {
+ state.displayBottomMessageOnBottom = action.payload;
+ },
},
});
-export const { setBottomMessage, setBottomMessageCloseTimeout } =
- uiStateSlice.actions;
+export const {
+ setBottomMessage,
+ setBottomMessageCloseTimeout,
+ setDialogMessage,
+ setDialogEntryOn,
+ setShowDialog,
+ setDisplayBottomMessageOnBottom,
+} = uiStateSlice.actions;
export default uiStateSlice.reducer;
diff --git a/extension/react-app/src/redux/store.ts b/extension/react-app/src/redux/store.ts
index d49513e5..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;
@@ -35,16 +32,21 @@ export interface RootStore {
uiState: {
bottomMessage: JSX.Element | undefined;
bottomMessageCloseTimeout: NodeJS.Timeout | undefined;
+ displayBottomMessageOnBottom: boolean;
+ showDialog: boolean;
+ dialogMessage: string | JSX.Element;
+ dialogEntryOn: boolean;
};
+ serverState: FullState;
}
const store = configureStore({
reducer: {
- debugState: debugStateReducer,
chat: chatReducer,
config: configReducer,
misc: miscReducer,
uiState: uiStateReducer,
+ serverState: serverStateReducer,
},
});