mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-05-02 17:07:13 +00:00
parent
9bea7739ed
commit
6e8d81c01e
7 changed files with 274 additions and 28 deletions
frontend/src
components
media/vectordbs
server/utils/helpers
232
frontend/src/components/Modals/Settings/VectorDbs/index.jsx
Normal file
232
frontend/src/components/Modals/Settings/VectorDbs/index.jsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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 = () => {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
BIN
frontend/src/media/vectordbs/chroma.png
Normal file
BIN
frontend/src/media/vectordbs/chroma.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2 KiB |
BIN
frontend/src/media/vectordbs/lancedb.png
Normal file
BIN
frontend/src/media/vectordbs/lancedb.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.9 KiB |
BIN
frontend/src/media/vectordbs/pinecone.png
Normal file
BIN
frontend/src/media/vectordbs/pinecone.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.1 KiB |
|
@ -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 = {};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue