better vector db selection UI on settings (#175)

update logout button
This commit is contained in:
Timothy Carambat 2023-08-03 15:59:51 -07:00 committed by GitHub
parent 9bea7739ed
commit 6e8d81c01e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 274 additions and 28 deletions

View File

@ -0,0 +1,232 @@
import React, { useState } from "react";
import System from "../../../../models/system";
import ChromaLogo from "../../../../media/vectordbs/chroma.png";
import PineconeLogo from "../../../../media/vectordbs/pinecone.png";
import LanceDbLogo from "../../../../media/vectordbs/lancedb.png";
const noop = () => false;
export default function VectorDBSelection({
hideModal = noop,
user,
settings = {},
}) {
const [hasChanges, setHasChanges] = useState(false);
const [vectorDB, setVectorDB] = useState(settings?.VectorDB || "lancedb");
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const canDebug = settings.MultiUserMode
? settings?.CanDebug && user?.role === "admin"
: settings?.CanDebug;
function updateVectorChoice(selection) {
if (!canDebug || selection === vectorDB) return false;
setHasChanges(true);
setVectorDB(selection);
}
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
setError(null);
const data = {};
const form = new FormData(e.target);
for (var [key, value] of form.entries()) data[key] = value;
const { error } = await System.updateSystem(data);
setError(error);
setSaving(false);
setHasChanges(!!error ? true : false);
};
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>
{!!error && (
<div className="mb-8 bg-red-700 dark:bg-orange-800 bg-opacity-30 border border-red-800 dark:border-orange-600 p-4 rounded-lg w-[90%] flex mx-auto">
<p className="text-red-800 dark:text-orange-300 text-sm">{error}</p>
</div>
)}
<form onSubmit={handleSubmit} onChange={() => setHasChanges(true)}>
<div className="px-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<p className="block text-sm font-medium text-gray-800 dark:text-slate-200">
Vector database provider
</p>
<div className="w-full flex overflow-x-scroll gap-x-4 no-scroll">
<input hidden={true} name="VectorDB" value={vectorDB} />
<VectorDBOption
name="Chroma"
value="chroma"
link="trychroma.com"
description="Open source vector database you can host yourself or on the cloud."
checked={vectorDB === "chroma"}
image={ChromaLogo}
onClick={updateVectorChoice}
/>
<VectorDBOption
name="Pinecone"
value="pinecone"
link="pinecone.io"
description="100% cloud-based vector database for enterprise use cases."
checked={vectorDB === "pinecone"}
image={PineconeLogo}
onClick={updateVectorChoice}
/>
<VectorDBOption
name="LanceDB"
value="lancedb"
link="lancedb.com"
description="100% local vector DB that runs on the same instance as AnythingLLM."
checked={vectorDB === "lancedb"}
image={LanceDbLogo}
onClick={updateVectorChoice}
/>
</div>
{vectorDB === "pinecone" && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Pinecone DB API Key
</label>
<input
type="text"
name="PineConeKey"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="Pinecone API Key"
defaultValue={settings?.PineConeKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Pinecone Index Environment
</label>
<input
type="text"
name="PineConeEnvironment"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="us-gcp-west-1"
defaultValue={settings?.PineConeEnvironment}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Pinecone Index Name
</label>
<input
type="text"
name="PineConeIndex"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="my-index"
defaultValue={settings?.PineConeIndex}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{vectorDB === "chroma" && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Chroma Endpoint
</label>
<input
type="url"
name="ChromaEndpoint"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="http://localhost:8000"
defaultValue={settings?.ChromaEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{vectorDB === "lancedb" && (
<div className="w-full h-40 items-center justify-center flex">
<p className="text-gray-800 dark:text-slate-400">
There is no configuration needed for LanceDB.
</p>
</div>
)}
</div>
</div>
<div className="w-full p-4">
<button
hidden={!hasChanges}
disabled={saving}
type="submit"
className="w-full 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"
>
{saving ? "Saving..." : "Save changes"}
</button>
</div>
</form>
<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>
);
}
const VectorDBOption = ({
name,
link,
description,
value,
image,
checked = false,
onClick,
}) => {
return (
<div onClick={() => onClick(value)}>
<input
type="checkbox"
value={value}
className="peer hidden"
checked={checked}
readOnly={true}
formNoValidate={true}
/>
<label className="transition-all duration-300 inline-flex h-full w-60 cursor-pointer items-center justify-between rounded-lg border border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:dark:bg-stone-800 peer-checked:text-gray-600 dark:border-slate-200 dark:bg-stone-800 dark:text-slate-400 dark:hover:bg-stone-700 dark:hover:text-slate-300 dark:peer-checked:text-slate-300">
<div className="block">
<img src={image} alt={name} className="mb-2 h-10 w-10 rounded-full" />
<div className="w-full text-lg font-semibold">{name}</div>
<div className="flex w-full flex-col gap-y-1 text-sm">
<p className="text-xs text-slate-400">{link}</p>
{description}
</div>
</div>
</label>
</div>
);
};

View File

@ -1,26 +1,27 @@
import React, { useEffect, useState } from "react";
import { Archive, Lock, Key, X, Users, LogOut } from "react-feather";
import { Archive, Lock, Key, X, Users, Database } from "react-feather";
import SystemKeys from "./Keys";
import ExportOrImportData from "./ExportImport";
import PasswordProtection from "./PasswordProtection";
import System from "../../../models/system";
import MultiUserMode from "./MultiUserMode";
import { AUTH_TOKEN, AUTH_USER } from "../../../utils/constants";
import paths from "../../../utils/paths";
import useUser from "../../../hooks/useUser";
import VectorDBSelection from "./VectorDbs";
const TABS = {
keys: SystemKeys,
exportimport: ExportOrImportData,
password: PasswordProtection,
multiuser: MultiUserMode,
vectordb: VectorDBSelection,
};
const noop = () => false;
export default function SystemSettingsModal({ hideModal = noop }) {
const { user } = useUser();
const [loading, setLoading] = useState(true);
const [selectedTab, setSelectedTab] = useState("keys");
// const [selectedTab, setSelectedTab] = useState("keys");
const [selectedTab, setSelectedTab] = useState("vectordb");
const [settings, setSettings] = useState(null);
const Component = TABS[selectedTab || "keys"];
@ -93,6 +94,13 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) {
icon={<Key className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
<SettingTab
active={selectedTab === "vectordb"}
displayName="Vector Database"
tabName="vectordb"
icon={<Database className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
<SettingTab
active={selectedTab === "exportimport"}
displayName="Export or Import"
@ -100,7 +108,7 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) {
icon={<Archive className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
{!settings?.MultiUserMode ? (
{!settings?.MultiUserMode && (
<>
<SettingTab
active={selectedTab === "multiuser"}
@ -117,8 +125,6 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) {
onClick={changeTab}
/>
</>
) : (
<LogoutTab user={user} />
)}
</ul>
);
@ -150,25 +156,6 @@ function SettingTab({
);
}
function LogoutTab({ user }) {
if (!user) return null;
return (
<li className="mr-2">
<button
onClick={() => {
window.localStorage.removeItem(AUTH_USER);
window.localStorage.removeItem(AUTH_TOKEN);
window.location.replace(paths.home());
}}
className="flex items-center gap-x-1 p-4 border-b-2 rounded-t-lg group whitespace-nowrap border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300"
>
<LogOut className="h-4 w-4 flex-shrink-0" /> Log out of {user.username}
</button>
</li>
);
}
export function useSystemSettingsModal() {
const [showing, setShowing] = useState(false);
const showModal = () => {

View File

@ -4,6 +4,7 @@ import {
Briefcase,
Cpu,
GitHub,
LogOut,
Menu,
Plus,
Shield,
@ -21,6 +22,8 @@ import ActiveWorkspaces from "./ActiveWorkspaces";
import paths from "../../utils/paths";
import Discord from "../Icons/Discord";
import useUser from "../../hooks/useUser";
import { userFromStorage } from "../../utils/request";
import { AUTH_TOKEN, AUTH_USER } from "../../utils/constants";
export default function Sidebar() {
const sidebarRef = useRef(null);
@ -103,6 +106,7 @@ export default function Sidebar() {
Enterprise Installation
</p>
</a>
<LogoutButton />
</div>
{/* Footer */}
@ -269,6 +273,7 @@ export function SidebarMobileHeader() {
Enterprise Installation
</p>
</a>
<LogoutButton />
</div>
{/* Footer */}
@ -325,3 +330,25 @@ function AdminHome() {
</a>
);
}
function LogoutButton() {
if (!window.localStorage.getItem(AUTH_USER)) return null;
const user = userFromStorage();
if (!user.username) return null;
return (
<button
onClick={() => {
window.localStorage.removeItem(AUTH_USER);
window.localStorage.removeItem(AUTH_TOKEN);
window.location.replace(paths.home());
}}
className="flex flex-grow w-[100%] h-[36px] gap-x-2 py-[5px] px-4 border border-slate-400 dark:border-transparent rounded-lg text-slate-800 dark:text-slate-200 justify-center items-center hover:bg-slate-100 dark:bg-stone-800 dark:hover:bg-stone-900"
>
<LogOut className="h-4 w-4" />
<p className="text-slate-800 dark:text-slate-200 text-xs leading-loose font-semibold">
Log out of {user.username}
</p>
</button>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -92,8 +92,8 @@ function validChromaURL(input = "") {
function updateENV(newENVs = {}) {
let error = "";
const validKeys = Object.keys(KEY_MAPPING);
const ENV_KEYS = Object.keys(newENVs).filter((key) =>
validKeys.includes(key)
const ENV_KEYS = Object.keys(newENVs).filter(
(key) => validKeys.includes(key) && !newENVs[key].includes("******") // strip out answers where the value is all asterisks
);
const newValues = {};