make settings modal have tabs for future expansions

This commit is contained in:
timothycarambat 2023-07-14 15:04:25 -07:00
parent 284fe6f356
commit 5b2c5cac08
3 changed files with 399 additions and 15 deletions

View File

@ -0,0 +1,280 @@
import React, { useState, useEffect } from "react";
import { AlertCircle, Loader, X } from "react-feather";
import System from "../../../../models/system";
const noop = () => false;
export default function SystemKeys({ hideModal = noop }) {
const [loading, setLoading] = useState(true);
const [settings, setSettings] = useState({});
function validSettings(settings) {
return (
settings?.OpenAiKey &&
!!settings?.OpenAiModelPref &&
!!settings?.VectorDB &&
(settings?.VectorDB === "chroma" ? !!settings?.ChromaEndpoint : true) &&
(settings?.VectorDB === "pinecone"
? !!settings?.PineConeKey &&
!!settings?.PineConeEnvironment &&
!!settings?.PineConeIndex
: true)
);
}
useEffect(() => {
async function fetchKeys() {
const settings = await System.keys();
setSettings(settings);
setLoading(false);
}
fetchKeys();
}, []);
return (
<div className="relative w-full max-w-2xl max-h-full">
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
<div className="flex items-start justify-between px-6 py-4">
<p className="text-gray-800 dark:text-stone-200 text-base ">
These are the credentials and settings for how your AnythingLLM
instance will function. Its important these keys are current and
correct.
</p>
</div>
<div className="p-6 space-y-6 flex h-full w-full">
{loading ? (
<div className="w-full h-full flex items-center justify-center">
<p className="text-gray-800 dark:text-gray-200 text-base">
loading system settings
</p>
</div>
) : (
<div className="w-full flex flex-col gap-y-4">
{!validSettings(settings) && (
<div className="bg-orange-300 p-4 rounded-lg border border-orange-600 text-orange-700 w-full items-center flex gap-x-2">
<AlertCircle className="h-8 w-8" />
<p className="text-sm md:text-base ">
Ensure all fields are green before attempting to use
AnythingLLM or it may not function as expected!
</p>
</div>
)}
<ShowKey
name="OpenAI API Key"
env="OpenAiKey"
value={settings?.OpenAiKey ? "*".repeat(20) : ""}
valid={settings?.OpenAiKey}
allowDebug={settings?.CanDebug}
/>
<ShowKey
name="OpenAI Model for chats"
env="OpenAiModelPref"
value={settings?.OpenAiModelPref}
valid={!!settings?.OpenAiModelPref}
allowDebug={settings?.CanDebug}
/>
<div className="h-[2px] w-full bg-gray-200 dark:bg-stone-600" />
<ShowKey
name="Vector DB Choice"
env="VectorDB"
value={settings?.VectorDB}
valid={!!settings?.VectorDB}
allowDebug={settings?.CanDebug}
/>
{settings?.VectorDB === "pinecone" && (
<>
<ShowKey
name="Pinecone DB API Key"
env="PineConeKey"
value={settings?.PineConeKey ? "*".repeat(20) : ""}
valid={!!settings?.PineConeKey}
allowDebug={settings?.CanDebug}
/>
<ShowKey
name="Pinecone DB Environment"
env="PineConeEnvironment"
value={settings?.PineConeEnvironment}
valid={!!settings?.PineConeEnvironment}
allowDebug={settings?.CanDebug}
/>
<ShowKey
name="Pinecone DB Index"
env="PineConeIndex"
value={settings?.PineConeIndex}
valid={!!settings?.PineConeIndex}
allowDebug={settings?.CanDebug}
/>
</>
)}
{settings?.VectorDB === "chroma" && (
<>
<ShowKey
name="Chroma Endpoint"
env="ChromaEndpoint"
value={settings?.ChromaEndpoint}
valid={!!settings?.ChromaEndpoint}
allowDebug={settings?.CanDebug}
/>
</>
)}
</div>
)}
</div>
<div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button
onClick={hideModal}
type="button"
className="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
>
Close
</button>
</div>
</div>
</div>
);
}
function ShowKey({ name, env, value, valid, allowDebug = true }) {
const [isValid, setIsValid] = useState(valid);
const [debug, setDebug] = useState(false);
const [saving, setSaving] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
const data = {};
const form = new FormData(e.target);
for (var [key, value] of form.entries()) data[key] = value;
const { newValues, error } = await System.updateSystem(data);
if (!!error) {
alert(error);
setSaving(false);
setIsValid(false);
return;
}
setSaving(false);
setDebug(false);
setIsValid(true);
};
if (!isValid) {
return (
<form onSubmit={handleSubmit}>
<div>
<label
htmlFor="error"
className="block mb-2 text-sm font-medium text-red-700 dark:text-red-500"
>
{name}
</label>
<input
type="text"
id="error"
name={env}
disabled={!debug}
className="bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 dark:bg-gray-700 focus:border-red-500 block w-full p-2.5 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500"
placeholder={name}
defaultValue={value}
required={true}
autoComplete="off"
/>
<div className="flex items-center justify-between">
<p className="mt-2 text-sm text-red-600 dark:text-red-500">
Need setup in .env file.
</p>
{allowDebug && (
<>
{debug ? (
<div className="flex items-center gap-x-2 mt-2">
{saving ? (
<>
<Loader className="animate-spin h-4 w-4 text-slate-300 dark:text-slate-500" />
</>
) : (
<>
<button
type="button"
onClick={() => setDebug(false)}
className="text-xs text-slate-300 dark:text-slate-500"
>
Cancel
</button>
<button
type="submit"
className="text-xs text-blue-300 dark:text-blue-500"
>
Save
</button>
</>
)}
</div>
) : (
<button
type="button"
onClick={() => setDebug(true)}
className="mt-2 text-xs text-slate-300 dark:text-slate-500"
>
Debug
</button>
)}
</>
)}
</div>
</div>
</form>
);
}
return (
<form onSubmit={handleSubmit}>
<div className="mb-6">
<label
htmlFor="success"
className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"
>
{name}
</label>
<input
type="text"
id="success"
name={env}
disabled={!debug}
className="border border-white text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 text-sm rounded-lg focus:ring-green-500 focus:border-green-500 block w-full p-2.5 dark:bg-gray-700 dark:border-green-500"
defaultValue={value}
required={true}
autoComplete="off"
/>
{allowDebug && (
<div className="flex items-center justify-end">
{debug ? (
<div className="flex items-center gap-x-2 mt-2">
{saving ? (
<>
<Loader className="animate-spin h-4 w-4 text-slate-300 dark:text-slate-500" />
</>
) : (
<>
<button
onClick={() => setDebug(false)}
className="text-xs text-slate-300 dark:text-slate-500"
>
Cancel
</button>
<button className="text-xs text-blue-300 dark:text-blue-500">
Save
</button>
</>
)}
</div>
) : (
<button
onClick={() => setDebug(true)}
className="mt-2 text-xs text-slate-300 dark:text-slate-500"
>
Debug
</button>
)}
</div>
)}
</div>
</form>
);
}

