Anthropic claude 2 support (#305)

* WIP Anythropic support for chat, chat and query w/context

* Add onboarding support for Anthropic

* cleanup

* fix Anthropic answer parsing
move embedding selector to general util
This commit is contained in:
Timothy Carambat 2023-10-30 15:44:03 -07:00 committed by GitHub
parent 669d7a396d
commit 5d56ab623b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1200 additions and 1900 deletions

View File

@ -15,6 +15,16 @@ OPEN_MODEL_PREF='gpt-3.5-turbo'
# OPEN_MODEL_PREF='my-gpt35-deployment' # This is the "deployment" on Azure you want to use. Not the base model.
# EMBEDDING_MODEL_PREF='embedder-model' # This is the "deployment" on Azure you want to use for embeddings. Not the base model. Valid base model is text-embedding-ada-002
# LLM_PROVIDER='anthropic'
# ANTHROPIC_API_KEY=sk-ant-xxxx
# ANTHROPIC_MODEL_PREF='claude-2'
###########################################
######## Embedding API SElECTION ##########
###########################################
# Only used if you are using an LLM that does not natively support embedding (openai or Azure)
# EMBEDDING_ENGINE='openai'
# OPEN_AI_KEY=sk-xxxx
###########################################
######## Vector Database Selection ########

View File

@ -22,6 +22,9 @@ const GeneralApiKeys = lazy(() => import("./pages/GeneralSettings/ApiKeys"));
const GeneralLLMPreference = lazy(() =>
import("./pages/GeneralSettings/LLMPreference")
);
const GeneralEmbeddingPreference = lazy(() =>
import("./pages/GeneralSettings/EmbeddingPreference")
);
const GeneralVectorDatabase = lazy(() =>
import("./pages/GeneralSettings/VectorDatabase")
);
@ -50,6 +53,10 @@ export default function App() {
path="/general/llm-preference"
element={<PrivateRoute Component={GeneralLLMPreference} />}
/>
<Route
path="/general/embedding-preference"
element={<PrivateRoute Component={GeneralEmbeddingPreference} />}
/>
<Route
path="/general/vector-database"
element={<PrivateRoute Component={GeneralVectorDatabase} />}

View File

@ -1,198 +0,0 @@
import { useEffect, useState } from "react";
import System from "../../../../models/system";
import PreLoader from "../../../Preloader";
import paths from "../../../../utils/paths";
import showToast from "../../../../utils/toast";
import { CheckCircle, Copy, RefreshCcw, Trash } from "react-feather";
export default function ApiKey() {
const [loading, setLoading] = useState(true);
const [generating, setGenerating] = useState(false);
const [copied, setCopied] = useState(false);
const [deleting, setDeleting] = useState(false);
const [apiKey, setApiKey] = useState(null);
useEffect(() => {
async function fetchExistingApiKey() {
const { apiKey: _apiKey } = await System.getApiKey();
setApiKey(_apiKey);
setLoading(false);
}
fetchExistingApiKey();
}, []);
const generateApiKey = async () => {
setGenerating(true);
const isRefresh = !!apiKey;
const { apiKey: newApiKey, error } = await System.generateApiKey();
if (!!error) {
showToast(error, "error");
} else {
showToast(
isRefresh ? "API key regenerated!" : "API key generated!",
"info"
);
setApiKey(newApiKey);
}
setGenerating(false);
};
const removeApiKey = async () => {
setDeleting(true);
const ok = await System.deleteApiKey();
if (ok) {
showToast("API key deleted from instance.", "info");
setApiKey(null);
} else {
showToast("API key could not be deleted.", "error");
}
setDeleting(false);
};
const copyToClipboard = async () => {
window.navigator.clipboard.writeText(apiKey.secret);
showToast("API key copied to clipboard!", "info");
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1200);
};
if (loading) {
return (
<div className="relative w-full w-full 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 ">
Generate an API Key for your AnythingLLM instance.
</p>
</div>
<div className="px-1 md:px-8 pb-10 ">
<PreLoader />
</div>
</div>
</div>
);
}
if (!apiKey) {
return (
<div className="relative w-full w-full 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 ">
Generate an API Key for your AnythingLLM instance.
</p>
</div>
<div className="md:px-8 pb-10 ">
<div className="flex flex-col gap-y-1 text-gray-800 dark:text-stone-200 mb-2">
<p>
No api key for this instance exists. Create one by clicking the
button below.
</p>
<a
href={paths.apiDocs()}
target="_blank"
className="dark:text-blue-300 text-blue-600 hover:underline"
>
View endpoint documentation &rarr;
</a>
</div>
<button
disabled={generating}
type="button"
onClick={generateApiKey}
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"
>
{generating ? "Generating..." : "Generate new API key"}
</button>
</div>
</div>
</div>
);
}
return (
<div className="relative w-full w-full max-h-full">
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
<div className="flex flex-col items-start justify-between px-6 py-4">
<p className="text-gray-800 dark:text-stone-200 text-base ">
Use this API key for interacting with your AnythingLLM instance
programmatically.
</p>
<a
href={paths.apiDocs()}
target="_blank"
className="dark:text-blue-300 text-blue-600 hover:underline"
>
View endpoint documentation &rarr;
</a>
</div>
<div className="md:px-8 pb-10">
<div className="mb-6">
<div className="flex flex-col md:flex-row items-center">
<div className="flex md:flex-row flex-col gap-y-2 w-full gap-x-2 items-center px-4 md:px-0">
<input
key={apiKey.secret}
type="text"
disabled={true}
className="w-full md:w-1/2 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 p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
defaultValue={apiKey.secret}
autoComplete="off"
spellCheck={false}
/>
<button
onClick={copyToClipboard}
disabled={copied}
className="w-full flex justify-center items-center gap-x-2 md:w-fit disabled:bg-green-300 dark:disabled:bg-green-600 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 p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200 group hover:bg-gray-100 dark:hover:bg-stone-600"
>
{copied ? (
<CheckCircle className="stroke-green-800 dark:stroke-green-300" />
) : (
<Copy />
)}
<p className="block md:hidden text-base">Copy API Key</p>
</button>
<button
onClick={() => {
if (
!confirm(
"Are you sure you want to refresh the API key? The old key will no longer work!"
)
)
return false;
generateApiKey();
}}
disabled={generating}
className="w-full flex justify-center items-center gap-x-2 md:w-fit disabled:bg-green-300 dark:disabled:bg-green-600 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 p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200 group hover:bg-gray-100 dark:hover:bg-stone-600"
>
<RefreshCcw />
<p className="block md:hidden text-base">
Regenerate API Key
</p>
</button>
<button
onClick={() => {
if (
!confirm(
"Are you sure you want to delete the API key? All API keys will be deleted."
)
)
return false;
removeApiKey();
}}
disabled={deleting}
className="w-full flex justify-center items-center gap-x-2 md:w-fit disabled:bg-red-300 dark:disabled:bg-red-600 border border-red-500 text-red-900 placeholder-red-500 text-sm rounded-lg dark:bg-transparent focus:border-red-500 block p-2.5 dark:text-red-200 dark:placeholder-red-500 dark:border-red-200 group hover:bg-red-100 dark:hover:bg-red-600"
>
<Trash />
<p className="block md:hidden text-base">Delete API Key</p>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,210 +0,0 @@
import { useEffect, useState } from "react";
import useLogo from "../../../../hooks/useLogo";
import usePrefersDarkMode from "../../../../hooks/usePrefersDarkMode";
import System from "../../../../models/system";
import EditingChatBubble from "../../../EditingChatBubble";
import AnythingLLM from "../../../../media/logo/anything-llm.png";
import showToast from "../../../../utils/toast";
export default function Appearance() {
const { logo: _initLogo } = useLogo();
const prefersDarkMode = usePrefersDarkMode();
const [logo, setLogo] = useState("");
const [hasChanges, setHasChanges] = useState(false);
const [messages, setMessages] = useState([]);
useEffect(() => {
async function fetchMessages() {
const messages = await System.getWelcomeMessages();
setMessages(messages);
}
fetchMessages();
}, []);
useEffect(() => {
async function setInitLogo() {
setLogo(_initLogo || "");
}
setInitLogo();
}, [_initLogo]);
const handleFileUpload = async (event) => {
const file = event.target.files[0];
if (!file) return false;
const formData = new FormData();
formData.append("logo", file);
const { success, error } = await System.uploadLogo(formData);
if (!success) {
console.error("Failed to upload logo:", error);
showToast(`Failed to upload logo: ${error}`, "error");
return;
}
const logoURL = await System.fetchLogo();
setLogo(logoURL);
showToast("Image uploaded successfully.", "success");
};
const handleRemoveLogo = async () => {
const { success, error } = await System.removeCustomLogo();
if (!success) {
console.error("Failed to remove logo:", error);
showToast(`Failed to remove logo: ${error}`, "error");
return;
}
const logoURL = await System.fetchLogo();
setLogo(logoURL);
showToast("Image successfully removed.", "success");
};
const addMessage = (type) => {
if (type === "user") {
setMessages([
...messages,
{ user: "Double click to edit...", response: "" },
]);
} else {
setMessages([
...messages,
{ user: "", response: "Double click to edit..." },
]);
}
};
const removeMessage = (index) => {
setHasChanges(true);
setMessages(messages.filter((_, i) => i !== index));
};
const handleMessageChange = (index, type, value) => {
setHasChanges(true);
const newMessages = [...messages];
newMessages[index][type] = value;
setMessages(newMessages);
};
const handleMessageSave = async () => {
const { success, error } = await System.setWelcomeMessages(messages);
if (!success) {
showToast(`Failed to update welcome messages: ${error}`, "error");
return;
}
showToast("Successfully updated welcome messages.", "success");
setHasChanges(false);
};
return (
<div className="relative w-full w-full 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 ">
Customize the appearance settings of AnythingLLM instance.
</p>
</div>
<div className="px-1 md:px-8 pb-10">
<div className="mb-6">
<div className="flex flex-col gap-y-2">
<h2 className="leading-tight font-medium text-black dark:text-white">
Custom Logo
</h2>
<p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
Change the logo that appears in the sidebar.
</p>
</div>
<div className="flex flex-col md:flex-row items-center">
<img
src={logo}
alt="Uploaded Logo"
className="w-48 h-48 object-contain mr-6"
onError={(e) => (e.target.src = AnythingLLM)}
/>
<div className="flex flex-col">
<div className="mb-4">
<label className="cursor-pointer 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">
Upload Image
<input
type="file"
accept="image/*"
className="hidden"
onChange={handleFileUpload}
/>
</label>
<button
onClick={handleRemoveLogo}
className="ml-4 cursor-pointer 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"
>
Remove Custom Logo
</button>
</div>
<div className="text-sm text-gray-600 dark:text-gray-300">
Upload your logo. Recommended size: 800x200.
</div>
</div>
</div>
</div>
<div className="mb-6">
<div className="flex flex-col gap-y-2">
<h2 className="leading-tight font-medium text-black dark:text-white">
Custom Messages
</h2>
<p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
Change the default messages that are displayed to the users.
</p>
</div>
<div className="mt-6 flex flex-col gap-y-6 bg-white dark:bg-black-900 p-4 rounded-lg">
{messages.map((message, index) => (
<div key={index} className="flex flex-col gap-y-2">
{message.user && (
<EditingChatBubble
message={message}
index={index}
type="user"
handleMessageChange={handleMessageChange}
removeMessage={removeMessage}
/>
)}
{message.response && (
<EditingChatBubble
message={message}
index={index}
type="response"
handleMessageChange={handleMessageChange}
removeMessage={removeMessage}
/>
)}
</div>
))}
<div className="flex gap-4 mt-4 justify-between">
<button
className="self-end text-orange-500 hover:text-orange-700 transition"
onClick={() => addMessage("response")}
>
+ System Message
</button>
<button
className="self-end text-orange-500 hover:text-orange-700 transition"
onClick={() => addMessage("user")}
>
+ User Message
</button>
</div>
</div>
{hasChanges && (
<div className="flex justify-center py-6">
<button
className="ml-4 cursor-pointer 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"
onClick={handleMessageSave}
>
Save Messages
</button>
</div>
)}
</div>
</div>
</div>
</div>
);
}

View File

@ -1,216 +0,0 @@
import React, { useState, useEffect, useRef } from "react";
import { AlertCircle, CheckCircle, Download, Loader, X } from "react-feather";
import System from "../../../../models/system";
import { API_BASE } from "../../../../utils/constants";
import paths from "../../../../utils/paths";
const noop = () => false;
export default function ExportOrImportData({ hideModal = noop }) {
return (
<div className="relative w-full w-full max-h-full">
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
<div className="flex flex-col items-start justify-between px-6 py-4">
<p className="text-gray-800 dark:text-stone-200 text-base ">
Have multiple AnythingLLM instances or simply want to backup or
re-import data from another instance? You can do so here.
<br />
<i>
This will not automatically sync your vector database embeddings!
</i>
</p>
<a
className="text-gray-400 dark:text-stone-500 my-2 text-xs"
href={paths.exports()}
target="_blank"
>
View previous exports &rarr;
</a>
</div>
<div className="px-6 pb-6 space-y-6 flex h-full w-full">
<div className="flex flex-col w-full gap-y-2">
<ExportData />
<div className="h-[1px] bg-slate-400 dark:bg-stone-600 w-full my-2" />
<ImportData />
</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 ExportData() {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const exportData = async function () {
setLoading(true);
const { filename, error } = await System.dataExport();
setLoading(false);
if (!filename) {
setError(error);
} else {
setResult(filename);
const link = document.createElement("a");
link.href = `${API_BASE}/system/data-exports/${filename}`;
link.target = "_blank";
document.body.appendChild(link);
link.click();
}
};
if (loading) {
return (
<div className="w-full flex flex-col gap-y-1 items-center px-6 py-4 border border-gray-200 rounded-lg dark:border-gray-600 bg-slate-200 group animate-pulse">
<p className="text-gray-800 text-lg">Exporting....</p>
<p className="text-gray-800 text-sm italic">
A download will start automatically.
</p>
</div>
);
}
if (error) {
return (
<button
type="button"
onClick={() => setError(null)}
className="w-full flex flex-col gap-y-1 items-center px-6 py-4 border border-red-200 rounded-lg dark:border-red-600 bg-red-200 group"
>
<p className="text-red-800 text-sm">{error}</p>
</button>
);
}
if (!!result) {
return (
<a
target="_blank"
href={`${API_BASE}/system/data-exports/${result}`}
className="w-full flex gap-1 justify-center items-center px-6 py-4 border border-green-200 rounded-lg dark:border-green-600 bg-green-200 group"
>
<Download className="h-4 w-4 text-green-800 " />
<p className="text-green-800 text-sm">Download Data Export</p>
</a>
);
}
return (
<button
onClick={exportData}
type="button"
className="w-full flex justify-center px-6 py-4 border border-gray-200 rounded-lg dark:border-gray-600 hover:bg-slate-200 group"
>
<p className="text-gray-800 dark:text-stone-200 group-hover:text-gray-800 text-lg">
Export AnythingLLM data
</p>
</button>
);
}
function ImportData() {
const inputRef = useRef(null);
const [loading, setLoading] = useState(false);
const [file, setFile] = useState(null);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const startInput = () => inputRef?.current?.click();
const handleUpload = async (e) => {
e.preventDefault();
setError(null);
setFile(null);
setResult(null);
const file = e.target.files?.[0];
if (!file) {
setError("Invalid file upload");
return false;
}
setFile(file);
setLoading(true);
const formData = new FormData();
formData.append("file", file, file.name);
const { success, error } = await System.importData(formData);
if (!success) {
setError(error);
} else {
setResult(true);
}
setLoading(false);
setFile(null);
};
if (loading) {
return (
<div className="w-full flex flex-col gap-y-1 items-center px-6 py-4 border border-gray-200 rounded-lg dark:border-gray-600 bg-slate-200 group animate-pulse">
<p className="text-gray-800 text-lg">Importing....</p>
<p className="text-gray-800 text-sm italic">{file.name}</p>
</div>
);
}
if (error) {
return (
<button
type="button"
onClick={() => setError(null)}
className="w-full flex flex-col gap-y-1 items-center px-6 py-4 border border-red-200 rounded-lg dark:border-red-600 bg-red-200 group"
>
<p className="text-red-800 text-sm">{error}</p>
</button>
);
}
if (!!result) {
return (
<div className="w-full flex flex-col gap-y-1 gap-1 justify-center items-center px-6 py-4 border border-green-200 rounded-lg dark:border-green-600 bg-green-200 group">
<div className="flex items-center gap-x-1">
<CheckCircle className="h-4 w-4 text-green-800 " />
<p className="text-green-800 text-sm">
Import was completed successfully
</p>
</div>
<p className="text-green-800 text-xs italic">
please reload the page to see the results of the import.
</p>
</div>
);
}
return (
<>
<input
ref={inputRef}
onChange={handleUpload}
name="import"
type="file"
multiple="false"
accept=".zip"
hidden={true}
/>
<button
type="button"
onClick={startInput}
className="w-full flex flex-col gap-y-1 items-center px-6 py-4 border border-gray-200 rounded-lg dark:border-gray-600 hover:bg-slate-200 group"
>
<p className="text-gray-800 dark:text-stone-200 group-hover:text-gray-800 text-lg">
Import AnythingLLM data
</p>
<p className="text-gray-800 dark:text-stone-200 group-hover:text-gray-800 text-xs italic">
this must be an export from an AnythingLLM instance.
</p>
</button>
</>
);
}

View File

@ -1,270 +0,0 @@
import React, { useState } from "react";
import System from "../../../../models/system";
import OpenAiLogo from "../../../../media/llmprovider/openai.png";
import AzureOpenAiLogo from "../../../../media/llmprovider/azure.png";
import AnthropicLogo from "../../../../media/llmprovider/anthropic.png";
import showToast from "../../../../utils/toast";
const noop = () => false;
export default function LLMSelection({
hideModal = noop,
user,
settings = {},
}) {
const [hasChanges, setHasChanges] = useState(false);
const [llmChoice, setLLMChoice] = useState(settings?.LLMProvider || "openai");
const [saving, setSaving] = useState(false);
const canDebug = settings.MultiUserMode
? settings?.CanDebug && user?.role === "admin"
: settings?.CanDebug;
function updateLLMChoice(selection) {
if (!canDebug || selection === llmChoice) return false;
setHasChanges(true);
setLLMChoice(selection);
}
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 { error } = await System.updateSystem(data);
if (error) {
showToast(`Failed to save LLM settings: ${error}`, "error");
} else {
showToast("LLM settings saved successfully.", "success");
}
setSaving(false);
setHasChanges(!!error ? true : false);
};
return (
<div className="relative w-full w-full 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 your preferred LLM chat &
embedding provider. Its important these keys are current and correct
or else AnythingLLM will not function properly.
</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">
LLM providers
</p>
<div className="w-full flex overflow-x-scroll gap-x-4">
<input hidden={true} name="LLMProvider" value={llmChoice} />
<LLMProviderOption
name="OpenAI"
value="openai"
link="openai.com"
description="The standard option for most non-commercial use. Provides both chat and embedding."
checked={llmChoice === "openai"}
image={OpenAiLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Azure OpenAi"
value="azure"
link="azure.microsoft.com"
description="The enterprise option of OpenAI hosted on Azure services. Provides both chat and embedding."
checked={llmChoice === "azure"}
image={AzureOpenAiLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Anthropic Claude 2"
value="anthropic-claude-2"
link="anthropic.com"
description="[COMING SOON] A friendly AI Assistant hosted by Anthropic. Provides chat services only!"
checked={llmChoice === "anthropic-claude-2"}
image={AnthropicLogo}
/>
</div>
{llmChoice === "openai" && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
API Key
</label>
<input
type="text"
name="OpenAiKey"
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="OpenAI API Key"
defaultValue={settings?.OpenAiKey ? "*".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">
Chat Model Selection
</label>
<select
disabled={!canDebug}
name="OpenAiModelPref"
defaultValue={settings?.OpenAiModelPref}
required={true}
className="bg-gray-50 border border-gray-500 text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-stone-700 dark:border-slate-200 dark:placeholder-stone-500 dark:text-slate-200"
>
{["gpt-3.5-turbo", "gpt-4"].map((model) => {
return (
<option key={model} value={model}>
{model}
</option>
);
})}
</select>
</div>
</>
)}
{llmChoice === "azure" && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Azure Service Endpoint
</label>
<input
type="url"
name="AzureOpenAiEndpoint"
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="https://my-azure.openai.azure.com"
defaultValue={settings?.AzureOpenAiEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
API Key
</label>
<input
type="password"
name="AzureOpenAiKey"
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="Azure OpenAI API Key"
defaultValue={
settings?.AzureOpenAiKey ? "*".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">
Chat Model Deployment Name
</label>
<input
type="text"
name="AzureOpenAiModelPref"
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="Azure OpenAI chat model deployment name"
defaultValue={settings?.AzureOpenAiModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Embedding Model Deployment Name
</label>
<input
type="text"
name="AzureOpenAiEmbeddingModelPref"
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="Azure OpenAI embedding model deployment name"
defaultValue={settings?.AzureOpenAiEmbeddingModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{llmChoice === "anthropic-claude-2" && (
<div className="w-full h-40 items-center justify-center flex">
<p className="text-gray-800 dark:text-slate-400">
This provider is unavailable and cannot be used in
AnythingLLM currently.
</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 LLMProviderOption = ({
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,158 +0,0 @@
import React, { useState } from "react";
import System from "../../../../models/system";
import {
AUTH_TIMESTAMP,
AUTH_TOKEN,
AUTH_USER,
} from "../../../../utils/constants";
import paths from "../../../../utils/paths";
const noop = () => false;
export default function MultiUserMode({ hideModal = noop }) {
const [saving, setSaving] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(null);
const [useMultiUserMode, setUseMultiUserMode] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
setSuccess(false);
setError(null);
const form = new FormData(e.target);
const data = {
username: form.get("username"),
password: form.get("password"),
};
const { success, error } = await System.setupMultiUser(data);
if (success) {
setSuccess(true);
setSaving(false);
setTimeout(() => {
window.localStorage.removeItem(AUTH_USER);
window.localStorage.removeItem(AUTH_TOKEN);
window.localStorage.removeItem(AUTH_TIMESTAMP);
window.location = paths.admin.users();
}, 2_000);
return;
}
setError(error);
setSaving(false);
};
return (
<div className="relative w-full w-full 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 ">
Update your AnythingLLM instance to support multiple concurrent
users with their own workspaces. As the admin you can view all
workspaces and add people into workspaces as well. This change is
not reversible and will permanently alter your AnythingLLM
installation.
</p>
</div>
{(error || success) && (
<div className="w-full flex px-6">
{error && (
<div className="w-full bg-red-300 text-red-800 font-semibold px-4 py-2 rounded-lg">
{error}
</div>
)}
{success && (
<div className="w-full bg-green-300 text-green-800 font-semibold px-4 py-2 rounded-lg">
Your page will refresh in a few seconds.
</div>
)}
</div>
)}
<div className="p-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<form onSubmit={handleSubmit}>
<div className="">
<label className="mb-2.5 block font-medium text-black dark:text-white">
Enable Multi-User Mode
</label>
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
onClick={() => setUseMultiUserMode(!useMultiUserMode)}
checked={useMultiUserMode}
className="peer sr-only pointer-events-none"
/>
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] 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-green-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:border-gray-600 dark:bg-stone-400 dark:peer-focus:ring-blue-800"></div>
</label>
</div>
<div className="w-full flex flex-col gap-y-2 my-2">
{useMultiUserMode && (
<>
<p className="text-gray-800 dark:text-stone-200 text-sm bg-gray-200 dark:bg-stone-800 rounded-lg p-4">
By default, you will be the only admin. As an admin you
will need to create accounts for all new users or admins.
Do not lose your password as only an Admin user can reset
passwords.
</p>
<div>
<label
htmlFor="username"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Admin account username
</label>
<input
name="username"
type="text"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-stone-600 dark:border-stone-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Your admin username"
minLength={2}
required={true}
autoComplete="off"
/>
</div>
<div>
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Admin account password
</label>
<input
name="password"
type="text"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-stone-600 dark:border-stone-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Your admin password"
minLength={8}
required={true}
autoComplete="off"
/>
</div>
<button
disabled={saving}
type="submit"
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"
>
{saving ? "Enabling..." : "Enable Multi-User mode"}
</button>
</>
)}
</div>
</form>
</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>
);
}

View File

@ -1,117 +0,0 @@
import React, { useState } from "react";
import System from "../../../../models/system";
import {
AUTH_TIMESTAMP,
AUTH_TOKEN,
AUTH_USER,
} from "../../../../utils/constants";
import showToast from "../../../../utils/toast";
const noop = () => false;
export default function PasswordProtection({
hideModal = noop,
settings = {},
}) {
const [saving, setSaving] = useState(false);
const [usePassword, setUsePassword] = useState(settings?.RequiresAuth);
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
const form = new FormData(e.target);
const data = {
usePassword,
newPassword: form.get("password"),
};
const { success, error } = await System.updateSystemPassword(data);
if (success) {
showToast("Your page will refresh in a few seconds.", "success");
setSaving(false);
setTimeout(() => {
window.localStorage.removeItem(AUTH_USER);
window.localStorage.removeItem(AUTH_TOKEN);
window.localStorage.removeItem(AUTH_TIMESTAMP);
window.location.reload();
}, 3_000);
return;
} else {
showToast(`Failed to update password: ${error}`, "error");
}
setSaving(false);
};
return (
<div className="relative w-full w-full 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 ">
Protect your AnythingLLM instance with a password. If you forget
this there is no recovery method so ensure you save this password.
</p>
</div>
<div className="p-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<form onSubmit={handleSubmit}>
<div className="">
<label className="mb-2.5 block font-medium text-black dark:text-white">
Password Protect Instance
</label>
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
name="use_password"
onClick={() => setUsePassword(!usePassword)}
checked={usePassword}
className="peer sr-only pointer-events-none"
/>
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] 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-green-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:border-gray-600 dark:bg-stone-400 dark:peer-focus:ring-blue-800"></div>
</label>
</div>
<div className="w-full flex flex-col gap-y-2 my-2">
{usePassword && (
<div>
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
New Password
</label>
<input
name="password"
type="text"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-stone-600 dark:border-stone-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Your Instance Password"
minLength={8}
required={true}
autoComplete="off"
/>
</div>
)}
<button
disabled={saving}
type="submit"
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"
>
{saving ? "Saving..." : "Save Changes"}
</button>
</div>
</form>
</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>
);
}

View File

@ -1,357 +0,0 @@
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";
import WeaviateLogo from "../../../../media/vectordbs/weaviate.png";
import QDrantLogo from "../../../../media/vectordbs/qdrant.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 w-full 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 providers
</p>
<div className="w-full flex md:flex-wrap overflow-x-scroll gap-4">
<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="QDrant"
value="qdrant"
link="qdrant.tech"
description="Open source local and distributed cloud vector database."
checked={vectorDB === "qdrant"}
image={QDrantLogo}
onClick={updateVectorChoice}
/>
<VectorDBOption
name="Weaviate"
value="weaviate"
link="weaviate.io"
description="Open source local and cloud hosted multi-modal vector database."
checked={vectorDB === "weaviate"}
image={WeaviateLogo}
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="password"
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>
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="ChromaAuthTokenHeader"
className="block text-sm font-medium text-gray-800 dark:text-slate-200"
>
API Header & Key
</label>
<p className="text-xs text-gray-800 dark:text-slate-200">
If your hosted Chroma instance is protected by an API
key - enter the header and api key here.
</p>
</div>
<div className="flex w-full items-center gap-x-4">
<input
name="ChromaApiHeader"
autoComplete="off"
type="text"
defaultValue={settings?.ChromaApiHeader}
className="w-[20%] 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="X-Api-Key"
/>
<input
name="ChromaApiKey"
autoComplete="off"
type="password"
defaultValue={
settings?.ChromaApiKey ? "*".repeat(20) : ""
}
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="sk-myApiKeyToAccessMyChromaInstance"
/>
</div>
</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>
)}
{vectorDB === "qdrant" && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
QDrant API Endpoint
</label>
<input
type="url"
name="QdrantEndpoint"
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:6633"
defaultValue={settings?.QdrantEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Api Key
</label>
<input
type="password"
name="QdrantApiKey"
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="wOeqxsYP4....1244sba"
defaultValue={settings?.QdrantApiKey}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{vectorDB === "weaviate" && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Weaviate Endpoint
</label>
<input
type="url"
name="WeaviateEndpoint"
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:8080"
defaultValue={settings?.WeaviateEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Api Key
</label>
<input
type="password"
name="WeaviateApiKey"
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="sk-123Abcweaviate"
defaultValue={settings?.WeaviateApiKey}
autoComplete="off"
spellCheck={false}
/>
</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,85 +0,0 @@
import React, { useEffect, useState } from "react";
import { X } from "react-feather";
import ExportOrImportData from "./ExportImport";
import PasswordProtection from "./PasswordProtection";
import System from "../../../models/system";
import MultiUserMode from "./MultiUserMode";
import useUser from "../../../hooks/useUser";
import VectorDBSelection from "./VectorDbs";
import LLMSelection from "./LLMSelection";
import Appearance from "./Appearance";
import ApiKey from "./ApiKey";
export const TABS = {
llm: LLMSelection,
exportimport: ExportOrImportData,
password: PasswordProtection,
multiuser: MultiUserMode,
vectordb: VectorDBSelection,
appearance: Appearance,
apikey: ApiKey,
};
const noop = () => false;
export default function SystemSettingsModal({ tab = null, hideModal = noop }) {
const { user } = useUser();
const [loading, setLoading] = useState(true);
const [settings, setSettings] = useState(null);
const Component = TABS[tab || "llm"];
useEffect(() => {
async function fetchKeys() {
const _settings = await System.keys();
setSettings(_settings);
setLoading(false);
}
fetchKeys();
}, []);
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 w-full md:w-1/2 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>
</div>
{loading ? (
<div className="w-full flex h-[400px] p-6">
<div className="w-full flex h-full bg-gray-200 dark:bg-stone-600 animate-pulse rounded-lg" />
</div>
) : (
<Component hideModal={hideModal} user={user} settings={settings} />
)}
</div>
</div>
</div>
);
}
export function useSystemSettingsModal() {
const [showing, setShowing] = useState(false);
const showModal = () => {
setShowing(true);
};
const hideModal = () => {
setShowing(false);
};
return { showing, showModal, hideModal };
}

View File

@ -21,6 +21,7 @@ function useIsAuthenticated() {
MultiUserMode,
RequiresAuth,
OpenAiKey = false,
AnthropicApiKey = false,
AzureOpenAiKey = false,
} = await System.keys();
@ -29,6 +30,7 @@ function useIsAuthenticated() {
!MultiUserMode &&
!RequiresAuth && // Not in Multi-user AND no password set.
!OpenAiKey &&
!AnthropicApiKey &&
!AzureOpenAiKey // AND no LLM API Key set at all.
) {
setShouldRedirectToOnboarding(true);

View File

@ -21,6 +21,7 @@ import {
House,
X,
List,
FileCode,
} from "@phosphor-icons/react";
import useUser from "../../hooks/useUser";
import { USER_BACKGROUND_COLOR } from "../../utils/constants";
@ -115,6 +116,11 @@ export default function SettingsSidebar() {
btnText="LLM Preference"
icon={<ChatText className="h-5 w-5 flex-shrink-0" />}
/>
<Option
href={paths.general.embeddingPreference()}
btnText="Embedding Preference"
icon={<FileCode className="h-5 w-5 flex-shrink-0" />}
/>
<Option
href={paths.general.vectorDatabase()}
btnText="Vector Database"
@ -312,6 +318,11 @@ export function SidebarMobileHeader() {
btnText="LLM Preference"
icon={<ChatText className="h-5 w-5 flex-shrink-0" />}
/>
<Option
href={paths.general.embeddingPreference()}
btnText="Embedding Preference"
icon={<FileCode className="h-5 w-5 flex-shrink-0" />}
/>
<Option
href={paths.general.vectorDatabase()}
btnText="Vector Database"

View File

@ -0,0 +1,227 @@
import React, { useEffect, useState } from "react";
import Sidebar, {
SidebarMobileHeader,
} from "../../../components/SettingsSidebar";
import { isMobile } from "react-device-detect";
import System from "../../../models/system";
import showToast from "../../../utils/toast";
import OpenAiLogo from "../../../media/llmprovider/openai.png";
import AzureOpenAiLogo from "../../../media/llmprovider/azure.png";
import PreLoader from "../../../components/Preloader";
import LLMProviderOption from "../../../components/LLMProviderOption";
export default function GeneralEmbeddingPreference() {
const [saving, setSaving] = useState(false);
const [hasChanges, setHasChanges] = useState(false);
const [embeddingChoice, setEmbeddingChoice] = useState("openai");
const [settings, setSettings] = useState(null);
const [loading, setLoading] = useState(true);
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 { error } = await System.updateSystem(data);
if (error) {
showToast(`Failed to save embedding preferences: ${error}`, "error");
} else {
showToast("Embedding preferences saved successfully.", "success");
}
setSaving(false);
setHasChanges(!!error);
};
const updateChoice = (selection) => {
setEmbeddingChoice(selection);
setHasChanges(true);
};
useEffect(() => {
async function fetchKeys() {
const _settings = await System.keys();
setSettings(_settings);
setEmbeddingChoice(_settings?.EmbeddingEngine || "openai");
setLoading(false);
}
fetchKeys();
}, []);
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
{!isMobile && <Sidebar />}
{loading ? (
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className="transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] bg-main-gradient md:min-w-[82%] p-[18px] h-full overflow-y-scroll animate-pulse"
>
<div className="w-full h-full flex justify-center items-center">
<PreLoader />
</div>
</div>
) : (
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className="transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] bg-main-gradient md:min-w-[82%] p-[18px] h-full overflow-y-scroll"
>
{isMobile && <SidebarMobileHeader />}
<form
onSubmit={handleSubmit}
onChange={() => setHasChanges(true)}
className="flex w-full"
>
<div className="flex flex-col w-full px-1 md:px-20 md:py-12 py-16">
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
<div className="items-center flex gap-x-4">
<p className="text-2xl font-semibold text-white">
Embedding Preference
</p>
{hasChanges && (
<button
type="submit"
disabled={saving}
className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
>
{saving ? "Saving..." : "Save changes"}
</button>
)}
</div>
<p className="text-sm font-base text-white text-opacity-60">
When using an LLM that does not natively support an embedding
engine - you may need to additionally specify credentials to
for embedding text.
<br />
Embedding is the process of turning text into vectors. These
credentials are required to turn your files and prompts into a
format which AnythingLLM can use to process.
</p>
</div>
{["openai", "azure"].includes(settings.LLMProvider) ? (
<div className="w-full h-20 items-center justify-center flex">
<p className="text-gray-800 dark:text-slate-400 text-center">
Your current LLM preference does not require you to set up
this part of AnythingLLM.
<br />
Embedding is being automatically managed by AnythingLLM.
</p>
</div>
) : (
<>
<div className="text-white text-sm font-medium py-4">
Embedding Providers
</div>
<div className="w-full flex md:flex-wrap overflow-x-scroll gap-4 max-w-[900px]">
<input
hidden={true}
name="EmbeddingEngine"
value={embeddingChoice}
/>
<LLMProviderOption
name="OpenAI"
value="openai"
link="openai.com"
description="Use OpenAI's text-embedding-ada-002 embedding model."
checked={embeddingChoice === "openai"}
image={OpenAiLogo}
onClick={updateChoice}
/>
<LLMProviderOption
name="Azure OpenAI"
value="azure"
link="azure.microsoft.com"
description="The enterprise option of OpenAI hosted on Azure services."
checked={embeddingChoice === "azure"}
image={AzureOpenAiLogo}
onClick={updateChoice}
/>
</div>
<div className="mt-10 flex flex-wrap gap-4 max-w-[800px]">
{embeddingChoice === "openai" && (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="text"
name="OpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="OpenAI API Key"
defaultValue={
settings?.OpenAiKey ? "*".repeat(20) : ""
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{embeddingChoice === "azure" && (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Azure Service Endpoint
</label>
<input
type="url"
name="AzureOpenAiEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="https://my-azure.openai.azure.com"
defaultValue={settings?.AzureOpenAiEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="AzureOpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI API Key"
defaultValue={
settings?.AzureOpenAiKey ? "*".repeat(20) : ""
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Embedding Deployment Name
</label>
<input
type="text"
name="AzureOpenAiEmbeddingModelPref"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI embedding model deployment name"
defaultValue={
settings?.AzureOpenAiEmbeddingModelPref
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
</div>
</>
)}
</div>
</form>
</div>
)}
</div>
);
}

View File

@ -10,6 +10,8 @@ import AzureOpenAiLogo from "../../../media/llmprovider/azure.png";
import AnthropicLogo from "../../../media/llmprovider/anthropic.png";
import PreLoader from "../../../components/Preloader";
import LLMProviderOption from "../../../components/LLMProviderOption";
import { Info } from "@phosphor-icons/react";
import paths from "../../../utils/paths";
export default function GeneralLLMPreference() {
const [saving, setSaving] = useState(false);
@ -31,7 +33,7 @@ export default function GeneralLLMPreference() {
showToast("LLM preferences saved successfully.", "success");
}
setSaving(false);
setHasChanges(!!error ? true : false);
setHasChanges(!!error);
};
const updateLLMChoice = (selection) => {
@ -120,11 +122,12 @@ export default function GeneralLLMPreference() {
/>
<LLMProviderOption
name="Anthropic Claude 2"
value="anthropic-claude-2"
value="anthropic"
link="anthropic.com"
description="[COMING SOON] A friendly AI Assistant hosted by Anthropic. Provides chat services only!"
checked={llmChoice === "anthropic-claude-2"}
description="A friendly AI Assistant hosted by Anthropic. Provides chat services only!"
checked={llmChoice === "anthropic"}
image={AnthropicLogo}
onClick={updateLLMChoice}
/>
</div>
<div className="mt-10 flex flex-wrap gap-4 max-w-[800px]">
@ -206,7 +209,7 @@ export default function GeneralLLMPreference() {
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chat Model Deployment Name
Chat Deployment Name
</label>
<input
type="text"
@ -222,7 +225,7 @@ export default function GeneralLLMPreference() {
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Embedding Model Deployment Name
Embedding Deployment Name
</label>
<input
type="text"
@ -238,12 +241,64 @@ export default function GeneralLLMPreference() {
</>
)}
{llmChoice === "anthropic-claude-2" && (
<div className="w-full h-40 items-center justify-center flex">
<p className="text-gray-800 dark:text-slate-400">
This provider is unavailable and cannot be used in
AnythingLLM currently.
</p>
{llmChoice === "anthropic" && (
<div className="w-full flex flex-col">
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-6 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
<div className="gap-x-2 flex items-center">
<Info size={12} className="hidden md:visible" />
<p className="text-sm md:text-base">
Anthropic as your LLM requires you to set an embedding
service to use.
</p>
</div>
<a
href={paths.general.embeddingPreference()}
className="text-sm md:text-base my-2 underline"
>
Manage embedding &rarr;
</a>
</div>
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Anthropic Claude-2 API Key
</label>
<input
type="text"
name="AnthropicApiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Anthropic Claude-2 API Key"
defaultValue={
settings?.AnthropicApiKey ? "*".repeat(20) : ""
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chat Model Selection
</label>
<select
name="AnthropicModelPref"
defaultValue={
settings?.AnthropicModelPref || "claude-2"
}
required={true}
className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
{["claude-2"].map((model) => {
return (
<option key={model} value={model}>
{model}
</option>
);
})}
</select>
</div>
</div>
</div>
)}
</div>

View File

@ -48,7 +48,7 @@ export default function GeneralVectorDatabase() {
showToast("Settings saved successfully.", "success");
}
setSaving(false);
setHasChanges(!!error ? true : false);
setHasChanges(!!error);
};
return (

View File

@ -0,0 +1,179 @@
import React, { memo, useEffect, useState } from "react";
import OpenAiLogo from "../../../../../media/llmprovider/openai.png";
import AzureOpenAiLogo from "../../../../../media/llmprovider/azure.png";
import System from "../../../../../models/system";
import PreLoader from "../../../../../components/Preloader";
import LLMProviderOption from "../../../../../components/LLMProviderOption";
function EmbeddingSelection({ nextStep, prevStep, currentStep, goToStep }) {
const [embeddingChoice, setEmbeddingChoice] = useState("openai");
const [llmChoice, setLLMChoice] = useState("openai");
const [settings, setSettings] = useState(null);
const [loading, setLoading] = useState(true);
const updateChoice = (selection) => {
setEmbeddingChoice(selection);
};
useEffect(() => {
async function fetchKeys() {
const _settings = await System.keys();
setSettings(_settings);
setEmbeddingChoice(_settings?.EmbeddingEngine || "openai");
setLoading(false);
}
fetchKeys();
}, [currentStep]);
const handleSubmit = async (e) => {
e.preventDefault();
const form = e.target;
const data = {};
const formData = new FormData(form);
for (var [key, value] of formData.entries()) data[key] = value;
const { error } = await System.updateSystem(data);
if (error) {
alert(`Failed to save LLM settings: ${error}`, "error");
return;
}
goToStep(2);
return;
};
if (loading)
return (
<div className="w-full h-full flex justify-center items-center p-20">
<PreLoader />
</div>
);
return (
<div className="w-full">
<form onSubmit={handleSubmit} className="flex flex-col w-full">
<div className="flex flex-col w-full px-1 md:px-8 py-12">
<div className="text-white text-sm font-medium pb-4">
Embedding Provider
</div>
<div className="w-full flex md:flex-wrap overflow-x-scroll gap-4 max-w-[900px]">
<input
hidden={true}
name="EmbeddingEngine"
value={embeddingChoice}
/>
<LLMProviderOption
name="OpenAI"
value="openai"
link="openai.com"
description="The standard option for most non-commercial use. Provides both chat and embedding."
checked={embeddingChoice === "openai"}
image={OpenAiLogo}
onClick={updateChoice}
/>
<LLMProviderOption
name="Azure OpenAI"
value="azure"
link="azure.microsoft.com"
description="The enterprise option of OpenAI hosted on Azure services. Provides both chat and embedding."
checked={embeddingChoice === "azure"}
image={AzureOpenAiLogo}
onClick={updateChoice}
/>
</div>
<div className="mt-10 flex flex-wrap gap-4 max-w-[800px]">
{embeddingChoice === "openai" && (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="OpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="OpenAI API Key"
defaultValue={settings?.OpenAiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{embeddingChoice === "azure" && (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Azure Service Endpoint
</label>
<input
type="url"
name="AzureOpenAiEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="https://my-azure.openai.azure.com"
defaultValue={settings?.AzureOpenAiEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="AzureOpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI API Key"
defaultValue={
settings?.AzureOpenAiKey ? "*".repeat(20) : ""
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Embedding Deployment Name
</label>
<input
type="text"
name="AzureOpenAiEmbeddingModelPref"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI embedding model deployment name"
defaultValue={settings?.AzureOpenAiEmbeddingModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
</div>
</div>
<div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
<button
onClick={() => goToStep(1)}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
>
Back
</button>
<button
type="submit"
className="border border-slate-200 px-4 py-2 rounded-lg text-slate-800 bg-slate-200 text-sm items-center flex gap-x-2 hover:text-white hover:bg-transparent focus:ring-gray-800 font-semibold shadow"
>
Continue
</button>
</div>
</form>
</div>
);
}
export default memo(EmbeddingSelection);

View File

@ -7,7 +7,7 @@ import System from "../../../../../models/system";
import PreLoader from "../../../../../components/Preloader";
import LLMProviderOption from "../../../../../components/LLMProviderOption";
function LLMSelection({ nextStep, prevStep, currentStep }) {
function LLMSelection({ nextStep, prevStep, currentStep, goToStep }) {
const [llmChoice, setLLMChoice] = useState("openai");
const [settings, setSettings] = useState(null);
const [loading, setLoading] = useState(true);
@ -40,7 +40,13 @@ function LLMSelection({ nextStep, prevStep, currentStep }) {
alert(`Failed to save LLM settings: ${error}`, "error");
return;
}
nextStep();
switch (data.LLMProvider) {
case "anthropic":
goToStep(7);
default:
nextStep();
}
return;
};
@ -59,7 +65,7 @@ function LLMSelection({ nextStep, prevStep, currentStep }) {
LLM Providers
</div>
<div className="w-full flex md:flex-wrap overflow-x-scroll gap-4 max-w-[900px]">
<input hidden={true} name="LLMProvider" defaultValue={llmChoice} />
<input hidden={true} name="LLMProvider" value={llmChoice} />
<LLMProviderOption
name="OpenAI"
value="openai"
@ -80,11 +86,12 @@ function LLMSelection({ nextStep, prevStep, currentStep }) {
/>
<LLMProviderOption
name="Anthropic Claude 2"
value="anthropic-claude-2"
value="anthropic"
link="anthropic.com"
description="[COMING SOON] A friendly AI Assistant hosted by Anthropic. Provides chat services only!"
checked={llmChoice === "anthropic-claude-2"}
description="A friendly AI Assistant hosted by Anthropic. Provides chat services only!"
checked={llmChoice === "anthropic"}
image={AnthropicLogo}
onClick={updateLLMChoice}
/>
</div>
<div className="mt-10 flex flex-wrap gap-4 max-w-[800px]">
@ -166,7 +173,7 @@ function LLMSelection({ nextStep, prevStep, currentStep }) {
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chat Model Deployment Name
Chat Deployment Name
</label>
<input
type="text"
@ -182,7 +189,7 @@ function LLMSelection({ nextStep, prevStep, currentStep }) {
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Embedding Model Deployment Name
Embedding Deployment Name
</label>
<input
type="text"
@ -198,12 +205,47 @@ function LLMSelection({ nextStep, prevStep, currentStep }) {
</>
)}
{llmChoice === "anthropic-claude-2" && (
<div className="w-full h-40 items-center justify-center flex">
<p className="text-gray-800 dark:text-slate-400">
This provider is unavailable and cannot be used in AnythingLLM
currently.
</p>
{llmChoice === "anthropic" && (
<div className="w-full flex flex-col">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Anthropic Claude-2 API Key
</label>
<input
type="text"
name="AnthropicApiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Anthropic Claude-2 API Key"
defaultValue={
settings?.AnthropicApiKey ? "*".repeat(20) : ""
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chat Model Selection
</label>
<select
name="AnthropicModelPref"
defaultValue={settings?.AnthropicModelPref || "claude-2"}
required={true}
className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
{["claude-2"].map((model) => {
return (
<option key={model} value={model}>
{model}
</option>
);
})}
</select>
</div>
</div>
</div>
)}
</div>

View File

@ -7,6 +7,7 @@ import UserModeSelection from "./Steps/UserModeSelection";
import PasswordProtection from "./Steps/PasswordProtection";
import MultiUserSetup from "./Steps/MultiUserSetup";
import CreateFirstWorkspace from "./Steps/CreateFirstWorkspace";
import EmbeddingSelection from "./Steps/EmbeddingSelection";
const DIALOG_ID = "onboarding-modal";
@ -54,6 +55,12 @@ const STEPS = {
description: "To get started, create a new workspace.",
component: CreateFirstWorkspace,
},
8: {
title: "Embedding Preference",
description:
"Due to your LLM selection you need to set up a provider for embedding files and text.",
component: EmbeddingSelection,
},
};
export const OnboardingModalId = DIALOG_ID;

View File

@ -43,6 +43,9 @@ export default {
llmPreference: () => {
return "/general/llm-preference";
},
embeddingPreference: () => {
return "/general/embedding-preference";
},
vectorDatabase: () => {
return "/general/vector-database";
},

View File

@ -5,9 +5,9 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea
###########################################
######## LLM API SElECTION ################
###########################################
LLM_PROVIDER='openai'
# LLM_PROVIDER='openai'
# OPEN_AI_KEY=
OPEN_MODEL_PREF='gpt-3.5-turbo'
# OPEN_MODEL_PREF='gpt-3.5-turbo'
# LLM_PROVIDER='azure'
# AZURE_OPENAI_ENDPOINT=
@ -15,6 +15,17 @@ OPEN_MODEL_PREF='gpt-3.5-turbo'
# OPEN_MODEL_PREF='my-gpt35-deployment' # This is the "deployment" on Azure you want to use. Not the base model.
# EMBEDDING_MODEL_PREF='embedder-model' # This is the "deployment" on Azure you want to use for embeddings. Not the base model. Valid base model is text-embedding-ada-002
# LLM_PROVIDER='anthropic'
# ANTHROPIC_API_KEY=sk-ant-xxxx
# ANTHROPIC_MODEL_PREF='claude-2'
###########################################
######## Embedding API SElECTION ##########
###########################################
# Only used if you are using an LLM that does not natively support embedding (openai or Azure)
# EMBEDDING_ENGINE='openai'
# OPEN_AI_KEY=sk-xxxx
###########################################
######## Vector Database Selection ########
###########################################

View File

@ -24,6 +24,7 @@ const SystemSettings = {
StorageDir: process.env.STORAGE_DIR,
MultiUserMode: await this.isMultiUserMode(),
VectorDB: vectorDB,
EmbeddingEngine: process.env.EMBEDDING_ENGINE,
...(vectorDB === "pinecone"
? {
PineConeEnvironment: process.env.PINECONE_ENVIRONMENT,
@ -66,6 +67,19 @@ const SystemSettings = {
AzureOpenAiEmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF,
}
: {}),
...(llmProvider === "anthropic"
? {
AnthropicApiKey: !!process.env.ANTHROPIC_API_KEY,
AnthropicModelPref: process.env.ANTHROPIC_MODEL_PREF || "claude-2",
// For embedding credentials when Anthropic is selected.
OpenAiKey: !!process.env.OPEN_AI_KEY,
AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT,
AzureOpenAiKey: !!process.env.AZURE_OPENAI_KEY,
AzureOpenAiEmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF,
}
: {}),
};
},

View File

@ -20,6 +20,7 @@
"seed": "node prisma/seed.js"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.8.1",
"@azure/openai": "^1.0.0-beta.3",
"@googleapis/youtube": "^9.0.0",
"@pinecone-database/pinecone": "^0.1.6",
@ -59,4 +60,4 @@
"nodemon": "^2.0.22",
"prettier": "^2.4.1"
}
}
}

View File

@ -0,0 +1,144 @@
const { v4 } = require("uuid");
const { chatPrompt } = require("../../chats");
class AnthropicLLM {
constructor(embedder = null) {
if (!process.env.ANTHROPIC_API_KEY)
throw new Error("No Anthropic API key was set.");
// Docs: https://www.npmjs.com/package/@anthropic-ai/sdk
const AnthropicAI = require("@anthropic-ai/sdk");
const anthropic = new AnthropicAI({
apiKey: process.env.ANTHROPIC_API_KEY,
});
this.anthropic = anthropic;
if (!embedder)
throw new Error(
"INVALID ANTHROPIC SETUP. No embedding engine has been set. Go to instance settings and set up an embedding interface to use Anthropic as your LLM."
);
this.embedder = embedder;
this.answerKey = v4().split("-")[0];
}
isValidChatModel(modelName = "") {
const validModels = ["claude-2"];
return validModels.includes(modelName);
}
// Moderation can be done with Anthropic, but its not really "exact" so we skip it
// https://docs.anthropic.com/claude/docs/content-moderation
async isSafe(_input = "") {
// Not implemented so must be stubbed
return { safe: true, reasons: [] };
}
constructPrompt({
systemPrompt = "",
contextTexts = [],
chatHistory = [],
userPrompt = "",
}) {
return `\n\nHuman: Please read question supplied within the <question> tags. Using all information generate an answer to the question and output it within <${
this.answerKey
}> tags. Previous conversations can be used within the <history> tags and can be used to influence the output. Content between the <system> tag is additional information and instruction that will impact how answers are formatted or responded to. Additional contextual information retrieved to help answer the users specific query is available to use for answering and can be found between <context> tags. When no <context> tags may are present use the knowledge available and in the conversation to answer. When one or more <context> tags are available you will use those to help answer the question or augment pre-existing knowledge. You should never say "Based on the provided context" or other phrasing that is not related to the user question.
<system>${systemPrompt}</system>
${contextTexts
.map((text, i) => {
return `<context>${text}</context>\n`;
})
.join("")}
<history>${chatHistory.map((history) => {
switch (history.role) {
case "assistant":
return `\n\nAssistant: ${history.content}`;
case "user":
return `\n\nHuman: ${history.content}`;
default:
return "\n";
}
})}</history>
<question>${userPrompt}</question>
\n\nAssistant:`;
}
// This is the interface used when no embeddings are present in the workspace
// This is just having a conversation with the LLM as one would normally.
async sendChat(chatHistory = [], prompt, workspace = {}) {
const model = process.env.ANTHROPIC_MODEL_PREF || "claude-2";
if (!this.isValidChatModel(model))
throw new Error(
`Anthropic chat: ${model} is not valid for chat completion!`
);
const { content, error } = await this.anthropic.completions
.create({
model: "claude-2",
max_tokens_to_sample: 300,
prompt: this.constructPrompt({
systemPrompt: chatPrompt(workspace),
userPrompt: prompt,
chatHistory,
}),
})
.then((res) => {
const { completion } = res;
const re = new RegExp(
"(?:<" + this.answerKey + ">)([\\s\\S]*)(?:</" + this.answerKey + ">)"
);
const response = completion.match(re)?.[1]?.trim();
if (!response)
throw new Error("Anthropic: No response could be parsed.");
return { content: response, error: null };
})
.catch((e) => {
return { content: null, error: e.message };
});
if (error) throw new Error(error);
return content;
}
async getChatCompletion(prompt = "", _opts = {}) {
const model = process.env.ANTHROPIC_MODEL_PREF || "claude-2";
if (!this.isValidChatModel(model))
throw new Error(
`Anthropic chat: ${model} is not valid for chat completion!`
);
const { content, error } = await this.anthropic.completions
.create({
model: "claude-2",
max_tokens_to_sample: 300,
prompt,
})
.then((res) => {
const { completion } = res;
const re = new RegExp(
"(?:<" + this.answerKey + ">)([\\s\\S]*)(?:</" + this.answerKey + ">)"
);
const response = completion.match(re)?.[1]?.trim();
if (!response)
throw new Error("Anthropic: No response could be parsed.");
return { content: response, error: null };
})
.catch((e) => {
return { content: null, error: e.message };
});
if (error) throw new Error(error);
return content;
}
// Simple wrapper for dynamic embedder & normalize interface for all LLM implementations
async embedTextInput(textInput) {
return await this.embedder.embedTextInput(textInput);
}
async embedChunks(textChunks = []) {
return await this.embedder.embedChunks(textChunks);
}
}
module.exports = {
AnthropicLLM,
};

View File

@ -1,17 +1,18 @@
const { toChunks } = require("../../helpers");
const { AzureOpenAiEmbedder } = require("../../EmbeddingEngines/azureOpenAi");
class AzureOpenAi {
class AzureOpenAiLLM extends AzureOpenAiEmbedder {
constructor() {
super();
const { OpenAIClient, AzureKeyCredential } = require("@azure/openai");
const openai = new OpenAIClient(
if (!process.env.AZURE_OPENAI_ENDPOINT)
throw new Error("No Azure API endpoint was set.");
if (!process.env.AZURE_OPENAI_KEY)
throw new Error("No Azure API key was set.");
this.openai = new OpenAIClient(
process.env.AZURE_OPENAI_ENDPOINT,
new AzureKeyCredential(process.env.AZURE_OPENAI_KEY)
);
this.openai = openai;
// The maximum amount of "inputs" that OpenAI API can process in a single call.
// https://learn.microsoft.com/en-us/azure/ai-services/openai/faq#i-am-trying-to-use-embeddings-and-received-the-error--invalidrequesterror--too-many-inputs--the-max-number-of-inputs-is-1---how-do-i-fix-this-:~:text=consisting%20of%20up%20to%2016%20inputs%20per%20API%20request
this.embeddingChunkLimit = 16;
}
isValidChatModel(_modelName = "") {
@ -21,6 +22,25 @@ class AzureOpenAi {
return true;
}
constructPrompt({
systemPrompt = "",
contextTexts = [],
chatHistory = [],
userPrompt = "",
}) {
const prompt = {
role: "system",
content: `${systemPrompt}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
return [prompt, ...chatHistory, { role: "user", content: userPrompt }];
}
async isSafe(_input = "") {
// Not implemented by Azure OpenAI so must be stubbed
return { safe: true, reasons: [] };
@ -75,70 +95,8 @@ class AzureOpenAi {
if (!data.hasOwnProperty("choices")) return null;
return data.choices[0].message.content;
}
async embedTextInput(textInput) {
const result = await this.embedChunks(textInput);
return result?.[0] || [];
}
async embedChunks(textChunks = []) {
const textEmbeddingModel =
process.env.EMBEDDING_MODEL_PREF || "text-embedding-ada-002";
if (!textEmbeddingModel)
throw new Error(
"No EMBEDDING_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an embedding model."
);
// Because there is a limit on how many chunks can be sent at once to Azure OpenAI
// we concurrently execute each max batch of text chunks possible.
// Refer to constructor embeddingChunkLimit for more info.
const embeddingRequests = [];
for (const chunk of toChunks(textChunks, this.embeddingChunkLimit)) {
embeddingRequests.push(
new Promise((resolve) => {
this.openai
.getEmbeddings(textEmbeddingModel, chunk)
.then((res) => {
resolve({ data: res.data, error: null });
})
.catch((e) => {
resolve({ data: [], error: e?.error });
});
})
);
}
const { data = [], error = null } = await Promise.all(
embeddingRequests
).then((results) => {
// If any errors were returned from Azure abort the entire sequence because the embeddings
// will be incomplete.
const errors = results
.filter((res) => !!res.error)
.map((res) => res.error)
.flat();
if (errors.length > 0) {
return {
data: [],
error: `(${errors.length}) Embedding Errors! ${errors
.map((error) => `[${error.type}]: ${error.message}`)
.join(", ")}`,
};
}
return {
data: results.map((res) => res?.data || []).flat(),
error: null,
};
});
if (!!error) throw new Error(`Azure OpenAI Failed to embed: ${error}`);
return data.length > 0 &&
data.every((embd) => embd.hasOwnProperty("embedding"))
? data.map((embd) => embd.embedding)
: null;
}
}
module.exports = {
AzureOpenAi,
AzureOpenAiLLM,
};

View File

@ -1,16 +1,15 @@
const { toChunks } = require("../../helpers");
const { OpenAiEmbedder } = require("../../EmbeddingEngines/openAi");
class OpenAi {
class OpenAiLLM extends OpenAiEmbedder {
constructor() {
super();
const { Configuration, OpenAIApi } = require("openai");
if (!process.env.OPEN_AI_KEY) throw new Error("No OpenAI API key was set.");
const config = new Configuration({
apiKey: process.env.OPEN_AI_KEY,
});
const openai = new OpenAIApi(config);
this.openai = openai;
// Arbitrary limit to ensure we stay within reasonable POST request size.
this.embeddingChunkLimit = 1_000;
this.openai = new OpenAIApi(config);
}
isValidChatModel(modelName = "") {
@ -18,6 +17,25 @@ class OpenAi {
return validModels.includes(modelName);
}
constructPrompt({
systemPrompt = "",
contextTexts = [],
chatHistory = [],
userPrompt = "",
}) {
const prompt = {
role: "system",
content: `${systemPrompt}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
return [prompt, ...chatHistory, { role: "user", content: userPrompt }];
}
async isSafe(input = "") {
const { flagged = false, categories = {} } = await this.openai
.createModeration({ input })
@ -97,66 +115,8 @@ class OpenAi {
if (!data.hasOwnProperty("choices")) return null;
return data.choices[0].message.content;
}
async embedTextInput(textInput) {
const result = await this.embedChunks(textInput);
return result?.[0] || [];
}
async embedChunks(textChunks = []) {
// Because there is a hard POST limit on how many chunks can be sent at once to OpenAI (~8mb)
// we concurrently execute each max batch of text chunks possible.
// Refer to constructor embeddingChunkLimit for more info.
const embeddingRequests = [];
for (const chunk of toChunks(textChunks, this.embeddingChunkLimit)) {
embeddingRequests.push(
new Promise((resolve) => {
this.openai
.createEmbedding({
model: "text-embedding-ada-002",
input: chunk,
})
.then((res) => {
resolve({ data: res.data?.data, error: null });
})
.catch((e) => {
resolve({ data: [], error: e?.error });
});
})
);
}
const { data = [], error = null } = await Promise.all(
embeddingRequests
).then((results) => {
// If any errors were returned from OpenAI abort the entire sequence because the embeddings
// will be incomplete.
const errors = results
.filter((res) => !!res.error)
.map((res) => res.error)
.flat();
if (errors.length > 0) {
return {
data: [],
error: `(${errors.length}) Embedding Errors! ${errors
.map((error) => `[${error.type}]: ${error.message}`)
.join(", ")}`,
};
}
return {
data: results.map((res) => res?.data || []).flat(),
error: null,
};
});
if (!!error) throw new Error(`OpenAI Failed to embed: ${error}`);
return data.length > 0 &&
data.every((embd) => embd.hasOwnProperty("embedding"))
? data.map((embd) => embd.embedding)
: null;
}
}
module.exports = {
OpenAi,
OpenAiLLM,
};

View File

@ -0,0 +1,87 @@
const { toChunks } = require("../../helpers");
class AzureOpenAiEmbedder {
constructor() {
const { OpenAIClient, AzureKeyCredential } = require("@azure/openai");
if (!process.env.AZURE_OPENAI_ENDPOINT)
throw new Error("No Azure API endpoint was set.");
if (!process.env.AZURE_OPENAI_KEY)
throw new Error("No Azure API key was set.");
const openai = new OpenAIClient(
process.env.AZURE_OPENAI_ENDPOINT,
new AzureKeyCredential(process.env.AZURE_OPENAI_KEY)
);
this.openai = openai;
// The maximum amount of "inputs" that OpenAI API can process in a single call.
// https://learn.microsoft.com/en-us/azure/ai-services/openai/faq#i-am-trying-to-use-embeddings-and-received-the-error--invalidrequesterror--too-many-inputs--the-max-number-of-inputs-is-1---how-do-i-fix-this-:~:text=consisting%20of%20up%20to%2016%20inputs%20per%20API%20request
this.embeddingChunkLimit = 16;
}
async embedTextInput(textInput) {
const result = await this.embedChunks(textInput);
return result?.[0] || [];
}
async embedChunks(textChunks = []) {
const textEmbeddingModel =
process.env.EMBEDDING_MODEL_PREF || "text-embedding-ada-002";
if (!textEmbeddingModel)
throw new Error(
"No EMBEDDING_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an embedding model."
);
// Because there is a limit on how many chunks can be sent at once to Azure OpenAI
// we concurrently execute each max batch of text chunks possible.
// Refer to constructor embeddingChunkLimit for more info.
const embeddingRequests = [];
for (const chunk of toChunks(textChunks, this.embeddingChunkLimit)) {
embeddingRequests.push(
new Promise((resolve) => {
this.openai
.getEmbeddings(textEmbeddingModel, chunk)
.then((res) => {
resolve({ data: res.data, error: null });
})
.catch((e) => {
resolve({ data: [], error: e?.error });
});
})
);
}
const { data = [], error = null } = await Promise.all(
embeddingRequests
).then((results) => {
// If any errors were returned from Azure abort the entire sequence because the embeddings
// will be incomplete.
const errors = results
.filter((res) => !!res.error)
.map((res) => res.error)
.flat();
if (errors.length > 0) {
return {
data: [],
error: `(${errors.length}) Embedding Errors! ${errors
.map((error) => `[${error.type}]: ${error.message}`)
.join(", ")}`,
};
}
return {
data: results.map((res) => res?.data || []).flat(),
error: null,
};
});
if (!!error) throw new Error(`Azure OpenAI Failed to embed: ${error}`);
return data.length > 0 &&
data.every((embd) => embd.hasOwnProperty("embedding"))
? data.map((embd) => embd.embedding)
: null;
}
}
module.exports = {
AzureOpenAiEmbedder,
};

View File

@ -0,0 +1,78 @@
const { toChunks } = require("../../helpers");
class OpenAiEmbedder {
constructor() {
const { Configuration, OpenAIApi } = require("openai");
if (!process.env.OPEN_AI_KEY) throw new Error("No OpenAI API key was set.");
const config = new Configuration({
apiKey: process.env.OPEN_AI_KEY,
});
const openai = new OpenAIApi(config);
this.openai = openai;
// Arbitrary limit to ensure we stay within reasonable POST request size.
this.embeddingChunkLimit = 1_000;
}
async embedTextInput(textInput) {
const result = await this.embedChunks(textInput);
return result?.[0] || [];
}
async embedChunks(textChunks = []) {
// Because there is a hard POST limit on how many chunks can be sent at once to OpenAI (~8mb)
// we concurrently execute each max batch of text chunks possible.
// Refer to constructor embeddingChunkLimit for more info.
const embeddingRequests = [];
for (const chunk of toChunks(textChunks, this.embeddingChunkLimit)) {
embeddingRequests.push(
new Promise((resolve) => {
this.openai
.createEmbedding({
model: "text-embedding-ada-002",
input: chunk,
})
.then((res) => {
resolve({ data: res.data?.data, error: null });
})
.catch((e) => {
resolve({ data: [], error: e?.error });
});
})
);
}
const { data = [], error = null } = await Promise.all(
embeddingRequests
).then((results) => {
// If any errors were returned from OpenAI abort the entire sequence because the embeddings
// will be incomplete.
const errors = results
.filter((res) => !!res.error)
.map((res) => res.error)
.flat();
if (errors.length > 0) {
return {
data: [],
error: `(${errors.length}) Embedding Errors! ${errors
.map((error) => `[${error.type}]: ${error.message}`)
.join(", ")}`,
};
}
return {
data: results.map((res) => res?.data || []).flat(),
error: null,
};
});
if (!!error) throw new Error(`OpenAI Failed to embed: ${error}`);
return data.length > 0 &&
data.every((embd) => embd.hasOwnProperty("embedding"))
? data.map((embd) => embd.embedding)
: null;
}
}
module.exports = {
OpenAiEmbedder,
};

View File

@ -1,10 +1,8 @@
const { v4: uuidv4 } = require("uuid");
const { OpenAi } = require("../AiProviders/openAi");
const { WorkspaceChats } = require("../../models/workspaceChats");
const { resetMemory } = require("./commands/reset");
const moment = require("moment");
const { getVectorDbClass, getLLMProvider } = require("../helpers");
const { AzureOpenAi } = require("../AiProviders/azureOpenAi");
function convertToChatHistory(history = []) {
const formattedHistory = [];
@ -67,14 +65,14 @@ async function chatWithWorkspace(
user = null
) {
const uuid = uuidv4();
const LLMConnector = getLLMProvider();
const VectorDb = getVectorDbClass();
const command = grepCommand(message);
if (!!command && Object.keys(VALID_COMMANDS).includes(command)) {
return await VALID_COMMANDS[command](workspace, message, uuid, user);
}
const LLMConnector = getLLMProvider();
const VectorDb = getVectorDbClass();
const { safe, reasons = [] } = await LLMConnector.isSafe(message);
if (!safe) {
return {

View File

@ -25,16 +25,36 @@ function getLLMProvider() {
const vectorSelection = process.env.LLM_PROVIDER || "openai";
switch (vectorSelection) {
case "openai":
const { OpenAi } = require("../AiProviders/openAi");
return new OpenAi();
const { OpenAiLLM } = require("../AiProviders/openAi");
return new OpenAiLLM();
case "azure":
const { AzureOpenAi } = require("../AiProviders/azureOpenAi");
return new AzureOpenAi();
const { AzureOpenAiLLM } = require("../AiProviders/azureOpenAi");
return new AzureOpenAiLLM();
case "anthropic":
const { AnthropicLLM } = require("../AiProviders/anthropic");
const embedder = getEmbeddingEngineSelection();
return new AnthropicLLM(embedder);
default:
throw new Error("ENV: No LLM_PROVIDER value found in environment!");
}
}
function getEmbeddingEngineSelection() {
const engineSelection = process.env.EMBEDDING_ENGINE;
switch (engineSelection) {
case "openai":
const { OpenAiEmbedder } = require("../../EmbeddingEngines/openAi");
return new OpenAiEmbedder();
case "azure":
const {
AzureOpenAiEmbedder,
} = require("../../EmbeddingEngines/azureOpenAi");
return new AzureOpenAiEmbedder();
default:
return null;
}
}
function toChunks(arr, size) {
return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) =>
arr.slice(i * size, i * size + size)
@ -42,6 +62,7 @@ function toChunks(arr, size) {
}
module.exports = {
getEmbeddingEngineSelection,
getVectorDbClass,
getLLMProvider,
toChunks,

View File

@ -30,6 +30,21 @@ const KEY_MAPPING = {
checks: [isNotEmpty],
},
// Anthropic Settings
AnthropicApiKey: {
envKey: "ANTHROPIC_API_KEY",
checks: [isNotEmpty, validAnthropicApiKey],
},
AnthropicModelPref: {
envKey: "ANTHROPIC_MODEL_PREF",
checks: [isNotEmpty, validAnthropicModel],
},
EmbeddingEngine: {
envKey: "EMBEDDING_ENGINE",
checks: [supportedEmbeddingModel],
},
// Vector Database Selection Settings
VectorDB: {
envKey: "VECTOR_DB",
@ -113,8 +128,14 @@ function validOpenAIKey(input = "") {
return input.startsWith("sk-") ? null : "OpenAI Key must start with sk-";
}
function validAnthropicApiKey(input = "") {
return input.startsWith("sk-ant-")
? null
: "Anthropic Key must start with sk-ant-";
}
function supportedLLM(input = "") {
return ["openai", "azure"].includes(input);
return ["openai", "azure", "anthropic"].includes(input);
}
function validOpenAIModel(input = "") {
@ -124,6 +145,20 @@ function validOpenAIModel(input = "") {
: `Invalid Model type. Must be one of ${validModels.join(", ")}.`;
}
function validAnthropicModel(input = "") {
const validModels = ["claude-2"];
return validModels.includes(input)
? null
: `Invalid Model type. Must be one of ${validModels.join(", ")}.`;
}
function supportedEmbeddingModel(input = "") {
const supported = ["openai", "azure"];
return supported.includes(input)
? null
: `Invalid Embedding model type. Must be one of ${supported.join(", ")}.`;
}
function supportedVectorDB(input = "") {
const supported = ["chroma", "pinecone", "lancedb", "weaviate", "qdrant"];
return supported.includes(input)

View File

@ -273,17 +273,11 @@ const Chroma = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});
@ -328,17 +322,12 @@ const Chroma = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
chatHistory,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});

View File

@ -246,17 +246,11 @@ const LanceDb = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});
@ -296,17 +290,12 @@ const LanceDb = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
chatHistory,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});

View File

@ -242,18 +242,11 @@ const Pinecone = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});
@ -290,18 +283,12 @@ const Pinecone = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
chatHistory,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});

View File

@ -282,17 +282,11 @@ const QDrant = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});
@ -332,17 +326,12 @@ const QDrant = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
chatHistory,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});

View File

@ -353,18 +353,11 @@ const Weaviate = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});
@ -404,17 +397,12 @@ const Weaviate = {
namespace,
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
const memory = LLMConnector.constructPrompt({
systemPrompt: chatPrompt(workspace),
contextTexts: contextTexts,
userPrompt: input,
chatHistory,
});
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});

View File

@ -10,6 +10,21 @@
"@fortaine/fetch-event-source" "^3.0.6"
cross-fetch "^3.1.5"
"@anthropic-ai/sdk@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.8.1.tgz#7c7c6cb262abe3e6d0bb8bd1179b4589edd7a6ad"
integrity sha512-59etePenCizVx1O8Qhi1T1ruE04ISfNzCnyhZNcsss1QljsLmYS83jttarMNEvGYcsUF7rwxw2lzcC3Zbxao7g==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
abort-controller "^3.0.0"
agentkeepalive "^4.2.1"
digest-fetch "^1.3.0"
form-data-encoder "1.7.2"
formdata-node "^4.3.2"
node-fetch "^2.6.7"
web-streams-polyfill "^3.2.1"
"@apache-arrow/ts@^12.0.0":
version "12.0.1"
resolved "https://registry.yarnpkg.com/@apache-arrow/ts/-/ts-12.0.1.tgz#a802a28f450886e77b32c516c370c24941767455"
@ -229,6 +244,14 @@
resolved "https://registry.yarnpkg.com/@types/command-line-usage/-/command-line-usage-5.0.2.tgz#ba5e3f6ae5a2009d466679cc431b50635bf1a064"
integrity sha512-n7RlEEJ+4x4TS7ZQddTmNSxP+zziEG0TNsMfiRIxcIVXt71ENJ9ojeXmGO3wPoTdn7pJcU2xc3CJYMktNT6DPg==
"@types/node-fetch@^2.6.4":
version "2.6.7"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.7.tgz#a1abe2ce24228b58ad97f99480fdcf9bbc6ab16d"
integrity sha512-lX17GZVpJ/fuCjguZ5b3TjEbSENxmEk1B2z02yoXSK9WMEWRivhdSY73wWMn6bpcCDAOh6qAdktpKHIlkDk2lg==
dependencies:
"@types/node" "*"
form-data "^4.0.0"
"@types/node@*":
version "20.4.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9"
@ -239,6 +262,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.5.tgz#4a13a6445862159303fc38586598a9396fc408b3"
integrity sha512-CRT4tMK/DHYhw1fcCEBwME9CSaZNclxfzVMe7GsO6ULSwsttbj70wSiX6rZdIjGblu93sTJxLdhNIT85KKI7Qw==
"@types/node@^18.11.18":
version "18.18.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.7.tgz#bb3a7068dc4ba421b6968f2a259298b3a4e129e8"
integrity sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ==
dependencies:
undici-types "~5.26.4"
"@types/pad-left@2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@types/pad-left/-/pad-left-2.1.1.tgz#17d906fc75804e1cc722da73623f1d978f16a137"
@ -261,6 +291,13 @@ abbrev@1:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
accepts@~1.3.4, accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
@ -290,6 +327,13 @@ agentkeepalive@^4.1.3:
depd "^2.0.0"
humanize-ms "^1.2.1"
agentkeepalive@^4.2.1:
version "4.5.0"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923"
integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==
dependencies:
humanize-ms "^1.2.1"
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@ -444,6 +488,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base-64@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==
base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@ -620,6 +669,11 @@ chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
charenc@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
check-disk-space@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/check-disk-space/-/check-disk-space-3.4.0.tgz#eb8e69eee7a378fd12e35281b8123a8b4c4a8ff7"
@ -796,6 +850,11 @@ cross-fetch@^3.1.5:
dependencies:
node-fetch "^2.6.12"
crypt@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -857,6 +916,14 @@ detect-libc@^2.0.0:
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d"
integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==
digest-fetch@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661"
integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==
dependencies:
base-64 "^0.1.0"
md5 "^2.3.0"
dotenv@^16.0.3:
version "16.3.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
@ -928,6 +995,11 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
eventemitter3@^4.0.4:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
@ -1050,6 +1122,11 @@ follow-redirects@^1.14.8, follow-redirects@^1.14.9:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
form-data-encoder@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040"
integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
@ -1068,6 +1145,14 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
formdata-node@^4.3.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2"
integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==
dependencies:
node-domexception "1.0.0"
web-streams-polyfill "4.0.0-beta.3"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -1416,6 +1501,11 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-buffer@~1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@ -1675,6 +1765,15 @@ make-fetch-happen@^9.1.0:
socks-proxy-agent "^6.0.0"
ssri "^8.0.0"
md5@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
dependencies:
charenc "0.0.2"
crypt "0.0.2"
is-buffer "~1.1.6"
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@ -1886,6 +1985,11 @@ node-addon-api@^5.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762"
integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==
node-domexception@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.6.9:
version "2.6.12"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba"
@ -2585,6 +2689,11 @@ undefsafe@^2.0.5:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici@^5.22.1:
version "5.23.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.23.0.tgz#e7bdb0ed42cebe7b7aca87ced53e6eaafb8f8ca0"
@ -2668,6 +2777,16 @@ weaviate-ts-client@^1.4.0:
isomorphic-fetch "^3.0.0"
uuid "^9.0.0"
web-streams-polyfill@4.0.0-beta.3:
version "4.0.0-beta.3"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38"
integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==
web-streams-polyfill@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"