WIP agent settings redesign

This commit is contained in:
shatfield4 2024-05-29 18:09:55 -07:00
parent 4324a8bb4f
commit 4aa9d0a39b
21 changed files with 1606 additions and 78 deletions

View File

@ -21,6 +21,7 @@ const AdminInvites = lazy(() => import("@/pages/Admin/Invitations"));
const AdminWorkspaces = lazy(() => import("@/pages/Admin/Workspaces"));
const AdminSystem = lazy(() => import("@/pages/Admin/System"));
const AdminLogs = lazy(() => import("@/pages/Admin/Logging"));
const AdminAgents = lazy(() => import("@/pages/Admin/Agents"));
const GeneralChats = lazy(() => import("@/pages/GeneralSettings/Chats"));
const GeneralAppearance = lazy(
() => import("@/pages/GeneralSettings/Appearance")
@ -106,6 +107,10 @@ export default function App() {
path="/settings/vector-database"
element={<AdminRoute Component={GeneralVectorDatabase} />}
/>
<Route
path="/settings/agents"
element={<AdminRoute Component={AdminAgents} />}
/>
<Route
path="/settings/event-logs"
element={<AdminRoute Component={AdminLogs} />}

View File

@ -22,6 +22,7 @@ import {
EyeSlash,
SplitVertical,
Microphone,
Robot,
} from "@phosphor-icons/react";
import useUser from "@/hooks/useUser";
import { USER_BACKGROUND_COLOR } from "@/utils/constants";
@ -257,6 +258,15 @@ const SidebarOptions = ({ user = null }) => (
flex={true}
allowedRole={["admin", "manager"]}
/>
<Option
href={paths.settings.agentSkills()}
btnText="Agent Skills"
icon={<Robot className="h-5 w-5 flex-shrink-0" />}
user={user}
flex={true}
allowedRole={["admin", "manager"]}
/>
<Option
href={paths.settings.appearance()}
btnText="Appearance"

View File

@ -0,0 +1,151 @@
// This component differs from the main LLMItem in that it shows if a provider is
// "ready for use" and if not - will then highjack the click handler to show a modal
// of the provider options that must be saved to continue.
import { createPortal } from "react-dom";
import ModalWrapper from "@/components/ModalWrapper";
import { useModal } from "@/hooks/useModal";
import { X } from "@phosphor-icons/react";
import System from "@/models/system";
import showToast from "@/utils/toast";
export default function WorkspaceLLM({
llm,
availableLLMs,
settings,
checked,
onClick,
}) {
const { isOpen, openModal, closeModal } = useModal();
const { name, value, logo, description } = llm;
function handleProviderSelection() {
// Determine if provider needs additional setup because its minimum required keys are
// not yet set in settings.
const requiresAdditionalSetup = (llm.requiredConfig || []).some(
(key) => !settings[key]
);
if (requiresAdditionalSetup) {
openModal();
return;
}
onClick(value);
}
return (
<>
<div
onClick={handleProviderSelection}
className={`w-full p-2 rounded-md hover:cursor-pointer hover:bg-white/10 ${
checked ? "bg-white/10" : ""
}`}
>
<input
type="checkbox"
value={value}
className="peer hidden"
checked={checked}
readOnly={true}
formNoValidate={true}
/>
<div className="flex gap-x-4 items-center">
<img
src={logo}
alt={`${name} logo`}
className="w-10 h-10 rounded-md"
/>
<div className="flex flex-col">
<div className="text-sm font-semibold text-white">{name}</div>
<div className="mt-1 text-xs text-[#D2D5DB]">{description}</div>
</div>
</div>
</div>
<SetupProvider
availableLLMs={availableLLMs}
isOpen={isOpen}
provider={value}
closeModal={closeModal}
postSubmit={onClick}
/>
</>
);
}
function SetupProvider({
availableLLMs,
isOpen,
provider,
closeModal,
postSubmit,
}) {
if (!isOpen) return null;
const LLMOption = availableLLMs.find((llm) => llm.value === provider);
if (!LLMOption) return null;
async function handleUpdate(e) {
e.preventDefault();
e.stopPropagation();
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 ${LLMOption.name} settings: ${error}`, "error");
return;
}
closeModal();
postSubmit();
return false;
}
// Cannot do nested forms, it will cause all sorts of issues, so we portal this out
// to the parent container form so we don't have nested forms.
return createPortal(
<ModalWrapper isOpen={isOpen}>
<div className="relative w-fit max-w-1/2 max-h-full">
<div className="relative bg-main-gradient rounded-xl shadow-[0_4px_14px_rgba(0,0,0,0.25)]">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
<h3 className="text-xl font-semibold text-white">
Setup {LLMOption.name}
</h3>
<button
onClick={closeModal}
type="button"
className="border-none transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
>
<X className="text-gray-300 text-lg" />
</button>
</div>
<form id="provider-form" onSubmit={handleUpdate}>
<div className="py-[17px] px-[20px] flex flex-col gap-y-6">
<p className="text-sm text-white">
To use {LLMOption.name} as this workspace's LLM you need to set
it up first.
</p>
<div>{LLMOption.options({ credentialsOnly: true })}</div>
</div>
<div className="flex w-full justify-between items-center p-3 space-x-2 border-t rounded-b border-gray-500/50">
<button
type="button"
onClick={closeModal}
className="border-none text-xs px-2 py-1 font-semibold rounded-lg bg-white hover:bg-transparent border-2 border-transparent hover:border-white hover:text-white h-[32px] w-fit -mr-8 whitespace-nowrap shadow-[0_4px_14px_rgba(0,0,0,0.25)]"
>
Cancel
</button>
<button
type="submit"
form="provider-form"
className="border-none text-xs px-2 py-1 font-semibold rounded-lg bg-[#46C8FF] hover:bg-[#2C2F36] border-2 border-transparent hover:border-[#46C8FF] hover:text-white h-[32px] w-fit -mr-8 whitespace-nowrap shadow-[0_4px_14px_rgba(0,0,0,0.25)]"
>
Save {LLMOption.name} settings
</button>
</div>
</form>
</div>
</div>
</ModalWrapper>,
document.getElementById("workspace-agent-settings-container")
);
}

View File

@ -0,0 +1,206 @@
import React, { useEffect, useRef, useState } from "react";
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
import AgentLLMItem from "./AgentLLMItem";
import { AVAILABLE_LLM_PROVIDERS } from "@/pages/GeneralSettings/LLMPreference";
import { CaretUpDown, Gauge, MagnifyingGlass, X } from "@phosphor-icons/react";
import AgentModelSelection from "../AgentModelSelection";
const ENABLED_PROVIDERS = [
"openai",
"anthropic",
"lmstudio",
"ollama",
"localai",
"groq",
"azure",
"koboldcpp",
"togetherai",
"openrouter",
"mistral",
"perplexity",
"textgenwebui",
// TODO: More agent support.
// "generic-openai", // Need to support text-input for agent model input for this to be enabled.
// "cohere", // Has tool calling and will need to build explicit support
// "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested.
// "gemini", // Too rate limited and broken in several ways to use for agents.
];
const WARN_PERFORMANCE = [
"lmstudio",
"groq",
"azure",
"koboldcpp",
"ollama",
"localai",
"openrouter",
"generic-openai",
"textgenwebui",
];
const LLM_DEFAULT = {
name: "Please make a selection",
value: "none",
logo: AnythingLLMIcon,
options: () => <React.Fragment />,
description: "Agents will not work until a valid selection is made.",
requiredConfig: [],
};
const LLMS = [
LLM_DEFAULT,
...AVAILABLE_LLM_PROVIDERS.filter((llm) =>
ENABLED_PROVIDERS.includes(llm.value)
),
];
export default function AgentLLMSelection({
settings,
workspace,
setHasChanges,
}) {
const [filteredLLMs, setFilteredLLMs] = useState([]);
const [selectedLLM, setSelectedLLM] = useState(
workspace?.agentProvider ?? "none"
);
const [searchQuery, setSearchQuery] = useState("");
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
const searchInputRef = useRef(null);
function updateLLMChoice(selection) {
setSearchQuery("");
setSelectedLLM(selection);
setSearchMenuOpen(false);
setHasChanges(true);
}
function handleXButton() {
if (searchQuery.length > 0) {
setSearchQuery("");
if (searchInputRef.current) searchInputRef.current.value = "";
} else {
setSearchMenuOpen(!searchMenuOpen);
}
}
useEffect(() => {
const filtered = LLMS.filter((llm) =>
llm.name.toLowerCase().includes(searchQuery.toLowerCase())
);
setFilteredLLMs(filtered);
}, [searchQuery, selectedLLM]);
const selectedLLMObject = LLMS.find((llm) => llm.value === selectedLLM);
return (
<div className="border-b border-white/40 pb-8">
{WARN_PERFORMANCE.includes(selectedLLM) && (
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
<div className="gap-x-2 flex items-center">
<Gauge className="shrink-0" size={25} />
<p className="text-sm">
Performance of LLMs that do not explicitly support tool-calling is
highly dependent on the model's capabilities and accuracy. Some
abilities may be limited or non-functional.
</p>
</div>
</div>
)}
<div className="flex flex-col">
<label htmlFor="name" className="block input-label">
Workspace Agent LLM Provider
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
The specific LLM provider & model that will be used for this
workspace's @agent agent.
</p>
</div>
<div className="relative">
<input type="hidden" name="agentProvider" value={selectedLLM} />
{searchMenuOpen && (
<div
className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-70 backdrop-blur-sm z-10"
onClick={() => setSearchMenuOpen(false)}
/>
)}
{searchMenuOpen ? (
<div className="absolute top-0 left-0 w-full max-w-[640px] max-h-[310px] overflow-auto white-scrollbar min-h-[64px] bg-[#18181B] rounded-lg flex flex-col justify-between cursor-pointer border-2 border-[#46C8FF] z-20">
<div className="w-full flex flex-col gap-y-1">
<div className="flex items-center sticky top-0 border-b border-[#9CA3AF] mx-4 bg-[#18181B]">
<MagnifyingGlass
size={20}
weight="bold"
className="absolute left-4 z-30 text-white -ml-4 my-2"
/>
<input
type="text"
name="llm-search"
autoComplete="off"
placeholder="Search available LLM providers"
className="border-none -ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none focus:border-white text-white placeholder:text-white placeholder:font-medium"
onChange={(e) => setSearchQuery(e.target.value)}
ref={searchInputRef}
onKeyDown={(e) => {
if (e.key === "Enter") e.preventDefault();
}}
/>
<X
size={20}
weight="bold"
className="cursor-pointer text-white hover:text-[#9CA3AF]"
onClick={handleXButton}
/>
</div>
<div className="flex-1 pl-4 pr-2 flex flex-col gap-y-1 overflow-y-auto white-scrollbar pb-4">
{filteredLLMs.map((llm) => {
return (
<AgentLLMItem
llm={llm}
key={llm.name}
availableLLMs={LLMS}
settings={settings}
checked={selectedLLM === llm.value}
onClick={() => updateLLMChoice(llm.value)}
/>
);
})}
</div>
</div>
</div>
) : (
<button
className="w-full max-w-[640px] h-[64px] bg-[#18181B] rounded-lg flex items-center p-[14px] justify-between cursor-pointer border-2 border-transparent hover:border-[#46C8FF] transition-all duration-300"
type="button"
onClick={() => setSearchMenuOpen(true)}
>
<div className="flex gap-x-4 items-center">
<img
src={selectedLLMObject.logo}
alt={`${selectedLLMObject.name} logo`}
className="w-10 h-10 rounded-md"
/>
<div className="flex flex-col text-left">
<div className="text-sm font-semibold text-white">
{selectedLLMObject.name}
</div>
<div className="mt-1 text-xs text-[#D2D5DB]">
{selectedLLMObject.description}
</div>
</div>
</div>
<CaretUpDown size={24} weight="bold" className="text-white" />
</button>
)}
</div>
{selectedLLM !== "none" && (
<div className="mt-4 flex flex-col gap-y-1">
<AgentModelSelection
provider={selectedLLM}
workspace={workspace}
setHasChanges={setHasChanges}
/>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,128 @@
import useGetProviderModels, {
DISABLED_PROVIDERS,
} from "@/hooks/useGetProvidersModels";
// These models do NOT support function calling
function supportedModel(provider, model = "") {
if (provider !== "openai") return true;
return (
["gpt-3.5-turbo-0301", "gpt-4-turbo-2024-04-09", "gpt-4-turbo"].includes(
model
) === false
);
}
export default function AgentModelSelection({
provider,
workspace,
setHasChanges,
}) {
const { defaultModels, customModels, loading } =
useGetProviderModels(provider);
if (DISABLED_PROVIDERS.includes(provider)) return null;
if (loading) {
return (
<div>
<div className="flex flex-col">
<label htmlFor="name" className="block input-label">
Workspace Agent 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's
@agent agent.
</p>
</div>
<select
name="agentModel"
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 input-label">
Workspace Agent model
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
The specific LLM model that will be used for this workspace's @agent
agent.
</p>
</div>
<select
name="agentModel"
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"
>
{defaultModels.length > 0 && (
<optgroup label="General models">
{defaultModels.map((model) => {
if (!supportedModel(provider, model)) return null;
return (
<option
key={model}
value={model}
selected={workspace?.agentModel === model}
>
{model}
</option>
);
})}
</optgroup>
)}
{Array.isArray(customModels) && customModels.length > 0 && (
<optgroup label="Custom models">
{customModels.map((model) => {
if (!supportedModel(provider, model.id)) return null;
return (
<option
key={model.id}
value={model.id}
selected={workspace?.agentModel === 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) => {
if (!supportedModel(provider, model.id)) return null;
return (
<option
key={model.id}
value={model.id}
selected={workspace?.agentModel === model.id}
>
{model.name}
</option>
);
})}
</optgroup>
))}
</>
)}
</select>
</div>
);
}

View File

@ -0,0 +1,39 @@
import React from "react";
export default function GenericSkill({
title,
description,
skill,
toggleSkill,
enabled = false,
disabled = false,
}) {
return (
<div className="border-b border-white/40 pb-4">
<div className="flex flex-col">
<div className="flex w-full justify-between items-center">
<label htmlFor="name" className="block input-label">
{title}
</label>
<label
className={`border-none relative inline-flex items-center mt-2 ${
disabled ? "cursor-not-allowed" : "cursor-pointer"
}`}
>
<input
type="checkbox"
disabled={disabled}
className="peer sr-only"
checked={enabled}
onClick={() => toggleSkill(skill)}
/>
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-stone-400 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border after:border-gray-600 after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-lime-300 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"></span>
</label>
</div>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
{description}
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,47 @@
import PostgreSQLLogo from "./icons/postgresql.png";
import MySQLLogo from "./icons/mysql.png";
import MSSQLLogo from "./icons/mssql.png";
import { X } from "@phosphor-icons/react";
export const DB_LOGOS = {
postgresql: PostgreSQLLogo,
mysql: MySQLLogo,
"sql-server": MSSQLLogo,
};
export default function DBConnection({ connection, onRemove }) {
const { database_id, engine } = connection;
function removeConfirmation() {
if (
!window.confirm(
`Delete ${database_id} from the list of available SQL connections? This cannot be undone.`
)
) {
return false;
}
onRemove(database_id);
}
return (
<div className="flex gap-x-4 items-center">
<img
src={DB_LOGOS?.[engine] ?? null}
alt={`${engine} logo`}
className="w-10 h-10 rounded-md"
/>
<div className="flex w-full items-center justify-between">
<div className="flex flex-col">
<div className="text-sm font-semibold text-white">{database_id}</div>
<div className="mt-1 text-xs text-[#D2D5DB]">{engine}</div>
</div>
<button
type="button"
onClick={removeConfirmation}
className="border-none text-white/40 hover:text-red-500"
>
<X size={24} />
</button>
</div>
</div>
);
}

View File

@ -0,0 +1,271 @@
import { useState } from "react";
import { createPortal } from "react-dom";
import ModalWrapper from "@/components/ModalWrapper";
import { WarningOctagon, X } from "@phosphor-icons/react";
import { DB_LOGOS } from "./DBConnection";
function assembleConnectionString({
engine,
username = "",
password = "",
host = "",
port = "",
database = "",
}) {
if ([username, password, host, database].every((i) => !!i) === false)
return `Please fill out all the fields above.`;
switch (engine) {
case "postgresql":
return `postgres://${username}:${password}@${host}:${port}/${database}`;
case "mysql":
return `mysql://${username}:${password}@${host}:${port}/${database}`;
case "sql-server":
return `mssql://${username}:${password}@${host}:${port}/${database}`;
default:
return null;
}
}
const DEFAULT_ENGINE = "postgresql";
const DEFAULT_CONFIG = {
username: null,
password: null,
host: null,
port: null,
database: null,
};
export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
const [engine, setEngine] = useState(DEFAULT_ENGINE);
const [config, setConfig] = useState(DEFAULT_CONFIG);
if (!isOpen) return null;
function handleClose() {
setEngine(DEFAULT_ENGINE);
setConfig(DEFAULT_CONFIG);
closeModal();
}
function onFormChange() {
const form = new FormData(document.getElementById("sql-connection-form"));
setConfig({
username: form.get("username").trim(),
password: form.get("password"),
host: form.get("host").trim(),
port: form.get("port").trim(),
database: form.get("database").trim(),
});
}
async function handleUpdate(e) {
e.preventDefault();
e.stopPropagation();
const form = new FormData(e.target);
onSubmit({
engine,
database_id: form.get("name"),
connectionString: assembleConnectionString({ engine, ...config }),
});
handleClose();
return false;
}
// Cannot do nested forms, it will cause all sorts of issues, so we portal this out
// to the parent container form so we don't have nested forms.
return createPortal(
<ModalWrapper isOpen={isOpen}>
<div className="relative w-1/3 max-h-full ">
<div className="relative bg-main-gradient rounded-xl shadow-[0_4px_14px_rgba(0,0,0,0.25)] max-h-[90vh] overflow-y-scroll no-scroll">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
<h3 className="text-xl font-semibold text-white">
New SQL Connection
</h3>
<button
onClick={handleClose}
type="button"
className="border-none transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
>
<X className="text-gray-300 text-lg" />
</button>
</div>
<form
id="sql-connection-form"
onSubmit={handleUpdate}
onChange={onFormChange}
>
<div className="py-[17px] px-[20px] flex flex-col gap-y-6">
<p className="text-sm text-white">
Add the connection information for your database below and it
will be available for future SQL agent calls.
</p>
<div className="flex flex-col w-full">
<div className="border border-red-800 bg-zinc-800 p-4 rounded-lg flex items-center gap-x-2 text-sm text-red-400">
<WarningOctagon size={28} className="shrink-0" />
<p>
<b>WARNING:</b> The SQL agent has been <i>instructed</i> to
only perform non-modifying queries. This <b>does not</b>{" "}
prevent a hallucination from still deleting data. Only
connect with a user who has <b>READ_ONLY</b> permissions.
</p>
</div>
<label className="text-white text-sm font-semibold block my-4">
Select your SQL engine
</label>
<div className="flex w-full flex-wrap gap-x-4">
<DBEngine
provider="postgresql"
active={engine === "postgresql"}
onClick={() => setEngine("postgresql")}
/>
<DBEngine
provider="mysql"
active={engine === "mysql"}
onClick={() => setEngine("mysql")}
/>
<DBEngine
provider="sql-server"
active={engine === "sql-server"}
onClick={() => setEngine("sql-server")}
/>
</div>
</div>
<div className="flex flex-col w-full">
<label className="text-white text-sm font-semibold block mb-4">
Connection name
</label>
<input
type="text"
name="name"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="a unique name to identify this SQL connection"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex gap-x-2">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Database user
</label>
<input
type="text"
name="username"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="root"
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">
Database user password
</label>
<input
type="text"
name="password"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="password123"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
<div className="flex gap-x-2">
<div className="flex flex-col w-full">
<label className="text-white text-sm font-semibold block mb-4">
Server endpoint
</label>
<input
type="text"
name="host"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="the hostname or endpoint for your database"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-30">
<label className="text-white text-sm font-semibold block mb-4">
Port
</label>
<input
type="text"
name="port"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="3306"
required={false}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Database
</label>
<input
type="text"
name="database"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="the database the agent will interact with"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<p className="text-white/40 text-sm">
{assembleConnectionString({ engine, ...config })}
</p>
</div>
<div className="flex w-full justify-between items-center p-3 space-x-2 border-t rounded-b border-gray-500/50">
<button
type="button"
onClick={handleClose}
className="border-none text-xs px-2 py-1 font-semibold rounded-lg bg-white hover:bg-transparent border-2 border-transparent hover:border-white hover:text-white h-[32px] w-fit -mr-8 whitespace-nowrap shadow-[0_4px_14px_rgba(0,0,0,0.25)]"
>
Cancel
</button>
<button
type="submit"
form="sql-connection-form"
className="border-none text-xs px-2 py-1 font-semibold rounded-lg bg-[#46C8FF] hover:bg-[#2C2F36] border-2 border-transparent hover:border-[#46C8FF] hover:text-white h-[32px] w-fit -mr-8 whitespace-nowrap shadow-[0_4px_14px_rgba(0,0,0,0.25)]"
>
Save connection
</button>
</div>
</form>
</div>
</div>
</ModalWrapper>,
document.getElementById("workspace-agent-settings-container")
);
}
function DBEngine({ provider, active, onClick }) {
return (
<button
type="button"
onClick={onClick}
className={`flex flex-col p-4 border border-white/40 bg-zinc-800 rounded-lg w-fit hover:bg-zinc-700 ${
active ? "!bg-blue-500/50" : ""
}`}
>
<img
src={DB_LOGOS[provider]}
className="h-[100px] rounded-md"
alt="PostgreSQL"
/>
</button>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,109 @@
import React, { useState } from "react";
import DBConnection from "./DBConnection";
import { Plus } from "@phosphor-icons/react";
import NewSQLConnection from "./NewConnectionModal";
import { useModal } from "@/hooks/useModal";
export default function AgentSQLConnectorSelection({
skill,
settings,
toggleSkill,
enabled = false,
}) {
const { isOpen, openModal, closeModal } = useModal();
const [connections, setConnections] = useState(
settings?.preferences?.agent_sql_connections || []
);
return (
<>
<div className="border-b border-white/40 pb-4">
<div className="flex flex-col">
<div className="flex w-full justify-between items-center">
<label htmlFor="name" className="block input-label">
SQL Agent
</label>
<label className="border-none relative inline-flex cursor-pointer items-center mt-2">
<input
type="checkbox"
className="peer sr-only"
checked={enabled}
onClick={() => toggleSkill(skill)}
/>
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-stone-400 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border after:border-gray-600 after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-lime-300 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"></span>
</label>
</div>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
Enable your agent to be able to leverage SQL to answer you questions
by connecting to various SQL database providers.
</p>
</div>
{enabled && (
<>
<input
name="system::agent_sql_connections"
type="hidden"
value={JSON.stringify(connections)}
/>
<input
type="hidden"
value={JSON.stringify(
connections.filter((conn) => conn.action !== "remove")
)}
/>
<div className="flex flex-col mt-2 gap-y-2">
<p className="text-white font-semibold text-sm">
Your database connections
</p>
<div className="flex flex-col gap-y-3">
{connections
.filter((connection) => connection.action !== "remove")
.map((connection) => (
<DBConnection
key={connection.database_id}
connection={connection}
onRemove={(databaseId) => {
setConnections((prev) =>
prev.map((conn) => {
if (conn.database_id === databaseId)
return { ...conn, action: "remove" };
return conn;
})
);
}}
/>
))}
<button
type="button"
onClick={openModal}
className="w-fit relative flex h-[40px] items-center border-none hover:bg-slate-600/20 rounded-lg"
>
<div className="flex w-full gap-x-2 items-center p-4">
<div className="bg-zinc-600 p-2 rounded-lg h-[24px] w-[24px] flex items-center justify-center">
<Plus
weight="bold"
size={14}
className="shrink-0 text-slate-100"
/>
</div>
<p className="text-left text-slate-100 text-sm">
New SQL connection
</p>
</div>
</button>
</div>
</div>
</>
)}
</div>
<NewSQLConnection
isOpen={isOpen}
closeModal={closeModal}
onSubmit={(newDb) =>
setConnections((prev) => [...prev, { action: "add", ...newDb }])
}
/>
</>
);
}

View File

@ -0,0 +1,27 @@
export default function SearchProviderItem({ provider, checked, onClick }) {
const { name, value, logo, description } = provider;
return (
<div
onClick={onClick}
className={`w-full p-2 rounded-md hover:cursor-pointer hover:bg-white/10 ${
checked ? "bg-white/10" : ""
}`}
>
<input
type="checkbox"
value={value}
className="peer hidden"
checked={checked}
readOnly={true}
formNoValidate={true}
/>
<div className="flex gap-x-4 items-center">
<img src={logo} alt={`${name} logo`} className="w-10 h-10 rounded-md" />
<div className="flex flex-col">
<div className="text-sm font-semibold text-white">{name}</div>
<div className="mt-1 text-xs text-[#D2D5DB]">{description}</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,149 @@
export function GoogleSearchOptions({ settings }) {
return (
<>
<p className="text-sm text-white/60 my-2">
You can get a free search engine & API key{" "}
<a
href="https://programmablesearchengine.google.com/controlpanel/create"
target="_blank"
rel="noreferrer"
className="text-blue-300 underline"
>
from Google here.
</a>
</p>
<div className="flex gap-x-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Search engine ID
</label>
<input
type="text"
name="env::AgentGoogleSearchEngineId"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Google Search Engine Id"
defaultValue={settings?.AgentGoogleSearchEngineId}
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">
Programmatic Access API Key
</label>
<input
type="password"
name="env::AgentGoogleSearchEngineKey"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Google Search Engine API Key"
defaultValue={
settings?.AgentGoogleSearchEngineKey ? "*".repeat(20) : ""
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
</>
);
}
export function SerperDotDevOptions({ settings }) {
return (
<>
<p className="text-sm text-white/60 my-2">
You can get a free API key{" "}
<a
href="https://serper.dev"
target="_blank"
rel="noreferrer"
className="text-blue-300 underline"
>
from Serper.dev.
</a>
</p>
<div className="flex gap-x-4">
<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="env::AgentSerperApiKey"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Serper.dev API Key"
defaultValue={settings?.AgentSerperApiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
</>
);
}
export function BingSearchOptions({ settings }) {
return (
<>
<p className="text-sm text-white/60 my-2">
You can get a Bing Web Search API subscription key{" "}
<a
href="https://portal.azure.com/"
target="_blank"
rel="noreferrer"
className="text-blue-300 underline"
>
from the Azure portal.
</a>
</p>
<div className="flex gap-x-4">
<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="env::AgentBingSearchApiKey"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Bing Web Search API Key"
defaultValue={settings?.AgentBingSearchApiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
<p className="text-sm text-white/60 my-2">
To set up a Bing Web Search API subscription:
</p>
<ol className="list-decimal text-sm text-white/60 ml-6">
<li>
Go to the Azure portal:{" "}
<a
href="https://portal.azure.com/"
target="_blank"
rel="noreferrer"
className="text-blue-300 underline"
>
https://portal.azure.com/
</a>
</li>
<li>Create a new Azure account or sign in with an existing one.</li>
<li>
Navigate to the "Create a resource" section and search for "Bing
Search v7".
</li>
<li>
Select the "Bing Search v7" resource and create a new subscription.
</li>
<li>
Choose the pricing tier that suits your needs (free tier available).
</li>
<li>Obtain the API key for your Bing Web Search subscription.</li>
</ol>
</>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,204 @@
import React, { useEffect, useRef, useState } from "react";
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
import GoogleSearchIcon from "./icons/google.png";
import SerperDotDevIcon from "./icons/serper.png";
import BingSearchIcon from "./icons/bing.png";
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
import SearchProviderItem from "./SearchProviderItem";
import {
SerperDotDevOptions,
GoogleSearchOptions,
BingSearchOptions,
} from "./SearchProviderOptions";
const SEARCH_PROVIDERS = [
{
name: "Please make a selection",
value: "none",
logo: AnythingLLMIcon,
options: () => <React.Fragment />,
description:
"Web search will be disabled until a provider and keys are provided.",
},
{
name: "Google Search Engine",
value: "google-search-engine",
logo: GoogleSearchIcon,
options: (settings) => <GoogleSearchOptions settings={settings} />,
description:
"Web search powered by a custom Google Search Engine. Free for 100 queries per day.",
},
{
name: "Serper.dev",
value: "serper-dot-dev",
logo: SerperDotDevIcon,
options: (settings) => <SerperDotDevOptions settings={settings} />,
description:
"Serper.dev web-search. Free account with a 2,500 calls, but then paid.",
},
{
name: "Bing Search",
value: "bing-search",
logo: BingSearchIcon,
options: (settings) => <BingSearchOptions settings={settings} />,
description:
"Web search powered by the Bing Search API. Free for 1000 queries per month.",
},
];
export default function AgentWebSearchSelection({
skill,
settings,
toggleSkill,
enabled = false,
}) {
const searchInputRef = useRef(null);
const [filteredResults, setFilteredResults] = useState([]);
const [selectedProvider, setSelectedProvider] = useState("none");
const [searchQuery, setSearchQuery] = useState("");
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
function updateChoice(selection) {
setSearchQuery("");
setSelectedProvider(selection);
setSearchMenuOpen(false);
}
function handleXButton() {
if (searchQuery.length > 0) {
setSearchQuery("");
if (searchInputRef.current) searchInputRef.current.value = "";
} else {
setSearchMenuOpen(!searchMenuOpen);
}
}
useEffect(() => {
const filtered = SEARCH_PROVIDERS.filter((provider) =>
provider.name.toLowerCase().includes(searchQuery.toLowerCase())
);
setFilteredResults(filtered);
}, [searchQuery, selectedProvider]);
useEffect(() => {
setSelectedProvider(settings?.preferences?.agent_search_provider ?? "none");
}, [settings?.preferences?.agent_search_provider]);
const selectedSearchProviderObject = SEARCH_PROVIDERS.find(
(provider) => provider.value === selectedProvider
);
return (
<div className="border-b border-white/40 pb-4">
<div className="flex flex-col">
<div className="flex w-full justify-between items-center">
<label htmlFor="name" className="block input-label">
Live web search and browsing
</label>
<label className="border-none relative inline-flex cursor-pointer items-center mt-2">
<input
type="checkbox"
className="peer sr-only"
checked={enabled}
onClick={() => toggleSkill(skill)}
/>
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-stone-400 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border after:border-gray-600 after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-lime-300 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"></span>
</label>
</div>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
Enable your agent to search the web to answer your questions by
connecting to a web-search (SERP) provider.
<br />
Web search during agent sessions will not work until this is set up.
</p>
</div>
<div hidden={!enabled}>
<div className="relative">
<input
type="hidden"
name="system::agent_search_provider"
value={selectedProvider}
/>
{searchMenuOpen && (
<div
className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-70 backdrop-blur-sm z-10"
onClick={() => setSearchMenuOpen(false)}
/>
)}
{searchMenuOpen ? (
<div className="absolute top-0 left-0 w-full max-w-[640px] max-h-[310px] overflow-auto white-scrollbar min-h-[64px] bg-[#18181B] rounded-lg flex flex-col justify-between cursor-pointer border-2 border-[#46C8FF] z-20">
<div className="w-full flex flex-col gap-y-1">
<div className="flex items-center sticky top-0 border-b border-[#9CA3AF] mx-4 bg-[#18181B]">
<MagnifyingGlass
size={20}
weight="bold"
className="absolute left-4 z-30 text-white -ml-4 my-2"
/>
<input
type="text"
name="web-provider-search"
autoComplete="off"
placeholder="Search available web-search providers"
className="border-none -ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none focus:border-white text-white placeholder:text-white placeholder:font-medium"
onChange={(e) => setSearchQuery(e.target.value)}
ref={searchInputRef}
onKeyDown={(e) => {
if (e.key === "Enter") e.preventDefault();
}}
/>
<X
size={20}
weight="bold"
className="cursor-pointer text-white hover:text-[#9CA3AF]"
onClick={handleXButton}
/>
</div>
<div className="flex-1 pl-4 pr-2 flex flex-col gap-y-1 overflow-y-auto white-scrollbar pb-4">
{filteredResults.map((provider) => {
return (
<SearchProviderItem
provider={provider}
key={provider.name}
checked={selectedProvider === provider.value}
onClick={() => updateChoice(provider.value)}
/>
);
})}
</div>
</div>
</div>
) : (
<button
className="w-full max-w-[640px] h-[64px] bg-[#18181B] rounded-lg flex items-center p-[14px] justify-between cursor-pointer border-2 border-transparent hover:border-[#46C8FF] transition-all duration-300"
type="button"
onClick={() => setSearchMenuOpen(true)}
>
<div className="flex gap-x-4 items-center">
<img
src={selectedSearchProviderObject.logo}
alt={`${selectedSearchProviderObject.name} logo`}
className="w-10 h-10 rounded-md"
/>
<div className="flex flex-col text-left">
<div className="text-sm font-semibold text-white">
{selectedSearchProviderObject.name}
</div>
<div className="mt-1 text-xs text-[#D2D5DB]">
{selectedSearchProviderObject.description}
</div>
</div>
</div>
<CaretUpDown size={24} weight="bold" className="text-white" />
</button>
)}
</div>
{selectedProvider !== "none" && (
<div className="mt-4 flex flex-col gap-y-1">
{selectedSearchProviderObject.options(settings)}
</div>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,162 @@
import { useEffect, useState } from "react";
import Sidebar from "@/components/SettingsSidebar";
import { isMobile } from "react-device-detect";
import Admin from "@/models/admin";
import showToast from "@/utils/toast";
import CTAButton from "@/components/lib/CTAButton";
import AgentWebSearchSelection from "./WebSearchSelection";
import AgentSQLConnectorSelection from "./SQLConnectorSelection";
import GenericSkill from "./GenericSkill";
const skillComponents = {
"web-search": AgentWebSearchSelection,
"sql-connector": AgentSQLConnectorSelection,
"rag-memory": GenericSkill,
"view-summarize": GenericSkill,
"scrape-websites": GenericSkill,
"create-chart": GenericSkill,
"save-file": GenericSkill,
};
const skillSettings = {
"web-search": {
title: "Web Search",
},
"sql-connector": {
title: "SQL Connector",
},
"rag-memory": {
title: "RAG & long-term memory",
description:
'Allow the agent to leverage your local documents to answer a query or ask the agent to "remember" pieces of content for long-term memory retrieval.',
enabled: true,
disabled: true,
},
"view-summarize": {
title: "View & summarize documents",
description:
"Allow the agent to list and summarize the content of workspace files currently embedded.",
enabled: true,
disabled: true,
},
"scrape-websites": {
title: "Scrape websites",
description: "Allow the agent to visit and scrape the content of websites.",
enabled: true,
disabled: true,
},
"create-chart": {
title: "Generate charts",
description:
"Enable the default agent to generate various types of charts from data provided or given in chat.",
skill: "create-chart",
},
"save-file": {
title: "Generate & save files to browser",
description:
"Enable the default agent to generate and write to files that save and can be downloaded in your browser.",
skill: "save-file-to-browser",
},
};
export default function AdminAgents() {
const [saving, setSaving] = useState(false);
const [hasChanges, setHasChanges] = useState(false);
const [settings, setSettings] = useState({});
const [selectedSkill, setSelectedSkill] = useState("web-search");
const [agentSkills, setAgentSkills] = useState([]);
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
await Admin.updateSystemPreferences({
...settings,
default_agent_skills: agentSkills,
});
setSaving(false);
setHasChanges(false);
showToast("System preferences updated successfully.", "success");
};
useEffect(() => {
async function fetchSettings() {
const _settings = await Admin.systemPreferences();
setSettings(_settings?.settings ?? {});
setAgentSkills(_settings?.settings?.default_agent_skills ?? []);
}
fetchSettings();
}, []);
function toggleAgentSkill(skillName = "") {
setAgentSkills((prev) => {
setHasChanges(true);
return prev.includes(skillName)
? prev.filter((name) => name !== skillName)
: [...prev, skillName];
});
}
const SelectedSkillComponent = skillComponents[selectedSkill];
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
<Sidebar />
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-main-gradient w-full h-full flex flex-col"
>
<form
onSubmit={handleSubmit}
className="flex flex-col w-full px-1 md:pl-6 md:pr-[50px] md:py-6 py-16 flex-grow"
>
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
<div className="items-center">
<p className="text-lg leading-6 font-bold text-white">
Available Agents
</p>
</div>
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
Improve the natural abilities of the default agent with these
pre-built skills. This setup applies to all workspaces.
</p>
</div>
{hasChanges && (
<div className="flex justify-end">
<CTAButton type="submit" className="mt-3 mr-0">
{saving ? "Saving..." : "Save changes"}
</CTAButton>
</div>
)}
<div className="bg-[#222628] rounded-lg mt-5 flex flex-grow overflow-y-scroll">
<div className="w-1/4 min-w-[200px] p-5 flex flex-col gap-y-2">
{Object.keys(skillComponents).map((skill) => (
<button
key={skill}
type="button"
className={`text-white w-full justify-start flex text-sm ${
selectedSkill === skill ? "bg-white/10 font-semibold" : ""
}`}
onClick={() => setSelectedSkill(skill)}
>
{skillSettings[skill]?.title || skill}
</button>
))}
</div>
<div className="w-[2px] bg-white/20 mx-4" />
<div className="w-3/4 flex-grow p-6">
<SelectedSkillComponent
skill={selectedSkill}
settings={settings}
toggleSkill={toggleAgentSkill}
enabled={agentSkills.includes(
skillSettings[selectedSkill]?.skill
)}
{...skillSettings[selectedSkill]}
/>
</div>
</div>
</form>
</div>
</div>
);
}

View File

@ -10,6 +10,8 @@ import GenericSkill from "./GenericSkill";
import Admin from "@/models/admin";
import * as Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";
import paths from "@/utils/paths";
import { useNavigate } from "react-router-dom";
export default function WorkspaceAgentConfiguration({ workspace }) {
const [settings, setSettings] = useState({});
@ -18,6 +20,8 @@ export default function WorkspaceAgentConfiguration({ workspace }) {
const [loading, setLoading] = useState(true);
const [agentSkills, setAgentSkills] = useState([]);
const navigate = useNavigate();
const formEl = useRef(null);
useEffect(() => {
async function fetchSettings() {
@ -96,11 +100,23 @@ export default function WorkspaceAgentConfiguration({ workspace }) {
workspace={workspace}
setHasChanges={setHasChanges}
/>
<AvailableAgentSkills
skills={agentSkills}
toggleAgentSkill={toggleAgentSkill}
settings={settings}
/>
{!hasChanges && (
<div className="flex flex-col gap-y-4">
<button onClick={() => navigate(paths.settings.agentSkills())}>
<div
type="button"
className="w-fit transition-all duration-300 border border-slate-200 px-5 py-2.5 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
>
Configure Agent Skills
</div>
</button>
<p className="text-white text-opacity-60 text-xs font-medium">
Customize and enhance the default agent's capabilities by enabling
or disabling specific skills. These settings will be applied
across all workspaces.
</p>
</div>
)}
{hasChanges && (
<button
type="submit"
@ -143,76 +159,77 @@ function LoadingSkeleton() {
);
}
function AvailableAgentSkills({ skills, settings, toggleAgentSkill }) {
return (
<div>
<div className="flex flex-col mb-8">
<div className="flex w-full justify-between items-center">
<label htmlFor="name" className="text-white text-md font-semibold">
Default agent skills
</label>
</div>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
Improve the natural abilities of the default agent with these
pre-built skills. This set up applies to all workspaces.
</p>
</div>
<input
name="system::default_agent_skills"
type="hidden"
value={skills.join(",")}
/>
<div className="flex flex-col gap-y-3">
<GenericSkill
title="RAG & long-term memory"
description='Allow the agent to leverage your local documents to answer a query or ask the agent to "remember" pieces of content for long-term memory retrieval.'
settings={settings}
enabled={true}
disabled={true}
/>
<GenericSkill
title="View & summarize documents"
description="Allow the agent to list and summarize the content of workspace files currently embedded."
settings={settings}
enabled={true}
disabled={true}
/>
<GenericSkill
title="Scrape websites"
description="Allow the agent to visit and scrape the content of websites."
settings={settings}
enabled={true}
disabled={true}
/>
<GenericSkill
title="Generate charts"
description="Enable the default agent to generate various types of charts from data provided or given in chat."
skill="create-chart"
settings={settings}
toggleSkill={toggleAgentSkill}
enabled={skills.includes("create-chart")}
/>
<GenericSkill
title="Generate & save files to browser"
description="Enable the default agent to generate and write to files that save and can be downloaded in your browser."
skill="save-file-to-browser"
settings={settings}
toggleSkill={toggleAgentSkill}
enabled={skills.includes("save-file-to-browser")}
/>
<AgentWebSearchSelection
skill="web-browsing"
settings={settings}
toggleSkill={toggleAgentSkill}
enabled={skills.includes("web-browsing")}
/>
<AgentSQLConnectorSelection
skill="sql-agent"
settings={settings}
toggleSkill={toggleAgentSkill}
enabled={skills.includes("sql-agent")}
/>
</div>
</div>
);
}
// TODO: remove
// function AvailableAgentSkills({ skills, settings, toggleAgentSkill }) {
// return (
// <div>
// <div className="flex flex-col mb-8">
// <div className="flex w-full justify-between items-center">
// <label htmlFor="name" className="text-white text-md font-semibold">
// Default agent skills
// </label>
// </div>
// <p className="text-white text-opacity-60 text-xs font-medium py-1.5">
// Improve the natural abilities of the default agent with these
// pre-built skills. This set up applies to all workspaces.
// </p>
// </div>
// <input
// name="system::default_agent_skills"
// type="hidden"
// value={skills.join(",")}
// />
// <div className="flex flex-col gap-y-3">
// <GenericSkill
// title="RAG & long-term memory"
// description='Allow the agent to leverage your local documents to answer a query or ask the agent to "remember" pieces of content for long-term memory retrieval.'
// settings={settings}
// enabled={true}
// disabled={true}
// />
// <GenericSkill
// title="View & summarize documents"
// description="Allow the agent to list and summarize the content of workspace files currently embedded."
// settings={settings}
// enabled={true}
// disabled={true}
// />
// <GenericSkill
// title="Scrape websites"
// description="Allow the agent to visit and scrape the content of websites."
// settings={settings}
// enabled={true}
// disabled={true}
// />
// <GenericSkill
// title="Generate charts"
// description="Enable the default agent to generate various types of charts from data provided or given in chat."
// skill="create-chart"
// settings={settings}
// toggleSkill={toggleAgentSkill}
// enabled={skills.includes("create-chart")}
// />
// <GenericSkill
// title="Generate & save files to browser"
// description="Enable the default agent to generate and write to files that save and can be downloaded in your browser."
// skill="save-file-to-browser"
// settings={settings}
// toggleSkill={toggleAgentSkill}
// enabled={skills.includes("save-file-to-browser")}
// />
// <AgentWebSearchSelection
// skill="web-browsing"
// settings={settings}
// toggleSkill={toggleAgentSkill}
// enabled={skills.includes("web-browsing")}
// />
// <AgentSQLConnectorSelection
// skill="sql-agent"
// settings={settings}
// toggleSkill={toggleAgentSkill}
// enabled={skills.includes("sql-agent")}
// />
// </div>
// </div>
// );
// }

View File

@ -117,6 +117,9 @@ export default {
appearance: () => {
return "/settings/appearance";
},
agentSkills: () => {
return "/settings/agents";
},
apiKeys: () => {
return "/settings/api-keys";
},