mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-21 01:22:54 +00:00
🌉 chore: Gate Skills UI by Agent Capability Checks (#12793)
- Updated the Skills component to include a check for `skillsEnabled` from agent capabilities, ensuring skills are only displayed when both permissions and capabilities allow. - Modified the `useHandleKeyUp` hook to prevent triggering skill commands when skills are disabled, enhancing user experience and preventing errors. - Added tests to verify that skills commands do not trigger when skills are disabled, ensuring robust functionality. - Refactored the `useSideNavLinks` hook to incorporate skills capability checks, ensuring consistent access control across the application.
This commit is contained in:
parent
d83cb84f59
commit
523994220d
4 changed files with 58 additions and 7 deletions
|
|
@ -1,8 +1,8 @@
|
|||
import React, { memo } from 'react';
|
||||
import { ScrollText } from 'lucide-react';
|
||||
import { CheckboxButton } from '@librechat/client';
|
||||
import { PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import { useLocalize, useHasAccess } from '~/hooks';
|
||||
import { Permissions, PermissionTypes, defaultAgentCapabilities } from 'librechat-data-provider';
|
||||
import { useLocalize, useHasAccess, useAgentCapabilities } from '~/hooks';
|
||||
import { useBadgeRowContext } from '~/Providers';
|
||||
|
||||
function Skills() {
|
||||
|
|
@ -15,7 +15,11 @@ function Skills() {
|
|||
permission: Permissions.USE,
|
||||
});
|
||||
|
||||
if (!canUseSkills) {
|
||||
const { skillsEnabled } = useAgentCapabilities(
|
||||
context?.agentsConfig?.capabilities ?? defaultAgentCapabilities,
|
||||
);
|
||||
|
||||
if (!canUseSkills || !skillsEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const mockSetShowSkillsPopover = jest.fn();
|
|||
const mockHasPromptsAccess = { current: true };
|
||||
const mockHasMultiConvoAccess = { current: true };
|
||||
const mockHasSkillsAccess = { current: true };
|
||||
const mockSkillsEnabled = { current: true };
|
||||
const mockEndpoint = { current: 'openAI' as string | null };
|
||||
const mockCommandToggles = { at: true, plus: true, slash: true, dollar: true };
|
||||
|
||||
|
|
@ -76,6 +77,16 @@ jest.mock('~/hooks/Roles/useHasAccess', () =>
|
|||
}),
|
||||
);
|
||||
|
||||
jest.mock('~/hooks/Agents/useGetAgentsConfig', () =>
|
||||
jest.fn(() => ({ agentsConfig: { capabilities: [] } })),
|
||||
);
|
||||
|
||||
jest.mock('~/hooks/Agents/useAgentCapabilities', () =>
|
||||
jest.fn(() => ({
|
||||
skillsEnabled: mockSkillsEnabled.current,
|
||||
})),
|
||||
);
|
||||
|
||||
import React from 'react';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import useHandleKeyUp from './useHandleKeyUp';
|
||||
|
|
@ -118,6 +129,7 @@ beforeEach(() => {
|
|||
mockHasPromptsAccess.current = true;
|
||||
mockHasMultiConvoAccess.current = true;
|
||||
mockHasSkillsAccess.current = true;
|
||||
mockSkillsEnabled.current = true;
|
||||
mockEndpoint.current = 'openAI';
|
||||
mockCommandToggles.at = true;
|
||||
mockCommandToggles.plus = true;
|
||||
|
|
@ -424,6 +436,16 @@ describe('useHandleKeyUp', () => {
|
|||
|
||||
expect(setShowSkillsPopover).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does NOT trigger $ skill command when skills capability is disabled on agents endpoint', () => {
|
||||
mockSkillsEnabled.current = false;
|
||||
const ref = makeTextAreaRef('$', 1);
|
||||
const { handleKeyUp, setShowSkillsPopover } = renderUseHandleKeyUp(ref);
|
||||
|
||||
act(() => handleKeyUp(makeKeyEvent('$')));
|
||||
|
||||
expect(setShowSkillsPopover).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('endpoint gating', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useSetRecoilState, useRecoilValue } from 'recoil';
|
||||
import { PermissionTypes, Permissions, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities';
|
||||
import useGetAgentsConfig from '~/hooks/Agents/useGetAgentsConfig';
|
||||
import useHasAccess from '~/hooks/Roles/useHasAccess';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -65,6 +67,8 @@ const useHandleKeyUp = ({
|
|||
permissionType: PermissionTypes.SKILLS,
|
||||
permission: Permissions.USE,
|
||||
});
|
||||
const { agentsConfig } = useGetAgentsConfig();
|
||||
const { skillsEnabled } = useAgentCapabilities(agentsConfig?.capabilities);
|
||||
const latestMessage = useRecoilValue(store.latestMessageFamily(index));
|
||||
const endpoint = useRecoilValue(store.effectiveEndpointByIndex(index));
|
||||
const setShowMentionPopover = useSetRecoilState(store.showMentionPopoverFamily(index));
|
||||
|
|
@ -109,13 +113,25 @@ const useHandleKeyUp = ({
|
|||
}, [textAreaRef, hasPromptsAccess, setShowPromptsPopover, slashCommandEnabled]);
|
||||
|
||||
const handleSkillsCommand = useCallback(() => {
|
||||
if (!hasSkillsAccess || !dollarCommandEnabled || isAssistantsEndpoint(endpoint)) {
|
||||
if (
|
||||
!hasSkillsAccess ||
|
||||
!skillsEnabled ||
|
||||
!dollarCommandEnabled ||
|
||||
isAssistantsEndpoint(endpoint)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (shouldTriggerCommand(textAreaRef, '$')) {
|
||||
setShowSkillsPopover(true);
|
||||
}
|
||||
}, [textAreaRef, hasSkillsAccess, setShowSkillsPopover, dollarCommandEnabled, endpoint]);
|
||||
}, [
|
||||
textAreaRef,
|
||||
hasSkillsAccess,
|
||||
skillsEnabled,
|
||||
setShowSkillsPopover,
|
||||
dollarCommandEnabled,
|
||||
endpoint,
|
||||
]);
|
||||
|
||||
const commandHandlers = useMemo(
|
||||
() => ({
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ import {
|
|||
} from 'librechat-data-provider';
|
||||
import type { TInterfaceConfig, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import type { NavLink } from '~/common';
|
||||
import {
|
||||
useAgentCapabilities,
|
||||
useMCPServerManager,
|
||||
useGetAgentsConfig,
|
||||
useHasAccess,
|
||||
} from '~/hooks';
|
||||
import MCPBuilderPanel from '~/components/SidePanel/MCPBuilder/MCPBuilderPanel';
|
||||
import AgentPanelSwitch from '~/components/SidePanel/Agents/AgentPanelSwitch';
|
||||
import BookmarkPanel from '~/components/SidePanel/Bookmarks/BookmarkPanel';
|
||||
|
|
@ -26,7 +32,6 @@ import PanelSwitch from '~/components/SidePanel/Builder/PanelSwitch';
|
|||
import Parameters from '~/components/SidePanel/Parameters/Panel';
|
||||
import { MemoryPanel } from '~/components/SidePanel/Memories';
|
||||
import FilesPanel from '~/components/SidePanel/Files/Panel';
|
||||
import { useHasAccess, useMCPServerManager } from '~/hooks';
|
||||
import { PromptsAccordion } from '~/components/Prompts';
|
||||
import { SkillsAccordion } from '~/components/Skills';
|
||||
|
||||
|
|
@ -85,6 +90,9 @@ export default function useSideNavLinks({
|
|||
});
|
||||
const { availableMCPServers } = useMCPServerManager();
|
||||
|
||||
const { agentsConfig } = useGetAgentsConfig({ endpointsConfig });
|
||||
const { skillsEnabled } = useAgentCapabilities(agentsConfig?.capabilities);
|
||||
|
||||
const Links = useMemo(() => {
|
||||
const links: NavLink[] = [];
|
||||
|
||||
|
|
@ -122,7 +130,7 @@ export default function useSideNavLinks({
|
|||
});
|
||||
}
|
||||
|
||||
if (hasAccessToSkills) {
|
||||
if (hasAccessToSkills && skillsEnabled) {
|
||||
links.push({
|
||||
title: 'com_ui_skills',
|
||||
label: '',
|
||||
|
|
@ -217,6 +225,7 @@ export default function useSideNavLinks({
|
|||
hasAccessToCreateAgents,
|
||||
hasAccessToPrompts,
|
||||
hasAccessToSkills,
|
||||
skillsEnabled,
|
||||
hasAccessToMemories,
|
||||
hasAccessToReadMemories,
|
||||
interfaceConfig.parameters,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue