summaryrefslogtreecommitdiff
path: root/extension/react-app/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'extension/react-app/src/components')
-rw-r--r--extension/react-app/src/components/ComboBox.tsx31
-rw-r--r--extension/react-app/src/components/ErrorStepContainer.tsx2
-rw-r--r--extension/react-app/src/components/Layout.tsx29
-rw-r--r--extension/react-app/src/components/ModelCard.tsx178
-rw-r--r--extension/react-app/src/components/ModelSettings.tsx6
-rw-r--r--extension/react-app/src/components/Suggestions.tsx17
-rw-r--r--extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx4
-rw-r--r--extension/react-app/src/components/dialogs/FTCDialog.tsx5
-rw-r--r--extension/react-app/src/components/index.ts13
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" })`