diff options
Diffstat (limited to 'extension/react-app/src')
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, }, }); |