+
+
+
+
+
+
+

+
+ Enable your agent to search the web to answer your questions by
+ connecting to a web-search (SERP) provider. Web search during agent
+ sessions will not work until this is set up.
+
+
+
+
+ {searchMenuOpen && (
+
setSearchMenuOpen(false)}
+ />
+ )}
+ {searchMenuOpen ? (
+
+
+
+
+ setSearchQuery(e.target.value)}
+ ref={searchInputRef}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") e.preventDefault();
+ }}
+ />
+
+
+
+ {filteredResults.map((provider) => {
+ return (
+ updateChoice(provider.value)}
+ />
+ );
+ })}
+
+
+
+ ) : (
+
+ )}
+
+ {selectedProvider !== "none" && (
+
+ {selectedSearchProviderObject.options(settings)}
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx
new file mode 100644
index 00000000000..d22bba82302
--- /dev/null
+++ b/frontend/src/pages/Admin/Agents/index.jsx
@@ -0,0 +1,232 @@
+import { useEffect, useRef, useState } from "react";
+import Sidebar from "@/components/SettingsSidebar";
+import { isMobile } from "react-device-detect";
+import Admin from "@/models/admin";
+import System from "@/models/system";
+import showToast from "@/utils/toast";
+import { CaretRight, Robot } from "@phosphor-icons/react";
+import ContextualSaveBar from "@/components/ContextualSaveBar";
+import { castToType } from "@/utils/types";
+import { FullScreenLoader } from "@/components/Preloader";
+import { defaultSkills, configurableSkills } from "./skills";
+import { DefaultBadge } from "./Badges/default";
+
+export default function AdminAgents() {
+ const [hasChanges, setHasChanges] = useState(false);
+ const [settings, setSettings] = useState({});
+ const [selectedSkill, setSelectedSkill] = useState("");
+ const [agentSkills, setAgentSkills] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const formEl = useRef(null);
+
+ // Alert user if they try to leave the page with unsaved changes
+ useEffect(() => {
+ const handleBeforeUnload = (event) => {
+ if (hasChanges) {
+ event.preventDefault();
+ event.returnValue = "";
+ }
+ };
+ window.addEventListener("beforeunload", handleBeforeUnload);
+ return () => {
+ window.removeEventListener("beforeunload", handleBeforeUnload);
+ };
+ }, [hasChanges]);
+
+ useEffect(() => {
+ async function fetchSettings() {
+ const _settings = await System.keys();
+ const _preferences = await Admin.systemPreferences();
+ setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
+ setAgentSkills(_preferences.settings?.default_agent_skills ?? []);
+ setLoading(false);
+ }
+ fetchSettings();
+ }, []);
+
+ const toggleAgentSkill = (skillName) => {
+ setAgentSkills((prev) => {
+ const updatedSkills = prev.includes(skillName)
+ ? prev.filter((name) => name !== skillName)
+ : [...prev, skillName];
+ setHasChanges(true);
+ return updatedSkills;
+ });
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const data = {
+ workspace: {},
+ system: {},
+ env: {},
+ };
+
+ const form = new FormData(formEl.current);
+ for (var [key, value] of form.entries()) {
+ if (key.startsWith("system::")) {
+ const [_, label] = key.split("system::");
+ data.system[label] = String(value);
+ continue;
+ }
+
+ if (key.startsWith("env::")) {
+ const [_, label] = key.split("env::");
+ data.env[label] = String(value);
+ continue;
+ }
+ data.workspace[key] = castToType(key, value);
+ }
+
+ const { success } = await Admin.updateSystemPreferences(data.system);
+ await System.updateSystem(data.env);
+
+ if (success) {
+ const _settings = await System.keys();
+ const _preferences = await Admin.systemPreferences();
+ setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
+ setAgentSkills(_preferences.settings?.default_agent_skills ?? []);
+ showToast(`Agent preferences saved successfully.`, "success", {
+ clear: true,
+ });
+ } else {
+ showToast(`Agent preferences failed to save.`, "error", { clear: true });
+ }
+
+ setHasChanges(false);
+ };
+
+ const SelectedSkillComponent =
+ configurableSkills[selectedSkill]?.component ||
+ defaultSkills[selectedSkill]?.component;
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+
+
+
setHasChanges(false)}
+ />
+
+
+ );
+}
+
+function SkillList({
+ isDefault = false,
+ skills = [],
+ selectedSkill = null,
+ handleClick = null,
+ activeSkills = [],
+}) {
+ if (skills.length === 0) return null;
+
+ return (
+
+ {Object.entries(skills).map(([skill, settings], index) => (
+
handleClick?.(skill)}
+ >
+
{settings.title}
+
+ {isDefault ? (
+
+ ) : (
+
+ {activeSkills.includes(skill) ? "On" : "Off"}
+
+ )}
+
+
+
+ ))}
+
+ );
+}
diff --git a/frontend/src/pages/Admin/Agents/skills.js b/frontend/src/pages/Admin/Agents/skills.js
new file mode 100644
index 00000000000..0d5ea7025dc
--- /dev/null
+++ b/frontend/src/pages/Admin/Agents/skills.js
@@ -0,0 +1,73 @@
+import AgentWebSearchSelection from "./WebSearchSelection";
+import AgentSQLConnectorSelection from "./SQLConnectorSelection";
+import GenericSkillPanel from "./GenericSkillPanel";
+import DefaultSkillPanel from "./DefaultSkillPanel";
+import {
+ Brain,
+ File,
+ Browser,
+ ChartBar,
+ FileMagnifyingGlass,
+} from "@phosphor-icons/react";
+import RAGImage from "@/media/agents/rag-memory.png";
+import SummarizeImage from "@/media/agents/view-summarize.png";
+import ScrapeWebsitesImage from "@/media/agents/scrape-websites.png";
+import GenerateChartsImage from "@/media/agents/generate-charts.png";
+import GenerateSaveImages from "@/media/agents/generate-save-files.png";
+
+export const defaultSkills = {
+ "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.',
+ component: DefaultSkillPanel,
+ icon: Brain,
+ image: RAGImage,
+ },
+ "view-summarize": {
+ title: "View & summarize documents",
+ description:
+ "Allow the agent to list and summarize the content of workspace files currently embedded.",
+ component: DefaultSkillPanel,
+ icon: File,
+ image: SummarizeImage,
+ },
+ "scrape-websites": {
+ title: "Scrape websites",
+ description: "Allow the agent to visit and scrape the content of websites.",
+ component: DefaultSkillPanel,
+ icon: Browser,
+ image: ScrapeWebsitesImage,
+ },
+};
+
+export const configurableSkills = {
+ "save-file": {
+ title: "Generate & save files to browser",
+ description:
+ "Enable the default agent to generate and write to files that can be saved to your computer.",
+ component: GenericSkillPanel,
+ skill: "save-file-to-browser",
+ icon: FileMagnifyingGlass,
+ image: GenerateSaveImages,
+ },
+ "create-chart": {
+ title: "Generate charts",
+ description:
+ "Enable the default agent to generate various types of charts from data provided or given in chat.",
+ component: GenericSkillPanel,
+ skill: "create-chart",
+ icon: ChartBar,
+ image: GenerateChartsImage,
+ },
+ "web-browsing": {
+ title: "Web Search",
+ component: AgentWebSearchSelection,
+ skill: "web-browsing",
+ },
+ "sql-agent": {
+ title: "SQL Connector",
+ component: AgentSQLConnectorSelection,
+ skill: "sql-agent",
+ },
+};
diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/index.jsx
deleted file mode 100644
index 848d44ed908..00000000000
--- a/frontend/src/pages/WorkspaceSettings/AgentConfig/SQLConnectorSelection/index.jsx
+++ /dev/null
@@ -1,111 +0,0 @@
-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,
- setHasChanges,
-}) {
- const { isOpen, openModal, closeModal } = useModal();
- const [connections, setConnections] = useState(
- settings?.preferences?.agent_sql_connections || []
- );
-
- return (
- <>
-
-
-
-
-
-
-
- Enable your agent to be able to leverage SQL to answer you questions
- by connecting to various SQL database providers.
-
-
- {enabled && (
- <>
-
-
conn.action !== "remove")
- )}
- />
-
-
- Your database connections
-
-
- {connections
- .filter((connection) => connection.action !== "remove")
- .map((connection) => (
-
{
- setConnections((prev) =>
- prev.map((conn) => {
- if (conn.database_id === databaseId)
- return { ...conn, action: "remove" };
- return conn;
- })
- );
- }}
- setHasChanges={setHasChanges}
- />
- ))}
-
-
-
- >
- )}
-
-
- setConnections((prev) => [...prev, { action: "add", ...newDb }])
- }
- />
- >
- );
-}
diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/index.jsx
deleted file mode 100644
index a71ac770b0c..00000000000
--- a/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/index.jsx
+++ /dev/null
@@ -1,214 +0,0 @@
-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 SerplySearchIcon from "./icons/serply.png";
-import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
-import SearchProviderItem from "./SearchProviderItem";
-import {
- SerperDotDevOptions,
- GoogleSearchOptions,
- BingSearchOptions,
- SerplySearchOptions,
-} from "./SearchProviderOptions";
-
-const SEARCH_PROVIDERS = [
- {
- name: "Please make a selection",
- value: "none",
- logo: AnythingLLMIcon,
- options: () => ,
- 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) => ,
- 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) => ,
- 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) => ,
- description:
- "Web search powered by the Bing Search API. Free for 1000 queries per month.",
- },
- {
- name: "Serply.io",
- value: "serply-engine",
- logo: SerplySearchIcon,
- options: (settings) => ,
- description:
- "Serply.io web-search. Free account with a 100 calls/month forever.",
- },
-];
-
-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 (
-
-
-
-
-
-
-
- Enable your agent to search the web to answer your questions by
- connecting to a web-search (SERP) provider.
-
- Web search during agent sessions will not work until this is set up.
-
-
-
-
-
- {searchMenuOpen && (
-
setSearchMenuOpen(false)}
- />
- )}
- {searchMenuOpen ? (
-
-
-
-
- setSearchQuery(e.target.value)}
- ref={searchInputRef}
- onKeyDown={(e) => {
- if (e.key === "Enter") e.preventDefault();
- }}
- />
-
-
-
- {filteredResults.map((provider) => {
- return (
- updateChoice(provider.value)}
- />
- );
- })}
-
-
-
- ) : (
-
- )}
-
- {selectedProvider !== "none" && (
-
- {selectedSearchProviderObject.options(settings)}
-
- )}
-
-
- );
-}
diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/index.jsx
index 0b31b9ae46f..78b9a502dfa 100644
--- a/frontend/src/pages/WorkspaceSettings/AgentConfig/index.jsx
+++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/index.jsx
@@ -4,27 +4,25 @@ import showToast from "@/utils/toast";
import { castToType } from "@/utils/types";
import { useEffect, useRef, useState } from "react";
import AgentLLMSelection from "./AgentLLMSelection";
-import AgentWebSearchSelection from "./WebSearchSelection";
-import AgentSQLConnectorSelection from "./SQLConnectorSelection";
-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({});
const [hasChanges, setHasChanges] = useState(false);
const [saving, setSaving] = useState(false);
const [loading, setLoading] = useState(true);
- const [agentSkills, setAgentSkills] = useState([]);
-
+ const navigate = useNavigate();
const formEl = useRef(null);
+
useEffect(() => {
async function fetchSettings() {
const _settings = await System.keys();
const _preferences = await Admin.systemPreferences();
setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
- setAgentSkills(_preferences.settings?.default_agent_skills ?? []);
setLoading(false);
}
fetchSettings();
@@ -73,14 +71,6 @@ export default function WorkspaceAgentConfiguration({ workspace }) {
setHasChanges(false);
};
- function toggleAgentSkill(skillName = "") {
- setAgentSkills((prev) => {
- return prev.includes(skillName)
- ? prev.filter((name) => name !== skillName)
- : [...prev, skillName];
- });
- }
-
if (!workspace || loading) return
;
return (
@@ -96,12 +86,23 @@ export default function WorkspaceAgentConfiguration({ workspace }) {
workspace={workspace}
setHasChanges={setHasChanges}
/>
-
+ {!hasChanges && (
+
+
+
+ Customize and enhance the default agent's capabilities by enabling
+ or disabling specific skills. These settings will be applied
+ across all workspaces.
+
+
+ )}
{hasChanges && (