From 2d3d96e5b55a225eb97251850909eb7a0a7242ed Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 20 Aug 2023 20:21:33 -0700 Subject: feat: :sparkles: delete context groups --- continuedev/src/continuedev/core/autopilot.py | 25 ++++- continuedev/src/continuedev/server/gui.py | 7 ++ extension/react-app/src/components/ComboBox.tsx | 109 ++------------------ extension/react-app/src/components/TextDialog.tsx | 13 +++ .../components/dialogs/AddContextGroupDialog.tsx | 50 ++++++++++ .../dialogs/SelectContextGroupDialog.tsx | 111 +++++++++++++++++++++ .../src/hooks/AbstractContinueGUIClientProtocol.ts | 2 + .../src/hooks/ContinueGUIClientProtocol.ts | 4 + 8 files changed, 213 insertions(+), 108 deletions(-) create mode 100644 extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx create mode 100644 extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 2e255198..05a8a8f2 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -544,11 +544,7 @@ class Autopilot(ContinueBaseModel): _saved_context_groups: Dict[str, List[ContextItem]] = {} - async def save_context_group(self, title: str, context_items: List[ContextItem]): - self._saved_context_groups[title] = context_items - await self.update_subscribers() - - # Update saved context groups + def _persist_context_groups(self): context_groups_file = getSavedContextGroupsPath() if os.path.exists(context_groups_file): with open(context_groups_file, "w") as f: @@ -558,6 +554,13 @@ class Autopilot(ContinueBaseModel): } json.dump(dict_to_save, f) + async def save_context_group(self, title: str, context_items: List[ContextItem]): + self._saved_context_groups[title] = context_items + await self.update_subscribers() + + # Update saved context groups + self._persist_context_groups() + posthog_logger.capture_event( "save_context_group", {"title": title, "length": len(context_items)} ) @@ -575,3 +578,15 @@ class Autopilot(ContinueBaseModel): posthog_logger.capture_event( "select_context_group", {"title": id, "length": len(context_group)} ) + + async def delete_context_group(self, id: str): + if id not in self._saved_context_groups: + logger.warning(f"Context group {id} not found") + return + del self._saved_context_groups[id] + await self.update_subscribers() + + # Update saved context groups + self._persist_context_groups() + + posthog_logger.capture_event("delete_context_group", {"title": id}) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index a4c45a06..5589284a 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -111,6 +111,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer): ) elif message_type == "select_context_group": self.select_context_group(data["id"]) + elif message_type == "delete_context_group": + self.delete_context_group(data["id"]) def on_main_input(self, input: str): # Do something with user input @@ -204,6 +206,11 @@ class GUIProtocolServer(AbstractGUIProtocolServer): self.session.autopilot.select_context_group(id), self.on_error ) + def delete_context_group(self, id: str): + create_async_task( + self.session.autopilot.delete_context_group(id), self.on_error + ) + @router.websocket("/ws") async def websocket_endpoint( diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index c407a779..31cb4371 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -7,8 +7,6 @@ import React, { import { useCombobox } from "downshift"; import styled from "styled-components"; import { - Button, - TextInput, defaultBorderRadius, lightGray, secondaryDark, @@ -33,44 +31,14 @@ import { } from "../redux/slices/uiStateSlice"; import { useDispatch, useSelector } from "react-redux"; import { RootStore } from "../redux/store"; +import SelectContextGroupDialog from "./dialogs/SelectContextGroupDialog"; +import AddContextGroupDialog from "./dialogs/AddContextGroupDialog"; const SEARCH_INDEX_NAME = "continue_context_items"; // #region styled components const mainInputFontSize = 13; -const MiniPillSpan = styled.span` - padding: 3px; - padding-left: 6px; - padding-right: 6px; - border-radius: ${defaultBorderRadius}; - color: ${vscForeground}; - background-color: #fff3; - overflow: hidden; - font-size: 12px; - display: flex; - align-items: center; - text-align: center; - justify-content: center; -`; - -const ContextGroupSelectDiv = styled.div` - display: flex; - align-items: center; - gap: 8px; - padding: 8px; - border-radius: ${defaultBorderRadius}; - background-color: ${secondaryDark}; - color: ${vscForeground}; - margin-top: 8px; - cursor: pointer; - - &:hover { - background-color: ${vscBackground}; - color: ${vscForeground}; - } -`; - const EmptyPillDiv = styled.div` padding: 4px; padding-left: 8px; @@ -374,81 +342,16 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { }, [inputRef.current]); const showSelectContextGroupDialog = () => { - dispatch( - setDialogMessage( -
-

Saved Context Groups

