705 feat new workspace settings layout ()

* forgot files

---------

Co-authored-by: shatfield4 <seanhatfield5@gmail.com>
This commit is contained in:
Timothy Carambat 2024-02-14 15:54:23 -08:00 committed by GitHub
parent 858b2fcedb
commit e8662d792d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 565 additions and 0 deletions
frontend/src/pages/WorkspaceSettings
ChatSettings
ChatHistorySettings
ChatModelSelection
ChatPromptSettings
ChatTemperatureSettings
GeneralAppearance
SuggestedChatMessages
VectorCount
WorkspaceName
VectorDatabase
DocumentSimilarityThreshold
MaxContextSnippets
VectorDBIdentifier

View file

@ -0,0 +1,35 @@
export default function ChatHistorySettings({ workspace, setHasChanges }) {
return (
<div>
<div className="flex flex-col gap-y-1 mb-4">
<label
htmlFor="name"
className="block mb-2 text-sm font-medium text-white"
>
Chat History
</label>
<p className="text-white text-opacity-60 text-xs font-medium">
The number of previous chats that will be included in the
response&apos;s short-term memory.
<i>Recommend 20. </i>
Anything more than 45 is likely to lead to continuous chat failures
depending on message size.
</p>
</div>
<input
name="openAiHistory"
type="number"
min={1}
max={45}
step={1}
onWheel={(e) => e.target.blur()}
defaultValue={workspace?.openAiHistory ?? 20}
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="20"
required={true}
autoComplete="off"
onChange={() => setHasChanges(true)}
/>
</div>
);
}

View file

@ -0,0 +1,120 @@
import useGetProviderModels, {
DISABLED_PROVIDERS,
} from "@/hooks/useGetProvidersModels";
export default function ChatModelSelection({
settings,
workspace,
setHasChanges,
}) {
const { defaultModels, customModels, loading } = useGetProviderModels(
settings?.LLMProvider
);
if (DISABLED_PROVIDERS.includes(settings?.LLMProvider)) return null;
if (loading) {
return (
<div>
<div className="flex flex-col">
<label
htmlFor="name"
className="block text-sm font-medium text-white"
>
Chat model
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
The specific chat model that will be used for this workspace. If
empty, will use the system LLM preference.
</p>
</div>
<select
name="chatModel"
required={true}
disabled={true}
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
>
<option disabled={true} selected={true}>
-- waiting for models --
</option>
</select>
</div>
);
}
return (
<div>
<div className="flex flex-col">
<label htmlFor="name" className="block text-sm font-medium text-white">
Chat model{" "}
<span className="font-normal">({settings?.LLMProvider})</span>
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
The specific chat model that will be used for this workspace. If
empty, will use the system LLM preference.
</p>
</div>
<select
name="chatModel"
required={true}
onChange={() => {
setHasChanges(true);
}}
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
>
<option disabled={true} selected={workspace?.chatModel === null}>
System default
</option>
{defaultModels.length > 0 && (
<optgroup label="General models">
{defaultModels.map((model) => {
return (
<option
key={model}
value={model}
selected={workspace?.chatModel === model}
>
{model}
</option>
);
})}
</optgroup>
)}
{Array.isArray(customModels) && customModels.length > 0 && (
<optgroup label="Custom models">
{customModels.map((model) => {
return (
<option
key={model.id}
value={model.id}
selected={workspace?.chatModel === model.id}
>
{model.id}
</option>
);
})}
</optgroup>
)}
{/* For providers like TogetherAi where we partition model by creator entity. */}
{!Array.isArray(customModels) &&
Object.keys(customModels).length > 0 && (
<>
{Object.entries(customModels).map(([organization, models]) => (
<optgroup key={organization} label={organization}>
{models.map((model) => (
<option
key={model.id}
value={model.id}
selected={workspace?.chatModel === model.id}
>
{model.name}
</option>
))}
</optgroup>
))}
</>
)}
</select>
</div>
);
}

View file

@ -0,0 +1,30 @@
import { chatPrompt } from "@/utils/chat";
export default function ChatPromptSettings({ workspace, setHasChanges }) {
return (
<div>
<div className="flex flex-col">
<label htmlFor="name" className="block text-sm font-medium text-white">
Prompt
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
The prompt that will be used on this workspace. Define the context and
instructions for the AI to generate a response. You should to provide
a carefully crafted prompt so the AI can generate a relevant and
accurate response.
</p>
</div>
<textarea
name="openAiPrompt"
rows={5}
defaultValue={chatPrompt(workspace)}
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 mt-2"
placeholder="Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed."
required={true}
wrap="soft"
autoComplete="off"
onChange={() => setHasChanges(true)}
/>
</div>
);
}

View file

