[FEAT] Agent skills UI redesign (#1565)
* WIP agent settings redesign * WIP rework new agent skill UI * WIP save bar/agent styles * WIP update settings fix * desktop agent config UI implementation * remove unneeded files * fix sql and web browsing plugins not starting & add default badges * fix serply merge conflict * review: cleanup unused files/folders/components * refactor components * refactor components * fix order of customized skills --------- Co-authored-by: timothycarambat <rambat1010@gmail.com>
@ -4,7 +4,9 @@
|
|||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,6 +21,7 @@ const AdminInvites = lazy(() => import("@/pages/Admin/Invitations"));
|
|||||||
const AdminWorkspaces = lazy(() => import("@/pages/Admin/Workspaces"));
|
const AdminWorkspaces = lazy(() => import("@/pages/Admin/Workspaces"));
|
||||||
const AdminSystem = lazy(() => import("@/pages/Admin/System"));
|
const AdminSystem = lazy(() => import("@/pages/Admin/System"));
|
||||||
const AdminLogs = lazy(() => import("@/pages/Admin/Logging"));
|
const AdminLogs = lazy(() => import("@/pages/Admin/Logging"));
|
||||||
|
const AdminAgents = lazy(() => import("@/pages/Admin/Agents"));
|
||||||
const GeneralChats = lazy(() => import("@/pages/GeneralSettings/Chats"));
|
const GeneralChats = lazy(() => import("@/pages/GeneralSettings/Chats"));
|
||||||
const GeneralAppearance = lazy(
|
const GeneralAppearance = lazy(
|
||||||
() => import("@/pages/GeneralSettings/Appearance")
|
() => import("@/pages/GeneralSettings/Appearance")
|
||||||
@ -106,6 +107,10 @@ export default function App() {
|
|||||||
path="/settings/vector-database"
|
path="/settings/vector-database"
|
||||||
element={<AdminRoute Component={GeneralVectorDatabase} />}
|
element={<AdminRoute Component={GeneralVectorDatabase} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/settings/agents"
|
||||||
|
element={<AdminRoute Component={AdminAgents} />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/settings/event-logs"
|
path="/settings/event-logs"
|
||||||
element={<AdminRoute Component={AdminLogs} />}
|
element={<AdminRoute Component={AdminLogs} />}
|
||||||
|
32
frontend/src/components/ContextualSaveBar/index.jsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Warning } from "@phosphor-icons/react";
|
||||||
|
|
||||||
|
export default function ContextualSaveBar({
|
||||||
|
showing = false,
|
||||||
|
onSave,
|
||||||
|
onCancel,
|
||||||
|
}) {
|
||||||
|
if (!showing) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-0 left-0 right-0 h-14 bg-[#18181B] flex items-center justify-end px-4 z-[9999]">
|
||||||
|
<div className="absolute left-1/2 transform -translate-x-1/2 flex items-center gap-x-2">
|
||||||
|
<Warning size={18} className="text-white" />
|
||||||
|
<p className="text-white font-medium text-xs">Unsaved Changes</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
<button
|
||||||
|
className="border-none text-white font-medium text-sm px-[10px] py-[6px] rounded-md bg-white/5 hover:bg-white/10"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="border-none text-[#222628] font-medium text-sm px-[10px] py-[6px] rounded-md bg-[#46C8FF] hover:bg-[#3DB5E8]"
|
||||||
|
onClick={onSave}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -22,6 +22,7 @@ import {
|
|||||||
EyeSlash,
|
EyeSlash,
|
||||||
SplitVertical,
|
SplitVertical,
|
||||||
Microphone,
|
Microphone,
|
||||||
|
Robot,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import useUser from "@/hooks/useUser";
|
import useUser from "@/hooks/useUser";
|
||||||
import { USER_BACKGROUND_COLOR } from "@/utils/constants";
|
import { USER_BACKGROUND_COLOR } from "@/utils/constants";
|
||||||
@ -258,6 +259,15 @@ const SidebarOptions = ({ user = null }) => (
|
|||||||
flex={true}
|
flex={true}
|
||||||
allowedRole={["admin", "manager"]}
|
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
|
<Option
|
||||||
href={paths.settings.appearance()}
|
href={paths.settings.appearance()}
|
||||||
btnText="Appearance"
|
btnText="Appearance"
|
||||||
|
BIN
frontend/src/media/agents/generate-charts.png
Normal file
After Width: | Height: | Size: 169 KiB |
BIN
frontend/src/media/agents/generate-save-files.png
Normal file
After Width: | Height: | Size: 172 KiB |
BIN
frontend/src/media/agents/rag-memory.png
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
frontend/src/media/agents/scrape-websites.png
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
frontend/src/media/agents/sql-agent.png
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
frontend/src/media/agents/view-summarize.png
Normal file
After Width: | Height: | Size: 170 KiB |
25
frontend/src/pages/Admin/Agents/Badges/default.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Tooltip } from "react-tooltip";
|
||||||
|
|
||||||
|
export function DefaultBadge({ title }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className="w-fit"
|
||||||
|
data-tooltip-id={`default-skill-${title}`}
|
||||||
|
data-tooltip-content="This skill is enabled by default and cannot be turned off."
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-x-1 w-fit rounded-full bg-[#F4FFD0]/10 px-2.5 py-0.5 text-sm font-medium text-sky-400 shadow-sm cursor-pointer">
|
||||||
|
<div className="text-[#F4FFD0] text-[12px] leading-[15px]">
|
||||||
|
Default
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<Tooltip
|
||||||
|
id={`default-skill-${title}`}
|
||||||
|
place="bottom"
|
||||||
|
delayShow={300}
|
||||||
|
className="tooltip !text-xs"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
29
frontend/src/pages/Admin/Agents/DefaultSkillPanel/index.jsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { DefaultBadge } from "../Badges/default";
|
||||||
|
|
||||||
|
export default function DefaultSkillPanel({ title, description, image, icon }) {
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
|
<div className="flex w-full justify-between items-center">
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
{icon &&
|
||||||
|
React.createElement(icon, {
|
||||||
|
size: 24,
|
||||||
|
color: "white",
|
||||||
|
weight: "bold",
|
||||||
|
})}
|
||||||
|
<label htmlFor="name" className="text-white text-md font-bold">
|
||||||
|
{title}
|
||||||
|
</label>
|
||||||
|
<DefaultBadge title={title} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img src={image} alt={title} className="w-full rounded-md" />
|
||||||
|
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,21 +1,30 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
export default function GenericSkill({
|
|
||||||
|
export default function GenericSkillPanel({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
skill,
|
skill,
|
||||||
toggleSkill,
|
toggleSkill,
|
||||||
enabled = false,
|
enabled = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
image,
|
||||||
|
icon,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-white/40 pb-4">
|
<div className="p-2">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
<div className="flex w-full justify-between items-center">
|
<div className="flex items-center gap-x-2">
|
||||||
<label htmlFor="name" className="block input-label">
|
{icon &&
|
||||||
|
React.createElement(icon, {
|
||||||
|
size: 24,
|
||||||
|
color: "white",
|
||||||
|
weight: "bold",
|
||||||
|
})}
|
||||||
|
<label htmlFor="name" className="text-white text-md font-bold">
|
||||||
{title}
|
{title}
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
className={`border-none relative inline-flex items-center mt-2 ${
|
className={`border-none relative inline-flex items-center ml-auto ${
|
||||||
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -24,12 +33,13 @@ export default function GenericSkill({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="peer sr-only"
|
className="peer sr-only"
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
onClick={() => toggleSkill(skill)}
|
onChange={() => 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>
|
<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>
|
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<img src={image} alt={title} className="w-full rounded-md" />
|
||||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
@ -74,8 +74,8 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
|
|||||||
// to the parent container form so we don't have nested forms.
|
// to the parent container form so we don't have nested forms.
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<ModalWrapper isOpen={isOpen}>
|
<ModalWrapper isOpen={isOpen}>
|
||||||
<div className="relative w-1/3 max-h-full ">
|
<div className="relative w-1/3 max-h-full mt-8">
|
||||||
<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="relative bg-main-gradient rounded-xl shadow-[0_4px_14px_rgba(0,0,0,0.25)] max-h-[85vh] overflow-y-scroll no-scroll">
|
||||||
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
|
<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">
|
<h3 className="text-xl font-semibold text-white">
|
||||||
New SQL Connection
|
New SQL Connection
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
117
frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import React, { 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";
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<div className="p-2">
|
||||||
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
<Database size={24} color="white" weight="bold" />
|
||||||
|
<label htmlFor="name" className="text-white text-md font-bold">
|
||||||
|
SQL Agent
|
||||||
|
</label>
|
||||||
|
<label className="border-none relative inline-flex cursor-pointer items-center ml-auto">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="peer sr-only"
|
||||||
|
checked={enabled}
|
||||||
|
onChange={() => 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>
|
||||||
|
<img
|
||||||
|
src={SQLAgentImage}
|
||||||
|
alt="SQL Agent"
|
||||||
|
className="w-full rounded-md"
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
{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) => {
|
||||||
|
setHasChanges(true);
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
<NewSQLConnection
|
||||||
|
isOpen={isOpen}
|
||||||
|
closeModal={closeModal}
|
||||||
|
onSubmit={(newDb) =>
|
||||||
|
setConnections((prev) => [...prev, { action: "add", ...newDb }])
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
227
frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
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,
|
||||||
|
ListMagnifyingGlass,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
import SearchProviderItem from "./SearchProviderItem";
|
||||||
|
import WebSearchImage from "@/media/agents/scrape-websites.png";
|
||||||
|
import {
|
||||||
|
SerperDotDevOptions,
|
||||||
|
GoogleSearchOptions,
|
||||||
|
BingSearchOptions,
|
||||||
|
SerplySearchOptions,
|
||||||
|
} 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.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Serply.io",
|
||||||
|
value: "serply-engine",
|
||||||
|
logo: SerplySearchIcon,
|
||||||
|
options: (settings) => <SerplySearchOptions settings={settings} />,
|
||||||
|
description:
|
||||||
|
"Serply.io web-search. Free account with a 100 calls/month forever.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function AgentWebSearchSelection({
|
||||||
|
skill,
|
||||||
|
settings,
|
||||||
|
toggleSkill,
|
||||||
|
enabled = false,
|
||||||
|
setHasChanges,
|
||||||
|
}) {
|
||||||
|
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);
|
||||||
|
setHasChanges(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
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="p-2">
|
||||||
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
<ListMagnifyingGlass size={24} color="white" weight="bold" />
|
||||||
|
<label htmlFor="name" className="text-white text-md font-bold">
|
||||||
|
Live web search and browsing
|
||||||
|
</label>
|
||||||
|
<label className="border-none relative inline-flex cursor-pointer items-center ml-auto">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="peer sr-only"
|
||||||
|
checked={enabled}
|
||||||
|
onChange={() => 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>
|
||||||
|
<img
|
||||||
|
src={WebSearchImage}
|
||||||
|
alt="Web Search"
|
||||||
|
className="w-full rounded-md"
|
||||||
|
/>
|
||||||
|
<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. Web search during agent
|
||||||
|
sessions will not work until this is set up.
|
||||||
|
</p>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
232
frontend/src/pages/Admin/Agents/index.jsx
Normal file
@ -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 (
|
||||||
|
<div
|
||||||
|
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
|
||||||
|
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] w-full h-full flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<FullScreenLoader />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="workspace-agent-settings-container"
|
||||||
|
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] w-full h-full flex"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
onChange={() => setHasChanges(true)}
|
||||||
|
ref={formEl}
|
||||||
|
className="flex-1 flex gap-x-6 p-4 mt-10"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
name="system::default_agent_skills"
|
||||||
|
type="hidden"
|
||||||
|
value={agentSkills.join(",")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Skill settings nav */}
|
||||||
|
<div className="flex flex-col gap-y-[18px]">
|
||||||
|
<div className="text-white flex items-center gap-x-2">
|
||||||
|
<Robot size={24} />
|
||||||
|
<p className="text-lg font-medium">Agent Skills</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Default skills list */}
|
||||||
|
<SkillList
|
||||||
|
isDefault={true}
|
||||||
|
skills={defaultSkills}
|
||||||
|
selectedSkill={selectedSkill}
|
||||||
|
handleClick={setSelectedSkill}
|
||||||
|
/>
|
||||||
|
{/* Configurable skills */}
|
||||||
|
<SkillList
|
||||||
|
skills={configurableSkills}
|
||||||
|
selectedSkill={selectedSkill}
|
||||||
|
handleClick={setSelectedSkill}
|
||||||
|
activeSkills={agentSkills}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Selected agent skill setting panel */}
|
||||||
|
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
|
||||||
|
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
|
||||||
|
{SelectedSkillComponent ? (
|
||||||
|
<SelectedSkillComponent
|
||||||
|
skill={configurableSkills[selectedSkill]?.skill}
|
||||||
|
settings={settings}
|
||||||
|
toggleSkill={toggleAgentSkill}
|
||||||
|
enabled={agentSkills.includes(
|
||||||
|
configurableSkills[selectedSkill]?.skill
|
||||||
|
)}
|
||||||
|
setHasChanges={setHasChanges}
|
||||||
|
{...(configurableSkills[selectedSkill] ||
|
||||||
|
defaultSkills[selectedSkill])}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full text-white/60">
|
||||||
|
<Robot size={40} />
|
||||||
|
<p className="font-medium">Select an agent skill</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<ContextualSaveBar
|
||||||
|
showing={hasChanges}
|
||||||
|
onSave={handleSubmit}
|
||||||
|
onCancel={() => setHasChanges(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SkillList({
|
||||||
|
isDefault = false,
|
||||||
|
skills = [],
|
||||||
|
selectedSkill = null,
|
||||||
|
handleClick = null,
|
||||||
|
activeSkills = [],
|
||||||
|
}) {
|
||||||
|
if (skills.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white/5 text-white min-w-[360px] w-fit rounded-xl">
|
||||||
|
{Object.entries(skills).map(([skill, settings], index) => (
|
||||||
|
<div
|
||||||
|
key={skill}
|
||||||
|
className={`py-3 px-4 flex items-center justify-between ${
|
||||||
|
index === 0 ? "rounded-t-xl" : ""
|
||||||
|
} ${
|
||||||
|
index === Object.keys(skills).length - 1
|
||||||
|
? "rounded-b-xl"
|
||||||
|
: "border-b border-white/10"
|
||||||
|
} cursor-pointer transition-all duration-300 hover:bg-white/5 ${
|
||||||
|
selectedSkill === skill ? "bg-white/10" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => handleClick?.(skill)}
|
||||||
|
>
|
||||||
|
<div className="text-sm font-light">{settings.title}</div>
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
{isDefault ? (
|
||||||
|
<DefaultBadge title={skill} />
|
||||||
|
) : (
|
||||||
|
<div className="text-sm text-white/60 font-medium">
|
||||||
|
{activeSkills.includes(skill) ? "On" : "Off"}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<CaretRight size={14} weight="bold" className="text-white/80" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
73
frontend/src/pages/Admin/Agents/skills.js
Normal file
@ -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",
|
||||||
|
},
|
||||||
|
};
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<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;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
setHasChanges={setHasChanges}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<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 }])
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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: () => <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.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Serply.io",
|
|
||||||
value: "serply-engine",
|
|
||||||
logo: SerplySearchIcon,
|
|
||||||
options: (settings) => <SerplySearchOptions settings={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 (
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -4,27 +4,25 @@ import showToast from "@/utils/toast";
|
|||||||
import { castToType } from "@/utils/types";
|
import { castToType } from "@/utils/types";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import AgentLLMSelection from "./AgentLLMSelection";
|
import AgentLLMSelection from "./AgentLLMSelection";
|
||||||
import AgentWebSearchSelection from "./WebSearchSelection";
|
|
||||||
import AgentSQLConnectorSelection from "./SQLConnectorSelection";
|
|
||||||
import GenericSkill from "./GenericSkill";
|
|
||||||
import Admin from "@/models/admin";
|
import Admin from "@/models/admin";
|
||||||
import * as Skeleton from "react-loading-skeleton";
|
import * as Skeleton from "react-loading-skeleton";
|
||||||
import "react-loading-skeleton/dist/skeleton.css";
|
import "react-loading-skeleton/dist/skeleton.css";
|
||||||
|
import paths from "@/utils/paths";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function WorkspaceAgentConfiguration({ workspace }) {
|
export default function WorkspaceAgentConfiguration({ workspace }) {
|
||||||
const [settings, setSettings] = useState({});
|
const [settings, setSettings] = useState({});
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [agentSkills, setAgentSkills] = useState([]);
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const formEl = useRef(null);
|
const formEl = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchSettings() {
|
async function fetchSettings() {
|
||||||
const _settings = await System.keys();
|
const _settings = await System.keys();
|
||||||
const _preferences = await Admin.systemPreferences();
|
const _preferences = await Admin.systemPreferences();
|
||||||
setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
|
setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
|
||||||
setAgentSkills(_preferences.settings?.default_agent_skills ?? []);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
fetchSettings();
|
fetchSettings();
|
||||||
@ -73,14 +71,6 @@ export default function WorkspaceAgentConfiguration({ workspace }) {
|
|||||||
setHasChanges(false);
|
setHasChanges(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
function toggleAgentSkill(skillName = "") {
|
|
||||||
setAgentSkills((prev) => {
|
|
||||||
return prev.includes(skillName)
|
|
||||||
? prev.filter((name) => name !== skillName)
|
|
||||||
: [...prev, skillName];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!workspace || loading) return <LoadingSkeleton />;
|
if (!workspace || loading) return <LoadingSkeleton />;
|
||||||
return (
|
return (
|
||||||
<div id="workspace-agent-settings-container">
|
<div id="workspace-agent-settings-container">
|
||||||
@ -96,12 +86,23 @@ export default function WorkspaceAgentConfiguration({ workspace }) {
|
|||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
setHasChanges={setHasChanges}
|
setHasChanges={setHasChanges}
|
||||||
/>
|
/>
|
||||||
<AvailableAgentSkills
|
{!hasChanges && (
|
||||||
skills={agentSkills}
|
<div className="flex flex-col gap-y-4">
|
||||||
toggleAgentSkill={toggleAgentSkill}
|
<button onClick={() => navigate(paths.settings.agentSkills())}>
|
||||||
settings={settings}
|
<div
|
||||||
setHasChanges={setHasChanges}
|
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 && (
|
{hasChanges && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -143,83 +144,3 @@ function LoadingSkeleton() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvailableAgentSkills({
|
|
||||||
skills,
|
|
||||||
settings,
|
|
||||||
toggleAgentSkill,
|
|
||||||
setHasChanges,
|
|
||||||
}) {
|
|
||||||
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")}
|
|
||||||
setHasChanges={setHasChanges}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -117,6 +117,9 @@ export default {
|
|||||||
appearance: () => {
|
appearance: () => {
|
||||||
return "/settings/appearance";
|
return "/settings/appearance";
|
||||||
},
|
},
|
||||||
|
agentSkills: () => {
|
||||||
|
return "/settings/agents";
|
||||||
|
},
|
||||||
apiKeys: () => {
|
apiKeys: () => {
|
||||||
return "/settings/api-keys";
|
return "/settings/api-keys";
|
||||||
},
|
},
|
||||||
|
@ -317,6 +317,7 @@ function adminEndpoints(app) {
|
|||||||
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
||||||
async (_, response) => {
|
async (_, response) => {
|
||||||
try {
|
try {
|
||||||
|
const embedder = getEmbeddingEngineSelection();
|
||||||
const settings = {
|
const settings = {
|
||||||
users_can_delete_workspaces:
|
users_can_delete_workspaces:
|
||||||
(await SystemSettings.get({ label: "users_can_delete_workspaces" }))
|
(await SystemSettings.get({ label: "users_can_delete_workspaces" }))
|
||||||
@ -337,13 +338,12 @@ function adminEndpoints(app) {
|
|||||||
text_splitter_chunk_size:
|
text_splitter_chunk_size:
|
||||||
(await SystemSettings.get({ label: "text_splitter_chunk_size" }))
|
(await SystemSettings.get({ label: "text_splitter_chunk_size" }))
|
||||||
?.value ||
|
?.value ||
|
||||||
getEmbeddingEngineSelection()?.embeddingMaxChunkLength ||
|
embedder?.embeddingMaxChunkLength ||
|
||||||
null,
|
null,
|
||||||
text_splitter_chunk_overlap:
|
text_splitter_chunk_overlap:
|
||||||
(await SystemSettings.get({ label: "text_splitter_chunk_overlap" }))
|
(await SystemSettings.get({ label: "text_splitter_chunk_overlap" }))
|
||||||
?.value || null,
|
?.value || null,
|
||||||
max_embed_chunk_size:
|
max_embed_chunk_size: embedder?.embeddingMaxChunkLength || 1000,
|
||||||
getEmbeddingEngineSelection()?.embeddingMaxChunkLength || 1000,
|
|
||||||
agent_search_provider:
|
agent_search_provider:
|
||||||
(await SystemSettings.get({ label: "agent_search_provider" }))
|
(await SystemSettings.get({ label: "agent_search_provider" }))
|
||||||
?.value || null,
|
?.value || null,
|
||||||
|