From 5eec484dc79bb56dabf9a56af0dbe6bc95227d39 Mon Sep 17 00:00:00 2001
From: Nate Sesti <33237525+sestinj@users.noreply.github.com>
Date: Tue, 22 Aug 2023 13:12:58 -0700
Subject: Config UI (#399)
* feat: :sparkles: UI for config!
* feat: :sparkles: (latent) edit models in settings
---
extension/react-app/src/App.tsx | 5 +
.../src/components/HeaderButtonWithText.tsx | 4 +-
extension/react-app/src/components/InfoHover.tsx | 19 ++
extension/react-app/src/components/Layout.tsx | 9 +
.../react-app/src/components/ModelSettings.tsx | 107 ++++++++++
extension/react-app/src/components/index.ts | 50 ++++-
.../src/hooks/AbstractContinueGUIClientProtocol.ts | 10 +
.../src/hooks/ContinueGUIClientProtocol.ts | 12 ++
extension/react-app/src/pages/settings.tsx | 229 +++++++++++++++++++++
.../src/redux/slices/serverStateReducer.ts | 4 +
10 files changed, 436 insertions(+), 13 deletions(-)
create mode 100644 extension/react-app/src/components/InfoHover.tsx
create mode 100644 extension/react-app/src/components/ModelSettings.tsx
create mode 100644 extension/react-app/src/pages/settings.tsx
(limited to 'extension/react-app/src')
diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx
index 05b322ff..65ad1ddd 100644
--- a/extension/react-app/src/App.tsx
+++ b/extension/react-app/src/App.tsx
@@ -17,6 +17,7 @@ import { setHighlightedCode } from "./redux/slices/miscSlice";
import { postVscMessage } from "./vscode";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import ErrorPage from "./pages/error";
+import SettingsPage from "./pages/settings";
const router = createBrowserRouter([
{
@@ -36,6 +37,10 @@ const router = createBrowserRouter([
path: "/history",
element: ,
},
+ {
+ path: "/settings",
+ element: ,
+ },
],
},
]);
diff --git a/extension/react-app/src/components/HeaderButtonWithText.tsx b/extension/react-app/src/components/HeaderButtonWithText.tsx
index bcd36972..3122c287 100644
--- a/extension/react-app/src/components/HeaderButtonWithText.tsx
+++ b/extension/react-app/src/components/HeaderButtonWithText.tsx
@@ -1,7 +1,5 @@
import React, { useState } from "react";
-import { Tooltip } from "react-tooltip";
-import styled from "styled-components";
-import { HeaderButton, StyledTooltip, defaultBorderRadius } from ".";
+import { HeaderButton, StyledTooltip } from ".";
interface HeaderButtonWithTextProps {
text: string;
diff --git a/extension/react-app/src/components/InfoHover.tsx b/extension/react-app/src/components/InfoHover.tsx
new file mode 100644
index 00000000..2cb8ad71
--- /dev/null
+++ b/extension/react-app/src/components/InfoHover.tsx
@@ -0,0 +1,19 @@
+import { InformationCircleIcon } from "@heroicons/react/24/outline";
+import { StyledTooltip } from ".";
+
+const InfoHover = ({ msg }: { msg: string }) => {
+ const id = "info-hover";
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default InfoHover;
diff --git a/extension/react-app/src/components/Layout.tsx b/extension/react-app/src/components/Layout.tsx
index cec3f8e1..c0f0929b 100644
--- a/extension/react-app/src/components/Layout.tsx
+++ b/extension/react-app/src/components/Layout.tsx
@@ -18,6 +18,7 @@ import {
BookOpenIcon,
ChatBubbleOvalLeftEllipsisIcon,
SparklesIcon,
+ Cog6ToothIcon,
} from "@heroicons/react/24/outline";
import HeaderButtonWithText from "./HeaderButtonWithText";
import { useNavigate } from "react-router-dom";
@@ -193,6 +194,14 @@ const Layout = () => {
+ {
+ navigate("/settings");
+ }}
+ text="Settings"
+ >
+
+
diff --git a/extension/react-app/src/components/ModelSettings.tsx b/extension/react-app/src/components/ModelSettings.tsx
new file mode 100644
index 00000000..99200502
--- /dev/null
+++ b/extension/react-app/src/components/ModelSettings.tsx
@@ -0,0 +1,107 @@
+import styled from "styled-components";
+import { LLM } from "../../../schema/LLM";
+import {
+ Label,
+ Select,
+ TextInput,
+ defaultBorderRadius,
+ lightGray,
+ vscForeground,
+} from ".";
+import { useState } from "react";
+import { useFormContext } from "react-hook-form";
+
+const Div = styled.div<{ dashed: boolean }>`
+ border: 1px ${(props) => (props.dashed ? "dashed" : "solid")} ${lightGray};
+ border-radius: ${defaultBorderRadius};
+ padding: 8px;
+ margin-bottom: 16px;
+`;
+
+type ModelOption = "api_key" | "model" | "context_length";
+
+const DefaultModelOptions: {
+ [key: string]: { [key in ModelOption]?: string };
+} = {
+ OpenAI: {
+ api_key: "",
+ model: "gpt-4",
+ },
+ MaybeProxyOpenAI: {
+ api_key: "",
+ model: "gpt-4",
+ },
+ Anthropic: {
+ api_key: "",
+ model: "claude-2",
+ },
+ default: {
+ api_key: "",
+ model: "gpt-4",
+ },
+};
+
+function ModelSettings(props: { llm: any | undefined; role: string }) {
+ const [modelOptions, setModelOptions] = useState<{
+ [key in ModelOption]?: string;
+ }>(DefaultModelOptions[props.llm?.class_name || "default"]);
+
+ const { register, setValue, getValues } = useFormContext();
+
+ return (
+
+ {props.llm ? (
+ <>
+
{props.role}:
{props.llm.class_name || "gpt-4"}
+
+ >
+ ) : (
+
+
Add Model
+
+
+
+
+ )}
+
+ );
+}
+
+export default ModelSettings;
diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts
index 6705ceb2..f2e154bc 100644
--- a/extension/react-app/src/components/index.ts
+++ b/extension/react-app/src/components/index.ts
@@ -40,21 +40,29 @@ export const StyledTooltip = styled(Tooltip)`
padding-left: 12px;
padding-right: 12px;
z-index: 100;
+
+ max-width: 80vw;
`;
export const TextArea = styled.textarea`
- width: 100%;
+ padding: 8px;
+ font-family: inherit;
border-radius: ${defaultBorderRadius};
- border: none;
+ margin: 16px auto;
+ height: auto;
+ width: calc(100% - 32px);
background-color: ${secondaryDark};
- resize: vertical;
-
- padding: 4px;
- caret-color: ${vscForeground};
- color: #{vscForeground};
+ color: ${vscForeground};
+ z-index: 1;
+ border: 1px solid transparent;
&:focus {
- outline: 1px solid ${buttonColor};
+ outline: 1px solid ${lightGray};
+ border: 1px solid transparent;
+ }
+
+ &::placeholder {
+ color: ${lightGray}80;
}
`;
@@ -84,11 +92,33 @@ export const H3 = styled.h3`
export const TextInput = styled.input.attrs({ type: "text" })`
width: 100%;
- padding: 12px 20px;
+ padding: 8px 12px;
+ margin: 8px 0;
+ box-sizing: border-box;
+ border-radius: ${defaultBorderRadius};
+ outline: 1px solid ${lightGray};
+ border: none;
+ background-color: ${vscBackground};
+ color: ${vscForeground};
+
+ &:focus {
+ background: ${secondaryDark};
+ }
+`;
+
+export const Select = styled.select`
+ padding: 8px 12px;
margin: 8px 0;
box-sizing: border-box;
border-radius: ${defaultBorderRadius};
- border: 2px solid gray;
+ outline: 1px solid ${lightGray};
+ border: none;
+ background-color: ${vscBackground};
+ color: ${vscForeground};
+`;
+
+export const Label = styled.label`
+ font-size: 13px;
`;
const spin = keyframes`
diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
index c9e7def2..f8c11527 100644
--- a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
@@ -37,6 +37,16 @@ abstract class AbstractContinueGUIClientProtocol {
abstract editStepAtIndex(userInput: string, index: number): void;
+ abstract setSystemMessage(message: string): void;
+
+ abstract setTemperature(temperature: number): void;
+
+ abstract setModelForRole(
+ role: string,
+ model_class: string,
+ model: string
+ ): void;
+
abstract saveContextGroup(title: string, contextItems: ContextItem[]): void;
abstract selectContextGroup(id: string): void;
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index b3ac2570..ce9b2a0a 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -133,6 +133,18 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
});
}
+ setSystemMessage(message: string): void {
+ this.messenger?.send("set_system_message", { message });
+ }
+
+ setTemperature(temperature: number): void {
+ this.messenger?.send("set_temperature", { temperature });
+ }
+
+ setModelForRole(role: string, model_class: string, model: any): void {
+ this.messenger?.send("set_model_for_role", { role, model, model_class });
+ }
+
saveContextGroup(title: string, contextItems: ContextItem[]): void {
this.messenger?.send("save_context_group", {
context_items: contextItems,
diff --git a/extension/react-app/src/pages/settings.tsx b/extension/react-app/src/pages/settings.tsx
new file mode 100644
index 00000000..8fd91ff5
--- /dev/null
+++ b/extension/react-app/src/pages/settings.tsx
@@ -0,0 +1,229 @@
+import React, { useContext, useEffect, useState } from "react";
+import { GUIClientContext } from "../App";
+import { useSelector } from "react-redux";
+import { RootStore } from "../redux/store";
+import { useNavigate } from "react-router-dom";
+import { ContinueConfig } from "../../../schema/ContinueConfig";
+import {
+ Button,
+ Select,
+ TextArea,
+ lightGray,
+ secondaryDark,
+} from "../components";
+import styled from "styled-components";
+import { ArrowLeftIcon } from "@heroicons/react/24/outline";
+import Loader from "../components/Loader";
+import InfoHover from "../components/InfoHover";
+import { FormProvider, useForm } from "react-hook-form";
+import ModelSettings from "../components/ModelSettings";
+
+const Hr = styled.hr`
+ border: 0.5px solid ${lightGray};
+`;
+
+const CancelButton = styled(Button)`
+ background-color: transparent;
+ color: ${lightGray};
+ border: 1px solid ${lightGray};
+ &:hover {
+ background-color: ${lightGray};
+ color: black;
+ }
+`;
+
+const SaveButton = styled(Button)`
+ &:hover {
+ opacity: 0.8;
+ }
+`;
+
+const Slider = styled.input.attrs({ type: "range" })`
+ --webkit-appearance: none;
+ width: 100%;
+ background-color: ${secondaryDark};
+ outline: none;
+ border: none;
+ opacity: 0.7;
+ -webkit-transition: 0.2s;
+ transition: opacity 0.2s;
+ &:hover {
+ opacity: 1;
+ }
+ &::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 8px;
+ cursor: pointer;
+ background: ${lightGray};
+ border-radius: 4px;
+ }
+ &::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 8px;
+ height: 8px;
+ cursor: pointer;
+ margin-top: -3px;
+ }
+ &::-moz-range-thumb {
+ width: 8px;
+ height: 8px;
+ cursor: pointer;
+ margin-top: -3px;
+ }
+
+ &:focus {
+ outline: none;
+ border: none;
+ }
+`;
+const ALL_MODEL_ROLES = ["default", "small", "medium", "large", "edit", "chat"];
+
+function Settings() {
+ const formMethods = useForm();
+ const onSubmit = (data: ContinueConfig) => console.log(data);
+
+ const navigate = useNavigate();
+ const client = useContext(GUIClientContext);
+ const config = useSelector((state: RootStore) => state.serverState.config);
+
+ const submitChanges = () => {
+ if (!client) return;
+
+ const systemMessage = formMethods.watch("system_message");
+ const temperature = formMethods.watch("temperature");
+ // const models = formMethods.watch("models");
+
+ if (systemMessage) client.setSystemMessage(systemMessage);
+ if (temperature) client.setTemperature(temperature);
+
+ // if (models) {
+ // for (const role of ALL_MODEL_ROLES) {
+ // if (models[role]) {
+ // client.setModelForRole(role, models[role] as string, models[role]);
+ // }
+ // }
+ // }
+ };
+
+ const submitAndLeave = () => {
+ submitChanges();
+ navigate("/");
+ };
+
+ return (
+
+
+
+
+
+ {
+ navigate("/");
+ }}
+ >
+ Cancel
+
+ Save
+
+
+
+ );
+}
+
+export default Settings;
diff --git a/extension/react-app/src/redux/slices/serverStateReducer.ts b/extension/react-app/src/redux/slices/serverStateReducer.ts
index a20476b2..cf26f094 100644
--- a/extension/react-app/src/redux/slices/serverStateReducer.ts
+++ b/extension/react-app/src/redux/slices/serverStateReducer.ts
@@ -29,6 +29,10 @@ const initialState: FullState = {
slash_commands: [],
adding_highlighted_code: false,
selected_context_items: [],
+ config: {
+ system_message: "",
+ temperature: 0.5,
+ },
};
export const serverStateSlice = createSlice({
--
cgit v1.2.3-70-g09d2