@ -0,0 +1,47 @@
function recommendedSettings(provider = null) {
switch (provider) {
case "mistral":
return { temp: 0 };
default:
return { temp: 0.7 };
}
}
export default function ChatTemperatureSettings({
settings,
workspace,
setHasChanges,
}) {
const defaults = recommendedSettings(settings?.LLMProvider);
return (
<div>
<div className="flex flex-col">
<label htmlFor="name" className="block text-sm font-medium text-white">
LLM Temperature
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
This setting controls how &quot;random&quot; or dynamic your chat
responses will be.
<br />
The higher the number (1.0 maximum) the more random and incoherent.
<br />
<i>Recommended: {defaults.temp}</i>
</p>
</div>
<input
name="openAiTemp"
type="number"
min={0.0}
max={1.0}
step={0.1}
onWheel={(e) => e.target.blur()}
defaultValue={workspace?.openAiTemp ?? defaults.temp}
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="0.7"
required={true}
autoComplete="off"
onChange={() => setHasChanges(true)}
/>
</div>
);
}

View file

@ -0,0 +1,193 @@
import PreLoader from "@/components/Preloader";
import Workspace from "@/models/workspace";
import showToast from "@/utils/toast";
import { useEffect, useState } from "react";
import { Plus, X } from "@phosphor-icons/react";
export default function SuggestedChatMessages({ slug }) {
const [suggestedMessages, setSuggestedMessages] = useState([]);
const [editingIndex, setEditingIndex] = useState(-1);
const [newMessage, setNewMessage] = useState({ heading: "", message: "" });
const [hasChanges, setHasChanges] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchWorkspace() {
if (!slug) return;
const suggestedMessages = await Workspace.getSuggestedMessages(slug);
setSuggestedMessages(suggestedMessages);
setLoading(false);
}
fetchWorkspace();
}, [slug]);
const handleSaveSuggestedMessages = async () => {
const validMessages = suggestedMessages.filter(
(msg) =>
msg?.heading?.trim()?.length > 0 || msg?.message?.trim()?.length > 0
);
const { success, error } = await Workspace.setSuggestedMessages(
slug,
validMessages
);
if (!success) {
showToast(`Failed to update welcome messages: ${error}`, "error");
return;
}
showToast("Successfully updated welcome messages.", "success");
setHasChanges(false);
};
const addMessage = () => {
setEditingIndex(-1);
if (suggestedMessages.length >= 4) {
showToast("Maximum of 4 messages allowed.", "warning");
return;
}
const defaultMessage = {
heading: "Explain to me",
message: "the benefits of AnythingLLM",
};
setNewMessage(defaultMessage);
setSuggestedMessages([...suggestedMessages, { ...defaultMessage }]);
setHasChanges(true);
};
const removeMessage = (index) => {
const messages = [...suggestedMessages];
messages.splice(index, 1);
setSuggestedMessages(messages);
setHasChanges(true);
};
const startEditing = (e, index) => {
e.preventDefault();
setEditingIndex(index);
setNewMessage({ ...suggestedMessages[index] });
};
const handleRemoveMessage = (index) => {
removeMessage(index);
setEditingIndex(-1);
};
const onEditChange = (e) => {
const updatedNewMessage = {
...newMessage,
[e.target.name]: e.target.value,
};
setNewMessage(updatedNewMessage);
const updatedMessages = suggestedMessages.map((message, index) => {
if (index === editingIndex) {
return { ...message, [e.target.name]: e.target.value };
}
return message;
});
setSuggestedMessages(updatedMessages);
setHasChanges(true);
};
if (loading)
return (
<div className="flex flex-col">
<label className="block text-sm font-medium text-white">
Suggested Chat Messages
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
Customize the messages that will be suggested to your workspace users.
</p>
<p className="text-white text-opacity-60 text-sm font-medium mt-6">
<PreLoader size="4" />
</p>
</div>
);
return (
<div className="w-screen">
<div className="flex flex-col">
<label className="block text-sm font-medium text-white">
Suggested Chat Messages
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
Customize the messages that will be suggested to your workspace users.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 text-white/60 text-xs mt-2 w-full justify-center max-w-[600px]">
{suggestedMessages.map((suggestion, index) => (
<div key={index} className="relative w-full">
<button
className="transition-all duration-300 absolute z-10 text-neutral-700 bg-white rounded-full hover:bg-zinc-600 hover:border-zinc-600 hover:text-white border-transparent border shadow-lg ml-2"
style={{
top: -8,
left: 265,
}}
onClick={() => handleRemoveMessage(index)}
>
<X className="m-[1px]" size={20} />
</button>
<button
key={index}
onClick={(e) => startEditing(e, index)}
className={`text-left p-2.5 border rounded-xl w-full border-white/20 bg-sidebar hover:bg-workspace-item-selected-gradient ${
editingIndex === index ? "border-sky-400" : ""
}`}
>
<p className="font-semibold">{suggestion.heading}</p>
<p>{suggestion.message}</p>
</button>
</div>
))}
</div>
{editingIndex >= 0 && (
<div className="flex flex-col gap-y-4 mr-2 mt-8">
<div className="w-1/2">
<label className="text-white text-sm font-semibold block mb-2">
Heading
</label>
<input
placeholder="Message heading"
className=" bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 w-full"
value={newMessage.heading}
name="heading"
onChange={onEditChange}
/>
</div>
<div className="w-1/2">
<label className="text-white text-sm font-semibold block mb-2">
Message
</label>
<input
placeholder="Message"
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 w-full"
value={newMessage.message}
name="message"
onChange={onEditChange}
/>
</div>
</div>
)}
{suggestedMessages.length < 4 && (
<button
type="button"
onClick={addMessage}
className="flex gap-x-2 items-center justify-center mt-6 text-white text-sm hover:text-sky-400 transition-all duration-300"
>
Add new message <Plus className="" size={24} weight="fill" />
</button>
)}
{hasChanges && (
<div className="flex justify-start py-6">
<button
type="button"
className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
onClick={handleSaveSuggestedMessages}
>
Save Messages
</button>
</div>
)}
</div>
);
}

