merlyn/frontend/src/components/Modals/Settings/LLMSelection/index.jsx
2023-08-08 18:02:30 -07:00

282 lines
12 KiB
JavaScript

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";
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 [error, setError] = useState(null);
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);
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 your preferred LLM chat &
embedding provider. Its important these keys are current and correct
or else AnythingLLM will not function properly.
</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">
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-3.5-turbo-0613",
"gpt-3.5-turbo-16k",
"gpt-4",
"gpt-4-0613",
"gpt-4-32k",
"gpt-4-32k-0613",
].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>
);
};