mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
Show agent skills, flows, and MCP tools in chat tools menu (#5444)
* show agent skills, flows, and MCP tools in collapsible sections in chat tools menu * fix tools menu toggle disabled bypass, add border-none to buttons, and useMemo improvements * replace mcp server cache with loading state for mcp servers * enable sub-skill management * refactor * Translations for chat tools menu improvements (#5448) * normalize translations * update translations * norm translations --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import Toggle from "@/components/lib/Toggle";
|
||||
import { useRef, useEffect } from "react";
|
||||
import { SimpleToggleSwitch } from "@/components/lib/Toggle";
|
||||
|
||||
export default function SkillRow({
|
||||
name,
|
||||
@@ -7,24 +8,30 @@ export default function SkillRow({
|
||||
highlighted = false,
|
||||
disabled = false,
|
||||
}) {
|
||||
let classNames = "flex items-center justify-between px-2 py-1 rounded";
|
||||
const ref = useRef(null);
|
||||
useEffect(() => {
|
||||
if (highlighted) ref.current?.scrollIntoView({ block: "nearest" });
|
||||
}, [highlighted]);
|
||||
|
||||
let classNames =
|
||||
"border-none bg-transparent w-full flex items-center justify-between px-2 py-1 rounded";
|
||||
if (highlighted) classNames += " bg-zinc-700/50 light:bg-slate-100";
|
||||
else classNames += " hover:bg-zinc-700/50 light:hover:bg-slate-100";
|
||||
|
||||
if (disabled) classNames += " opacity-60 cursor-not-allowed";
|
||||
else classNames += " cursor-pointer";
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={classNames}
|
||||
onClick={() => !disabled && onToggle()}
|
||||
data-tooltip-id={disabled ? "agent-skill-disabled-tooltip" : undefined}
|
||||
>
|
||||
<span className="text-xs text-white light:text-slate-900">{name}</span>
|
||||
<Toggle
|
||||
size="sm"
|
||||
enabled={enabled}
|
||||
onChange={onToggle}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
<div className="pointer-events-none" aria-hidden="true">
|
||||
<SimpleToggleSwitch size="sm" enabled={enabled} />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { CaretDown } from "@phosphor-icons/react";
|
||||
|
||||
export default function SkillSection({
|
||||
name,
|
||||
expanded,
|
||||
onToggle,
|
||||
enabledCount,
|
||||
totalCount,
|
||||
isMcp = false,
|
||||
indented = false,
|
||||
highlighted = false,
|
||||
children,
|
||||
}) {
|
||||
const ref = useRef(null);
|
||||
useEffect(() => {
|
||||
if (highlighted) ref.current?.scrollIntoView({ block: "nearest" });
|
||||
}, [highlighted]);
|
||||
|
||||
let headerClasses =
|
||||
"border-none bg-transparent w-full flex items-center justify-between px-2 py-1 rounded cursor-pointer";
|
||||
if (highlighted) headerClasses += " bg-zinc-700/50 light:bg-slate-100";
|
||||
else headerClasses += " hover:bg-zinc-700/30 light:hover:bg-slate-50";
|
||||
|
||||
return (
|
||||
<div className={indented ? "ml-3" : ""}>
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={headerClasses}
|
||||
onClick={onToggle}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CaretDown
|
||||
size={10}
|
||||
weight="bold"
|
||||
className={`text-zinc-400 light:text-slate-500 transition-transform duration-150 ${
|
||||
expanded ? "" : "-rotate-90"
|
||||
}`}
|
||||
/>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wide text-zinc-400 light:text-slate-500">
|
||||
{name}
|
||||
</span>
|
||||
{isMcp && (
|
||||
<span className="text-[8px] px-1 py-px rounded bg-zinc-600/50 light:bg-slate-200 text-zinc-300 light:text-slate-500 font-medium leading-tight">
|
||||
MCP
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[10px] text-zinc-500 light:text-slate-400 tabular-nums">
|
||||
{enabledCount}/{totalCount}
|
||||
</span>
|
||||
</button>
|
||||
{expanded && <div className="pl-3">{children}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
import Admin from "@/models/admin";
|
||||
import System from "@/models/system";
|
||||
import AgentPlugins from "@/models/experimental/agentPlugins";
|
||||
import AgentFlows from "@/models/agentFlows";
|
||||
import {
|
||||
getDefaultSkills,
|
||||
getConfigurableSkills,
|
||||
getAppIntegrationSkills,
|
||||
} from "@/pages/Admin/Agents/skills";
|
||||
import useToolsMenuItems from "../../useToolsMenuItems";
|
||||
import useAgentSkillsState from "./useAgentSkillsState";
|
||||
import useSkillSections from "./useSkillSections";
|
||||
import SkillRow from "./SkillRow";
|
||||
import { Wrench } from "@phosphor-icons/react";
|
||||
import SkillSection from "./SkillSection";
|
||||
import { Wrench, MagnifyingGlass, CircleNotch } from "@phosphor-icons/react";
|
||||
import { useIsAgentSessionActive } from "@/utils/chat/agent";
|
||||
|
||||
const MIN_ITEMS_TO_SHOW_SEARCH = 10;
|
||||
|
||||
export default function AgentSkillsTab({
|
||||
highlightedIndex = -1,
|
||||
registerItemCount,
|
||||
@@ -23,133 +25,151 @@ export default function AgentSkillsTab({
|
||||
const { t } = useTranslation();
|
||||
const { showAgentCommand = true } = workspace ?? {};
|
||||
const agentSessionActive = useIsAgentSessionActive();
|
||||
|
||||
// Get skill definitions
|
||||
const defaultSkills = getDefaultSkills(t);
|
||||
const [fileSystemAgentAvailable, setFileSystemAgentAvailable] =
|
||||
useState(false);
|
||||
const appIntegrationSkills = getAppIntegrationSkills(t);
|
||||
|
||||
// All skill state management
|
||||
const {
|
||||
fileSystemAgentAvailable,
|
||||
importedSkills,
|
||||
flows,
|
||||
mcpServers,
|
||||
loading,
|
||||
mcpLoading,
|
||||
isSkillEnabled,
|
||||
toggleSkill,
|
||||
toggleImportedSkill,
|
||||
toggleFlow,
|
||||
toggleMcpTool,
|
||||
isSubSkillEnabled,
|
||||
toggleSubSkill,
|
||||
disabledSubSkills,
|
||||
} = useAgentSkillsState(defaultSkills);
|
||||
|
||||
const configurableSkills = getConfigurableSkills(t, {
|
||||
fileSystemAgentAvailable,
|
||||
});
|
||||
const [disabledDefaults, setDisabledDefaults] = useState([]);
|
||||
const [enabledConfigurable, setEnabledConfigurable] = useState([]);
|
||||
const [importedSkills, setImportedSkills] = useState([]);
|
||||
const [flows, setFlows] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// UI state
|
||||
const [expandedSections, setExpandedSections] = useState({});
|
||||
const [expandedSubSections, setExpandedSubSections] = useState({});
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const showAgentCmdActivationAlert = showAgentCommand && !agentSessionActive;
|
||||
|
||||
useEffect(() => {
|
||||
fetchSkillSettings();
|
||||
}, []);
|
||||
// Build all sections
|
||||
const sections = useSkillSections({
|
||||
t,
|
||||
defaultSkills,
|
||||
configurableSkills,
|
||||
appIntegrationSkills,
|
||||
importedSkills,
|
||||
flows,
|
||||
mcpServers,
|
||||
isSkillEnabled,
|
||||
toggleSkill,
|
||||
isSubSkillEnabled,
|
||||
toggleSubSkill,
|
||||
toggleImportedSkill,
|
||||
toggleFlow,
|
||||
toggleMcpTool,
|
||||
disabledSubSkills,
|
||||
});
|
||||
|
||||
async function fetchSkillSettings() {
|
||||
try {
|
||||
const [prefs, flowsRes, fsAgentAvailable] = await Promise.all([
|
||||
Admin.systemPreferencesByFields([
|
||||
"disabled_agent_skills",
|
||||
"default_agent_skills",
|
||||
"imported_agent_skills",
|
||||
]),
|
||||
AgentFlows.listFlows(),
|
||||
System.isFileSystemAgentAvailable(),
|
||||
]);
|
||||
// Section expansion helpers
|
||||
function isSectionExpanded(sectionId) {
|
||||
return !!(searchQuery.trim() || expandedSections[sectionId]);
|
||||
}
|
||||
|
||||
if (prefs?.settings) {
|
||||
setDisabledDefaults(prefs.settings.disabled_agent_skills ?? []);
|
||||
setEnabledConfigurable(prefs.settings.default_agent_skills ?? []);
|
||||
setImportedSkills(prefs.settings.imported_agent_skills ?? []);
|
||||
function toggleSection(sectionId) {
|
||||
setExpandedSections((prev) => ({
|
||||
...prev,
|
||||
[sectionId]: !prev[sectionId],
|
||||
}));
|
||||
}
|
||||
|
||||
function isSubSectionExpanded(subSectionId) {
|
||||
return !!(searchQuery.trim() || expandedSubSections[subSectionId]);
|
||||
}
|
||||
|
||||
function toggleSubSection(subSectionId) {
|
||||
setExpandedSubSections((prev) => ({
|
||||
...prev,
|
||||
[subSectionId]: !prev[subSectionId],
|
||||
}));
|
||||
}
|
||||
|
||||
// Filter sections by search query
|
||||
const filteredSections = useMemo(() => {
|
||||
if (!searchQuery.trim()) return sections;
|
||||
const q = searchQuery.toLowerCase();
|
||||
return sections
|
||||
.map((section) => {
|
||||
const items = section.items.filter((item) => {
|
||||
const nameMatches = item.name.toLowerCase().includes(q);
|
||||
const subSkillMatches =
|
||||
item.subSkills?.some((sub) => sub.name.toLowerCase().includes(q)) ??
|
||||
false;
|
||||
return nameMatches || subSkillMatches;
|
||||
});
|
||||
return {
|
||||
...section,
|
||||
items,
|
||||
enabledCount: items.filter((i) => i.enabled).length,
|
||||
};
|
||||
})
|
||||
.filter((section) => section.items.length > 0);
|
||||
}, [sections, searchQuery]);
|
||||
|
||||
// Flat list of navigable items for keyboard nav
|
||||
const { flatItems, flatIndexMap } = useMemo(() => {
|
||||
const items = [];
|
||||
const indexMap = {};
|
||||
for (const section of filteredSections) {
|
||||
indexMap[section.id] = items.length;
|
||||
items.push({
|
||||
type: "header",
|
||||
id: section.id,
|
||||
onToggle: () => toggleSection(section.id),
|
||||
});
|
||||
if (isSectionExpanded(section.id)) {
|
||||
for (const item of section.items) {
|
||||
indexMap[item.id] = items.length;
|
||||
items.push(item);
|
||||
|
||||
if (item.hasSubSkills && item.subSkills) {
|
||||
indexMap[`subsection-${item.id}`] = items.length;
|
||||
items.push({
|
||||
type: "subheader",
|
||||
id: `subsection-${item.id}`,
|
||||
parentId: item.id,
|
||||
onToggle: () => toggleSubSection(item.id),
|
||||
});
|
||||
|
||||
if (isSubSectionExpanded(item.id)) {
|
||||
for (const subItem of item.subSkills) {
|
||||
indexMap[subItem.id] = items.length;
|
||||
items.push(subItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flowsRes?.flows) setFlows(flowsRes.flows);
|
||||
setFileSystemAgentAvailable(fsAgentAvailable);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
return { flatItems: items, flatIndexMap: indexMap };
|
||||
}, [filteredSections, expandedSections, expandedSubSections, searchQuery]);
|
||||
|
||||
function toggleItem(arr, item) {
|
||||
return arr.includes(item) ? arr.filter((s) => s !== item) : [...arr, item];
|
||||
}
|
||||
|
||||
function isSkillEnabled(key) {
|
||||
return key in defaultSkills
|
||||
? !disabledDefaults.includes(key)
|
||||
: enabledConfigurable.includes(key);
|
||||
}
|
||||
|
||||
async function toggleSkill(key) {
|
||||
if (key in defaultSkills) {
|
||||
const updated = toggleItem(disabledDefaults, key);
|
||||
setDisabledDefaults(updated);
|
||||
await Admin.updateSystemPreferences({
|
||||
disabled_agent_skills: updated.join(","),
|
||||
default_agent_skills: enabledConfigurable.join(","),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = toggleItem(enabledConfigurable, key);
|
||||
setEnabledConfigurable(updated);
|
||||
await Admin.updateSystemPreferences({
|
||||
disabled_agent_skills: disabledDefaults.join(","),
|
||||
default_agent_skills: updated.join(","),
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleImportedSkill(skill) {
|
||||
const newActive = !skill.active;
|
||||
setImportedSkills((prev) =>
|
||||
prev.map((s) =>
|
||||
s.hubId === skill.hubId ? { ...s, active: newActive } : s
|
||||
)
|
||||
);
|
||||
await AgentPlugins.toggleFeature(skill.hubId, newActive);
|
||||
}
|
||||
|
||||
async function toggleFlow(flow) {
|
||||
const newActive = !flow.active;
|
||||
setFlows((prev) =>
|
||||
prev.map((f) => (f.uuid === flow.uuid ? { ...f, active: newActive } : f))
|
||||
);
|
||||
await AgentFlows.toggleFlow(flow.uuid, newActive);
|
||||
}
|
||||
|
||||
// Build list of all skill items for rendering/keyboard navigation
|
||||
const items = useMemo(() => {
|
||||
const list = [];
|
||||
for (const [key, { title }] of Object.entries({
|
||||
...defaultSkills,
|
||||
...configurableSkills,
|
||||
})) {
|
||||
list.push({
|
||||
id: key,
|
||||
name: title,
|
||||
enabled: isSkillEnabled(key),
|
||||
onToggle: () => toggleSkill(key),
|
||||
});
|
||||
}
|
||||
for (const skill of importedSkills) {
|
||||
list.push({
|
||||
id: skill.hubId,
|
||||
name: skill.name,
|
||||
enabled: skill.active,
|
||||
onToggle: () => toggleImportedSkill(skill),
|
||||
});
|
||||
}
|
||||
for (const flow of flows) {
|
||||
list.push({
|
||||
id: flow.uuid,
|
||||
name: flow.name,
|
||||
enabled: flow.active,
|
||||
onToggle: () => toggleFlow(flow),
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}, [disabledDefaults, enabledConfigurable, importedSkills, flows]);
|
||||
const totalItemCount = sections.reduce((sum, s) => sum + s.items.length, 0);
|
||||
|
||||
useToolsMenuItems({
|
||||
items,
|
||||
items: flatItems,
|
||||
highlightedIndex,
|
||||
onSelect: agentSessionActive ? () => {} : (item) => item.onToggle(),
|
||||
onSelect: (item) => {
|
||||
if (item.type === "header") return item.onToggle();
|
||||
if (!agentSessionActive) item.onToggle();
|
||||
},
|
||||
registerItemCount,
|
||||
});
|
||||
|
||||
@@ -162,18 +182,82 @@ export default function AgentSkillsTab({
|
||||
{t("chat_window.use_agent_session_to_use_tools")}
|
||||
</p>
|
||||
)}
|
||||
{items.map((item, index) => (
|
||||
<SkillRow
|
||||
key={item.id}
|
||||
name={item.name}
|
||||
enabled={item.enabled}
|
||||
onToggle={item.onToggle}
|
||||
highlighted={highlightedIndex === index}
|
||||
disabled={agentSessionActive}
|
||||
{totalItemCount >= MIN_ITEMS_TO_SHOW_SEARCH && (
|
||||
<SearchInput
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
placeholder={t("common.search")}
|
||||
/>
|
||||
)}
|
||||
{filteredSections.map((section) => (
|
||||
<SkillSection
|
||||
key={section.id}
|
||||
name={section.name}
|
||||
expanded={isSectionExpanded(section.id)}
|
||||
onToggle={() => toggleSection(section.id)}
|
||||
enabledCount={section.enabledCount}
|
||||
totalCount={section.items.length}
|
||||
isMcp={section.isMcp}
|
||||
highlighted={highlightedIndex === flatIndexMap[section.id]}
|
||||
>
|
||||
{section.items.map((item) => (
|
||||
<div key={item.id}>
|
||||
<SkillRow
|
||||
name={item.name}
|
||||
enabled={item.enabled}
|
||||
onToggle={item.onToggle}
|
||||
highlighted={highlightedIndex === flatIndexMap[item.id]}
|
||||
disabled={agentSessionActive}
|
||||
/>
|
||||
{item.hasSubSkills && item.subSkills && item.enabled && (
|
||||
<SkillSection
|
||||
name={t("chat_window.sub_skills")}
|
||||
expanded={isSubSectionExpanded(item.id)}
|
||||
onToggle={() => toggleSubSection(item.id)}
|
||||
enabledCount={item.subSkills.filter((s) => s.enabled).length}
|
||||
totalCount={item.subSkills.length}
|
||||
highlighted={
|
||||
highlightedIndex === flatIndexMap[`subsection-${item.id}`]
|
||||
}
|
||||
indented
|
||||
>
|
||||
{item.subSkills.map((subItem) => (
|
||||
<SkillRow
|
||||
key={subItem.id}
|
||||
name={subItem.name}
|
||||
enabled={subItem.enabled}
|
||||
onToggle={subItem.onToggle}
|
||||
highlighted={
|
||||
highlightedIndex === flatIndexMap[subItem.id]
|
||||
}
|
||||
disabled={agentSessionActive || !subItem.parentEnabled}
|
||||
/>
|
||||
))}
|
||||
</SkillSection>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</SkillSection>
|
||||
))}
|
||||
{mcpLoading && (
|
||||
<div className="flex items-center gap-1.5 px-2 py-1.5">
|
||||
<CircleNotch
|
||||
size={12}
|
||||
className="text-zinc-500 light:text-slate-400 animate-spin"
|
||||
weight="bold"
|
||||
/>
|
||||
<span className="text-[10px] text-zinc-500 light:text-slate-400">
|
||||
{t("chat_window.loading_mcp_servers")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{filteredSections.length === 0 && !mcpLoading && searchQuery.trim() && (
|
||||
<p className="text-xs text-zinc-500 light:text-slate-400 text-center py-2">
|
||||
{t("chat_window.no_tools_found")}
|
||||
</p>
|
||||
)}
|
||||
<Link to={paths.settings.agentSkills()}>
|
||||
<button className="flex items-center gap-1.5 px-2 h-6 rounded cursor-pointer hover:bg-zinc-700/50 light:hover:bg-slate-100 text-theme-text-primary">
|
||||
<button className="border-none flex items-center gap-1.5 px-2 h-6 rounded cursor-pointer hover:bg-zinc-700/50 light:hover:bg-slate-100 text-theme-text-primary">
|
||||
<Wrench size={12} className="text-theme-text-primary" />
|
||||
<span className="text-xs text-theme-text-primary">
|
||||
{t("chat_window.manage_agent_skills")}
|
||||
@@ -183,3 +267,30 @@ export default function AgentSkillsTab({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchInput({ value, onChange, placeholder }) {
|
||||
return (
|
||||
<div className="relative shrink-0">
|
||||
<MagnifyingGlass
|
||||
size={12}
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 text-zinc-400 light:text-slate-400"
|
||||
weight="bold"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
onChange("");
|
||||
e.target.blur();
|
||||
}
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
className="w-full pl-7 pr-2 py-1 text-xs bg-zinc-700/50 light:bg-slate-100 border border-zinc-600 light:border-slate-300 rounded text-white light:text-slate-900 placeholder:text-zinc-500 light:placeholder:text-slate-400 outline-none focus:border-zinc-500 light:focus:border-slate-400"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { getCreateFileSkills } from "@/pages/Admin/Agents/CreateFileSkillPanel";
|
||||
import { getFileSystemSubSkills } from "@/pages/Admin/Agents/FileSystemSkillPanel";
|
||||
import { getGmailSkills } from "@/pages/Admin/Agents/GMailSkillPanel/utils";
|
||||
import { getGoogleCalendarSkills } from "@/pages/Admin/Agents/GoogleCalendarSkillPanel/utils";
|
||||
import { getOutlookSkills } from "@/pages/Admin/Agents/OutlookSkillPanel/utils";
|
||||
|
||||
/**
|
||||
* Flattens categorized skills (used by app integrations) into a flat array.
|
||||
*/
|
||||
function flattenCategorySkills(categorizedSkills) {
|
||||
return Object.values(categorizedSkills).flatMap(
|
||||
(category) => category.skills
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry of all skills that have sub-skills.
|
||||
* Each entry maps a skill key to its configuration:
|
||||
* - preferenceKey: The system preference key for storing disabled sub-skills
|
||||
* - getSubSkills: Function that returns the sub-skills array (receives translation function)
|
||||
*
|
||||
* To add a new skill with sub-skills:
|
||||
* 1. Add an entry here with the skill key, preference key, and getter function
|
||||
* 2. The rest is handled automatically by useSubSkillPreferences hook
|
||||
*/
|
||||
export const SUB_SKILL_REGISTRY = {
|
||||
"create-files-agent": {
|
||||
preferenceKey: "disabled_create_files_skills",
|
||||
getSubSkills: (t) => getCreateFileSkills(t),
|
||||
},
|
||||
"filesystem-agent": {
|
||||
preferenceKey: "disabled_filesystem_skills",
|
||||
getSubSkills: (t) => getFileSystemSubSkills(t),
|
||||
},
|
||||
"gmail-agent": {
|
||||
preferenceKey: "disabled_gmail_skills",
|
||||
getSubSkills: (t) => flattenCategorySkills(getGmailSkills(t)),
|
||||
},
|
||||
"google-calendar-agent": {
|
||||
preferenceKey: "disabled_google_calendar_skills",
|
||||
getSubSkills: (t) => flattenCategorySkills(getGoogleCalendarSkills(t)),
|
||||
},
|
||||
"outlook-agent": {
|
||||
preferenceKey: "disabled_outlook_skills",
|
||||
getSubSkills: (t) => flattenCategorySkills(getOutlookSkills(t)),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all preference keys that need to be fetched for sub-skills.
|
||||
*/
|
||||
export function getSubSkillPreferenceKeys() {
|
||||
return Object.values(SUB_SKILL_REGISTRY).map(
|
||||
(config) => config.preferenceKey
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sub-skills for a given skill key.
|
||||
* Returns null if the skill has no sub-skills.
|
||||
*/
|
||||
export function getSubSkillsForSkill(skillKey, t) {
|
||||
const config = SUB_SKILL_REGISTRY[skillKey];
|
||||
if (!config) return null;
|
||||
return config.getSubSkills(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preference key for a skill's sub-skills.
|
||||
* Returns null if the skill has no sub-skills.
|
||||
*/
|
||||
export function getPreferenceKeyForSkill(skillKey) {
|
||||
return SUB_SKILL_REGISTRY[skillKey]?.preferenceKey ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a skill has sub-skills.
|
||||
*/
|
||||
export function hasSubSkills(skillKey) {
|
||||
return skillKey in SUB_SKILL_REGISTRY;
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import Admin from "@/models/admin";
|
||||
import System from "@/models/system";
|
||||
import AgentPlugins from "@/models/experimental/agentPlugins";
|
||||
import AgentFlows from "@/models/agentFlows";
|
||||
import MCPServers from "@/models/mcpServers";
|
||||
import { getSubSkillPreferenceKeys } from "./skillRegistry";
|
||||
import useSubSkillPreferences from "./useSubSkillPreferences";
|
||||
|
||||
/**
|
||||
* Core hook for managing all agent skill state.
|
||||
* Handles fetching, toggling, and persisting skill preferences.
|
||||
*/
|
||||
export default function useAgentSkillsState(defaultSkills) {
|
||||
// Core skill state
|
||||
const [fileSystemAgentAvailable, setFileSystemAgentAvailable] =
|
||||
useState(false);
|
||||
const [disabledDefaults, setDisabledDefaults] = useState([]);
|
||||
const [enabledConfigurable, setEnabledConfigurable] = useState([]);
|
||||
const [importedSkills, setImportedSkills] = useState([]);
|
||||
const [flows, setFlows] = useState([]);
|
||||
const [mcpServers, setMcpServers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [mcpLoading, setMcpLoading] = useState(true);
|
||||
|
||||
// Sub-skill preferences (managed by dedicated hook)
|
||||
const subSkillPrefs = useSubSkillPreferences();
|
||||
|
||||
// Fetch all skill settings on mount
|
||||
useEffect(() => {
|
||||
fetchSkillSettings();
|
||||
fetchMcpServers();
|
||||
}, []);
|
||||
|
||||
async function fetchSkillSettings() {
|
||||
try {
|
||||
const subSkillPrefKeys = getSubSkillPreferenceKeys();
|
||||
const [prefs, flowsRes, fsAgentAvailable] = await Promise.all([
|
||||
Admin.systemPreferencesByFields([
|
||||
"disabled_agent_skills",
|
||||
"default_agent_skills",
|
||||
"imported_agent_skills",
|
||||
...subSkillPrefKeys,
|
||||
]),
|
||||
AgentFlows.listFlows(),
|
||||
System.isFileSystemAgentAvailable(),
|
||||
]);
|
||||
|
||||
if (prefs?.settings) {
|
||||
setDisabledDefaults(prefs.settings.disabled_agent_skills ?? []);
|
||||
setEnabledConfigurable(prefs.settings.default_agent_skills ?? []);
|
||||
setImportedSkills(prefs.settings.imported_agent_skills ?? []);
|
||||
subSkillPrefs.loadFromSettings(prefs.settings);
|
||||
}
|
||||
if (flowsRes?.flows) setFlows(flowsRes.flows);
|
||||
setFileSystemAgentAvailable(fsAgentAvailable);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMcpServers() {
|
||||
try {
|
||||
const { servers = [] } = await MCPServers.listServers();
|
||||
setMcpServers(servers);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setMcpLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Skill enabled/disabled checks
|
||||
const isSkillEnabled = useCallback(
|
||||
(key) => {
|
||||
return key in defaultSkills
|
||||
? !disabledDefaults.includes(key)
|
||||
: enabledConfigurable.includes(key);
|
||||
},
|
||||
[defaultSkills, disabledDefaults, enabledConfigurable]
|
||||
);
|
||||
|
||||
// Toggle functions
|
||||
const toggleSkill = useCallback(
|
||||
async (key) => {
|
||||
const toggleItem = (arr, item) =>
|
||||
arr.includes(item) ? arr.filter((s) => s !== item) : [...arr, item];
|
||||
|
||||
if (key in defaultSkills) {
|
||||
const updated = toggleItem(disabledDefaults, key);
|
||||
setDisabledDefaults(updated);
|
||||
await Admin.updateSystemPreferences({
|
||||
disabled_agent_skills: updated.join(","),
|
||||
default_agent_skills: enabledConfigurable.join(","),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = toggleItem(enabledConfigurable, key);
|
||||
setEnabledConfigurable(updated);
|
||||
await Admin.updateSystemPreferences({
|
||||
disabled_agent_skills: disabledDefaults.join(","),
|
||||
default_agent_skills: updated.join(","),
|
||||
});
|
||||
},
|
||||
[defaultSkills, disabledDefaults, enabledConfigurable]
|
||||
);
|
||||
|
||||
const toggleImportedSkill = useCallback(async (skill) => {
|
||||
const newActive = !skill.active;
|
||||
setImportedSkills((prev) =>
|
||||
prev.map((s) =>
|
||||
s.hubId === skill.hubId ? { ...s, active: newActive } : s
|
||||
)
|
||||
);
|
||||
await AgentPlugins.toggleFeature(skill.hubId, newActive);
|
||||
}, []);
|
||||
|
||||
const toggleFlow = useCallback(async (flow) => {
|
||||
const newActive = !flow.active;
|
||||
setFlows((prev) =>
|
||||
prev.map((f) => (f.uuid === flow.uuid ? { ...f, active: newActive } : f))
|
||||
);
|
||||
await AgentFlows.toggleFlow(flow.uuid, newActive);
|
||||
}, []);
|
||||
|
||||
const toggleMcpTool = useCallback(
|
||||
async (serverName, toolName, currentlyEnabled) => {
|
||||
const newEnabled = !currentlyEnabled;
|
||||
setMcpServers((prev) => {
|
||||
return prev.map((server) => {
|
||||
if (server.name !== serverName) return server;
|
||||
const currentSuppressed =
|
||||
server.config?.anythingllm?.suppressedTools || [];
|
||||
const newSuppressed = newEnabled
|
||||
? currentSuppressed.filter((t) => t !== toolName)
|
||||
: [...currentSuppressed, toolName];
|
||||
return {
|
||||
...server,
|
||||
config: {
|
||||
...server.config,
|
||||
anythingllm: {
|
||||
...server.config?.anythingllm,
|
||||
suppressedTools: newSuppressed,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
await MCPServers.toggleTool(serverName, toolName, newEnabled);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
// State
|
||||
fileSystemAgentAvailable,
|
||||
disabledDefaults,
|
||||
enabledConfigurable,
|
||||
importedSkills,
|
||||
flows,
|
||||
mcpServers,
|
||||
loading,
|
||||
mcpLoading,
|
||||
|
||||
// Skill checks
|
||||
isSkillEnabled,
|
||||
|
||||
// Toggle functions
|
||||
toggleSkill,
|
||||
toggleImportedSkill,
|
||||
toggleFlow,
|
||||
toggleMcpTool,
|
||||
|
||||
// Sub-skill preferences (delegated)
|
||||
isSubSkillEnabled: subSkillPrefs.isSubSkillEnabled,
|
||||
toggleSubSkill: subSkillPrefs.toggleSubSkill,
|
||||
disabledSubSkills: subSkillPrefs.disabledSubSkills,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import { useMemo } from "react";
|
||||
import { titleCase } from "text-case";
|
||||
import { getSubSkillsForSkill, hasSubSkills } from "./skillRegistry";
|
||||
|
||||
/**
|
||||
* Builds a skill item with optional sub-skills.
|
||||
*/
|
||||
function buildSkillItem({
|
||||
key,
|
||||
title,
|
||||
isEnabled,
|
||||
onToggle,
|
||||
t,
|
||||
isSubSkillEnabled,
|
||||
toggleSubSkill,
|
||||
}) {
|
||||
const subSkills = getSubSkillsForSkill(key, t);
|
||||
const parentEnabled = isEnabled(key);
|
||||
|
||||
return {
|
||||
id: key,
|
||||
name: title,
|
||||
enabled: parentEnabled,
|
||||
onToggle: () => onToggle(key),
|
||||
hasSubSkills: hasSubSkills(key),
|
||||
subSkills: subSkills
|
||||
? subSkills.map((sub) => ({
|
||||
id: `${key}::${sub.name}`,
|
||||
name: sub.title,
|
||||
enabled: parentEnabled && isSubSkillEnabled(key, sub.name),
|
||||
onToggle: () => toggleSubSkill(key, sub.name),
|
||||
parentEnabled,
|
||||
}))
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to build all skill sections for the menu.
|
||||
* Separates the section-building logic from the main component.
|
||||
*/
|
||||
export default function useSkillSections({
|
||||
t,
|
||||
defaultSkills,
|
||||
configurableSkills,
|
||||
appIntegrationSkills,
|
||||
importedSkills,
|
||||
flows,
|
||||
mcpServers,
|
||||
isSkillEnabled,
|
||||
toggleSkill,
|
||||
isSubSkillEnabled,
|
||||
toggleSubSkill,
|
||||
toggleImportedSkill,
|
||||
toggleFlow,
|
||||
toggleMcpTool,
|
||||
disabledSubSkills,
|
||||
}) {
|
||||
return useMemo(() => {
|
||||
const sectionList = [];
|
||||
|
||||
// Agent Skills (default + configurable)
|
||||
const skillItems = [];
|
||||
for (const [key, { title }] of Object.entries({
|
||||
...defaultSkills,
|
||||
...configurableSkills,
|
||||
})) {
|
||||
skillItems.push(
|
||||
buildSkillItem({
|
||||
key,
|
||||
title,
|
||||
isEnabled: isSkillEnabled,
|
||||
onToggle: toggleSkill,
|
||||
t,
|
||||
isSubSkillEnabled,
|
||||
toggleSubSkill,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (skillItems.length > 0) {
|
||||
sectionList.push({
|
||||
id: "agent-skills",
|
||||
name: t("chat_window.agent_skills"),
|
||||
items: skillItems,
|
||||
enabledCount: skillItems.filter((i) => i.enabled).length,
|
||||
});
|
||||
}
|
||||
|
||||
// App Integrations
|
||||
const appIntegrationItems = [];
|
||||
for (const [key, { title }] of Object.entries(appIntegrationSkills)) {
|
||||
appIntegrationItems.push(
|
||||
buildSkillItem({
|
||||
key,
|
||||
title,
|
||||
isEnabled: isSkillEnabled,
|
||||
onToggle: toggleSkill,
|
||||
t,
|
||||
isSubSkillEnabled,
|
||||
toggleSubSkill,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (appIntegrationItems.length > 0) {
|
||||
sectionList.push({
|
||||
id: "app-integrations",
|
||||
name: t("chat_window.app_integrations"),
|
||||
items: appIntegrationItems,
|
||||
enabledCount: appIntegrationItems.filter((i) => i.enabled).length,
|
||||
});
|
||||
}
|
||||
|
||||
// Custom Skills (imported)
|
||||
if (importedSkills.length > 0) {
|
||||
const items = importedSkills.map((skill) => ({
|
||||
id: skill.hubId,
|
||||
name: skill.name,
|
||||
enabled: skill.active,
|
||||
onToggle: () => toggleImportedSkill(skill),
|
||||
}));
|
||||
sectionList.push({
|
||||
id: "custom-skills",
|
||||
name: t("chat_window.custom_skills"),
|
||||
items,
|
||||
enabledCount: items.filter((i) => i.enabled).length,
|
||||
});
|
||||
}
|
||||
|
||||
// Agent Flows
|
||||
if (flows.length > 0) {
|
||||
const items = flows.map((flow) => ({
|
||||
id: flow.uuid,
|
||||
name: flow.name,
|
||||
enabled: flow.active,
|
||||
onToggle: () => toggleFlow(flow),
|
||||
}));
|
||||
sectionList.push({
|
||||
id: "agent-flows",
|
||||
name: t("chat_window.agent_flows"),
|
||||
items,
|
||||
enabledCount: items.filter((i) => i.enabled).length,
|
||||
});
|
||||
}
|
||||
|
||||
// MCP Servers
|
||||
for (const server of mcpServers) {
|
||||
if (!server.running || server.tools.length === 0) continue;
|
||||
const suppressedTools = server.config?.anythingllm?.suppressedTools || [];
|
||||
const items = server.tools.map((tool) => ({
|
||||
id: `mcp::${server.name}::${tool.name}`,
|
||||
name: tool.name,
|
||||
enabled: !suppressedTools.includes(tool.name),
|
||||
onToggle: () =>
|
||||
toggleMcpTool(
|
||||
server.name,
|
||||
tool.name,
|
||||
!suppressedTools.includes(tool.name)
|
||||
),
|
||||
}));
|
||||
sectionList.push({
|
||||
id: `mcp-${server.name}`,
|
||||
name: titleCase(server.name.replace(/[_-]/g, " ")),
|
||||
isMcp: true,
|
||||
items,
|
||||
enabledCount: items.filter((i) => i.enabled).length,
|
||||
});
|
||||
}
|
||||
|
||||
return sectionList;
|
||||
}, [
|
||||
t,
|
||||
defaultSkills,
|
||||
configurableSkills,
|
||||
appIntegrationSkills,
|
||||
importedSkills,
|
||||
flows,
|
||||
mcpServers,
|
||||
isSkillEnabled,
|
||||
toggleSkill,
|
||||
isSubSkillEnabled,
|
||||
toggleSubSkill,
|
||||
toggleImportedSkill,
|
||||
toggleFlow,
|
||||
toggleMcpTool,
|
||||
disabledSubSkills,
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import Admin from "@/models/admin";
|
||||
import { SUB_SKILL_REGISTRY, getPreferenceKeyForSkill } from "./skillRegistry";
|
||||
|
||||
/**
|
||||
* Hook to manage sub-skill preferences for all skills in the registry.
|
||||
* Handles loading, checking enabled state, and toggling sub-skills.
|
||||
*
|
||||
* This hook eliminates the need for separate state variables for each skill's
|
||||
* sub-skills. Adding a new skill with sub-skills only requires updating the
|
||||
* skillRegistry.js file.
|
||||
*/
|
||||
export default function useSubSkillPreferences() {
|
||||
// Single state object holding disabled sub-skills for all skills
|
||||
// Key: preferenceKey, Value: array of disabled sub-skill names
|
||||
const [disabledSubSkills, setDisabledSubSkills] = useState({});
|
||||
|
||||
/**
|
||||
* Load sub-skill preferences from settings object.
|
||||
* Called after fetching system preferences.
|
||||
*/
|
||||
const loadFromSettings = useCallback((settings) => {
|
||||
if (!settings) return;
|
||||
|
||||
const loaded = {};
|
||||
for (const [, config] of Object.entries(SUB_SKILL_REGISTRY)) {
|
||||
const value = settings[config.preferenceKey];
|
||||
loaded[config.preferenceKey] = value ?? [];
|
||||
}
|
||||
setDisabledSubSkills(loaded);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Check if a sub-skill is enabled for a given skill.
|
||||
*/
|
||||
const isSubSkillEnabled = useCallback(
|
||||
(skillKey, subSkillName) => {
|
||||
const prefKey = getPreferenceKeyForSkill(skillKey);
|
||||
if (!prefKey) return true;
|
||||
|
||||
const disabled = disabledSubSkills[prefKey] ?? [];
|
||||
return !disabled.includes(subSkillName);
|
||||
},
|
||||
[disabledSubSkills]
|
||||
);
|
||||
|
||||
/**
|
||||
* Toggle a sub-skill's enabled state.
|
||||
*/
|
||||
const toggleSubSkill = useCallback(
|
||||
async (skillKey, subSkillName) => {
|
||||
const prefKey = getPreferenceKeyForSkill(skillKey);
|
||||
if (!prefKey) return;
|
||||
|
||||
const current = disabledSubSkills[prefKey] ?? [];
|
||||
const updated = current.includes(subSkillName)
|
||||
? current.filter((s) => s !== subSkillName)
|
||||
: [...current, subSkillName];
|
||||
|
||||
setDisabledSubSkills((prev) => ({
|
||||
...prev,
|
||||
[prefKey]: updated,
|
||||
}));
|
||||
|
||||
await Admin.updateSystemPreferences({
|
||||
[prefKey]: updated.join(","),
|
||||
});
|
||||
},
|
||||
[disabledSubSkills]
|
||||
);
|
||||
|
||||
return {
|
||||
loadFromSettings,
|
||||
isSubSkillEnabled,
|
||||
toggleSubSkill,
|
||||
disabledSubSkills,
|
||||
};
|
||||
}
|
||||
@@ -142,7 +142,7 @@ export default function ToolsMenu({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1 overflow-y-auto no-scroll flex-1 min-h-0">
|
||||
<div className="flex flex-col gap-1 overflow-y-auto no-scroll min-h-0">
|
||||
<ActiveTab
|
||||
sendCommand={sendCommand}
|
||||
setShowing={setShowing}
|
||||
|
||||
@@ -1174,6 +1174,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "تمت الموافقة على طلب الحصول على الأدوات.",
|
||||
tool_call_was_rejected: "تم رفض طلب الاتصال بالأداة.",
|
||||
},
|
||||
custom_skills: "المهارات المخصصة",
|
||||
agent_flows: "تدفقات الوكلاء",
|
||||
no_tools_found: "لم يتم العثور على أدوات مطابقة.",
|
||||
loading_mcp_servers: "تحميل خوادم MCP...",
|
||||
app_integrations: "تكامل التطبيقات",
|
||||
sub_skills: "مهارات فرعية",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "تحرير الحساب",
|
||||
|
||||
@@ -1399,6 +1399,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "La crida a l'eina ha estat aprovada",
|
||||
tool_call_was_rejected: "La crida a l'eina ha estat rebutjada",
|
||||
},
|
||||
custom_skills: "Habilitats personalitzades",
|
||||
agent_flows: "Fluxos d'agents",
|
||||
no_tools_found: "No s'han trobat eines corresponents.",
|
||||
loading_mcp_servers: "Carregant servidors MCP...",
|
||||
app_integrations: "Integracions d'aplicacions",
|
||||
sub_skills: "Habilitats específiques",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Edita el compte",
|
||||
|
||||
@@ -1308,6 +1308,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "Žádost o použití nástroje byla schválena.",
|
||||
tool_call_was_rejected: "Žádost o použití nástroje byla zamítnuta.",
|
||||
},
|
||||
custom_skills: "Vlastní dovednosti",
|
||||
agent_flows: "Toky agentů",
|
||||
no_tools_found: "Nebyla nalezena žádná odpovídající nářadí.",
|
||||
loading_mcp_servers: "Načítají se servery pro MCP...",
|
||||
app_integrations: "Integrace aplikací",
|
||||
sub_skills: "Specifické dovednosti",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Upravit účet",
|
||||
|
||||
@@ -1188,6 +1188,12 @@ const TRANSLATIONS = {
|
||||
"Anmodningen om at bruge værktøjet blev godkendt.",
|
||||
tool_call_was_rejected: "Anmodningen om at bruge værktøjet blev afvist.",
|
||||
},
|
||||
custom_skills: "Skræddersyede færdigheder",
|
||||
agent_flows: "Agentstrømme",
|
||||
no_tools_found: "Ingen matchende værktøjer fundet",
|
||||
loading_mcp_servers: "Indlæser MCP-servere...",
|
||||
app_integrations: "App-integrationer",
|
||||
sub_skills: "Specifikke færdigheder",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Rediger konto",
|
||||
|
||||
@@ -1304,6 +1304,12 @@ const TRANSLATIONS = {
|
||||
"Die Genehmigung für die Bestellung der Werkzeuge wurde erteilt.",
|
||||
tool_call_was_rejected: "Die Anfrage nach dem Werkzeug wurde abgelehnt.",
|
||||
},
|
||||
custom_skills: "Individuelle Fähigkeiten",
|
||||
agent_flows: "Datenströme",
|
||||
no_tools_found: "Keine passenden Werkzeuge gefunden.",
|
||||
loading_mcp_servers: "MCP-Server laden...",
|
||||
app_integrations: "Anwendungen und Integrationen",
|
||||
sub_skills: "Spezifische Fähigkeiten",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Account bearbeiten",
|
||||
|
||||
@@ -1336,6 +1336,12 @@ const TRANSLATIONS = {
|
||||
slash_commands: "Slash Commands",
|
||||
agent_skills: "Agent Skills",
|
||||
manage_agent_skills: "Manage Agent Skills",
|
||||
app_integrations: "App Integrations",
|
||||
custom_skills: "Custom Skills",
|
||||
agent_flows: "Agent Flows",
|
||||
sub_skills: "Sub-skills",
|
||||
no_tools_found: "No matching tools found",
|
||||
loading_mcp_servers: "Loading MCP servers...",
|
||||
start_agent_session: "Start Agent Session",
|
||||
agent_skills_disabled_in_session:
|
||||
"Can't modify skills during an active agent session. Use /exit to end the session first.",
|
||||
|
||||
@@ -1320,6 +1320,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "La solicitud de herramientas ha sido aprobada.",
|
||||
tool_call_was_rejected: "La solicitud de herramienta fue rechazada.",
|
||||
},
|
||||
custom_skills: "Habilidades personalizadas",
|
||||
agent_flows: "Flujos de agentes",
|
||||
no_tools_found: "No se encontraron herramientas coincidentes.",
|
||||
loading_mcp_servers: "Cargando servidores de MCP...",
|
||||
app_integrations: "Integraciones de aplicaciones",
|
||||
sub_skills: "Habilidades específicas",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Editar cuenta",
|
||||
|
||||
@@ -1247,6 +1247,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "Vahendite tellimuse kinnitati.",
|
||||
tool_call_was_rejected: "Vahendite taotlus jäeti rahuldamata.",
|
||||
},
|
||||
custom_skills: "Kohandatud oskused",
|
||||
agent_flows: "Agentide liiklus",
|
||||
no_tools_found: "Välja ei leitud sobivaid tööriistu",
|
||||
loading_mcp_servers: "MCP-serverite laadimine...",
|
||||
app_integrations: "Rakenduste integreerimine",
|
||||
sub_skills: "Alamspetsid",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Muuda kontot",
|
||||
|
||||
@@ -1180,6 +1180,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "درخواست برای تهیه ابزار تأیید شد.",
|
||||
tool_call_was_rejected: "درخواست استفاده از ابزار رد شد.",
|
||||
},
|
||||
custom_skills: "مهارتهای تخصصی",
|
||||
agent_flows: "جریانهای نمایندگی",
|
||||
no_tools_found: "هیچ ابزار مشابهی یافت نشد.",
|
||||
loading_mcp_servers: "بارگذاری سرورهای MCP...",
|
||||
app_integrations: "ادغام با برنامهها",
|
||||
sub_skills: "مهارتهای پایه",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "ویرایش حساب",
|
||||
|
||||
@@ -1212,6 +1212,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_rejected:
|
||||
"La demande d'utilisation de l'outil a été rejetée.",
|
||||
},
|
||||
custom_skills: "Compétences spécifiques",
|
||||
agent_flows: "Flux des agents",
|
||||
no_tools_found: "Aucun outil correspondant n'a été trouvé.",
|
||||
loading_mcp_servers: "Chargement des serveurs MCP...",
|
||||
app_integrations: "Intégrations d'applications",
|
||||
sub_skills: "Compétences spécifiques",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Modifier le compte",
|
||||
|
||||
@@ -1236,6 +1236,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "הבקשה לקבלת הכלי אושרה.",
|
||||
tool_call_was_rejected: "בקשת השימוש בכלי נדחתה.",
|
||||
},
|
||||
custom_skills: "כישורים מותאמים אישית",
|
||||
agent_flows: "זרימת סוכנים",
|
||||
no_tools_found: "לא נמצאו כלים תואמים.",
|
||||
loading_mcp_servers: "טעינת שרתי ה-MCP...",
|
||||
app_integrations: "אינטגרציות עם אפליקציות",
|
||||
sub_skills: "כישורים ספציפיים",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "ערוך חשבון",
|
||||
|
||||
@@ -1217,6 +1217,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_rejected:
|
||||
"La richiesta di accesso all'attrezzatura è stata rifiutata.",
|
||||
},
|
||||
custom_skills: "Competenze personalizzate",
|
||||
agent_flows: "Flussi di agenti",
|
||||
no_tools_found: "Nessuno strumento corrispondente trovato.",
|
||||
loading_mcp_servers: "Inizio caricamento dei server MCP...",
|
||||
app_integrations: "Integrazioni di applicazioni",
|
||||
sub_skills: "Competenze specifiche",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Modifica account",
|
||||
|
||||
@@ -1168,6 +1168,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "ツールの使用許可が承認されました",
|
||||
tool_call_was_rejected: "ツール呼び出しは拒否されました",
|
||||
},
|
||||
custom_skills: "カスタマイズ可能なスキル",
|
||||
agent_flows: "エージェント間の流れ",
|
||||
no_tools_found: "一致するツールは見つかりませんでした",
|
||||
loading_mcp_servers: "MCP サーバーの読み込み中...",
|
||||
app_integrations: "アプリケーション連携",
|
||||
sub_skills: "専門スキル",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "アカウントを編集",
|
||||
|
||||
@@ -1249,6 +1249,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "도구 사용 승인",
|
||||
tool_call_was_rejected: "도구 호출이 거부되었습니다.",
|
||||
},
|
||||
custom_skills: "맞춤형 기술",
|
||||
agent_flows: "에이전트 흐름",
|
||||
no_tools_found: "일치하는 도구가 없습니다.",
|
||||
loading_mcp_servers: "MCP 서버 로딩 중...",
|
||||
app_integrations: "앱 통합",
|
||||
sub_skills: "세부 기술",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "계정 정보 수정",
|
||||
|
||||
@@ -1312,6 +1312,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "Įrankių užsakymas buvo patvirtintas.",
|
||||
tool_call_was_rejected: "Klausimas dėl įrankio buvo atmetamas.",
|
||||
},
|
||||
custom_skills: "Individualūs įgūdžiai",
|
||||
agent_flows: "Agentų srautai",
|
||||
no_tools_found: "Nėra rasti atitikusių įrankių.",
|
||||
loading_mcp_servers: "Įkrauname MCP serverius...",
|
||||
app_integrations: "Programų integracijos",
|
||||
sub_skills: "Pagrindinės įgūdžios",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Redaguoti paskyrą",
|
||||
|
||||
@@ -1293,6 +1293,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_rejected:
|
||||
"Pieprasījums par instrumenta izmantošanu tika atgrūstīts.",
|
||||
},
|
||||
custom_skills: "Pielāgotas prasmes",
|
||||
agent_flows: "Aģentu plūsmas",
|
||||
no_tools_found: "Neatradusies atbilstošas instrumentus",
|
||||
loading_mcp_servers: "Ielāde MCP serverus...",
|
||||
app_integrations: "Dienvidligzdas integrācijas",
|
||||
sub_skills: "Īpašās prasmes",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Rediģēt kontu",
|
||||
|
||||
@@ -1196,6 +1196,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_rejected:
|
||||
"De aanvraag om het gereedschap te gebruiken is afgewezen.",
|
||||
},
|
||||
custom_skills: "Aangepaste vaardigheden",
|
||||
agent_flows: "Stroom van agenten",
|
||||
no_tools_found: "Geen overeenkomende gereedschappen gevonden.",
|
||||
loading_mcp_servers: "MCP-servers worden geladen...",
|
||||
app_integrations: "Integraties met apps",
|
||||
sub_skills: "Specifieke vaardigheden",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Account bewerken",
|
||||
|
||||
@@ -1297,6 +1297,12 @@ const TRANSLATIONS = {
|
||||
"Zgłoszenie dotyczące narzędzia zostało zatwierdzone.",
|
||||
tool_call_was_rejected: "Żądanie użycia narzędzia zostało odrzucone.",
|
||||
},
|
||||
custom_skills: "Dostosowane umiejętności",
|
||||
agent_flows: "Przepływy agencji",
|
||||
no_tools_found: "Nie znaleziono odpowiadających narzędzi.",
|
||||
loading_mcp_servers: "Ładowanie serwerów MCP...",
|
||||
app_integrations: "Integracje z aplikacjami",
|
||||
sub_skills: "Specyficzne umiejętności",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Edytuj konto",
|
||||
|
||||
@@ -1280,6 +1280,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_rejected:
|
||||
"A solicitação de acesso à ferramenta foi rejeitada.",
|
||||
},
|
||||
custom_skills: "Habilidades personalizadas",
|
||||
agent_flows: "Fluxo de Agentes",
|
||||
no_tools_found: "Nenhuma ferramenta correspondente encontrada.",
|
||||
loading_mcp_servers: "Carregando servidores MCP...",
|
||||
app_integrations: "Integrações de aplicativos",
|
||||
sub_skills: "Habilidades específicas",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Editar conta",
|
||||
|
||||
@@ -577,6 +577,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_rejected:
|
||||
"Cererea de utilizare a instrumentului a fost respinsă.",
|
||||
},
|
||||
custom_skills: "Abilități personalizate",
|
||||
agent_flows: "Fluxuri de agenți",
|
||||
no_tools_found: "Nu au fost găsite instrumente corespunzătoare.",
|
||||
loading_mcp_servers: "Încărcare servere MCP...",
|
||||
app_integrations: "Integrarea aplicațiilor",
|
||||
sub_skills: "Abilități specifice",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Editează contul",
|
||||
|
||||
@@ -1206,6 +1206,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_rejected:
|
||||
"Запрос на предоставление инструмента был отклонен.",
|
||||
},
|
||||
custom_skills: "Индивидуальные навыки",
|
||||
agent_flows: "Поток агентов",
|
||||
no_tools_found: "Не найдено соответствующих инструментов.",
|
||||
loading_mcp_servers: "Загрузка серверов MCP...",
|
||||
app_integrations: "Интеграция с приложениями",
|
||||
sub_skills: "Подквалификация",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Редактировать учётную запись",
|
||||
|
||||
@@ -1200,6 +1200,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "Araç talebi onaylandı.",
|
||||
tool_call_was_rejected: "Ara çağrısı reddedildi.",
|
||||
},
|
||||
custom_skills: "Özel Beceri",
|
||||
agent_flows: "Ajans Akışları",
|
||||
no_tools_found: "Uyumlu herhangi bir araç bulunamadı",
|
||||
loading_mcp_servers: "MCP sunucularının yüklenmesi...",
|
||||
app_integrations: "Uygulama Entegrasyonları",
|
||||
sub_skills: "Alt beceriler",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Hesabı Düzenle",
|
||||
|
||||
@@ -1184,6 +1184,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "Đã được phê duyệt yêu cầu dụng cụ.",
|
||||
tool_call_was_rejected: "Yêu cầu gọi công cụ đã bị từ chối.",
|
||||
},
|
||||
custom_skills: "Kỹ năng tùy chỉnh",
|
||||
agent_flows: "Dòng chảy của đại lý",
|
||||
no_tools_found: "Không tìm thấy công cụ tương ứng.",
|
||||
loading_mcp_servers: "Đang tải các máy chủ MCP...",
|
||||
app_integrations: "Tích hợp ứng dụng",
|
||||
sub_skills: "Kỹ năng chuyên môn",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "Chỉnh sửa Tài khoản",
|
||||
|
||||
@@ -1195,6 +1195,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "工具使用申请已获得批准。",
|
||||
tool_call_was_rejected: "请求获取工具已被拒绝。",
|
||||
},
|
||||
custom_skills: "定制技能",
|
||||
agent_flows: "代理人流动",
|
||||
no_tools_found: "未找到匹配的工具",
|
||||
loading_mcp_servers: "正在加载 MCP 服务器…",
|
||||
app_integrations: "应用程序集成",
|
||||
sub_skills: "基本技能",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "编辑帐户",
|
||||
|
||||
@@ -1108,6 +1108,12 @@ const TRANSLATIONS = {
|
||||
tool_call_was_approved: "工具請求已獲得批准。",
|
||||
tool_call_was_rejected: "請求已遭拒絕",
|
||||
},
|
||||
custom_skills: "客製化技能",
|
||||
agent_flows: "代理人流",
|
||||
no_tools_found: "未找到匹配的工具",
|
||||
loading_mcp_servers: "正在載入 MCP 伺服器...",
|
||||
app_integrations: "應用程式整合",
|
||||
sub_skills: "細項技能",
|
||||
},
|
||||
profile_settings: {
|
||||
edit_account: "編輯帳戶",
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@phosphor-icons/react";
|
||||
import Admin from "@/models/admin";
|
||||
|
||||
const getCreateFileSkills = (t) => [
|
||||
export const getCreateFileSkills = (t) => [
|
||||
{
|
||||
name: "create-text-file",
|
||||
title: t("agent.skill.createFiles.skills.create-text-file.title"),
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from "@phosphor-icons/react";
|
||||
import Admin from "@/models/admin";
|
||||
|
||||
const getFileSystemSubSkills = (t) => {
|
||||
export const getFileSystemSubSkills = (t) => {
|
||||
return [
|
||||
{
|
||||
name: "filesystem-read-text-file",
|
||||
|
||||
Reference in New Issue
Block a user