diff options
Diffstat (limited to 'extension/react-app/src')
4 files changed, 138 insertions, 19 deletions
diff --git a/extension/react-app/src/components/UserInputContainer.tsx b/extension/react-app/src/components/UserInputContainer.tsx index 90cd549b..9784a615 100644 --- a/extension/react-app/src/components/UserInputContainer.tsx +++ b/extension/react-app/src/components/UserInputContainer.tsx @@ -1,16 +1,24 @@ -import React, { useState } from "react"; +import React, { useContext, useEffect, useRef, useState } from "react"; import ReactMarkdown from "react-markdown"; import styled from "styled-components"; -import { defaultBorderRadius, secondaryDark, vscBackground } from "."; +import { + defaultBorderRadius, + secondaryDark, + vscBackground, + vscForeground, +} from "."; import HeaderButtonWithText from "./HeaderButtonWithText"; -import { XMarkIcon } from "@heroicons/react/24/outline"; +import { XMarkIcon, PencilIcon, CheckIcon } from "@heroicons/react/24/outline"; import { HistoryNode } from "../../../schema/HistoryNode"; import StyledMarkdownPreview from "./StyledMarkdownPreview"; +import { GUIClientContext } from "../App"; +import { text } from "stream/consumers"; interface UserInputContainerProps { onDelete: () => void; children: string; historyNode: HistoryNode; + index: number; } const StyledDiv = styled.div` @@ -40,8 +48,69 @@ const StyledPre = styled.pre` font-size: 13px; `; +const TextArea = styled.textarea` + margin: 8px; + margin-right: 22px; + padding: 8px; + white-space: pre-wrap; + word-wrap: break-word; + font-family: "Lexend", sans-serif; + font-size: 13px; + width: 100%; + border-radius: ${defaultBorderRadius}; + height: 100%; + border: none; + background-color: ${vscBackground}; + resize: none; + outline: none; + border: none; + color: ${vscForeground}; + + &:focus { + border: none; + outline: none; + } +`; + const UserInputContainer = (props: UserInputContainerProps) => { const [isHovered, setIsHovered] = useState(false); + const [isEditing, setIsEditing] = useState(false); + + const textAreaRef = useRef<HTMLTextAreaElement>(null); + const client = useContext(GUIClientContext); + + useEffect(() => { + if (isEditing) { + textAreaRef.current?.focus(); + // Select all text + textAreaRef.current?.setSelectionRange( + 0, + textAreaRef.current.value.length + ); + } + }, [isEditing]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setIsEditing(false); + } + }; + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + const doneEditing = (e: any) => { + if (!textAreaRef.current?.value) { + return; + } + client?.editStepAtIndex(textAreaRef.current.value, props.index); + setIsEditing(false); + e.stopPropagation(); + }; + return ( <StyledDiv onMouseEnter={() => { @@ -51,24 +120,64 @@ const UserInputContainer = (props: UserInputContainerProps) => { setIsHovered(false); }} > - {/* <StyledMarkdownPreview - light={true} - source={props.children} - className="mr-6" - /> */} - <StyledPre className="mr-6">{props.children}</StyledPre> + {isEditing ? ( + <TextArea + ref={textAreaRef} + onKeyDown={(e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + doneEditing(e); + } + }} + defaultValue={props.children} + /> + ) : ( + <StyledPre + onClick={() => { + setIsEditing(true); + }} + className="mr-6 cursor-text w-full" + > + {props.children} + </StyledPre> + )} {/* <ReactMarkdown children={props.children} className="w-fit mr-10" /> */} <DeleteButtonDiv> - {isHovered && ( - <HeaderButtonWithText - onClick={(e) => { - props.onDelete(); - e.stopPropagation(); - }} - text="Delete" - > - <XMarkIcon width="1.4em" height="1.4em" /> - </HeaderButtonWithText> + {(isHovered || isEditing) && ( + <div className="flex"> + {isEditing ? ( + <HeaderButtonWithText + onClick={(e) => { + doneEditing(e); + }} + text="Done" + > + <CheckIcon width="1.4em" height="1.4em" /> + </HeaderButtonWithText> + ) : ( + <> + <HeaderButtonWithText + onClick={(e) => { + setIsEditing((prev) => !prev); + e.stopPropagation(); + }} + text="Edit" + > + <PencilIcon width="1.4em" height="1.4em" /> + </HeaderButtonWithText> + + <HeaderButtonWithText + onClick={(e) => { + props.onDelete(); + e.stopPropagation(); + }} + text="Delete" + > + <XMarkIcon width="1.4em" height="1.4em" /> + </HeaderButtonWithText> + </> + )} + </div> )} </DeleteButtonDiv> </StyledDiv> diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts index e018c03c..804362aa 100644 --- a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts @@ -34,6 +34,8 @@ abstract class AbstractContinueGUIClientProtocol { abstract loadSession(session_id?: string): void; abstract onReconnectAtSession(session_id: string): void; + + abstract editStepAtIndex(userInput: string, index: number): void; } export default AbstractContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index c2285f6d..82aeee28 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -125,6 +125,13 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { selectContextItem(id: string, query: string): void { this.messenger?.send("select_context_item", { id, query }); } + + editStepAtIndex(userInput: string, index: number): void { + this.messenger?.send("edit_step_at_index", { + user_input: userInput, + index, + }); + } } export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index 4c89bbaa..a4a3d379 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -412,6 +412,7 @@ function GUI(props: GUIProps) { return node.step.name === "User Input" ? ( node.step.hide || ( <UserInputContainer + index={index} onDelete={() => { client?.deleteAtIndex(index); }} |