mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-11-13 02:00:10 +01:00
[FEAT] Implement model provider UI updates (#996)
* implement new LLM preference UI * implement new vector db preferences UI to match LLM preferences * implement new embedding preferences UI to match LLM preferences * normalize placeholder text for search input * implement new transcription preferences UI to match LLM preferences * remove uneeded css * implement new UI for llm preference onboarding * implement new UI for embedder preference onboarding * implement new UI for vector db preference onboarding * fix placeholder text * unset onboarding * move autocomplete field --------- Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
parent
b643639d0f
commit
1cd9e1336b
@ -9,8 +9,8 @@ export default function EmbedderItem({
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick(value)}
|
||||
className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
|
||||
checked && "bg-white/10"
|
||||
className={`w-full p-2 rounded-md hover:cursor-pointer hover:bg-white/10 ${
|
||||
checked ? "bg-white/10" : ""
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
@ -28,8 +28,8 @@ export default function EmbedderItem({
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">{name}</div>
|
||||
<div className="mt-1 text-xs text-white/60">{description}</div>
|
||||
<div className="text-sm font-semibold text-white">{name}</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
export default function NativeEmbeddingOptions() {
|
||||
return (
|
||||
<div className="w-full h-20 items-center justify-center flex">
|
||||
<div className="w-full h-10 items-center flex">
|
||||
<p className="text-sm font-base text-white text-opacity-60">
|
||||
There is no set up required when using AnythingLLM's native embedding
|
||||
engine.
|
||||
|
@ -9,8 +9,8 @@ export default function LLMItem({
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick(value)}
|
||||
className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
|
||||
checked && "bg-white/10"
|
||||
className={`w-full p-2 rounded-md hover:cursor-pointer hover:bg-white/10 ${
|
||||
checked ? "bg-white/10" : ""
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
@ -28,8 +28,8 @@ export default function LLMItem({
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">{name}</div>
|
||||
<div className="mt-1 text-xs text-white/60">{description}</div>
|
||||
<div className="text-sm font-semibold text-white">{name}</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
export default function LanceDBOptions() {
|
||||
return (
|
||||
<div className="w-full h-10 items-center justify-center flex">
|
||||
<div className="w-full h-10 items-center flex">
|
||||
<p className="text-sm font-base text-white text-opacity-60">
|
||||
There is no configuration needed for LanceDB.
|
||||
</p>
|
||||
|
@ -9,7 +9,7 @@ export default function VectorDBItem({
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick(value)}
|
||||
className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
|
||||
className={`w-full p-2 rounded-md hover:cursor-pointer hover:bg-white/10 ${
|
||||
checked ? "bg-white/10" : ""
|
||||
}`}
|
||||
>
|
||||
@ -28,8 +28,8 @@ export default function VectorDBItem({
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">{name}</div>
|
||||
<div className="mt-1 text-xs text-white/60">{description}</div>
|
||||
<div className="text-sm font-semibold text-white">{name}</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -633,3 +633,32 @@ does not extend the close button beyond the viewport. */
|
||||
.upload-modal-arrow {
|
||||
margin-top: 25%;
|
||||
}
|
||||
|
||||
/* Scrollbar container */
|
||||
.white-scrollbar {
|
||||
overflow-y: scroll;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #ffffff #18181b;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Webkit browsers (Chrome, Safari) */
|
||||
.white-scrollbar::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
background-color: #18181b;
|
||||
}
|
||||
|
||||
.white-scrollbar::-webkit-scrollbar-track {
|
||||
background-color: #18181b;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.white-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: #ffffff;
|
||||
border-radius: 4px;
|
||||
border: 2px solid #18181b;
|
||||
}
|
||||
|
||||
.white-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Sidebar from "@/components/SettingsSidebar";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import System from "@/models/system";
|
||||
@ -16,7 +16,7 @@ import LocalAiOptions from "@/components/EmbeddingSelection/LocalAiOptions";
|
||||
import NativeEmbeddingOptions from "@/components/EmbeddingSelection/NativeEmbeddingOptions";
|
||||
import OllamaEmbeddingOptions from "@/components/EmbeddingSelection/OllamaOptions";
|
||||
import EmbedderItem from "@/components/EmbeddingSelection/EmbedderItem";
|
||||
import { MagnifyingGlass } from "@phosphor-icons/react";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
|
||||
@ -29,6 +29,8 @@ export default function GeneralEmbeddingPreference() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [filteredEmbedders, setFilteredEmbedders] = useState([]);
|
||||
const [selectedEmbedder, setSelectedEmbedder] = useState(null);
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
@ -65,10 +67,21 @@ export default function GeneralEmbeddingPreference() {
|
||||
};
|
||||
|
||||
const updateChoice = (selection) => {
|
||||
setSearchQuery("");
|
||||
setSelectedEmbedder(selection);
|
||||
setSearchMenuOpen(false);
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const handleXButton = () => {
|
||||
if (searchQuery.length > 0) {
|
||||
setSearchQuery("");
|
||||
if (searchInputRef.current) searchInputRef.current.value = "";
|
||||
} else {
|
||||
setSearchMenuOpen(!searchMenuOpen);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
const _settings = await System.keys();
|
||||
@ -126,6 +139,10 @@ export default function GeneralEmbeddingPreference() {
|
||||
setFilteredEmbedders(filtered);
|
||||
}, [searchQuery, selectedEmbedder]);
|
||||
|
||||
const selectedEmbedderObject = EMBEDDERS.find(
|
||||
(embedder) => embedder.value === selectedEmbedder
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
@ -174,55 +191,96 @@ export default function GeneralEmbeddingPreference() {
|
||||
format which AnythingLLM can use to process.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-white mt-6 mb-4">
|
||||
Embedding Providers
|
||||
<div className="text-base font-bold text-white mt-6 mb-4">
|
||||
Embedding Provider
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="w-full relative border-slate-300/20 shadow border-4 rounded-xl text-white">
|
||||
<div className="w-full p-4 absolute top-0 rounded-t-lg backdrop-blur-sm">
|
||||
<div className="w-full flex items-center sticky top-0">
|
||||
<MagnifyingGlass
|
||||
size={16}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search Embedding providers"
|
||||
className="bg-zinc-600 z-20 pl-10 h-[38px] rounded-full w-full px-4 py-1 text-sm border-2 border-slate-300/40 outline-none focus:border-white text-white"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<div className="relative">
|
||||
{searchMenuOpen && (
|
||||
<div
|
||||
className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-70 backdrop-blur-sm z-10"
|
||||
onClick={() => setSearchMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{searchMenuOpen ? (
|
||||
<div className="absolute top-0 left-0 w-full max-w-[640px] max-h-[310px] overflow-auto white-scrollbar min-h-[64px] bg-[#18181B] rounded-lg flex flex-col justify-between cursor-pointer border-2 border-[#46C8FF] z-20">
|
||||
<div className="w-full flex flex-col gap-y-1">
|
||||
<div className="flex items-center sticky top-0 border-b border-[#9CA3AF] mx-4 bg-[#18181B]">
|
||||
<MagnifyingGlass
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white -ml-4 my-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="embedder-search"
|
||||
autoComplete="off"
|
||||
placeholder="Search all embedding providers"
|
||||
className="-ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none focus:border-white text-white placeholder:text-white placeholder:font-medium"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
ref={searchInputRef}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<X
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="cursor-pointer text-white hover:text-[#9CA3AF]"
|
||||
onClick={handleXButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 pl-4 pr-2 flex flex-col gap-y-1 overflow-y-auto white-scrollbar pb-4">
|
||||
{filteredEmbedders.map((embedder) => (
|
||||
<EmbedderItem
|
||||
key={embedder.name}
|
||||
name={embedder.name}
|
||||
value={embedder.value}
|
||||
image={embedder.logo}
|
||||
description={embedder.description}
|
||||
checked={selectedEmbedder === embedder.value}
|
||||
onClick={() => updateChoice(embedder.value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 pt-[70px] flex flex-col gap-y-1 max-h-[390px] overflow-y-auto no-scroll pb-4">
|
||||
{filteredEmbedders.map((embedder) => {
|
||||
return (
|
||||
<EmbedderItem
|
||||
key={embedder.name}
|
||||
name={embedder.name}
|
||||
value={embedder.value}
|
||||
image={embedder.logo}
|
||||
description={embedder.description}
|
||||
checked={selectedEmbedder === embedder.value}
|
||||
onClick={() => updateChoice(embedder.value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onChange={() => setHasChanges(true)}
|
||||
className="mt-4 flex flex-col gap-y-1"
|
||||
>
|
||||
{selectedEmbedder &&
|
||||
EMBEDDERS.find(
|
||||
(embedder) => embedder.value === selectedEmbedder
|
||||
)?.options}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="w-full max-w-[640px] h-[64px] bg-[#18181B] rounded-lg flex items-center p-[14px] justify-between cursor-pointer border-2 border-transparent hover:border-[#46C8FF] transition-all duration-300"
|
||||
type="button"
|
||||
onClick={() => setSearchMenuOpen(true)}
|
||||
>
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<img
|
||||
src={selectedEmbedderObject.logo}
|
||||
alt={`${selectedEmbedderObject.name} logo`}
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col text-left">
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{selectedEmbedderObject.name}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">
|
||||
{selectedEmbedderObject.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CaretUpDown
|
||||
size={24}
|
||||
weight="bold"
|
||||
className="text-white"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
onChange={() => setHasChanges(true)}
|
||||
className="mt-4 flex flex-col gap-y-1"
|
||||
>
|
||||
{selectedEmbedder &&
|
||||
EMBEDDERS.find(
|
||||
(embedder) => embedder.value === selectedEmbedder
|
||||
)?.options}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import Sidebar from "@/components/SettingsSidebar";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import System from "@/models/system";
|
||||
@ -34,7 +34,7 @@ import OpenRouterOptions from "@/components/LLMSelection/OpenRouterOptions";
|
||||
import GroqAiOptions from "@/components/LLMSelection/GroqAiOptions";
|
||||
|
||||
import LLMItem from "@/components/LLMSelection/LLMItem";
|
||||
import { MagnifyingGlass } from "@phosphor-icons/react";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
|
||||
export default function GeneralLLMPreference() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
@ -44,6 +44,8 @@ export default function GeneralLLMPreference() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [filteredLLMs, setFilteredLLMs] = useState([]);
|
||||
const [selectedLLM, setSelectedLLM] = useState(null);
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
const isHosted = window.location.hostname.includes("useanything.com");
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
@ -66,10 +68,21 @@ export default function GeneralLLMPreference() {
|
||||
};
|
||||
|
||||
const updateLLMChoice = (selection) => {
|
||||
setSearchQuery("");
|
||||
setSelectedLLM(selection);
|
||||
setSearchMenuOpen(false);
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const handleXButton = () => {
|
||||
if (searchQuery.length > 0) {
|
||||
setSearchQuery("");
|
||||
if (searchInputRef.current) searchInputRef.current.value = "";
|
||||
} else {
|
||||
setSearchMenuOpen(!searchMenuOpen);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
const _settings = await System.keys();
|
||||
@ -193,6 +206,8 @@ export default function GeneralLLMPreference() {
|
||||
},
|
||||
];
|
||||
|
||||
const selectedLLMObject = LLMS.find((llm) => llm.value === selectedLLM);
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
@ -234,54 +249,97 @@ export default function GeneralLLMPreference() {
|
||||
properly.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-white mt-6 mb-4">
|
||||
LLM Providers
|
||||
<div className="text-base font-bold text-white mt-6 mb-4">
|
||||
LLM Provider
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="w-full relative border-slate-300/20 shadow border-4 rounded-xl text-white">
|
||||
<div className="w-full p-4 absolute top-0 rounded-t-lg backdrop-blur-sm">
|
||||
<div className="w-full flex items-center sticky top-0">
|
||||
<MagnifyingGlass
|
||||
size={16}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search LLM providers"
|
||||
className="bg-zinc-600 z-20 pl-10 h-[38px] rounded-full w-full px-4 py-1 text-sm border-2 border-slate-300/40 outline-none focus:border-white text-white"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<div className="relative">
|
||||
{searchMenuOpen && (
|
||||
<div
|
||||
className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-70 backdrop-blur-sm z-10"
|
||||
onClick={() => setSearchMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{searchMenuOpen ? (
|
||||
<div className="absolute top-0 left-0 w-full max-w-[640px] max-h-[310px] overflow-auto white-scrollbar min-h-[64px] bg-[#18181B] rounded-lg flex flex-col justify-between cursor-pointer border-2 border-[#46C8FF] z-20">
|
||||
<div className="w-full flex flex-col gap-y-1">
|
||||
<div className="flex items-center sticky top-0 border-b border-[#9CA3AF] mx-4 bg-[#18181B]">
|
||||
<MagnifyingGlass
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white -ml-4 my-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="llm-search"
|
||||
autoComplete="off"
|
||||
placeholder="Search all LLM providers"
|
||||
className="-ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none focus:border-white text-white placeholder:text-white placeholder:font-medium"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
ref={searchInputRef}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<X
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="cursor-pointer text-white hover:text-[#9CA3AF]"
|
||||
onClick={handleXButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 pl-4 pr-2 flex flex-col gap-y-1 overflow-y-auto white-scrollbar pb-4">
|
||||
{filteredLLMs.map((llm) => {
|
||||
if (llm.value === "native" && isHosted) return null;
|
||||
return (
|
||||
<LLMItem
|
||||
key={llm.name}
|
||||
name={llm.name}
|
||||
value={llm.value}
|
||||
image={llm.logo}
|
||||
description={llm.description}
|
||||
checked={selectedLLM === llm.value}
|
||||
onClick={() => updateLLMChoice(llm.value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 pt-[70px] flex flex-col gap-y-1 max-h-[390px] overflow-y-auto no-scroll pb-4">
|
||||
{filteredLLMs.map((llm) => {
|
||||
if (llm.value === "native" && isHosted) return null;
|
||||
return (
|
||||
<LLMItem
|
||||
key={llm.name}
|
||||
name={llm.name}
|
||||
value={llm.value}
|
||||
image={llm.logo}
|
||||
description={llm.description}
|
||||
checked={selectedLLM === llm.value}
|
||||
onClick={() => updateLLMChoice(llm.value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onChange={() => setHasChanges(true)}
|
||||
className="mt-4 flex flex-col gap-y-1"
|
||||
>
|
||||
{selectedLLM &&
|
||||
LLMS.find((llm) => llm.value === selectedLLM)?.options}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="w-full max-w-[640px] h-[64px] bg-[#18181B] rounded-lg flex items-center p-[14px] justify-between cursor-pointer border-2 border-transparent hover:border-[#46C8FF] transition-all duration-300"
|
||||
type="button"
|
||||
onClick={() => setSearchMenuOpen(true)}
|
||||
>
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<img
|
||||
src={selectedLLMObject.logo}
|
||||
alt={`${selectedLLMObject.name} logo`}
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col text-left">
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{selectedLLMObject.name}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">
|
||||
{selectedLLMObject.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CaretUpDown
|
||||
size={24}
|
||||
weight="bold"
|
||||
className="text-white"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
onChange={() => setHasChanges(true)}
|
||||
className="mt-4 flex flex-col gap-y-1"
|
||||
>
|
||||
{selectedLLM &&
|
||||
LLMS.find((llm) => llm.value === selectedLLM)?.options}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,16 +1,15 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import Sidebar from "@/components/SettingsSidebar";
|
||||
import System from "@/models/system";
|
||||
import showToast from "@/utils/toast";
|
||||
import PreLoader from "@/components/Preloader";
|
||||
|
||||
import OpenAiLogo from "@/media/llmprovider/openai.png";
|
||||
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
|
||||
import OpenAiWhisperOptions from "@/components/TranscriptionSelection/OpenAiOptions";
|
||||
import NativeTranscriptionOptions from "@/components/TranscriptionSelection/NativeTranscriptionOptions";
|
||||
import LLMItem from "@/components/LLMSelection/LLMItem";
|
||||
import { MagnifyingGlass } from "@phosphor-icons/react";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
|
||||
export default function TranscriptionModelPreference() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
@ -20,6 +19,8 @@ export default function TranscriptionModelPreference() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [filteredProviders, setFilteredProviders] = useState([]);
|
||||
const [selectedProvider, setSelectedProvider] = useState(null);
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -41,10 +42,21 @@ export default function TranscriptionModelPreference() {
|
||||
};
|
||||
|
||||
const updateProviderChoice = (selection) => {
|
||||
setSearchQuery("");
|
||||
setSelectedProvider(selection);
|
||||
setSearchMenuOpen(false);
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const handleXButton = () => {
|
||||
if (searchQuery.length > 0) {
|
||||
setSearchQuery("");
|
||||
if (searchInputRef.current) searchInputRef.current.value = "";
|
||||
} else {
|
||||
setSearchMenuOpen(!searchMenuOpen);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
const _settings = await System.keys();
|
||||
@ -55,13 +67,6 @@ export default function TranscriptionModelPreference() {
|
||||
fetchKeys();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = PROVIDERS.filter((provider) =>
|
||||
provider.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
setFilteredProviders(filtered);
|
||||
}, [searchQuery, selectedProvider]);
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
name: "OpenAI",
|
||||
@ -80,6 +85,17 @@ export default function TranscriptionModelPreference() {
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = PROVIDERS.filter((provider) =>
|
||||
provider.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
setFilteredProviders(filtered);
|
||||
}, [searchQuery, selectedProvider]);
|
||||
|
||||
const selectedProviderObject = PROVIDERS.find(
|
||||
(provider) => provider.value === selectedProvider
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
@ -121,55 +137,96 @@ export default function TranscriptionModelPreference() {
|
||||
transcribe.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-white mt-6 mb-4">
|
||||
Transcription Providers
|
||||
<div className="text-base font-bold text-white mt-6 mb-4">
|
||||
Transcription Provider
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="w-full relative border-slate-300/20 shadow border-4 rounded-xl text-white">
|
||||
<div className="w-full p-4 absolute top-0 rounded-t-lg backdrop-blur-sm">
|
||||
<div className="w-full flex items-center sticky top-0">
|
||||
<MagnifyingGlass
|
||||
size={16}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search audio transcription providers"
|
||||
className="bg-zinc-600 z-20 pl-10 h-[38px] rounded-full w-full px-4 py-1 text-sm border-2 border-slate-300/40 outline-none focus:border-white text-white"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<div className="relative">
|
||||
{searchMenuOpen && (
|
||||
<div
|
||||
className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-70 backdrop-blur-sm z-10"
|
||||
onClick={() => setSearchMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{searchMenuOpen ? (
|
||||
<div className="absolute top-0 left-0 w-full max-w-[640px] max-h-[310px] overflow-auto white-scrollbar min-h-[64px] bg-[#18181B] rounded-lg flex flex-col justify-between cursor-pointer border-2 border-[#46C8FF] z-20">
|
||||
<div className="w-full flex flex-col gap-y-1">
|
||||
<div className="flex items-center sticky top-0 border-b border-[#9CA3AF] mx-4 bg-[#18181B]">
|
||||
<MagnifyingGlass
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white -ml-4 my-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="provider-search"
|
||||
autoComplete="off"
|
||||
placeholder="Search audio transcription providers"
|
||||
className="-ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none focus:border-white text-white placeholder:text-white placeholder:font-medium"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
ref={searchInputRef}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<X
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="cursor-pointer text-white hover:text-[#9CA3AF]"
|
||||
onClick={handleXButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 pl-4 pr-2 flex flex-col gap-y-1 overflow-y-auto white-scrollbar pb-4">
|
||||
{filteredProviders.map((provider) => (
|
||||
<LLMItem
|
||||
key={provider.name}
|
||||
name={provider.name}
|
||||
value={provider.value}
|
||||
image={provider.logo}
|
||||
description={provider.description}
|
||||
checked={selectedProvider === provider.value}
|
||||
onClick={() => updateProviderChoice(provider.value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 pt-[70px] flex flex-col gap-y-1 max-h-[390px] overflow-y-auto no-scroll pb-4">
|
||||
{filteredProviders.map((provider) => {
|
||||
return (
|
||||
<LLMItem
|
||||
key={provider.name}
|
||||
name={provider.name}
|
||||
value={provider.value}
|
||||
image={provider.logo}
|
||||
description={provider.description}
|
||||
checked={selectedProvider === provider.value}
|
||||
onClick={() => updateProviderChoice(provider.value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onChange={() => setHasChanges(true)}
|
||||
className="mt-4 flex flex-col gap-y-1"
|
||||
>
|
||||
{selectedProvider &&
|
||||
PROVIDERS.find(
|
||||
(provider) => provider.value === selectedProvider
|
||||
)?.options}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="w-full max-w-[640px] h-[64px] bg-[#18181B] rounded-lg flex items-center p-[14px] justify-between cursor-pointer border-2 border-transparent hover:border-[#46C8FF] transition-all duration-300"
|
||||
type="button"
|
||||
onClick={() => setSearchMenuOpen(true)}
|
||||
>
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<img
|
||||
src={selectedProviderObject.logo}
|
||||
alt={`${selectedProviderObject.name} logo`}
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col text-left">
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{selectedProviderObject.name}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">
|
||||
{selectedProviderObject.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CaretUpDown
|
||||
size={24}
|
||||
weight="bold"
|
||||
className="text-white"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
onChange={() => setHasChanges(true)}
|
||||
className="mt-4 flex flex-col gap-y-1"
|
||||
>
|
||||
{selectedProvider &&
|
||||
PROVIDERS.find(
|
||||
(provider) => provider.value === selectedProvider
|
||||
)?.options}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import Sidebar from "@/components/SettingsSidebar";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import System from "@/models/system";
|
||||
@ -13,7 +13,7 @@ import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import ChangeWarningModal from "@/components/ChangeWarning";
|
||||
import { MagnifyingGlass } from "@phosphor-icons/react";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import LanceDBOptions from "@/components/VectorDBSelection/LanceDBOptions";
|
||||
import ChromaDBOptions from "@/components/VectorDBSelection/ChromaDBOptions";
|
||||
import PineconeDBOptions from "@/components/VectorDBSelection/PineconeDBOptions";
|
||||
@ -35,8 +35,55 @@ export default function GeneralVectorDatabase() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [filteredVDBs, setFilteredVDBs] = useState([]);
|
||||
const [selectedVDB, setSelectedVDB] = useState(null);
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (selectedVDB !== settings?.VectorDB && hasChanges && hasEmbeddings) {
|
||||
openModal();
|
||||
} else {
|
||||
await handleSaveSettings();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveSettings = async () => {
|
||||
setSaving(true);
|
||||
const form = document.getElementById("vectordb-form");
|
||||
const settingsData = {};
|
||||
const formData = new FormData(form);
|
||||
settingsData.VectorDB = selectedVDB;
|
||||
for (var [key, value] of formData.entries()) settingsData[key] = value;
|
||||
|
||||
const { error } = await System.updateSystem(settingsData);
|
||||
if (error) {
|
||||
showToast(`Failed to save vector database settings: ${error}`, "error");
|
||||
setHasChanges(true);
|
||||
} else {
|
||||
showToast("Vector database preferences saved successfully.", "success");
|
||||
setHasChanges(false);
|
||||
}
|
||||
setSaving(false);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const updateVectorChoice = (selection) => {
|
||||
setSearchQuery("");
|
||||
setSelectedVDB(selection);
|
||||
setSearchMenuOpen(false);
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const handleXButton = () => {
|
||||
if (searchQuery.length > 0) {
|
||||
setSearchQuery("");
|
||||
if (searchInputRef.current) searchInputRef.current.value = "";
|
||||
} else {
|
||||
setSearchMenuOpen(!searchMenuOpen);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
const _settings = await System.keys();
|
||||
@ -48,6 +95,13 @@ export default function GeneralVectorDatabase() {
|
||||
fetchKeys();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = VECTOR_DBS.filter((vdb) =>
|
||||
vdb.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
setFilteredVDBs(filtered);
|
||||
}, [searchQuery, selectedVDB]);
|
||||
|
||||
const VECTOR_DBS = [
|
||||
{
|
||||
name: "LanceDB",
|
||||
@ -111,46 +165,7 @@ export default function GeneralVectorDatabase() {
|
||||
},
|
||||
];
|
||||
|
||||
const updateVectorChoice = (selection) => {
|
||||
setHasChanges(true);
|
||||
setSelectedVDB(selection);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (selectedVDB !== settings?.VectorDB && hasChanges && hasEmbeddings) {
|
||||
openModal();
|
||||
} else {
|
||||
await handleSaveSettings();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveSettings = async () => {
|
||||
setSaving(true);
|
||||
const form = document.getElementById("vectordb-form");
|
||||
const settingsData = {};
|
||||
const formData = new FormData(form);
|
||||
settingsData.VectorDB = selectedVDB;
|
||||
for (var [key, value] of formData.entries()) settingsData[key] = value;
|
||||
|
||||
const { error } = await System.updateSystem(settingsData);
|
||||
if (error) {
|
||||
showToast(`Failed to save vector database settings: ${error}`, "error");
|
||||
setHasChanges(true);
|
||||
} else {
|
||||
showToast("Vector database preferences saved successfully.", "success");
|
||||
setHasChanges(false);
|
||||
}
|
||||
setSaving(false);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = VECTOR_DBS.filter((vdb) =>
|
||||
vdb.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
setFilteredVDBs(filtered);
|
||||
}, [searchQuery, selectedVDB]);
|
||||
const selectedVDBObject = VECTOR_DBS.find((vdb) => vdb.value === selectedVDB);
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
@ -176,7 +191,7 @@ export default function GeneralVectorDatabase() {
|
||||
>
|
||||
<div className="flex flex-col w-full px-1 md:pl-6 md:pr-[86px] md:py-6 py-16">
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Vector Database
|
||||
</p>
|
||||
@ -196,55 +211,94 @@ export default function GeneralVectorDatabase() {
|
||||
are current and correct.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-white mt-6 mb-4">
|
||||
Vector Database Providers
|
||||
<div className="text-base font-bold text-white mt-6 mb-4">
|
||||
Vector Database Provider
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="w-full relative border-slate-300/20 shadow border-4 rounded-xl text-white">
|
||||
<div className="w-full p-4 absolute top-0 rounded-t-lg backdrop-blur-sm">
|
||||
<div className="w-full flex items-center sticky top-0">
|
||||
<MagnifyingGlass
|
||||
size={16}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search vector databases"
|
||||
className="bg-zinc-600 z-20 pl-10 h-[38px] rounded-full w-full px-4 py-1 text-sm border-2 border-slate-300/40 outline-none focus:border-white text-white"
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setSearchQuery(e.target.value);
|
||||
}}
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<div className="relative">
|
||||
{searchMenuOpen && (
|
||||
<div
|
||||
className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-70 backdrop-blur-sm z-10"
|
||||
onClick={() => setSearchMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{searchMenuOpen ? (
|
||||
<div className="absolute top-0 left-0 w-full max-w-[640px] max-h-[310px] overflow-auto white-scrollbar min-h-[64px] bg-[#18181B] rounded-lg flex flex-col justify-between cursor-pointer border-2 border-[#46C8FF] z-20">
|
||||
<div className="w-full flex flex-col gap-y-1">
|
||||
<div className="flex items-center sticky top-0 border-b border-[#9CA3AF] mx-4 bg-[#18181B]">
|
||||
<MagnifyingGlass
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white -ml-4 my-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="vdb-search"
|
||||
autoComplete="off"
|
||||
placeholder="Search all vector database providers"
|
||||
className="-ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none focus:border-white text-white placeholder:text-white placeholder:font-medium"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
ref={searchInputRef}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<X
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="cursor-pointer text-white hover:text-[#9CA3AF]"
|
||||
onClick={handleXButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 pl-4 pr-2 flex flex-col gap-y-1 overflow-y-auto white-scrollbar pb-4">
|
||||
{filteredVDBs.map((vdb) => (
|
||||
<VectorDBItem
|
||||
key={vdb.name}
|
||||
name={vdb.name}
|
||||
value={vdb.value}
|
||||
image={vdb.logo}
|
||||
description={vdb.description}
|
||||
checked={selectedVDB === vdb.value}
|
||||
onClick={() => updateVectorChoice(vdb.value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 pt-[70px] flex flex-col gap-y-1 max-h-[390px] overflow-y-auto no-scroll pb-4">
|
||||
{filteredVDBs.map((vdb) => (
|
||||
<VectorDBItem
|
||||
key={vdb.name}
|
||||
name={vdb.name}
|
||||
value={vdb.value}
|
||||
image={vdb.logo}
|
||||
description={vdb.description}
|
||||
checked={selectedVDB === vdb.value}
|
||||
onClick={() => updateVectorChoice(vdb.value)}
|
||||
) : (
|
||||
<button
|
||||
className="w-full max-w-[640px] h-[64px] bg-[#18181B] rounded-lg flex items-center p-[14px] justify-between cursor-pointer border-2 border-transparent hover:border-[#46C8FF] transition-all duration-300"
|
||||
type="button"
|
||||
onClick={() => setSearchMenuOpen(true)}
|
||||
>
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<img
|
||||
src={selectedVDBObject.logo}
|
||||
alt={`${selectedVDBObject.name} logo`}
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onChange={() => setHasChanges(true)}
|
||||
className="mt-4 flex flex-col gap-y-1"
|
||||
>
|
||||
{selectedVDB &&
|
||||
VECTOR_DBS.find((vdb) => vdb.value === selectedVDB)
|
||||
?.options}
|
||||
</div>
|
||||
<div className="flex flex-col text-left">
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{selectedVDBObject.name}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">
|
||||
{selectedVDBObject.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CaretUpDown
|
||||
size={24}
|
||||
weight="bold"
|
||||
className="text-white"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
onChange={() => setHasChanges(true)}
|
||||
className="mt-4 flex flex-col gap-y-1"
|
||||
>
|
||||
{selectedVDB &&
|
||||
VECTOR_DBS.find((vdb) => vdb.value === selectedVDB)?.options}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
Loading…
Reference in New Issue
Block a user