View File

@ -0,0 +1,97 @@
import React, { useState } from "react";
import { Key, X } from "react-feather";
import SystemKeys from "./Keys";
const TABS = {
keys: SystemKeys,
};
const noop = () => false;
export default function SystemSettingsModal({ hideModal = noop }) {
const [selectedTab, setSelectedTab] = useState("keys");
const Component = TABS[selectedTab || "keys"];
return (
<div className="fixed top-0 left-0 right-0 z-50 w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] h-full bg-black bg-opacity-50 flex items-center justify-center">
<div
className="flex fixed top-0 left-0 right-0 w-full h-full"
onClick={hideModal}
/>
<div className="relative w-full max-w-2xl max-h-full">
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
<div className="flex flex-col gap-y-1 border-b dark:border-gray-600 px-4 pt-4 ">
<div className="flex items-start justify-between rounded-t ">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">
System Settings
</h3>
<button
onClick={hideModal}
type="button"
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
data-modal-hide="staticModal"
>
<X className="text-gray-300 text-lg" />
</button>
</div>
<SettingTabs selectedTab={selectedTab} changeTab={setSelectedTab} />
</div>
<Component hideModal={hideModal} />
</div>
</div>
</div>
);
}
function SettingTabs({ selectedTab, changeTab }) {
return (
<div>
<ul className="flex md:flex-wrap overflow-x-scroll no-scroll -mb-px text-sm gap-x-2 font-medium text-center text-gray-500 dark:text-gray-400">
<SettingTab
active={selectedTab === "keys"}
displayName="Keys"
tabName="keys"
icon={<Key className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
</ul>
</div>
);
}
function SettingTab({
active = false,
displayName,
tabName,
icon = "",
onClick,
}) {
const classes = active
? "text-blue-600 border-blue-600 active dark:text-blue-400 dark:border-blue-400 bg-blue-500 bg-opacity-5"
: "border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300";
return (
<li className="mr-2">
<button
disabled={active}
onClick={() => onClick(tabName)}
className={
"flex items-center gap-x-1 p-4 border-b-2 rounded-t-lg group whitespace-nowrap " +
classes
}
>
{icon} {displayName}
</button>
</li>
);
}
export function useSystemSettingsModal() {
const [showing, setShowing] = useState(false);
const showModal = () => {
setShowing(true);
};
const hideModal = () => {
setShowing(false);
};
return { showing, showModal, hideModal };
}

View File

@ -7,10 +7,13 @@ import {
Key,
Menu,
Plus,
Tool,
} from "react-feather";
import IndexCount from "./IndexCount";
import LLMStatus from "./LLMStatus";
import KeysModal, { useKeysModal } from "../Modals/Keys";
import SystemSettingsModal, {
useSystemSettingsModal,
} from "../Modals/Settings";
import NewWorkspaceModal, {
useNewWorkspaceModal,
} from "../Modals/NewWorkspace";
@ -20,10 +23,10 @@ import paths from "../../utils/paths";
export default function Sidebar() {
const sidebarRef = useRef(null);
const {
showing: showingKeyModal,
showModal: showKeyModal,
hideModal: hideKeyModal,
} = useKeysModal();
showing: showingSystemSettingsModal,
showModal: showSystemSettingsModal,
hideModal: hideSystemSettingsModal,
} = useSystemSettingsModal();
const {
showing: showingNewWsModal,
showModal: showNewWsModal,
@ -45,10 +48,10 @@ export default function Sidebar() {
</p>
<div className="flex gap-x-2 items-center text-slate-500">
<button
onClick={showKeyModal}
onClick={showSystemSettingsModal}
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-stone-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"
>
<Key className="h-4 w-4 " />
<Tool className="h-4 w-4 " />
</button>
</div>
</div>
@ -126,7 +129,9 @@ export default function Sidebar() {
</div>
</div>
</div>
{showingKeyModal && <KeysModal hideModal={hideKeyModal} />}
{showingSystemSettingsModal && (
<SystemSettingsModal hideModal={hideSystemSettingsModal} />
)}
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
</>
);
@ -137,10 +142,10 @@ export function SidebarMobileHeader() {
const [showBgOverlay, setShowBgOverlay] = useState(false);
const sidebarRef = useRef(null);
const {
showing: showingKeyModal,
showModal: showKeyModal,
hideModal: hideKeyModal,
} = useKeysModal();
showing: showingSystemSettingsModal,
showModal: showSystemSettingsModal,
hideModal: hideSystemSettingsModal,
} = useSystemSettingsModal();
const {
showing: showingNewWsModal,
showModal: showNewWsModal,
@ -199,10 +204,10 @@ export function SidebarMobileHeader() {
</p>
<div className="flex gap-x-2 items-center text-slate-500">
<button
onClick={showKeyModal}
onClick={showSystemSettingsModal}
className="transition-all duration-300 p-2 rounded-full bg-slate-200 text-slate-400 dark:bg-stone-800 hover:bg-slate-800 hover:text-slate-200 dark:hover:text-slate-200"
>
<Key className="h-4 w-4 " />
<Tool className="h-4 w-4 " />
</button>
</div>
</div>
@ -283,7 +288,9 @@ export function SidebarMobileHeader() {
</div>
</div>
</div>
{showingKeyModal && <KeysModal hideModal={hideKeyModal} />}
{showingSystemSettingsModal && (
<SystemSettingsModal hideModal={hideSystemSettingsModal} />
)}
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
</div>
</>