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:
parent
4f3f77119d
commit
55567239b0
@ -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({
|
export default function SkillRow({
|
||||||
name,
|
name,
|
||||||
@ -7,24 +8,30 @@ export default function SkillRow({
|
|||||||
highlighted = false,
|
highlighted = false,
|
||||||
disabled = 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";
|
if (highlighted) classNames += " bg-zinc-700/50 light:bg-slate-100";
|
||||||
else classNames += " hover:bg-zinc-700/50 light:hover:bg-slate-100";
|
else classNames += " hover:bg-zinc-700/50 light:hover:bg-slate-100";
|
||||||
|
|
||||||
if (disabled) classNames += " opacity-60 cursor-not-allowed";
|
if (disabled) classNames += " opacity-60 cursor-not-allowed";
|
||||||
else classNames += " cursor-pointer";
|
else classNames += " cursor-pointer";
|
||||||
return (
|
return (
|
||||||
<div
|
<button
|
||||||
|
ref={ref}
|
||||||
|
type="button"
|
||||||
className={classNames}
|
className={classNames}
|
||||||
|
onClick={() => !disabled && onToggle()}
|
||||||
data-tooltip-id={disabled ? "agent-skill-disabled-tooltip" : undefined}
|
data-tooltip-id={disabled ? "agent-skill-disabled-tooltip" : undefined}
|
||||||
>
|
>
|
||||||
<span className="text-xs text-white light:text-slate-900">{name}</span>
|
<span className="text-xs text-white light:text-slate-900">{name}</span>
|
||||||
<Toggle
|
<div className="pointer-events-none" aria-hidden="true">
|
||||||
size="sm"
|
<SimpleToggleSwitch size="sm" enabled={enabled} />
|
||||||
enabled={enabled}
|
</div>
|
||||||
onChange={onToggle}
|
</button>
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import paths from "@/utils/paths";
|
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 {
|
import {
|
||||||
getDefaultSkills,
|
getDefaultSkills,
|
||||||
getConfigurableSkills,
|
getConfigurableSkills,
|
||||||
|
getAppIntegrationSkills,
|
||||||
} from "@/pages/Admin/Agents/skills";
|
} from "@/pages/Admin/Agents/skills";
|
||||||
import useToolsMenuItems from "../../useToolsMenuItems";
|
import useToolsMenuItems from "../../useToolsMenuItems";
|
||||||
|
import useAgentSkillsState from "./useAgentSkillsState";
|
||||||
|
import useSkillSections from "./useSkillSections";
|
||||||
import SkillRow from "./SkillRow";
|
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";
|
import { useIsAgentSessionActive } from "@/utils/chat/agent";
|
||||||
|
|
||||||
|
const MIN_ITEMS_TO_SHOW_SEARCH = 10;
|
||||||
|
|
||||||
export default function AgentSkillsTab({
|
export default function AgentSkillsTab({
|
||||||
highlightedIndex = -1,
|
highlightedIndex = -1,
|
||||||
registerItemCount,
|
registerItemCount,
|
||||||
@ -23,133 +25,151 @@ export default function AgentSkillsTab({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { showAgentCommand = true } = workspace ?? {};
|
const { showAgentCommand = true } = workspace ?? {};
|
||||||
const agentSessionActive = useIsAgentSessionActive();
|
const agentSessionActive = useIsAgentSessionActive();
|
||||||
|
|
||||||
|
// Get skill definitions
|
||||||
const defaultSkills = getDefaultSkills(t);
|
const defaultSkills = getDefaultSkills(t);
|
||||||
const [fileSystemAgentAvailable, setFileSystemAgentAvailable] =
|
const appIntegrationSkills = getAppIntegrationSkills(t);
|
||||||
useState(false);
|
|
||||||
|
// 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, {
|
const configurableSkills = getConfigurableSkills(t, {
|
||||||
fileSystemAgentAvailable,
|
fileSystemAgentAvailable,
|
||||||
});
|
});
|
||||||
const [disabledDefaults, setDisabledDefaults] = useState([]);
|
|
||||||
const [enabledConfigurable, setEnabledConfigurable] = useState([]);
|
// UI state
|
||||||
const [importedSkills, setImportedSkills] = useState([]);
|
const [expandedSections, setExpandedSections] = useState({});
|
||||||
const [flows, setFlows] = useState([]);
|
const [expandedSubSections, setExpandedSubSections] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
|
||||||
const showAgentCmdActivationAlert = showAgentCommand && !agentSessionActive;
|
const showAgentCmdActivationAlert = showAgentCommand && !agentSessionActive;
|
||||||
|
|
||||||
useEffect(() => {
|
// Build all sections
|
||||||
fetchSkillSettings();
|
const sections = useSkillSections({
|
||||||
}, []);
|
t,
|
||||||
|
defaultSkills,
|
||||||
|
configurableSkills,
|
||||||
|
appIntegrationSkills,
|
||||||
|
importedSkills,
|
||||||
|
flows,
|
||||||
|
mcpServers,
|
||||||
|
isSkillEnabled,
|
||||||
|
toggleSkill,
|
||||||
|
isSubSkillEnabled,
|
||||||
|
toggleSubSkill,
|
||||||
|
toggleImportedSkill,
|
||||||
|
toggleFlow,
|
||||||
|
toggleMcpTool,
|
||||||
|
disabledSubSkills,
|
||||||
|
});
|
||||||
|
|
||||||
async function fetchSkillSettings() {
|
// Section expansion helpers
|
||||||
try {
|
function isSectionExpanded(sectionId) {
|
||||||
const [prefs, flowsRes, fsAgentAvailable] = await Promise.all([
|
return !!(searchQuery.trim() || expandedSections[sectionId]);
|
||||||
Admin.systemPreferencesByFields([
|
}
|
||||||
"disabled_agent_skills",
|
|
||||||
"default_agent_skills",
|
|
||||||
"imported_agent_skills",
|
|
||||||
]),
|
|
||||||
AgentFlows.listFlows(),
|
|
||||||
System.isFileSystemAgentAvailable(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (prefs?.settings) {
|
function toggleSection(sectionId) {
|
||||||
setDisabledDefaults(prefs.settings.disabled_agent_skills ?? []);
|
setExpandedSections((prev) => ({
|
||||||
setEnabledConfigurable(prefs.settings.default_agent_skills ?? []);
|
...prev,
|
||||||
setImportedSkills(prefs.settings.imported_agent_skills ?? []);
|
[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) {
|
const totalItemCount = sections.reduce((sum, s) => sum + s.items.length, 0);
|
||||||
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]);
|
|
||||||
|
|
||||||
useToolsMenuItems({
|
useToolsMenuItems({
|
||||||
items,
|
items: flatItems,
|
||||||
highlightedIndex,
|
highlightedIndex,
|
||||||
onSelect: agentSessionActive ? () => {} : (item) => item.onToggle(),
|
onSelect: (item) => {
|
||||||
|
if (item.type === "header") return item.onToggle();
|
||||||
|
if (!agentSessionActive) item.onToggle();
|
||||||
|
},
|
||||||
registerItemCount,
|
registerItemCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -162,18 +182,82 @@ export default function AgentSkillsTab({
|
|||||||
{t("chat_window.use_agent_session_to_use_tools")}
|
{t("chat_window.use_agent_session_to_use_tools")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{items.map((item, index) => (
|
{totalItemCount >= MIN_ITEMS_TO_SHOW_SEARCH && (
|
||||||
<SkillRow
|
<SearchInput
|
||||||
key={item.id}
|
value={searchQuery}
|
||||||
name={item.name}
|
onChange={setSearchQuery}
|
||||||
enabled={item.enabled}
|
placeholder={t("common.search")}
|
||||||
onToggle={item.onToggle}
|
|
||||||
highlighted={highlightedIndex === index}
|
|
||||||
disabled={agentSessionActive}
|
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{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()}>
|
<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" />
|
<Wrench size={12} className="text-theme-text-primary" />
|
||||||
<span className="text-xs text-theme-text-primary">
|
<span className="text-xs text-theme-text-primary">
|
||||||
{t("chat_window.manage_agent_skills")}
|
{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>
|
||||||
|
|
||||||
<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
|
<ActiveTab
|
||||||
sendCommand={sendCommand}
|
sendCommand={sendCommand}
|
||||||
setShowing={setShowing}
|
setShowing={setShowing}
|
||||||
|
|||||||
@ -1174,6 +1174,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "تمت الموافقة على طلب الحصول على الأدوات.",
|
tool_call_was_approved: "تمت الموافقة على طلب الحصول على الأدوات.",
|
||||||
tool_call_was_rejected: "تم رفض طلب الاتصال بالأداة.",
|
tool_call_was_rejected: "تم رفض طلب الاتصال بالأداة.",
|
||||||
},
|
},
|
||||||
|
custom_skills: "المهارات المخصصة",
|
||||||
|
agent_flows: "تدفقات الوكلاء",
|
||||||
|
no_tools_found: "لم يتم العثور على أدوات مطابقة.",
|
||||||
|
loading_mcp_servers: "تحميل خوادم MCP...",
|
||||||
|
app_integrations: "تكامل التطبيقات",
|
||||||
|
sub_skills: "مهارات فرعية",
|
||||||
},
|
},
|
||||||
profile_settings: {
|
profile_settings: {
|
||||||
edit_account: "تحرير الحساب",
|
edit_account: "تحرير الحساب",
|
||||||
|
|||||||
@ -1399,6 +1399,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "La crida a l'eina ha estat aprovada",
|
tool_call_was_approved: "La crida a l'eina ha estat aprovada",
|
||||||
tool_call_was_rejected: "La crida a l'eina ha estat rebutjada",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Edita el compte",
|
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_approved: "Žádost o použití nástroje byla schválena.",
|
||||||
tool_call_was_rejected: "Žádost o použití nástroje byla zamítnuta.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Upravit účet",
|
edit_account: "Upravit účet",
|
||||||
|
|||||||
@ -1188,6 +1188,12 @@ const TRANSLATIONS = {
|
|||||||
"Anmodningen om at bruge værktøjet blev godkendt.",
|
"Anmodningen om at bruge værktøjet blev godkendt.",
|
||||||
tool_call_was_rejected: "Anmodningen om at bruge værktøjet blev afvist.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Rediger konto",
|
edit_account: "Rediger konto",
|
||||||
|
|||||||
@ -1304,6 +1304,12 @@ const TRANSLATIONS = {
|
|||||||
"Die Genehmigung für die Bestellung der Werkzeuge wurde erteilt.",
|
"Die Genehmigung für die Bestellung der Werkzeuge wurde erteilt.",
|
||||||
tool_call_was_rejected: "Die Anfrage nach dem Werkzeug wurde abgelehnt.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Account bearbeiten",
|
edit_account: "Account bearbeiten",
|
||||||
|
|||||||
@ -1336,6 +1336,12 @@ const TRANSLATIONS = {
|
|||||||
slash_commands: "Slash Commands",
|
slash_commands: "Slash Commands",
|
||||||
agent_skills: "Agent Skills",
|
agent_skills: "Agent Skills",
|
||||||
manage_agent_skills: "Manage 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",
|
start_agent_session: "Start Agent Session",
|
||||||
agent_skills_disabled_in_session:
|
agent_skills_disabled_in_session:
|
||||||
"Can't modify skills during an active agent session. Use /exit to end the session first.",
|
"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_approved: "La solicitud de herramientas ha sido aprobada.",
|
||||||
tool_call_was_rejected: "La solicitud de herramienta fue rechazada.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Editar cuenta",
|
edit_account: "Editar cuenta",
|
||||||
|
|||||||
@ -1247,6 +1247,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "Vahendite tellimuse kinnitati.",
|
tool_call_was_approved: "Vahendite tellimuse kinnitati.",
|
||||||
tool_call_was_rejected: "Vahendite taotlus jäeti rahuldamata.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Muuda kontot",
|
edit_account: "Muuda kontot",
|
||||||
|
|||||||
@ -1180,6 +1180,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "درخواست برای تهیه ابزار تأیید شد.",
|
tool_call_was_approved: "درخواست برای تهیه ابزار تأیید شد.",
|
||||||
tool_call_was_rejected: "درخواست استفاده از ابزار رد شد.",
|
tool_call_was_rejected: "درخواست استفاده از ابزار رد شد.",
|
||||||
},
|
},
|
||||||
|
custom_skills: "مهارتهای تخصصی",
|
||||||
|
agent_flows: "جریانهای نمایندگی",
|
||||||
|
no_tools_found: "هیچ ابزار مشابهی یافت نشد.",
|
||||||
|
loading_mcp_servers: "بارگذاری سرورهای MCP...",
|
||||||
|
app_integrations: "ادغام با برنامهها",
|
||||||
|
sub_skills: "مهارتهای پایه",
|
||||||
},
|
},
|
||||||
profile_settings: {
|
profile_settings: {
|
||||||
edit_account: "ویرایش حساب",
|
edit_account: "ویرایش حساب",
|
||||||
|
|||||||
@ -1212,6 +1212,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_rejected:
|
tool_call_was_rejected:
|
||||||
"La demande d'utilisation de l'outil a été rejetée.",
|
"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: {
|
profile_settings: {
|
||||||
edit_account: "Modifier le compte",
|
edit_account: "Modifier le compte",
|
||||||
|
|||||||
@ -1236,6 +1236,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "הבקשה לקבלת הכלי אושרה.",
|
tool_call_was_approved: "הבקשה לקבלת הכלי אושרה.",
|
||||||
tool_call_was_rejected: "בקשת השימוש בכלי נדחתה.",
|
tool_call_was_rejected: "בקשת השימוש בכלי נדחתה.",
|
||||||
},
|
},
|
||||||
|
custom_skills: "כישורים מותאמים אישית",
|
||||||
|
agent_flows: "זרימת סוכנים",
|
||||||
|
no_tools_found: "לא נמצאו כלים תואמים.",
|
||||||
|
loading_mcp_servers: "טעינת שרתי ה-MCP...",
|
||||||
|
app_integrations: "אינטגרציות עם אפליקציות",
|
||||||
|
sub_skills: "כישורים ספציפיים",
|
||||||
},
|
},
|
||||||
profile_settings: {
|
profile_settings: {
|
||||||
edit_account: "ערוך חשבון",
|
edit_account: "ערוך חשבון",
|
||||||
|
|||||||
@ -1217,6 +1217,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_rejected:
|
tool_call_was_rejected:
|
||||||
"La richiesta di accesso all'attrezzatura è stata rifiutata.",
|
"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: {
|
profile_settings: {
|
||||||
edit_account: "Modifica account",
|
edit_account: "Modifica account",
|
||||||
|
|||||||
@ -1168,6 +1168,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "ツールの使用許可が承認されました",
|
tool_call_was_approved: "ツールの使用許可が承認されました",
|
||||||
tool_call_was_rejected: "ツール呼び出しは拒否されました",
|
tool_call_was_rejected: "ツール呼び出しは拒否されました",
|
||||||
},
|
},
|
||||||
|
custom_skills: "カスタマイズ可能なスキル",
|
||||||
|
agent_flows: "エージェント間の流れ",
|
||||||
|
no_tools_found: "一致するツールは見つかりませんでした",
|
||||||
|
loading_mcp_servers: "MCP サーバーの読み込み中...",
|
||||||
|
app_integrations: "アプリケーション連携",
|
||||||
|
sub_skills: "専門スキル",
|
||||||
},
|
},
|
||||||
profile_settings: {
|
profile_settings: {
|
||||||
edit_account: "アカウントを編集",
|
edit_account: "アカウントを編集",
|
||||||
|
|||||||
@ -1249,6 +1249,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "도구 사용 승인",
|
tool_call_was_approved: "도구 사용 승인",
|
||||||
tool_call_was_rejected: "도구 호출이 거부되었습니다.",
|
tool_call_was_rejected: "도구 호출이 거부되었습니다.",
|
||||||
},
|
},
|
||||||
|
custom_skills: "맞춤형 기술",
|
||||||
|
agent_flows: "에이전트 흐름",
|
||||||
|
no_tools_found: "일치하는 도구가 없습니다.",
|
||||||
|
loading_mcp_servers: "MCP 서버 로딩 중...",
|
||||||
|
app_integrations: "앱 통합",
|
||||||
|
sub_skills: "세부 기술",
|
||||||
},
|
},
|
||||||
profile_settings: {
|
profile_settings: {
|
||||||
edit_account: "계정 정보 수정",
|
edit_account: "계정 정보 수정",
|
||||||
|
|||||||
@ -1312,6 +1312,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "Įrankių užsakymas buvo patvirtintas.",
|
tool_call_was_approved: "Įrankių užsakymas buvo patvirtintas.",
|
||||||
tool_call_was_rejected: "Klausimas dėl įrankio buvo atmetamas.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Redaguoti paskyrą",
|
edit_account: "Redaguoti paskyrą",
|
||||||
|
|||||||
@ -1293,6 +1293,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_rejected:
|
tool_call_was_rejected:
|
||||||
"Pieprasījums par instrumenta izmantošanu tika atgrūstīts.",
|
"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: {
|
profile_settings: {
|
||||||
edit_account: "Rediģēt kontu",
|
edit_account: "Rediģēt kontu",
|
||||||
|
|||||||
@ -1196,6 +1196,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_rejected:
|
tool_call_was_rejected:
|
||||||
"De aanvraag om het gereedschap te gebruiken is afgewezen.",
|
"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: {
|
profile_settings: {
|
||||||
edit_account: "Account bewerken",
|
edit_account: "Account bewerken",
|
||||||
|
|||||||
@ -1297,6 +1297,12 @@ const TRANSLATIONS = {
|
|||||||
"Zgłoszenie dotyczące narzędzia zostało zatwierdzone.",
|
"Zgłoszenie dotyczące narzędzia zostało zatwierdzone.",
|
||||||
tool_call_was_rejected: "Żądanie użycia narzędzia zostało odrzucone.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Edytuj konto",
|
edit_account: "Edytuj konto",
|
||||||
|
|||||||
@ -1280,6 +1280,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_rejected:
|
tool_call_was_rejected:
|
||||||
"A solicitação de acesso à ferramenta foi rejeitada.",
|
"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: {
|
profile_settings: {
|
||||||
edit_account: "Editar conta",
|
edit_account: "Editar conta",
|
||||||
|
|||||||
@ -577,6 +577,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_rejected:
|
tool_call_was_rejected:
|
||||||
"Cererea de utilizare a instrumentului a fost respinsă.",
|
"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: {
|
profile_settings: {
|
||||||
edit_account: "Editează contul",
|
edit_account: "Editează contul",
|
||||||
|
|||||||
@ -1206,6 +1206,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_rejected:
|
tool_call_was_rejected:
|
||||||
"Запрос на предоставление инструмента был отклонен.",
|
"Запрос на предоставление инструмента был отклонен.",
|
||||||
},
|
},
|
||||||
|
custom_skills: "Индивидуальные навыки",
|
||||||
|
agent_flows: "Поток агентов",
|
||||||
|
no_tools_found: "Не найдено соответствующих инструментов.",
|
||||||
|
loading_mcp_servers: "Загрузка серверов MCP...",
|
||||||
|
app_integrations: "Интеграция с приложениями",
|
||||||
|
sub_skills: "Подквалификация",
|
||||||
},
|
},
|
||||||
profile_settings: {
|
profile_settings: {
|
||||||
edit_account: "Редактировать учётную запись",
|
edit_account: "Редактировать учётную запись",
|
||||||
|
|||||||
@ -1200,6 +1200,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "Araç talebi onaylandı.",
|
tool_call_was_approved: "Araç talebi onaylandı.",
|
||||||
tool_call_was_rejected: "Ara çağrısı reddedildi.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Hesabı Düzenle",
|
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_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.",
|
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: {
|
profile_settings: {
|
||||||
edit_account: "Chỉnh sửa Tài khoản",
|
edit_account: "Chỉnh sửa Tài khoản",
|
||||||
|
|||||||
@ -1195,6 +1195,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "工具使用申请已获得批准。",
|
tool_call_was_approved: "工具使用申请已获得批准。",
|
||||||
tool_call_was_rejected: "请求获取工具已被拒绝。",
|
tool_call_was_rejected: "请求获取工具已被拒绝。",
|
||||||
},
|
},
|
||||||
|
custom_skills: "定制技能",
|
||||||
|
agent_flows: "代理人流动",
|
||||||
|
no_tools_found: "未找到匹配的工具",
|
||||||
|
loading_mcp_servers: "正在加载 MCP 服务器…",
|
||||||
|
app_integrations: "应用程序集成",
|
||||||
|
sub_skills: "基本技能",
|
||||||
},
|
},
|
||||||
profile_settings: {
|
profile_settings: {
|
||||||
edit_account: "编辑帐户",
|
edit_account: "编辑帐户",
|
||||||
|
|||||||
@ -1108,6 +1108,12 @@ const TRANSLATIONS = {
|
|||||||
tool_call_was_approved: "工具請求已獲得批准。",
|
tool_call_was_approved: "工具請求已獲得批准。",
|
||||||
tool_call_was_rejected: "請求已遭拒絕",
|
tool_call_was_rejected: "請求已遭拒絕",
|
||||||
},
|
},
|
||||||
|
custom_skills: "客製化技能",
|
||||||
|
agent_flows: "代理人流",
|
||||||
|
no_tools_found: "未找到匹配的工具",
|
||||||
|
loading_mcp_servers: "正在載入 MCP 伺服器...",
|
||||||
|
app_integrations: "應用程式整合",
|
||||||
|
sub_skills: "細項技能",
|
||||||
},
|
},
|
||||||
profile_settings: {
|
profile_settings: {
|
||||||
edit_account: "編輯帳戶",
|
edit_account: "編輯帳戶",
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import Admin from "@/models/admin";
|
import Admin from "@/models/admin";
|
||||||
|
|
||||||
const getCreateFileSkills = (t) => [
|
export const getCreateFileSkills = (t) => [
|
||||||
{
|
{
|
||||||
name: "create-text-file",
|
name: "create-text-file",
|
||||||
title: t("agent.skill.createFiles.skills.create-text-file.title"),
|
title: t("agent.skill.createFiles.skills.create-text-file.title"),
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import Admin from "@/models/admin";
|
import Admin from "@/models/admin";
|
||||||
|
|
||||||
const getFileSystemSubSkills = (t) => {
|
export const getFileSystemSubSkills = (t) => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: "filesystem-read-text-file",
|
name: "filesystem-read-text-file",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user