View file

@ -0,0 +1,39 @@
import PreLoader from "@/components/Preloader";
import System from "@/models/system";
import { useEffect, useState } from "react";
export default function VectorCount({ reload, workspace }) {
const [totalVectors, setTotalVectors] = useState(null);
useEffect(() => {
async function fetchVectorCount() {
const totalVectors = await System.totalIndexes(workspace.slug);
setTotalVectors(totalVectors);
}
fetchVectorCount();
}, [workspace?.slug, reload]);
if (totalVectors === null)
return (
<div>
<h3 className="text-white text-sm font-semibold">Number of vectors</h3>
<p className="text-white text-opacity-60 text-xs font-medium py-1">
Total number of vectors in your vector database.
</p>
<p className="text-white text-opacity-60 text-sm font-medium">
<PreLoader size="4" />
</p>
</div>
);
return (
<div>
<h3 className="text-white text-sm font-semibold">Number of vectors</h3>
<p className="text-white text-opacity-60 text-xs font-medium py-1">
Total number of vectors in your vector database.
</p>
<p className="text-white text-opacity-60 text-sm font-medium">
{totalVectors}
</p>
</div>
);
}

View file

@ -0,0 +1,26 @@
export default function WorkspaceName({ workspace, setHasChanges }) {
return (
<div>
<div className="flex flex-col">
<label htmlFor="name" className="block text-sm font-medium text-white">
Workspace Name
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
This will only change the display name of your workspace.
</p>
</div>
<input
name="name"
type="text"
minLength={2}
maxLength={80}
defaultValue={workspace?.name}
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="My Workspace"
required={true}
autoComplete="off"
onChange={() => setHasChanges(true)}
/>
</div>
);
}

View file

@ -0,0 +1,31 @@
export default function DocumentSimilarityThreshold({
workspace,
setHasChanges,
}) {
return (
<div>
<div className="flex flex-col">
<label htmlFor="name" className="block text-sm font-medium text-white">
Document similarity threshold
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
The minimum similarity score required for a source to be considered
related to the chat. The higher the number, the more similar the
source must be to the chat.
</p>
</div>
<select
name="similarityThreshold"
defaultValue={workspace?.similarityThreshold ?? 0.25}
className="bg-zinc-900 text-white text-sm mt-2 rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
onChange={() => setHasChanges(true)}
required={true}
>
<option value={0.0}>No restriction</option>
<option value={0.25}>Low (similarity score &ge; .25)</option>
<option value={0.5}>Medium (similarity score &ge; .50)</option>
<option value={0.75}>High (similarity score &ge; .75)</option>
</select>
</div>
);
}

View file

@ -0,0 +1,31 @@
export default function MaxContextSnippets({ workspace, setHasChanges }) {
return (
<div>
<div className="flex flex-col">
<label htmlFor="name" className="block text-sm font-medium text-white">
Max Context Snippets
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
This setting controls the maximum amount of context snippets the will
be sent to the LLM for per chat or query.
<br />
<i>Recommended: 4</i>
</p>
</div>
<input
name="topN"
type="number"
min={1}
max={12}
step={1}
onWheel={(e) => e.target.blur()}
defaultValue={workspace?.topN ?? 4}
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 mt-2"
placeholder="4"
required={true}
autoComplete="off"
onChange={() => setHasChanges(true)}
/>
</div>
);
}

View file

@ -0,0 +1,13 @@
export default function VectorDBIdentifier({ workspace }) {
return (
<div>
<h3 className="text-white text-sm font-semibold">
Vector database identifier
</h3>
<p className="text-white text-opacity-60 text-xs font-medium py-1"> </p>
<p className="text-white text-opacity-60 text-sm font-medium">
{workspace?.slug}
</p>
</div>
);
}