From 5884a894519a43c34261eb8b5c98d11b1978fe67 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Fri, 30 May 2025 17:38:02 -0700 Subject: [PATCH 1/5] rework ui for new styles/simplify css --- .../pages/Admin/Agents/AgentFlows/index.jsx | 50 +++-- .../Admin/Agents/Imported/SkillList/index.jsx | 54 +++-- .../pages/Admin/Agents/MCPServers/index.jsx | 68 ++++--- frontend/src/pages/Admin/Agents/index.jsx | 190 +++++++----------- frontend/src/pages/Admin/Agents/skills.js | 3 - 5 files changed, 176 insertions(+), 189 deletions(-) diff --git a/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx b/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx index 4bb7d17e51..a6099e2576 100644 --- a/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx +++ b/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx @@ -13,6 +13,7 @@ export default function AgentFlowsList({ Learn more about Agent Flows. @@ -23,32 +24,39 @@ export default function AgentFlowsList({ return (
- {flows.map((flow, index) => ( + {flows.map((flow) => (
handleClick?.(flow)} + className={` + relative + cursor-pointer + transition-all duration-300 + after:content-[''] + after:absolute + after:bottom-0 + after:left-4 + after:right-4 + after:h-[1px] + after:bg-theme-action-menu-bg + last:after:hidden + first:rounded-t-xl + last:rounded-b-xl + ${selectedFlow?.uuid === flow.uuid ? "bg-white/10 light:bg-theme-bg-sidebar" : "hover:bg-theme-bg-primary"} + `} > -
{flow.name}
-
-
- {flow.active ? "On" : "Off"} +
+
{flow.name}
+
+
+ {flow.active ? "On" : "Off"} +
+
-
))} diff --git a/frontend/src/pages/Admin/Agents/Imported/SkillList/index.jsx b/frontend/src/pages/Admin/Agents/Imported/SkillList/index.jsx index f69e98d4f3..21bd5db538 100644 --- a/frontend/src/pages/Admin/Agents/Imported/SkillList/index.jsx +++ b/frontend/src/pages/Admin/Agents/Imported/SkillList/index.jsx @@ -15,6 +15,7 @@ export default function ImportedSkillList({
AnythingLLM Agent Docs @@ -25,33 +26,42 @@ export default function ImportedSkillList({ ); return ( -
- {skills.map((config, index) => ( +
+ {skills.map((config) => (
handleClick?.({ ...config, imported: true })} + className={` + relative + cursor-pointer + transition-all duration-300 + after:content-[''] + after:absolute + after:bottom-0 + after:left-4 + after:right-4 + after:h-[1px] + after:bg-theme-action-menu-bg + last:after:hidden + first:rounded-t-xl + last:rounded-b-xl + ${selectedSkill?.hubId === config.hubId ? "bg-white/10 light:bg-theme-bg-sidebar" : "hover:bg-theme-bg-primary"} + `} > -
{sentenceCase(config.name)}
-
-
- {config.active ? "On" : "Off"} +
+
+ {sentenceCase(config.name)} +
+
+
+ {config.active ? "On" : "Off"} +
+
-
))} diff --git a/frontend/src/pages/Admin/Agents/MCPServers/index.jsx b/frontend/src/pages/Admin/Agents/MCPServers/index.jsx index 7c6b4445cb..9effe97eea 100644 --- a/frontend/src/pages/Admin/Agents/MCPServers/index.jsx +++ b/frontend/src/pages/Admin/Agents/MCPServers/index.jsx @@ -1,6 +1,10 @@ import { useState, useEffect } from "react"; import { titleCase } from "text-case"; -import { BookOpenText, ArrowClockwise } from "@phosphor-icons/react"; +import { + BookOpenText, + ArrowClockwise, + ArrowsClockwise, +} from "@phosphor-icons/react"; import MCPLogo from "@/media/agents/mcp-logo.svg"; import MCPServers from "@/models/mcpServers"; import showToast from "@/utils/toast"; @@ -48,31 +52,28 @@ export function MCPServerHeader({ <>
- MCP Logo -

MCP Servers

+ MCP Logo +

MCP Servers

- +
@@ -121,30 +122,37 @@ export function MCPServersList({ return (
- {servers.map((server, index) => ( + {servers.map((server) => (
handleClick?.(server)} + className={` + relative + cursor-pointer + transition-all duration-300 + after:content-[''] + after:absolute + after:bottom-0 + after:left-4 + after:right-4 + after:h-[1px] + after:bg-theme-action-menu-bg + last:after:hidden + first:rounded-t-xl + last:rounded-b-xl + ${selectedServer?.name === server.name ? "bg-white/10 light:bg-theme-bg-sidebar" : "hover:bg-theme-bg-primary"} + `} > -
- {titleCase(server.name.replace(/[_-]/g, " "))} -
-
-
- {server.running ? "On" : "Stopped"} +
+
+ {titleCase(server.name.replace(/[_-]/g, " "))} +
+
+
+ {server.running ? "On" : "Stopped"} +
diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index a322be5abf..02380aa0c5 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -11,11 +11,13 @@ import { Robot, Hammer, FlowArrow, + HeadCircuit, + Plus, } 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 { defaultSkills } from "./skills"; import { DefaultBadge } from "./Badges/default"; import ImportedSkillList from "./Imported/SkillList"; import ImportedSkillConfig from "./Imported/ImportedSkillConfig"; @@ -181,8 +183,6 @@ export default function AdminAgents() { SelectedSkillComponent = ServerPanel; } else if (selectedSkill?.imported) { SelectedSkillComponent = ImportedSkillConfig; - } else if (configurableSkills[selectedSkill]) { - SelectedSkillComponent = configurableSkills[selectedSkill]?.component; } else { SelectedSkillComponent = defaultSkills[selectedSkill]?.component; } @@ -282,13 +282,6 @@ export default function AdminAgents() { (skill) => !disabledAgentSkills.includes(skill) )} /> - {/* Configurable skills */} -
@@ -393,14 +386,14 @@ export default function AdminAgents() { ) : ( // The selected skill is a configurable skill - show the configurable skill panel )} @@ -457,35 +450,33 @@ export default function AdminAgents() { {/* Skill settings nav - Make this section scrollable */}
-
+
- -

Agent Skills

+ +

Agent Skills

-
+
+
+

Default Skills

+
{/* Default skills list */} !disabledAgentSkills.includes(skill) - )} - /> - {/* Configurable skills */} - !disabledAgentSkills.includes(skill) + ), + ...agentSkills, + ]} />
- -

Custom Skills

+

Custom Skills

- -

Agent Flows

+

Agent Flows

- {agentFlows.length === 0 ? ( - - -

Create Flow

- - ) : ( - - -

Open Builder

- - )} + + +

Create

+
) : ( - <> - {defaultSkills?.[selectedSkill] ? ( - // The selected skill is a default skill - show the default skill panel - - ) : ( - // The selected skill is a configurable skill - show the configurable skill panel - - )} - + : agentSkills.includes( + defaultSkills[selectedSkill]?.skill + ) + } + setHasChanges={setHasChanges} + {...defaultSkills[selectedSkill]} + /> )} ) : ( @@ -621,7 +592,7 @@ function SkillLayout({ children, hasChanges, handleSubmit, handleCancel }) {
{children} -
- {Object.entries(skills).map(([skill, settings], index) => ( -
handleClick?.(skill)} - > -
{settings.title}
+
+ {Object.entries(skills).map(([skill, settings]) => ( +
handleClick?.(skill)} + className={` + relative + cursor-pointer + transition-all duration-300 + after:content-[''] + after:absolute + after:bottom-0 + after:left-4 + after:right-4 + after:h-[1px] + after:bg-theme-action-menu-bg + last:after:hidden + first:rounded-t-xl + last:rounded-b-xl + ${selectedSkill === skill ? "bg-white/10 light:bg-theme-bg-sidebar" : "hover:bg-theme-bg-primary"} + `} + > +
+
{settings.title}
- {isDefault ? ( + {settings.isDefault ? ( ) : (
@@ -682,17 +655,8 @@ function SkillList({ />
- ))} -
- {/* Tooltip for default skills - only render when skill list is passed isDefault */} - {isDefault && ( - - )} - +
+ ))} +
); } diff --git a/frontend/src/pages/Admin/Agents/skills.js b/frontend/src/pages/Admin/Agents/skills.js index 0b73cca675..0c1b312c62 100644 --- a/frontend/src/pages/Admin/Agents/skills.js +++ b/frontend/src/pages/Admin/Agents/skills.js @@ -42,9 +42,6 @@ export const defaultSkills = { image: ScrapeWebsitesImage, skill: "web-scraping", }, -}; - -export const configurableSkills = { "save-file-to-browser": { title: "Generate & save files to browser", description: From 1413449bf1c8ac7e055fa5d408e6b3c72574d812 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Fri, 30 May 2025 17:40:03 -0700 Subject: [PATCH 2/5] remove unused imports --- frontend/src/pages/Admin/Agents/MCPServers/index.jsx | 6 +----- frontend/src/pages/Admin/Agents/index.jsx | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/src/pages/Admin/Agents/MCPServers/index.jsx b/frontend/src/pages/Admin/Agents/MCPServers/index.jsx index 9effe97eea..58819de438 100644 --- a/frontend/src/pages/Admin/Agents/MCPServers/index.jsx +++ b/frontend/src/pages/Admin/Agents/MCPServers/index.jsx @@ -1,10 +1,6 @@ import { useState, useEffect } from "react"; import { titleCase } from "text-case"; -import { - BookOpenText, - ArrowClockwise, - ArrowsClockwise, -} from "@phosphor-icons/react"; +import { BookOpenText, ArrowsClockwise } from "@phosphor-icons/react"; import MCPLogo from "@/media/agents/mcp-logo.svg"; import MCPServers from "@/models/mcpServers"; import showToast from "@/utils/toast"; diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index 02380aa0c5..92f588fb01 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -9,7 +9,6 @@ import { CaretRight, Plug, Robot, - Hammer, FlowArrow, HeadCircuit, Plus, @@ -21,7 +20,6 @@ import { defaultSkills } from "./skills"; import { DefaultBadge } from "./Badges/default"; import ImportedSkillList from "./Imported/SkillList"; import ImportedSkillConfig from "./Imported/ImportedSkillConfig"; -import { Tooltip } from "react-tooltip"; import AgentFlowsList from "./AgentFlows"; import FlowPanel from "./AgentFlows/FlowPanel"; import { MCPServersList, MCPServerHeader } from "./MCPServers"; From ac2f566b60c6cd401510c147ae0018c8fb43e844 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Mon, 2 Jun 2025 18:11:23 -0700 Subject: [PATCH 3/5] toggle switch in row ui wip refactor save functionality --- .../pages/Admin/Agents/AgentFlows/index.jsx | 27 ++++++++-- .../Admin/Agents/Imported/SkillList/index.jsx | 18 ++++++- .../pages/Admin/Agents/MCPServers/index.jsx | 49 ++++++++++------- frontend/src/pages/Admin/Agents/index.jsx | 53 +++++++++++++++++-- 4 files changed, 119 insertions(+), 28 deletions(-) diff --git a/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx b/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx index a6099e2576..bc1a82768e 100644 --- a/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx +++ b/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx @@ -3,8 +3,10 @@ import { CaretRight } from "@phosphor-icons/react"; export default function AgentFlowsList({ flows = [], - selectedFlow, - handleClick, + selectedFlow = null, + handleClick = null, + activeFlowIds = [], + toggleFlow = () => {}, }) { if (flows.length === 0) { return ( @@ -48,8 +50,25 @@ export default function AgentFlowsList({
{flow.name}
-
- {flow.active ? "On" : "Off"} +
{ + e.stopPropagation(); + toggleFlow(flow.uuid); + }} + className="relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-300" + style={{ + backgroundColor: activeFlowIds.includes(flow.uuid) + ? "#32D583" + : "#CFCFD0", + }} + > +
{}, }) { if (skills.length === 0) return ( @@ -53,8 +54,21 @@ export default function ImportedSkillList({ {sentenceCase(config.name)}
-
- {config.active ? "On" : "Off"} +
{ + e.stopPropagation(); + toggleSkill(config); + }} + className="relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-300" + style={{ + backgroundColor: config.active ? "#32D583" : "#CFCFD0", + }} + > +
{}, + isLoading = false, }) { if (isLoading) { return (
-

Loading MCP Servers from configuration file...

- - Learn more about MCP Servers. - +

Loading MCP servers...

); } @@ -105,12 +102,12 @@ export function MCPServersList({ ); @@ -145,10 +142,26 @@ export function MCPServersList({
{ + e.stopPropagation(); + toggleServer(server.name); + }} + className="relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-300" + style={{ + backgroundColor: server.running ? "#32D583" : "#CFCFD0", + }} > - {server.running ? "On" : "Stopped"} +
+
diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index 92f588fb01..787a27a6d6 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -279,6 +279,8 @@ export default function AdminAgents() { activeSkills={Object.keys(defaultSkills).filter( (skill) => !disabledAgentSkills.includes(skill) )} + toggleDefaultSkill={toggleDefaultSkill} + toggleAgentSkill={toggleAgentSkill} />
@@ -289,6 +291,13 @@ export default function AdminAgents() { skills={importedSkills} selectedSkill={selectedSkill} handleClick={handleSkillClick} + toggleSkill={(skill) => { + const updatedSkills = importedSkills.map((s) => + s.hubId === skill.hubId ? { ...s, active: !s.active } : s + ); + setImportedSkills(updatedSkills); + setHasChanges(true); + }} />
@@ -299,6 +308,8 @@ export default function AdminAgents() { flows={agentFlows} selectedFlow={selectedFlow} handleClick={handleFlowClick} + activeFlowIds={activeFlowIds} + toggleFlow={toggleFlow} /> ); }} @@ -342,7 +354,7 @@ export default function AdminAgents() {
-
+
{SelectedSkillComponent ? ( <> @@ -455,7 +467,7 @@ export default function AdminAgents() {
-
+

Default Skills

@@ -471,6 +483,8 @@ export default function AdminAgents() { ), ...agentSkills, ]} + toggleDefaultSkill={toggleDefaultSkill} + toggleAgentSkill={toggleAgentSkill} />
@@ -480,6 +494,13 @@ export default function AdminAgents() { skills={importedSkills} selectedSkill={selectedSkill} handleClick={handleSkillClick} + toggleSkill={(skill) => { + const updatedSkills = importedSkills.map((s) => + s.hubId === skill.hubId ? { ...s, active: !s.active } : s + ); + setImportedSkills(updatedSkills); + setHasChanges(true); + }} />
@@ -498,6 +519,8 @@ export default function AdminAgents() { flows={agentFlows} selectedFlow={selectedFlow} handleClick={handleFlowClick} + activeFlowIds={activeFlowIds} + toggleFlow={toggleFlow} /> ); }} @@ -608,6 +632,8 @@ function SkillList({ selectedSkill = null, handleClick = null, activeSkills = [], + toggleDefaultSkill = () => {}, + toggleAgentSkill = () => {}, }) { if (skills.length === 0) return null; @@ -642,8 +668,27 @@ function SkillList({ {settings.isDefault ? ( ) : ( -
- {activeSkills.includes(skill) ? "On" : "Off"} +
{ + e.stopPropagation(); + settings.isDefault + ? toggleDefaultSkill(skill) + : toggleAgentSkill(skill); + }} + className="relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-300" + style={{ + backgroundColor: activeSkills.includes(skill) + ? "#32D583" + : "#CFCFD0", + }} + > +
)} Date: Tue, 3 Jun 2025 18:24:09 -0700 Subject: [PATCH 4/5] wip another attempt simplify agent skills page/backend --- .../pages/Admin/Agents/SkillLayout/index.jsx | 30 ++ .../pages/Admin/Agents/SkillList/index.jsx | 72 ++++ frontend/src/pages/Admin/Agents/index.jsx | 380 +++++++++--------- server/models/systemSettings.js | 9 - 4 files changed, 296 insertions(+), 195 deletions(-) create mode 100644 frontend/src/pages/Admin/Agents/SkillLayout/index.jsx create mode 100644 frontend/src/pages/Admin/Agents/SkillList/index.jsx diff --git a/frontend/src/pages/Admin/Agents/SkillLayout/index.jsx b/frontend/src/pages/Admin/Agents/SkillLayout/index.jsx new file mode 100644 index 0000000000..0a21680240 --- /dev/null +++ b/frontend/src/pages/Admin/Agents/SkillLayout/index.jsx @@ -0,0 +1,30 @@ +import { isMobile } from "react-device-detect"; +import Sidebar from "@/components/SettingsSidebar"; +import ContextualSaveBar from "@/components/ContextualSaveBar"; + +export default function SkillLayout({ + children, + hasChanges, + handleSubmit, + handleCancel, +}) { + return ( +
+ +
+ {children} + +
+
+ ); +} diff --git a/frontend/src/pages/Admin/Agents/SkillList/index.jsx b/frontend/src/pages/Admin/Agents/SkillList/index.jsx new file mode 100644 index 0000000000..730be1571a --- /dev/null +++ b/frontend/src/pages/Admin/Agents/SkillList/index.jsx @@ -0,0 +1,72 @@ +import { CaretRight } from "@phosphor-icons/react"; +import { isMobile } from "react-device-detect"; + +export default function SkillList({ + skills = [], + selectedSkill = null, + handleClick = null, + activeSkills = [], + toggleSkill = () => {}, +}) { + if (skills.length === 0) return null; + + return ( +
+ {Object.entries(skills).map(([skill, settings]) => ( +
handleClick?.(skill)} + className={` + relative + cursor-pointer + transition-all duration-300 + after:content-[''] + after:absolute + after:bottom-0 + after:left-4 + after:right-4 + after:h-[1px] + after:bg-theme-action-menu-bg + last:after:hidden + first:rounded-t-xl + last:rounded-b-xl + ${selectedSkill === skill ? "bg-white/10 light:bg-theme-bg-sidebar" : "hover:bg-theme-bg-primary"} + `} + > +
+
{settings.title}
+
+
{ + e.stopPropagation(); + toggleSkill(skill); + }} + className="relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-300" + style={{ + backgroundColor: activeSkills.includes(skill) + ? "#32D583" + : "#CFCFD0", + }} + > + +
+ +
+
+
+ ))} +
+ ); +} diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index 787a27a6d6..49222c7ce0 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -1,23 +1,19 @@ 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 { CaretLeft, - CaretRight, Plug, Robot, FlowArrow, HeadCircuit, Plus, } from "@phosphor-icons/react"; -import ContextualSaveBar from "@/components/ContextualSaveBar"; import { castToType } from "@/utils/types"; import { FullScreenLoader } from "@/components/Preloader"; import { defaultSkills } from "./skills"; -import { DefaultBadge } from "./Badges/default"; import ImportedSkillList from "./Imported/SkillList"; import ImportedSkillConfig from "./Imported/ImportedSkillConfig"; import AgentFlowsList from "./AgentFlows"; @@ -27,6 +23,8 @@ import ServerPanel from "./MCPServers/ServerPanel"; import { Link } from "react-router-dom"; import paths from "@/utils/paths"; import AgentFlows from "@/models/agentFlows"; +import SkillList from "./SkillList"; +import SkillLayout from "./SkillLayout"; export default function AdminAgents() { const formEl = useRef(null); @@ -67,14 +65,12 @@ export default function AdminAgents() { const _settings = await System.keys(); const _preferences = await Admin.systemPreferencesByFields([ "disabled_agent_skills", - "default_agent_skills", "imported_agent_skills", "active_agent_flows", ]); const { flows = [] } = await AgentFlows.listFlows(); setSettings({ ..._settings, preferences: _preferences.settings } ?? {}); - setAgentSkills(_preferences.settings?.default_agent_skills ?? []); setDisabledAgentSkills( _preferences.settings?.disabled_agent_skills ?? [] ); @@ -86,42 +82,71 @@ export default function AdminAgents() { fetchSettings(); }, []); - const toggleDefaultSkill = (skillName) => { - setDisabledAgentSkills((prev) => { - const updatedSkills = prev.includes(skillName) - ? prev.filter((name) => name !== skillName) - : [...prev, skillName]; - setHasChanges(true); - return updatedSkills; + const toggleFlow = async (flowId) => { + const updatedFlowIds = activeFlowIds.includes(flowId) + ? activeFlowIds.filter((id) => id !== flowId) + : [...activeFlowIds, flowId]; + + const { success, error } = await Admin.updateSystemPreferences({ + active_agent_flows: updatedFlowIds.join(","), }); + + if (success) { + setActiveFlowIds(updatedFlowIds); + showToast(`Flow status updated successfully.`, "success", { + clear: true, + }); + } else { + showToast(`Failed to update flow status: ${error}`, "error", { + clear: true, + }); + } }; - const toggleAgentSkill = (skillName) => { - setAgentSkills((prev) => { - const updatedSkills = prev.includes(skillName) - ? prev.filter((name) => name !== skillName) - : [...prev, skillName]; - setHasChanges(true); - return updatedSkills; - }); + const toggleMCP = async (server) => { + try { + const { success, error } = await Admin.updateSystemPreferences({ + mcp_servers: JSON.stringify( + mcpServers.map((s) => + s.name === server.name ? { ...s, active: !s.active } : s + ) + ), + }); + + if (!success) throw new Error(error); + setMcpServers((prev) => + prev.map((s) => + s.name === server.name ? { ...s, active: !s.active } : s + ) + ); + showToast(`MCP server status updated successfully.`, "success", { + clear: true, + }); + } catch (error) { + console.error("Failed to toggle MCP server:", error); + showToast(`Failed to update MCP server status: ${error}`, "error", { + clear: true, + }); + } }; - const toggleFlow = (flowId) => { - setActiveFlowIds((prev) => { - const updatedFlows = prev.includes(flowId) - ? prev.filter((id) => id !== flowId) - : [...prev, flowId]; - return updatedFlows; + const toggleSkill = async (skillName) => { + const updatedSkills = disabledAgentSkills.includes(skillName) + ? disabledAgentSkills.filter((name) => name !== skillName) + : [...disabledAgentSkills, skillName]; + + const { success, error } = await Admin.updateSystemPreferences({ + disabled_agent_skills: updatedSkills.join(","), }); - }; - const toggleMCP = (serverName) => { - setMcpServers((prev) => { - return prev.map((server) => { - if (server.name !== serverName) return server; - return { ...server, running: !server.running }; + if (success) { + setDisabledAgentSkills(updatedSkills); + showToast(`Skill ${skillName} updated successfully.`, "success", { + clear: true, }); - }); + } else { + showToast(`Failed to update skill: ${error}`, "error", { clear: true }); + } }; const handleSubmit = async (e) => { @@ -155,15 +180,15 @@ export default function AdminAgents() { const _settings = await System.keys(); const _preferences = await Admin.systemPreferencesByFields([ "disabled_agent_skills", - "default_agent_skills", "imported_agent_skills", ]); setSettings({ ..._settings, preferences: _preferences.settings } ?? {}); - setAgentSkills(_preferences.settings?.default_agent_skills ?? []); setDisabledAgentSkills( _preferences.settings?.disabled_agent_skills ?? [] ); setImportedSkills(_preferences.settings?.imported_agent_skills ?? []); + setActiveFlowIds(_preferences.settings?.active_agent_flows ?? []); + setAgentFlows(flows); showToast(`Agent preferences saved successfully.`, "success", { clear: true, }); @@ -227,6 +252,27 @@ export default function AdminAgents() { ); }; + const handleImportedSkillToggle = async (skill) => { + const updatedSkills = importedSkills.map((s) => + s.hubId === skill.hubId ? { ...s, active: !s.active } : s + ); + + const { success, error } = await Admin.updateSystemPreferences({ + imported_agent_skills: JSON.stringify(updatedSkills), + }); + + if (success) { + setImportedSkills(updatedSkills); + showToast(`Imported skill updated successfully.`, "success", { + clear: true, + }); + } else { + showToast(`Failed to update imported skill: ${error}`, "error", { + clear: true, + }); + } + }; + if (loading) { return (
- !disabledAgentSkills.includes(skill) - )} - toggleDefaultSkill={toggleDefaultSkill} - toggleAgentSkill={toggleAgentSkill} + activeSkills={[ + ...Object.keys(defaultSkills).filter( + (skill) => !disabledAgentSkills.includes(skill) + ), + ]} + toggleSkill={toggleSkill} />
@@ -291,13 +333,7 @@ export default function AdminAgents() { skills={importedSkills} selectedSkill={selectedSkill} handleClick={handleSkillClick} - toggleSkill={(skill) => { - const updatedSkills = importedSkills.map((s) => - s.hubId === skill.hubId ? { ...s, active: !s.active } : s - ); - setImportedSkills(updatedSkills); - setHasChanges(true); - }} + toggleSkill={handleImportedSkillToggle} />
@@ -384,7 +420,7 @@ export default function AdminAgents() { @@ -435,17 +473,10 @@ export default function AdminAgents() { >
- !selectedSkill?.imported && !selectedFlow && setHasChanges(true) - } + onChange={() => !selectedFlow && setHasChanges(true)} ref={formEl} className="flex-1 flex gap-x-6 p-4 mt-10" > - !disabledAgentSkills.includes(skill) ), - ...agentSkills, ]} - toggleDefaultSkill={toggleDefaultSkill} - toggleAgentSkill={toggleAgentSkill} + toggleSkill={toggleSkill} />
@@ -494,13 +523,7 @@ export default function AdminAgents() { skills={importedSkills} selectedSkill={selectedSkill} handleClick={handleSkillClick} - toggleSkill={(skill) => { - const updatedSkills = importedSkills.map((s) => - s.hubId === skill.hubId ? { ...s, active: !s.active } : s - ); - setImportedSkills(updatedSkills); - setHasChanges(true); - }} + toggleSkill={handleImportedSkillToggle} />
@@ -571,19 +594,11 @@ export default function AdminAgents() { - -
- {children} - -
-
- ); -} - -function SkillList({ - skills = [], - selectedSkill = null, - handleClick = null, - activeSkills = [], - toggleDefaultSkill = () => {}, - toggleAgentSkill = () => {}, -}) { - if (skills.length === 0) return null; - - return ( -
- {Object.entries(skills).map(([skill, settings]) => ( -
handleClick?.(skill)} - className={` - relative - cursor-pointer - transition-all duration-300 - after:content-[''] - after:absolute - after:bottom-0 - after:left-4 - after:right-4 - after:h-[1px] - after:bg-theme-action-menu-bg - last:after:hidden - first:rounded-t-xl - last:rounded-b-xl - ${selectedSkill === skill ? "bg-white/10 light:bg-theme-bg-sidebar" : "hover:bg-theme-bg-primary"} - `} - > -
-
{settings.title}
-
- {settings.isDefault ? ( - - ) : ( -
{ - e.stopPropagation(); - settings.isDefault - ? toggleDefaultSkill(skill) - : toggleAgentSkill(skill); - }} - className="relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-300" - style={{ - backgroundColor: activeSkills.includes(skill) - ? "#32D583" - : "#CFCFD0", - }} - > - -
- )} - -
-
-
- ))} -
- ); -} +// function SkillLayout({ children, hasChanges, handleSubmit, handleCancel }) { +// return ( +//
+// +//
+// {children} +// +//
+//
+// ); +// } + +// function SkillList({ +// skills = [], +// selectedSkill = null, +// handleClick = null, +// activeSkills = [], +// toggleSkill = () => {}, +// }) { +// if (skills.length === 0) return null; + +// return ( +//
+// {Object.entries(skills).map(([skill, settings]) => ( +//
handleClick?.(skill)} +// className={` +// relative +// cursor-pointer +// transition-all duration-300 +// after:content-[''] +// after:absolute +// after:bottom-0 +// after:left-4 +// after:right-4 +// after:h-[1px] +// after:bg-theme-action-menu-bg +// last:after:hidden +// first:rounded-t-xl +// last:rounded-b-xl +// ${selectedSkill === skill ? "bg-white/10 light:bg-theme-bg-sidebar" : "hover:bg-theme-bg-primary"} +// `} +// > +//
+//
{settings.title}
+//
+//
{ +// e.stopPropagation(); +// toggleSkill(skill); +// }} +// className="relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-300" +// style={{ +// backgroundColor: activeSkills.includes(skill) +// ? "#32D583" +// : "#CFCFD0", +// }} +// > +// +//
+// +//
+//
+//
+// ))} +//
+// ); +// } diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 89e397ea64..8582dad8f4 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -119,15 +119,6 @@ const SystemSettings = { return null; } }, - default_agent_skills: (updates) => { - try { - const skills = updates.split(",").filter((skill) => !!skill); - return JSON.stringify(skills); - } catch (e) { - console.error(`Could not validate agent skills.`); - return JSON.stringify([]); - } - }, disabled_agent_skills: (updates) => { try { const skills = updates.split(",").filter((skill) => !!skill); From 2ca2a1267aa34b719d5cf19856934ee4c2900dda Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Wed, 4 Jun 2025 12:46:06 -0700 Subject: [PATCH 5/5] update ui for sql connector --- .../NewConnectionForm.jsx | 264 +++++++++++++++++ .../NewConnectionModal.jsx | 274 ------------------ .../Agents/SQLConnectorSelection/index.jsx | 182 ++++++------ 3 files changed, 354 insertions(+), 366 deletions(-) create mode 100644 frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionForm.jsx delete mode 100644 frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionForm.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionForm.jsx new file mode 100644 index 0000000000..517d94f403 --- /dev/null +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionForm.jsx @@ -0,0 +1,264 @@ +import { useState } from "react"; +import { WarningOctagon } from "@phosphor-icons/react"; +import { DB_LOGOS } from "./DBConnection"; + +function assembleConnectionString({ + engine, + username = "", + password = "", + host = "", + port = "", + database = "", +}) { + if (!username || !password || !host || !database) { + 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: "", + password: "", + host: "", + port: "", + database: "", +}; + +export default function NewSQLConnection({ onSubmit, onCancel }) { + const [engine, setEngine] = useState(DEFAULT_ENGINE); + const [config, setConfig] = useState(DEFAULT_CONFIG); + + function handleClose() { + setEngine(DEFAULT_ENGINE); + setConfig(DEFAULT_CONFIG); + onCancel(); + } + + function onFormChange(e) { + const form = e.target.form; + if (!form) return; + + const formData = new FormData(form); + setConfig({ + username: formData.get("username")?.trim() || "", + password: formData.get("password") || "", + host: formData.get("host")?.trim() || "", + port: formData.get("port")?.trim() || "", + database: formData.get("database")?.trim() || "", + }); + } + + function handleSubmit(e) { + e.preventDefault(); + if ( + !config.username || + !config.password || + !config.host || + !config.database + ) { + return; + } + const form = e.target; + const formData = new FormData(form); + onSubmit({ + engine, + database_id: formData.get("name"), + connectionString: assembleConnectionString({ engine, ...config }), + }); + handleClose(); + } + + return ( +
+

+ New SQL Connection +

+ +
+

+ Add the connection information for your database below and it will + be available for future SQL agent calls. +

+
+
+ +

+ WARNING: The SQL agent has been instructed to only + perform non-modifying queries. This does not prevent a + hallucination from still deleting data. Only connect with a user + who has READ_ONLY permissions. +

+
+ + +
+ setEngine("postgresql")} + /> + setEngine("mysql")} + /> + setEngine("sql-server")} + /> +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+

+ {assembleConnectionString({ engine, ...config })} +

+
+
+ + +
+ +
+ ); +} + +function DBEngine({ provider, active, onClick }) { + return ( + + ); +} diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx deleted file mode 100644 index 1d781894ce..0000000000 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx +++ /dev/null @@ -1,274 +0,0 @@ -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( - -
-
-
-
-

- New SQL Connection -

-
- -
-
-
-
-

- Add the connection information for your database below and it - will be available for future SQL agent calls. -

-
-
- -

- WARNING: The SQL agent has been instructed{" "} - to only perform non-modifying queries. This{" "} - does not prevent a hallucination from still - deleting data. Only connect with a user who has{" "} - READ_ONLY permissions. -

-
- - -
- setEngine("postgresql")} - /> - setEngine("mysql")} - /> - setEngine("sql-server")} - /> -
-
- -
- - -
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
- - -
-

- {assembleConnectionString({ engine, ...config })} -

-
-
-
- - -
-
-
-
-
, - document.getElementById("workspace-agent-settings-container") - ); -} - -function DBEngine({ provider, active, onClick }) { - return ( - - ); -} diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx index 515c3c897c..cc3c786d09 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx @@ -1,19 +1,16 @@ import React, { useEffect, useState } from "react"; import DBConnection from "./DBConnection"; -import { Plus, Database } from "@phosphor-icons/react"; -import NewSQLConnection from "./NewConnectionModal"; -import { useModal } from "@/hooks/useModal"; -import SQLAgentImage from "@/media/agents/sql-agent.png"; +import { Plus } from "@phosphor-icons/react"; +import NewSQLConnection from "./NewConnectionForm"; import Admin from "@/models/admin"; export default function AgentSQLConnectorSelection({ skill, - settings, // unused. toggleSkill, enabled = false, setHasChanges, }) { - const { isOpen, openModal, closeModal } = useModal(); + const [showNewConnection, setShowNewConnection] = useState(false); const [connections, setConnections] = useState([]); useEffect(() => { Admin.systemPreferencesByFields(["agent_sql_connections"]) @@ -22,108 +19,109 @@ export default function AgentSQLConnectorSelection({ }, []); return ( - <> -
-
-
- +
+
+ +
- SQL Agent -

- Enable your agent to be able to leverage SQL to answer you questions - by connecting to various SQL database providers. -

- {enabled && ( - <> - - conn.action !== "remove") - )} - /> -
+
+ + +
+

+ 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 + My connections

-
- {connections - .filter((connection) => connection.action !== "remove") - .map((connection) => ( - { - setHasChanges(true); - setConnections((prev) => - prev.map((conn) => { - if (conn.database_id === databaseId) - return { ...conn, action: "remove" }; - return conn; - }) - ); - }} - /> - ))} + {connections.length === 0 && ( +

+ You currently have no connections +

+ )} +
+
+ {connections + .filter((connection) => connection.action !== "remove") + .map((connection) => ( + { + setHasChanges(true); + setConnections((prev) => + prev.map((conn) => { + if (conn.database_id === databaseId) + return { ...conn, action: "remove" }; + return conn; + }) + ); + }} + /> + ))} + {!showNewConnection ? ( -
+ ) : ( + { + setConnections((prev) => [ + ...prev, + { action: "add", ...newDb }, + ]); + setShowNewConnection(false); + }} + onCancel={() => setShowNewConnection(false)} + /> + )}
- - )} -
+
+ + )}
- - setConnections((prev) => [...prev, { action: "add", ...newDb }]) - } - /> - +
); }