- - {savedContextGroups && Object.keys(savedContextGroups).length > 0 ? ( -
- {Object.keys(savedContextGroups).map((key: string) => { - const contextGroup = savedContextGroups[key]; - return ( - { - dispatch(setDialogMessage(undefined)); - dispatch(setShowDialog(false)); - client?.selectContextGroup(key); - }} - > - {key}: - - {contextGroup.map((contextItem) => { - return ( - - {contextItem.description.name} - - ); - })} - - ); - })} -
- ) : ( -
No saved context groups
- )} - -
- ) - ); + dispatch(setDialogMessage()); dispatch(setShowDialog(true)); }; const showDialogToSaveContextGroup = () => { - let inputElement: HTMLInputElement | null = null; dispatch( setDialogMessage( -
- { - inputElement = input; - }} - /> -
- -
+ ) ); dispatch(setShowDialog(true)); diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index 7fcc41f1..44d25ae6 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -58,6 +58,19 @@ const TextDialog = (props: { onClose: () => void; message?: string | JSX.Element; }) => { + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + props.onClose(); + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, [props]); + return ( { diff --git a/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx b/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx new file mode 100644 index 00000000..f6c7c626 --- /dev/null +++ b/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx @@ -0,0 +1,50 @@ +import { useContext } from "react"; +import { Button, TextInput } from ".."; +import { GUIClientContext } from "../../App"; +import { useDispatch } from "react-redux"; +import { + setDialogMessage, + setShowDialog, +} from "../../redux/slices/uiStateSlice"; +import { ContextItem } from "../../../../schema/FullState"; + +function AddContextGroupDialog({ + selectedContextItems, +}: { + selectedContextItems: ContextItem[]; +}) { + const dispatch = useDispatch(); + const client = useContext(GUIClientContext); + + let inputElement: HTMLInputElement | null = null; + + const handleCreate = () => { + dispatch(setDialogMessage(undefined)); + dispatch(setShowDialog(false)); + const title = inputElement ? inputElement.value : "My Context Group"; + client?.saveContextGroup(title, selectedContextItems); + }; + + return ( +
+ { + inputElement = input; + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleCreate(); + } + }} + /> +
+ +
+ ); +} + +export default AddContextGroupDialog; diff --git a/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx b/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx new file mode 100644 index 00000000..b8fc2bb7 --- /dev/null +++ b/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx @@ -0,0 +1,111 @@ +import { useDispatch, useSelector } from "react-redux"; +import { RootStore } from "../../redux/store"; +import { useContext } from "react"; +import { GUIClientContext } from "../../App"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import HeaderButtonWithText from "../HeaderButtonWithText"; +import { + setDialogMessage, + setShowDialog, +} from "../../redux/slices/uiStateSlice"; +import { + Button, + defaultBorderRadius, + secondaryDark, + vscBackground, + vscForeground, +} from ".."; +import styled from "styled-components"; + +const MiniPillSpan = styled.span` + padding: 3px; + padding-left: 6px; + padding-right: 6px; + border-radius: ${defaultBorderRadius}; + color: ${vscForeground}; + background-color: #fff3; + overflow: hidden; + font-size: 12px; + display: flex; + align-items: center; + text-align: center; + justify-content: center; +`; + +const ContextGroupSelectDiv = styled.div` + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + border-radius: ${defaultBorderRadius}; + background-color: ${secondaryDark}; + color: ${vscForeground}; + margin-top: 8px; + cursor: pointer; + + &:hover { + background-color: ${vscBackground}; + color: ${vscForeground}; + } +`; + +function SelectContextGroupDialog() { + const dispatch = useDispatch(); + const savedContextGroups = useSelector( + (state: RootStore) => state.serverState.saved_context_groups + ); + const client = useContext(GUIClientContext); + + return ( +
+

Saved Context Groups

+ + {savedContextGroups && Object.keys(savedContextGroups).length > 0 ? ( +
+ {Object.keys(savedContextGroups).map((key: string) => { + const contextGroup = savedContextGroups[key]; + return ( + { + dispatch(setDialogMessage(undefined)); + dispatch(setShowDialog(false)); + client?.selectContextGroup(key); + }} + > + {key}: + + {contextGroup.map((contextItem) => { + return ( + {contextItem.description.name} + ); + })} + { + e.stopPropagation(); + client?.deleteContextGroup(key); + }} + > + + + + ); + })} +
+ ) : ( +
No saved context groups
+ )} + +
+ ); +} + +export default SelectContextGroupDialog; diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts index 2e8aaeef..c9e7def2 100644 --- a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts @@ -40,6 +40,8 @@ abstract class AbstractContinueGUIClientProtocol { abstract saveContextGroup(title: string, contextItems: ContextItem[]): void; abstract selectContextGroup(id: string): void; + + abstract deleteContextGroup(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 aa558adb..b3ac2570 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -143,6 +143,10 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { selectContextGroup(id: string): void { this.messenger?.send("select_context_group", { id }); } + + deleteContextGroup(id: string): void { + this.messenger?.send("delete_context_group", { id }); + } } export default ContinueGUIClientProtocol; -- cgit v1.2.3-70-g09d2