summaryrefslogtreecommitdiff
path: root/extension/react-app/src/components/Suggestions.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'extension/react-app/src/components/Suggestions.tsx')
-rw-r--r--extension/react-app/src/components/Suggestions.tsx228
1 files changed, 228 insertions, 0 deletions
diff --git a/extension/react-app/src/components/Suggestions.tsx b/extension/react-app/src/components/Suggestions.tsx
new file mode 100644
index 00000000..1709288c
--- /dev/null
+++ b/extension/react-app/src/components/Suggestions.tsx
@@ -0,0 +1,228 @@
+import React, { useCallback, useEffect, useState } from "react";
+import styled from "styled-components";
+import {
+ StyledTooltip,
+ defaultBorderRadius,
+ lightGray,
+ secondaryDark,
+ vscForeground,
+} from ".";
+import {
+ PaperAirplaneIcon,
+ SparklesIcon,
+ XMarkIcon,
+} from "@heroicons/react/24/outline";
+import { useSelector } from "react-redux";
+import { RootStore } from "../redux/store";
+import HeaderButtonWithText from "./HeaderButtonWithText";
+
+const Div = styled.div<{ isDisabled: boolean }>`
+ border-radius: ${defaultBorderRadius};
+ cursor: ${(props) => (props.isDisabled ? "not-allowed" : "pointer")};
+ padding: 8px 8px;
+ background-color: ${secondaryDark};
+ border: 1px solid transparent;
+
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ color: ${(props) => (props.isDisabled ? lightGray : vscForeground)};
+
+ &:hover {
+ border: ${(props) =>
+ props.isDisabled ? "1px solid transparent" : `1px solid ${lightGray}`};
+ }
+`;
+
+const P = styled.p`
+ font-size: 13px;
+ margin: 0;
+`;
+
+interface SuggestionsDivProps {
+ title: string;
+ description: string;
+ textInput: string;
+ onClick?: () => void;
+ disabled: boolean;
+}
+
+function SuggestionsDiv(props: SuggestionsDivProps) {
+ const [isHovered, setIsHovered] = useState(false);
+
+ return (
+ <>
+ <Div
+ data-tooltip-id={`suggestion-disabled-${props.textInput.replace(
+ " ",
+ ""
+ )}`}
+ onClick={props.onClick}
+ onMouseEnter={() => {
+ if (props.disabled) return;
+ setIsHovered(true);
+ }}
+ onMouseLeave={() => setIsHovered(false)}
+ isDisabled={props.disabled}
+ >
+ <P>{props.description}</P>
+ <PaperAirplaneIcon
+ width="1.6em"
+ height="1.6em"
+ style={{
+ opacity: isHovered ? 1 : 0,
+ backgroundColor: secondaryDark,
+ boxShadow: `1px 1px 10px ${secondaryDark}`,
+ borderRadius: defaultBorderRadius,
+ }}
+ />
+ </Div>
+ <StyledTooltip
+ id={`suggestion-disabled-${props.textInput.replace(" ", "")}`}
+ place="bottom"
+ hidden={!props.disabled}
+ >
+ Must highlight code first
+ </StyledTooltip>
+ </>
+ );
+}
+
+const stageDescriptions = [
+ <p>Ask a question</p>,
+ <ol>
+ <li>Highlight code in the editor</li>
+ <li>Press cmd+M to select the code</li>
+ <li>Ask a question</li>
+ </ol>,
+ <ol>
+ <li>Highlight code in the editor</li>
+ <li>Press cmd+shift+M to select the code</li>
+ <li>Request and edit</li>
+ </ol>,
+];
+
+const suggestionsStages: any[][] = [
+ [
+ {
+ title: stageDescriptions[0],
+ description: "How does merge sort work?",
+ textInput: "How does merge sort work?",
+ },
+ {
+ title: stageDescriptions[0],
+ description: "How do I sum over a column in SQL?",
+ textInput: "How do I sum over a column in SQL?",
+ },
+ ],
+ [
+ {
+ title: stageDescriptions[1],
+ description: "Is there any way to make this code more efficient?",
+ textInput: "Is there any way to make this code more efficient?",
+ },
+ {
+ title: stageDescriptions[1],
+ description: "What does this function do?",
+ textInput: "What does this function do?",
+ },
+ ],
+ [
+ {
+ title: stageDescriptions[2],
+ description: "/edit write comments for this code",
+ textInput: "/edit write comments for this code",
+ },
+ {
+ title: stageDescriptions[2],
+ description: "/edit make this code more efficient",
+ textInput: "/edit make this code more efficient",
+ },
+ ],
+];
+
+const TutorialDiv = styled.div`
+ margin: 4px;
+ position: relative;
+ background-color: #ff02;
+ border-radius: ${defaultBorderRadius};
+ padding: 8px 4px;
+`;
+
+function SuggestionsArea(props: { onClick: (textInput: string) => void }) {
+ const [stage, setStage] = useState(
+ parseInt(localStorage.getItem("stage") || "0")
+ );
+ const timeline = useSelector(
+ (state: RootStore) => state.serverState.history.timeline
+ );
+ const sessionId = useSelector(
+ (state: RootStore) => state.serverState.session_info?.session_id
+ );
+ const codeIsHighlighted = useSelector((state: RootStore) =>
+ state.serverState.selected_context_items.some(
+ (item) => item.description.id.provider_title === "code"
+ )
+ );
+
+ const [hide, setHide] = useState(false);
+
+ useEffect(() => {
+ setHide(false);
+ }, [sessionId]);
+
+ const [numTutorialInputs, setNumTutorialInputs] = useState(0);
+
+ const inputsAreOnlyTutorial = useCallback(() => {
+ const inputs = timeline.filter(
+ (node) => !node.step.hide && node.step.name === "User Input"
+ );
+ return inputs.length - numTutorialInputs === 0;
+ }, [timeline, numTutorialInputs]);
+
+ return (
+ <>
+ {hide || stage > 2 || !inputsAreOnlyTutorial() || (
+ <TutorialDiv>
+ <div className="flex">
+ <SparklesIcon width="1.3em" height="1.3em" color="yellow" />
+ <b className="ml-1">Tutorial</b>
+ </div>
+ <p style={{ color: lightGray }}>
+ {stage < suggestionsStages.length &&
+ suggestionsStages[stage][0]?.title}
+ </p>
+ <HeaderButtonWithText
+ className="absolute right-1 top-1 cursor-pointer"
+ text="Close Tutorial"
+ onClick={() => {
+ console.log("HIDE");
+ setHide(true);
+ }}
+ >
+ <XMarkIcon width="1.2em" height="1.2em" />
+ </HeaderButtonWithText>
+ <div className="grid grid-cols-2 gap-2 mt-2">
+ {suggestionsStages[stage]?.map((suggestion) => (
+ <SuggestionsDiv
+ disabled={stage > 0 && !codeIsHighlighted}
+ {...suggestion}
+ onClick={() => {
+ if (stage > 0 && !codeIsHighlighted) return;
+ props.onClick(suggestion.textInput);
+ setStage(stage + 1);
+ localStorage.setItem("stage", (stage + 1).toString());
+ setHide(true);
+ setNumTutorialInputs((prev) => prev + 1);
+ }}
+ />
+ ))}
+ </div>
+ </TutorialDiv>
+ )}
+ </>
+ );
+}
+
+export default SuggestionsArea;