Skip to content
This repository has been archived by the owner on Sep 25, 2024. It is now read-only.

refactor: options page #70

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/components/Switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default function Switch({
isChecked,
onChange,
text,
}: {
isChecked: boolean;
onChange: () => void;
text: string;
}) {
return (
<div className="flex items-center mt-2">
<label className="relative inline-flex cursor-pointer items-center">
<input
id="switch"
type="checkbox"
checked={isChecked}
className="peer sr-only"
onClick={onChange}
/>
<label htmlFor="switch" className="hidden"></label>
<div className="peer h-6 w-11 rounded-full border bg-slate-200 after:absolute after:left-[2px] after:top-0.5 after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:ring-green-300"></div>
</label>

<span className="ml-3 text-gray-900 text-sm">{text}</span>
</div>
);
}
210 changes: 143 additions & 67 deletions src/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ import React, { ChangeEvent, useCallback, useEffect, useState } from "react";
import { createRoot } from "react-dom/client";
import "./options.css";
import { DEFAULT_PROMPT, getStorage, setStorage } from "./utils";
import Switch from "./components/Switch";

const Options = () => {
const TABS = [
"Basic Settings",
"Prompt Settings",
"Style Settings",
"Feature Flags",
];

function BasicSettings() {
const [model, setModel] = useState<string | undefined>("gpt-3.5-turbo");
const [apiURL, setApiURL] = useState<string | undefined>(
"https://api.openai.com/v1/chat/completions"
);
const [prompt, setPrompt] = useState<string | undefined>(DEFAULT_PROMPT);
const [isPromptValid, setIsPromptValid] = useState<boolean>(true);
const promptFormatWarning: string = `{{tabURL}}, {{tabTitle}} and {{types}} must be in the prompt`;

useEffect(() => {
getStorage<string>("model").then(setModel);
getStorage<string>("apiURL").then(setApiURL);
getStorage<string>("prompt").then(setPrompt);
}, []);

const updateModel = useCallback((e: ChangeEvent<HTMLSelectElement>) => {
Expand All @@ -28,6 +32,55 @@ const Options = () => {
setStorage("apiURL", e.target.value);
}, []);

return (
<div className="flex flex-col gap-y-8 p-4">
<div className="flex flex-col gap-y-2">
<label htmlFor="models" className="text-xl font-medium">
Choose an model
</label>

<select
value={model}
onChange={updateModel}
id="models"
className="bg-gray-50 border w-64 border-gray-300 text-gray-900 text-sm rounded-lg
focus:ring-blue-500 focus:border-blue-500 block"
>
<option selected>Choose a model</option>
<option value="gpt-4">GPT 4</option>
<option value="gpt-4-32k">GPT 4 32k</option>
<option value="gpt-3.5-turbo-1106">GPT 3.5 turbo 1106</option>
<option value="gpt-3.5-turbo">GPT 3.5 turbo</option>
</select>
</div>

<div className="flex flex-col gap-y-2">
<label htmlFor="api_url" className="text-xl font-medium">
API URL
</label>

<input
className="bg-gray-50 border w-64 border-gray-300 text-gray-900 text-sm rounded-lg
focus:ring-blue-500 focus:border-blue-500 block"
value={apiURL}
onChange={updateApiURL}
id="api_url"
/>
</div>
</div>
);
}

function PromptSettings() {
const [prompt, setPrompt] = useState<string | undefined>(DEFAULT_PROMPT);
const [isPromptValid, setIsPromptValid] = useState<boolean>(true);

const promptFormatWarning: string = `{{tabURL}}, {{tabTitle}} and {{types}} must be in the prompt`;

useEffect(() => {
getStorage<string>("prompt").then(setPrompt);
}, []);

const updatePrompt = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
const newPrompt: string = e.target.value;
setIsPromptValid(
Expand All @@ -42,76 +95,99 @@ const Options = () => {
}, []);

return (
<div className="w-screen h-screen flex justify-center p-12">
<div className="w-full max-w-4xl flex flex-col gap-y-8">
<h1 className="text-4xl font-semibold border-b border-black pb-2">
Options Page
</h1>

<div className="flex flex-col gap-y-2">
<label htmlFor="models" className="text-xl font-medium">
Choose an model
<div className="flex flex-col gap-y-8 p-4">
<div className="flex flex-col gap-y-2">
<label htmlFor="prompt" className="text-xl font-medium">
Prompt
</label>
{isPromptValid && (
<label
htmlFor="prompt"
className="text-sm font-normal w-64 text-blue-500"
>
{promptFormatWarning}
</label>
)}

<select
value={model}
onChange={updateModel}
id="models"
className="bg-gray-50 border w-64 border-gray-300 text-gray-900 text-sm rounded-lg
focus:ring-blue-500 focus:border-blue-500 block"
{!isPromptValid && (
<div
className=" w-64 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
role="alert"
>
<option selected>Choose a model</option>
<option value="gpt-4">GPT 4</option>
<option value="gpt-4-32k">GPT 4 32k</option>
<option value="gpt-3.5-turbo-1106">GPT 3.5 turbo 1106</option>
<option value="gpt-3.5-turbo">GPT 3.5 turbo</option>
</select>
</div>
<span className="block sm:inline">{promptFormatWarning}</span>
</div>
)}

<div className="flex flex-col gap-y-2">
<label htmlFor="api_url" className="text-xl font-medium">
API URL
</label>

<input
className="bg-gray-50 border w-64 border-gray-300 text-gray-900 text-sm rounded-lg
<textarea
className="bg-gray-50 border w-64 h-64 border-gray-300 text-gray-900 text-sm rounded-lg
focus:ring-blue-500 focus:border-blue-500 block"
value={apiURL}
onChange={updateApiURL}
id="api_url"
/>
</div>
value={prompt}
onChange={updatePrompt}
id="prompt"
/>
</div>
</div>
);
}

<div className="flex flex-col gap-y-2">
<label htmlFor="prompt" className="text-xl font-medium">
Prompt
</label>
{isPromptValid && (
<label
htmlFor="prompt"
className="text-sm font-normal w-64 text-blue-500"
>
{promptFormatWarning}
</label>
)}

{!isPromptValid && (
<div
className=" w-64 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
role="alert"
>
<span className="block sm:inline">{promptFormatWarning}</span>
</div>
)}
function StyleSettings() {
return <div className="flex flex-col p-4"></div>;
}

<textarea
className="bg-gray-50 border w-64 h-64 border-gray-300 text-gray-900 text-sm rounded-lg
focus:ring-blue-500 focus:border-blue-500 block"
value={prompt}
onChange={updatePrompt}
id="prompt"
/>
function FeatureFlags() {
const [isColorsEnabled, setIsColorsEnabled] = useState<boolean | undefined>(
false
);

useEffect(() => {
getStorage<boolean>("colorsEnabled").then(setIsColorsEnabled);
}, []);

const updateColorsEnabled = useCallback(() => {
setIsColorsEnabled(!isColorsEnabled);
setStorage("colorsEnabled", !isColorsEnabled);
}, [isColorsEnabled]);

return (
<div className="flex flex-col p-4">
<Switch
isChecked={isColorsEnabled !== undefined ? isColorsEnabled : false}
onChange={() => {
updateColorsEnabled();
}}
text="Enable Colors Customization"
/>
</div>
);
}

const Options = () => {
const [activeTab, setActiveTab] = useState<string>(TABS[0]);

return (
<div className="w-screen h-screen bg-slate-100 flex justify-center p-12">
<div className="w-full max-w-4xl flex flex-col">
<div className="flex w-full p-2 bg-slate-200 rounded-2xl">
{TABS.map((tab) => (
<button
className={`${
activeTab === tab ? "bg-white" : ""
} flex-1 text-center py-4 font-medium rounded-lg`}
onClick={() => setActiveTab(tab)}
>
{tab}
</button>
))}
</div>

{/* Basic Settings */}
{activeTab === TABS[0] && <BasicSettings />}

{activeTab === TABS[1] && <PromptSettings />}

{activeTab === TABS[2] && <StyleSettings />}

{activeTab === TABS[3] && <FeatureFlags />}
</div>
</div>
);
Expand Down