diff options
Diffstat (limited to 'extension/react-app/src/components')
9 files changed, 205 insertions, 80 deletions
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index c08c05de..1d0ca1a5 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -285,15 +285,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { useEffect(() => { if (!inputRef.current) return; - if (inputRef.current.scrollHeight > inputRef.current.clientHeight) { - inputRef.current.style.height = "auto"; - inputRef.current.style.height = - Math.min(inputRef.current.scrollHeight, 300) + "px"; - } + inputRef.current.style.height = "auto"; + inputRef.current.style.height = + Math.min(inputRef.current.scrollHeight, 300) + "px"; }, [ inputRef.current?.scrollHeight, inputRef.current?.clientHeight, - props.value, + inputRef.current?.value, ]); // Whether the current input follows an '@' and should be treated as context query @@ -344,7 +342,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { useEffect(() => { if (!nestedContextProvider) { - dispatch(setTakenActionTrue(null)); setItems( contextProviders?.map((provider) => ({ name: provider.display_title, @@ -437,7 +434,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { setNestedContextProvider(undefined); // Handle slash commands - dispatch(setTakenActionTrue(null)); setItems( availableSlashCommands?.filter((slashCommand) => { const sc = slashCommand.name.toLowerCase(); @@ -445,6 +441,10 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { return sc.startsWith(iv) && sc !== iv; }) || [] ); + + if (inputValue.startsWith("/") || inputValue.startsWith("@")) { + dispatch(setTakenActionTrue(null)); + } }, [ availableSlashCommands, @@ -756,6 +756,8 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { props.index ); inputRef.current?.focus(); + setPreviewingContextItem(undefined); + setFocusedContextItem(undefined); }} onKeyDown={(e: any) => { if (e.key === "Backspace") { @@ -880,6 +882,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { paddingLeft: "12px", cursor: "default", paddingTop: getFontSize(), + width: "fit-content", }} > {props.active ? "Using" : "Used"} {selectedContextItems.length}{" "} @@ -937,17 +940,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { {...getInputProps({ onCompositionStart: () => setIsComposing(true), onCompositionEnd: () => setIsComposing(false), - onChange: (e) => { - const target = e.target as HTMLTextAreaElement; - // Update the height of the textarea to match the content, up to a max of 200px. - target.style.height = "auto"; - target.style.height = `${Math.min( - target.scrollHeight, - 300 - ).toString()}px`; - - // setShowContextDropdown(target.value.endsWith("@")); - }, + onChange: (e) => {}, onFocus: (e) => { setInputFocused(true); dispatch(setBottomMessage(undefined)); diff --git a/extension/react-app/src/components/ErrorStepContainer.tsx b/extension/react-app/src/components/ErrorStepContainer.tsx index 666780c5..07c0a046 100644 --- a/extension/react-app/src/components/ErrorStepContainer.tsx +++ b/extension/react-app/src/components/ErrorStepContainer.tsx @@ -42,7 +42,7 @@ function ErrorStepContainer(props: ErrorStepContainerProps) { </HeaderButtonWithText> </div> <Div> - <pre className="overflow-x-scroll"> + <pre style={{ whiteSpace: "pre-wrap", wordWrap: "break-word" }}> {props.historyNode.observation?.error as string} </pre> </Div> diff --git a/extension/react-app/src/components/Layout.tsx b/extension/react-app/src/components/Layout.tsx index a54c0ed4..db31c8db 100644 --- a/extension/react-app/src/components/Layout.tsx +++ b/extension/react-app/src/components/Layout.tsx @@ -30,6 +30,20 @@ const LayoutTopDiv = styled.div` border-radius: ${defaultBorderRadius}; scrollbar-base-color: transparent; scrollbar-width: thin; + + & * { + ::-webkit-scrollbar { + width: 4px; + } + + ::-webkit-scrollbar:horizontal { + height: 4px; + } + + ::-webkit-scrollbar-thumb { + border-radius: 2px; + } + } `; const BottomMessageDiv = styled.div<{ displayOnBottom: boolean }>` @@ -47,7 +61,6 @@ const BottomMessageDiv = styled.div<{ displayOnBottom: boolean }>` z-index: 100; box-shadow: 0px 0px 2px 0px ${vscForeground}; max-height: 35vh; - overflow: scroll; `; const Footer = styled.footer` @@ -131,6 +144,20 @@ const Layout = () => { }; }, [client, timeline]); + useEffect(() => { + const handler = (event: any) => { + if (event.data.type === "addModel") { + navigate("/models"); + } else if (event.data.type === "openSettings") { + navigate("/settings"); + } + }; + window.addEventListener("message", handler); + return () => { + window.removeEventListener("message", handler); + }; + }, []); + return ( <LayoutTopDiv> <div diff --git a/extension/react-app/src/components/ModelCard.tsx b/extension/react-app/src/components/ModelCard.tsx index d1cb3165..0ab6ac32 100644 --- a/extension/react-app/src/components/ModelCard.tsx +++ b/extension/react-app/src/components/ModelCard.tsx @@ -1,16 +1,16 @@ -import React, { useContext } from "react"; +import React, { useContext, useState } from "react"; import styled from "styled-components"; import { buttonColor, defaultBorderRadius, lightGray } from "."; import { useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import { BookOpenIcon } from "@heroicons/react/24/outline"; import HeaderButtonWithText from "./HeaderButtonWithText"; -import { MODEL_PROVIDER_TAG_COLORS } from "../util/modelData"; +import { MODEL_PROVIDER_TAG_COLORS, PackageDimension } from "../util/modelData"; +import InfoHover from "./InfoHover"; -const Div = styled.div<{ color: string; disabled: boolean }>` +const Div = styled.div<{ color: string; disabled: boolean; hovered: boolean }>` border: 1px solid ${lightGray}; border-radius: ${defaultBorderRadius}; - padding: 4px 8px; position: relative; width: 100%; transition: all 0.5s; @@ -20,13 +20,45 @@ const Div = styled.div<{ color: string; disabled: boolean }>` ? ` opacity: 0.5; ` - : ` - &:hover { + : props.hovered + ? ` border: 1px solid ${props.color}; background-color: ${props.color}22; + cursor: pointer;` + : ""} +`; + +const DimensionsDiv = styled.div` + display: flex; + justify-content: flex-end; + margin-left: auto; + padding: 4px; + /* width: fit-content; */ + + border-top: 1px solid ${lightGray}; +`; + +const DimensionOptionDiv = styled.div<{ selected: boolean }>` + display: flex; + flex-direction: column; + align-items: center; + margin-right: 8px; + background-color: ${lightGray}; + padding: 4px; + border-radius: ${defaultBorderRadius}; + outline: 0.5px solid ${lightGray}; + + ${(props) => + props.selected && + ` + background-color: ${buttonColor}; + color: white; + `} + + &:hover { cursor: pointer; + outline: 1px solid ${buttonColor}; } - `} `; interface ModelCardProps { @@ -35,8 +67,12 @@ interface ModelCardProps { tags?: string[]; refUrl?: string; icon?: string; - onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void; + onClick: ( + e: React.MouseEvent<HTMLDivElement, MouseEvent>, + dimensionChoices?: string[] + ) => void; disabled?: boolean; + dimensions?: PackageDimension[]; } function ModelCard(props: ModelCardProps) { @@ -44,53 +80,103 @@ function ModelCard(props: ModelCardProps) { (state: RootStore) => state.config.vscMediaUrl ); + const [dimensionChoices, setDimensionChoices] = useState<string[]>( + props.dimensions?.map((d) => Object.keys(d.options)[0]) || [] + ); + + const [hovered, setHovered] = useState(false); + return ( <Div disabled={props.disabled || false} color={buttonColor} - onClick={props.disabled ? undefined : (e) => props.onClick(e)} + hovered={hovered} > - <div style={{ display: "flex", alignItems: "center" }}> - {vscMediaUrl && props.icon && ( - <img - src={`${vscMediaUrl}/logos/${props.icon}`} - height="24px" - style={{ marginRight: "10px" }} - /> - )} - <h3>{props.title}</h3> - </div> - {props.tags?.map((tag) => { - return ( - <span + <div + onMouseEnter={() => setHovered(true)} + onMouseLeave={() => setHovered(false)} + className="px-2 py-1" + onClick={ + props.disabled + ? undefined + : (e) => { + if ((e.target as any).closest("a")) { + return; + } + props.onClick(e, dimensionChoices); + } + } + > + <div style={{ display: "flex", alignItems: "center" }}> + {vscMediaUrl && props.icon && ( + <img + src={`${vscMediaUrl}/logos/${props.icon}`} + height="24px" + style={{ marginRight: "10px" }} + /> + )} + <h3>{props.title}</h3> + </div> + {props.tags?.map((tag) => { + return ( + <span + style={{ + backgroundColor: `${MODEL_PROVIDER_TAG_COLORS[tag]}55`, + color: "white", + padding: "2px 4px", + borderRadius: defaultBorderRadius, + marginRight: "4px", + }} + > + {tag} + </span> + ); + })} + <p>{props.description}</p> + + {props.refUrl && ( + <a style={{ - backgroundColor: `${MODEL_PROVIDER_TAG_COLORS[tag]}55`, - color: "white", - padding: "2px 4px", - borderRadius: defaultBorderRadius, - marginRight: "4px", + position: "absolute", + right: "8px", + top: "8px", }} + href={props.refUrl} + target="_blank" > - {tag} - </span> - ); - })} - <p>{props.description}</p> + <HeaderButtonWithText text="Read the docs"> + <BookOpenIcon width="1.6em" height="1.6em" /> + </HeaderButtonWithText> + </a> + )} + </div> - {props.refUrl && ( - <a - style={{ - position: "absolute", - right: "8px", - top: "8px", - }} - href={props.refUrl} - target="_blank" - > - <HeaderButtonWithText text="Read the docs"> - <BookOpenIcon width="1.6em" height="1.6em" /> - </HeaderButtonWithText> - </a> + {props.dimensions?.length && ( + <DimensionsDiv> + {props.dimensions?.map((dimension, i) => { + return ( + <div className="flex items-center"> + <InfoHover msg={dimension.description} /> + <p className="mx-2 text-sm my-0 py-0">{dimension.name}</p> + {Object.keys(dimension.options).map((key) => { + return ( + <DimensionOptionDiv + onClick={(e) => { + e.stopPropagation(); + const newChoices = [...dimensionChoices]; + newChoices[i] = key; + setDimensionChoices(newChoices); + }} + selected={dimensionChoices[i] === key} + > + {key} + </DimensionOptionDiv> + ); + })} + </div> + ); + })} + </DimensionsDiv> )} </Div> ); diff --git a/extension/react-app/src/components/ModelSettings.tsx b/extension/react-app/src/components/ModelSettings.tsx index 4b9d5e64..3f9414b1 100644 --- a/extension/react-app/src/components/ModelSettings.tsx +++ b/extension/react-app/src/components/ModelSettings.tsx @@ -3,7 +3,7 @@ import { LLM } from "../../../schema/LLM"; import { Label, Select, - TextInput, + Input, defaultBorderRadius, lightGray, vscForeground, @@ -58,7 +58,7 @@ function ModelSettings(props: { llm: any | undefined; role: string }) { {typeof modelOptions.api_key !== undefined && ( <> <Label fontSize={getFontSize()}>API Key</Label> - <TextInput + <Input type="text" defaultValue={props.llm.api_key} placeholder="API Key" @@ -69,7 +69,7 @@ function ModelSettings(props: { llm: any | undefined; role: string }) { {modelOptions.model && ( <> <Label fontSize={getFontSize()}>Model</Label> - <TextInput + <Input type="text" defaultValue={props.llm.model} placeholder="Model" diff --git a/extension/react-app/src/components/Suggestions.tsx b/extension/react-app/src/components/Suggestions.tsx index bdda7579..5779eea8 100644 --- a/extension/react-app/src/components/Suggestions.tsx +++ b/extension/react-app/src/components/Suggestions.tsx @@ -16,6 +16,7 @@ import { useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import HeaderButtonWithText from "./HeaderButtonWithText"; import { getFontSize } from "../util"; +import { usePostHog } from "posthog-js/react"; const Div = styled.div<{ isDisabled: boolean }>` border-radius: ${defaultBorderRadius}; @@ -159,6 +160,7 @@ const TutorialDiv = styled.div` `; function SuggestionsArea(props: { onClick: (textInput: string) => void }) { + const posthog = usePostHog(); const [stage, setStage] = useState( parseInt(localStorage.getItem("stage") || "0") ); @@ -207,8 +209,18 @@ function SuggestionsArea(props: { onClick: (textInput: string) => void }) { className="absolute right-1 top-1 cursor-pointer" text="Close Tutorial" onClick={() => { - console.log("HIDE"); setHide(true); + const tutorialClosedCount = parseInt( + localStorage.getItem("tutorialClosedCount") || "0" + ); + localStorage.setItem( + "tutorialClosedCount", + (tutorialClosedCount + 1).toString() + ); + posthog?.capture("tutorial_closed", { + stage, + tutorialClosedCount, + }); }} > <XMarkIcon width="1.2em" height="1.2em" /> @@ -219,8 +231,9 @@ function SuggestionsArea(props: { onClick: (textInput: string) => void }) { disabled={!codeIsHighlighted} {...suggestion} onClick={() => { - if (stage > 0 && !codeIsHighlighted) return; + if (!codeIsHighlighted) return; props.onClick(suggestion.textInput); + posthog?.capture("tutorial_stage_complete", { stage }); setStage(stage + 1); localStorage.setItem("stage", (stage + 1).toString()); setHide(true); diff --git a/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx b/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx index 9cd0a95e..a6cf151c 100644 --- a/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx +++ b/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx @@ -1,5 +1,5 @@ import { useContext } from "react"; -import { Button, TextInput } from ".."; +import { Button, Input } from ".."; import { GUIClientContext } from "../../App"; import { useDispatch } from "react-redux"; import { @@ -27,7 +27,7 @@ function AddContextGroupDialog({ return ( <div className="p-4"> - <TextInput + <Input defaultValue="My Context Group" type="text" ref={(input) => { diff --git a/extension/react-app/src/components/dialogs/FTCDialog.tsx b/extension/react-app/src/components/dialogs/FTCDialog.tsx index 3ea753bc..5fa2d4e6 100644 --- a/extension/react-app/src/components/dialogs/FTCDialog.tsx +++ b/extension/react-app/src/components/dialogs/FTCDialog.tsx @@ -1,6 +1,6 @@ import React, { useContext } from "react"; import styled from "styled-components"; -import { Button, TextInput } from ".."; +import { Button, Input } from ".."; import { useNavigate } from "react-router-dom"; import { GUIClientContext } from "../../App"; import { useDispatch } from "react-redux"; @@ -37,7 +37,7 @@ function FTCDialog() { OpenAIFreeTrial object. </p> - <TextInput + <Input type="text" placeholder="Enter your OpenAI API key" value={apiKey} @@ -46,6 +46,7 @@ function FTCDialog() { <GridDiv> <Button onClick={() => { + dispatch(setShowDialog(false)); navigate("/models"); }} > diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index 9d9b7c40..12b84759 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -10,9 +10,10 @@ export const vscBackgroundTransparent = "#1e1e1ede"; export const buttonColor = "#1bbe84"; export const buttonColorHover = "#1bbe84a8"; -export const secondaryDark = "var(--vscode-list-hoverBackground)"; -export const vscBackground = "var(--vscode-editor-background)"; -export const vscForeground = "var(--vscode-editor-foreground)"; +export const secondaryDark = + "var(--vscode-list-hoverBackground, rgb(45 45 45))"; +export const vscBackground = "var(--vscode-editor-background, rgb(30 30 30))"; +export const vscForeground = "var(--vscode-editor-foreground, white)"; export const Button = styled.button` padding: 10px 12px; @@ -92,7 +93,7 @@ export const H3 = styled.h3` width: fit-content; `; -export const TextInput = styled.input.attrs({ type: "text" })` +export const Input = styled.input` width: 100%; padding: 8px 12px; margin: 8px 0; @@ -106,6 +107,10 @@ export const TextInput = styled.input.attrs({ type: "text" })` &:focus { background: ${secondaryDark}; } + + &:invalid { + outline: 1px solid red; + } `; export const NumberInput = styled.input.attrs({ type: "number" })` |