mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🔁 refactor: Capabilities for Tools/File handling for Direct Endpoints (#8253)
* feat: add useAgentCapabilities hook to manage agent capabilities * refactor: move agents and endpoints configuration to AgentPanel context provider * refactor: implement useGetAgentsConfig hook for consolidated agents and endpoints management * refactor: enhance ToolsDropdown to utilize agent capabilities and streamline dropdown item rendering * chore: reorder return values in useAgentCapabilities for improved clarity * refactor: enhance agent capabilities handling in AttachFileMenu and update file handling logic to allow capabilities to be used for non-agents endpoints
This commit is contained in:
parent
a288ad1d9c
commit
f5511e4a4e
15 changed files with 308 additions and 250 deletions
|
|
@ -1,4 +1,10 @@
|
|||
const { CacheKeys, EModelEndpoint, orderEndpointsConfig } = require('librechat-data-provider');
|
||||
const {
|
||||
CacheKeys,
|
||||
EModelEndpoint,
|
||||
isAgentsEndpoint,
|
||||
orderEndpointsConfig,
|
||||
defaultAgentCapabilities,
|
||||
} = require('librechat-data-provider');
|
||||
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
|
||||
const loadConfigEndpoints = require('./loadConfigEndpoints');
|
||||
const getLogStores = require('~/cache/getLogStores');
|
||||
|
|
@ -80,8 +86,12 @@ async function getEndpointsConfig(req) {
|
|||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const checkCapability = async (req, capability) => {
|
||||
const isAgents = isAgentsEndpoint(req.body?.original_endpoint || req.body?.endpoint);
|
||||
const endpointsConfig = await getEndpointsConfig(req);
|
||||
const capabilities = endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [];
|
||||
const capabilities =
|
||||
isAgents || endpointsConfig?.[EModelEndpoint.agents]?.capabilities != null
|
||||
? (endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [])
|
||||
: defaultAgentCapabilities;
|
||||
return capabilities.includes(capability);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { Constants, EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { TPlugin, AgentToolType, Action, MCP } from 'librechat-data-provider';
|
||||
import type { MCP, Action, TPlugin, AgentToolType } from 'librechat-data-provider';
|
||||
import type { AgentPanelContextType } from '~/common';
|
||||
import { useAvailableToolsQuery, useGetActionsQuery } from '~/data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { useLocalize, useGetAgentsConfig } from '~/hooks';
|
||||
import { Panel } from '~/common';
|
||||
|
||||
const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined);
|
||||
|
|
@ -75,21 +75,25 @@ export function AgentPanelProvider({ children }: { children: React.ReactNode })
|
|||
{} as Record<string, AgentToolType & { tools?: AgentToolType[] }>,
|
||||
);
|
||||
|
||||
const value = {
|
||||
action,
|
||||
setAction,
|
||||
const { agentsConfig, endpointsConfig } = useGetAgentsConfig();
|
||||
|
||||
const value: AgentPanelContextType = {
|
||||
mcp,
|
||||
setMcp,
|
||||
mcps,
|
||||
setMcps,
|
||||
activePanel,
|
||||
setActivePanel,
|
||||
setCurrentAgentId,
|
||||
agent_id,
|
||||
groupedTools,
|
||||
/** Query data for actions and tools */
|
||||
actions,
|
||||
tools,
|
||||
action,
|
||||
setMcp,
|
||||
actions,
|
||||
setMcps,
|
||||
agent_id,
|
||||
setAction,
|
||||
activePanel,
|
||||
groupedTools,
|
||||
agentsConfig,
|
||||
setActivePanel,
|
||||
endpointsConfig,
|
||||
setCurrentAgentId,
|
||||
};
|
||||
|
||||
return <AgentPanelContext.Provider value={value}>{children}</AgentPanelContext.Provider>;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,25 @@
|
|||
import React, { createContext, useContext, useEffect, useRef } from 'react';
|
||||
import { Tools, LocalStorageKeys, AgentCapabilities, Constants } from 'librechat-data-provider';
|
||||
import { useMCPSelect, useToolToggle, useCodeApiKeyForm, useSearchApiKeyForm } from '~/hooks';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { Tools, Constants, LocalStorageKeys, AgentCapabilities } from 'librechat-data-provider';
|
||||
import type { TAgentsEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
useSearchApiKeyForm,
|
||||
useGetAgentsConfig,
|
||||
useCodeApiKeyForm,
|
||||
useToolToggle,
|
||||
useMCPSelect,
|
||||
} from '~/hooks';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
import { ephemeralAgentByConvoId } from '~/store';
|
||||
|
||||
interface BadgeRowContextType {
|
||||
conversationId?: string | null;
|
||||
agentsConfig?: TAgentsEndpoint | null;
|
||||
mcpSelect: ReturnType<typeof useMCPSelect>;
|
||||
webSearch: ReturnType<typeof useToolToggle>;
|
||||
codeInterpreter: ReturnType<typeof useToolToggle>;
|
||||
fileSearch: ReturnType<typeof useToolToggle>;
|
||||
artifacts: ReturnType<typeof useToolToggle>;
|
||||
fileSearch: ReturnType<typeof useToolToggle>;
|
||||
codeInterpreter: ReturnType<typeof useToolToggle>;
|
||||
codeApiKeyForm: ReturnType<typeof useCodeApiKeyForm>;
|
||||
searchApiKeyForm: ReturnType<typeof useSearchApiKeyForm>;
|
||||
startupConfig: ReturnType<typeof useGetStartupConfig>['data'];
|
||||
|
|
@ -40,6 +48,7 @@ export default function BadgeRowProvider({
|
|||
}: BadgeRowProviderProps) {
|
||||
const hasInitializedRef = useRef(false);
|
||||
const lastKeyRef = useRef<string>('');
|
||||
const { agentsConfig } = useGetAgentsConfig();
|
||||
const key = conversationId ?? Constants.NEW_CONVO;
|
||||
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(key));
|
||||
|
||||
|
|
@ -165,8 +174,9 @@ export default function BadgeRowProvider({
|
|||
const value: BadgeRowContextType = {
|
||||
mcpSelect,
|
||||
webSearch,
|
||||
fileSearch,
|
||||
artifacts,
|
||||
fileSearch,
|
||||
agentsConfig,
|
||||
startupConfig,
|
||||
conversationId,
|
||||
codeApiKeyForm,
|
||||
|
|
|
|||
|
|
@ -206,9 +206,7 @@ export type AgentPanelProps = {
|
|||
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
|
||||
setMcp: React.Dispatch<React.SetStateAction<t.MCP | undefined>>;
|
||||
setAction: React.Dispatch<React.SetStateAction<t.Action | undefined>>;
|
||||
endpointsConfig?: t.TEndpointsConfig;
|
||||
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
agentsConfig?: t.TAgentsEndpoint | null;
|
||||
};
|
||||
|
||||
export type AgentPanelContextType = {
|
||||
|
|
@ -225,6 +223,8 @@ export type AgentPanelContextType = {
|
|||
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
groupedTools?: Record<string, t.AgentToolType & { tools?: t.AgentToolType[] }>;
|
||||
agent_id?: string;
|
||||
agentsConfig?: t.TAgentsEndpoint | null;
|
||||
endpointsConfig?: t.TEndpointsConfig | null;
|
||||
};
|
||||
|
||||
export type AgentModelPanelProps = {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ import { useSetRecoilState } from 'recoil';
|
|||
import * as Ariakit from '@ariakit/react';
|
||||
import React, { useRef, useState, useMemo } from 'react';
|
||||
import { FileSearch, ImageUpIcon, TerminalSquareIcon, FileType2Icon } from 'lucide-react';
|
||||
import { EToolResources, EModelEndpoint, defaultAgentCapabilities } from 'librechat-data-provider';
|
||||
import type { EndpointFileConfig } from 'librechat-data-provider';
|
||||
import { useLocalize, useGetAgentsConfig, useFileHandling, useAgentCapabilities } from '~/hooks';
|
||||
import { FileUpload, TooltipAnchor, DropdownPopup, AttachmentIcon } from '~/components';
|
||||
import { EToolResources, EModelEndpoint } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { useLocalize, useFileHandling } from '~/hooks';
|
||||
import { ephemeralAgentByConvoId } from '~/store';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -23,20 +22,17 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
|
|||
const [isPopoverActive, setIsPopoverActive] = useState(false);
|
||||
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(conversationId));
|
||||
const [toolResource, setToolResource] = useState<EToolResources | undefined>();
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
const { handleFileChange } = useFileHandling({
|
||||
overrideEndpoint: EModelEndpoint.agents,
|
||||
overrideEndpointFileConfig: endpointFileConfig,
|
||||
});
|
||||
|
||||
const { agentsConfig } = useGetAgentsConfig();
|
||||
/** TODO: Ephemeral Agent Capabilities
|
||||
* Allow defining agent capabilities on a per-endpoint basis
|
||||
* Use definition for agents endpoint for ephemeral agents
|
||||
* */
|
||||
const capabilities = useMemo(
|
||||
() => endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [],
|
||||
[endpointsConfig],
|
||||
);
|
||||
const capabilities = useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
|
||||
|
||||
const handleUploadClick = (isImage?: boolean) => {
|
||||
if (!inputRef.current) {
|
||||
|
|
@ -60,7 +56,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
|
|||
},
|
||||
];
|
||||
|
||||
if (capabilities.includes(EToolResources.ocr)) {
|
||||
if (capabilities.ocrEnabled) {
|
||||
items.push({
|
||||
label: localize('com_ui_upload_ocr_text'),
|
||||
onClick: () => {
|
||||
|
|
@ -71,7 +67,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
|
|||
});
|
||||
}
|
||||
|
||||
if (capabilities.includes(EToolResources.file_search)) {
|
||||
if (capabilities.fileSearchEnabled) {
|
||||
items.push({
|
||||
label: localize('com_ui_upload_file_search'),
|
||||
onClick: () => {
|
||||
|
|
@ -83,7 +79,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
|
|||
});
|
||||
}
|
||||
|
||||
if (capabilities.includes(EToolResources.execute_code)) {
|
||||
if (capabilities.codeEnabled) {
|
||||
items.push({
|
||||
label: localize('com_ui_upload_code_files'),
|
||||
onClick: () => {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ import React, { useState, useMemo, useCallback } from 'react';
|
|||
import * as Ariakit from '@ariakit/react';
|
||||
import { Globe, Settings, Settings2, TerminalSquareIcon } from 'lucide-react';
|
||||
import type { MenuItemProps } from '~/common';
|
||||
import { Permissions, PermissionTypes, AuthType, ArtifactModes } from 'librechat-data-provider';
|
||||
import {
|
||||
AuthType,
|
||||
Permissions,
|
||||
ArtifactModes,
|
||||
PermissionTypes,
|
||||
defaultAgentCapabilities,
|
||||
} from 'librechat-data-provider';
|
||||
import { TooltipAnchor, DropdownPopup } from '~/components';
|
||||
import { useLocalize, useHasAccess, useAgentCapabilities } from '~/hooks';
|
||||
import ArtifactsSubMenu from '~/components/Chat/Input/ArtifactsSubMenu';
|
||||
import MCPSubMenu from '~/components/Chat/Input/MCPSubMenu';
|
||||
import { PinIcon, VectorIcon } from '~/components/svg';
|
||||
import { useLocalize, useHasAccess } from '~/hooks';
|
||||
import { useBadgeRowContext } from '~/Providers';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -24,11 +30,15 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
|||
mcpSelect,
|
||||
artifacts,
|
||||
fileSearch,
|
||||
agentsConfig,
|
||||
startupConfig,
|
||||
codeApiKeyForm,
|
||||
codeInterpreter,
|
||||
searchApiKeyForm,
|
||||
} = useBadgeRowContext();
|
||||
const { codeEnabled, webSearchEnabled, artifactsEnabled, fileSearchEnabled } =
|
||||
useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
|
||||
|
||||
const { setIsDialogOpen: setIsCodeDialogOpen, menuTriggerRef: codeMenuTriggerRef } =
|
||||
codeApiKeyForm;
|
||||
const { setIsDialogOpen: setIsSearchDialogOpen, menuTriggerRef: searchMenuTriggerRef } =
|
||||
|
|
@ -128,9 +138,10 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
|||
|
||||
const mcpPlaceholder = startupConfig?.interface?.mcpServers?.placeholder;
|
||||
|
||||
const dropdownItems = useMemo(() => {
|
||||
const items: MenuItemProps[] = [];
|
||||
items.push({
|
||||
const dropdownItems: MenuItemProps[] = [];
|
||||
|
||||
if (fileSearchEnabled) {
|
||||
dropdownItems.push({
|
||||
onClick: handleFileSearchToggle,
|
||||
hideOnClick: false,
|
||||
render: (props) => (
|
||||
|
|
@ -159,117 +170,118 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
|||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (canUseWebSearch) {
|
||||
items.push({
|
||||
onClick: handleWebSearchToggle,
|
||||
hideOnClick: false,
|
||||
render: (props) => (
|
||||
<div {...props}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="icon-md" />
|
||||
<span>{localize('com_ui_web_search')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{showWebSearchSettings && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsSearchDialogOpen(true);
|
||||
}}
|
||||
className={cn(
|
||||
'rounded p-1 transition-all duration-200',
|
||||
'hover:bg-surface-secondary hover:shadow-sm',
|
||||
'text-text-secondary hover:text-text-primary',
|
||||
)}
|
||||
aria-label="Configure web search"
|
||||
ref={searchMenuTriggerRef}
|
||||
>
|
||||
<div className="h-4 w-4">
|
||||
<Settings className="h-4 w-4" />
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
if (canUseWebSearch && webSearchEnabled) {
|
||||
dropdownItems.push({
|
||||
onClick: handleWebSearchToggle,
|
||||
hideOnClick: false,
|
||||
render: (props) => (
|
||||
<div {...props}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="icon-md" />
|
||||
<span>{localize('com_ui_web_search')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{showWebSearchSettings && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsSearchPinned(!isSearchPinned);
|
||||
setIsSearchDialogOpen(true);
|
||||
}}
|
||||
className={cn(
|
||||
'rounded p-1 transition-all duration-200',
|
||||
'hover:bg-surface-secondary hover:shadow-sm',
|
||||
!isSearchPinned && 'text-text-secondary hover:text-text-primary',
|
||||
'text-text-secondary hover:text-text-primary',
|
||||
)}
|
||||
aria-label={isSearchPinned ? 'Unpin' : 'Pin'}
|
||||
aria-label="Configure web search"
|
||||
ref={searchMenuTriggerRef}
|
||||
>
|
||||
<div className="h-4 w-4">
|
||||
<PinIcon unpin={isSearchPinned} />
|
||||
<Settings className="h-4 w-4" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (canRunCode) {
|
||||
items.push({
|
||||
onClick: handleCodeInterpreterToggle,
|
||||
hideOnClick: false,
|
||||
render: (props) => (
|
||||
<div {...props}>
|
||||
<div className="flex items-center gap-2">
|
||||
<TerminalSquareIcon className="icon-md" />
|
||||
<span>{localize('com_assistants_code_interpreter')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{showCodeSettings && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsCodeDialogOpen(true);
|
||||
}}
|
||||
ref={codeMenuTriggerRef}
|
||||
className={cn(
|
||||
'rounded p-1 transition-all duration-200',
|
||||
'hover:bg-surface-secondary hover:shadow-sm',
|
||||
'text-text-secondary hover:text-text-primary',
|
||||
)}
|
||||
aria-label="Configure code interpreter"
|
||||
>
|
||||
<div className="h-4 w-4">
|
||||
<Settings className="h-4 w-4" />
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsSearchPinned(!isSearchPinned);
|
||||
}}
|
||||
className={cn(
|
||||
'rounded p-1 transition-all duration-200',
|
||||
'hover:bg-surface-secondary hover:shadow-sm',
|
||||
!isSearchPinned && 'text-text-secondary hover:text-text-primary',
|
||||
)}
|
||||
aria-label={isSearchPinned ? 'Unpin' : 'Pin'}
|
||||
>
|
||||
<div className="h-4 w-4">
|
||||
<PinIcon unpin={isSearchPinned} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (canRunCode && codeEnabled) {
|
||||
dropdownItems.push({
|
||||
onClick: handleCodeInterpreterToggle,
|
||||
hideOnClick: false,
|
||||
render: (props) => (
|
||||
<div {...props}>
|
||||
<div className="flex items-center gap-2">
|
||||
<TerminalSquareIcon className="icon-md" />
|
||||
<span>{localize('com_assistants_code_interpreter')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{showCodeSettings && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsCodePinned(!isCodePinned);
|
||||
setIsCodeDialogOpen(true);
|
||||
}}
|
||||
ref={codeMenuTriggerRef}
|
||||
className={cn(
|
||||
'rounded p-1 transition-all duration-200',
|
||||
'hover:bg-surface-secondary hover:shadow-sm',
|
||||
!isCodePinned && 'text-text-primary hover:text-text-primary',
|
||||
'text-text-secondary hover:text-text-primary',
|
||||
)}
|
||||
aria-label={isCodePinned ? 'Unpin' : 'Pin'}
|
||||
aria-label="Configure code interpreter"
|
||||
>
|
||||
<div className="h-4 w-4">
|
||||
<PinIcon unpin={isCodePinned} />
|
||||
<Settings className="h-4 w-4" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsCodePinned(!isCodePinned);
|
||||
}}
|
||||
className={cn(
|
||||
'rounded p-1 transition-all duration-200',
|
||||
'hover:bg-surface-secondary hover:shadow-sm',
|
||||
!isCodePinned && 'text-text-primary hover:text-text-primary',
|
||||
)}
|
||||
aria-label={isCodePinned ? 'Unpin' : 'Pin'}
|
||||
>
|
||||
<div className="h-4 w-4">
|
||||
<PinIcon unpin={isCodePinned} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// Add Artifacts option
|
||||
items.push({
|
||||
if (artifactsEnabled) {
|
||||
dropdownItems.push({
|
||||
hideOnClick: false,
|
||||
render: (props) => (
|
||||
<ArtifactsSubMenu
|
||||
|
|
@ -283,57 +295,24 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
|||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (mcpServerNames && mcpServerNames.length > 0) {
|
||||
items.push({
|
||||
hideOnClick: false,
|
||||
render: (props) => (
|
||||
<MCPSubMenu
|
||||
{...props}
|
||||
mcpValues={mcpValues}
|
||||
isMCPPinned={isMCPPinned}
|
||||
placeholder={mcpPlaceholder}
|
||||
mcpServerNames={mcpServerNames}
|
||||
setIsMCPPinned={setIsMCPPinned}
|
||||
handleMCPToggle={handleMCPToggle}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [
|
||||
localize,
|
||||
mcpValues,
|
||||
canRunCode,
|
||||
isMCPPinned,
|
||||
isCodePinned,
|
||||
mcpPlaceholder,
|
||||
mcpServerNames,
|
||||
isSearchPinned,
|
||||
setIsMCPPinned,
|
||||
canUseWebSearch,
|
||||
setIsCodePinned,
|
||||
handleMCPToggle,
|
||||
showCodeSettings,
|
||||
setIsSearchPinned,
|
||||
handleShadcnToggle,
|
||||
handleCustomToggle,
|
||||
isFileSearchPinned,
|
||||
isArtifactsPinned,
|
||||
codeMenuTriggerRef,
|
||||
setIsCodeDialogOpen,
|
||||
searchMenuTriggerRef,
|
||||
showWebSearchSettings,
|
||||
setIsFileSearchPinned,
|
||||
artifacts.toggleState,
|
||||
setIsArtifactsPinned,
|
||||
handleWebSearchToggle,
|
||||
setIsSearchDialogOpen,
|
||||
handleFileSearchToggle,
|
||||
handleArtifactsToggle,
|
||||
handleCodeInterpreterToggle,
|
||||
]);
|
||||
if (mcpServerNames && mcpServerNames.length > 0) {
|
||||
dropdownItems.push({
|
||||
hideOnClick: false,
|
||||
render: (props) => (
|
||||
<MCPSubMenu
|
||||
{...props}
|
||||
mcpValues={mcpValues}
|
||||
isMCPPinned={isMCPPinned}
|
||||
placeholder={mcpPlaceholder}
|
||||
mcpServerNames={mcpServerNames}
|
||||
setIsMCPPinned={setIsMCPPinned}
|
||||
handleMCPToggle={handleMCPToggle}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const menuTrigger = (
|
||||
<TooltipAnchor
|
||||
|
|
|
|||
|
|
@ -2,20 +2,20 @@ import { useMemo } from 'react';
|
|||
import { ChevronLeft } from 'lucide-react';
|
||||
import { AgentCapabilities } from 'librechat-data-provider';
|
||||
import { useFormContext, Controller } from 'react-hook-form';
|
||||
import type { AgentForm, AgentPanelProps } from '~/common';
|
||||
import type { AgentForm } from '~/common';
|
||||
import { useAgentPanelContext } from '~/Providers';
|
||||
import MaxAgentSteps from './MaxAgentSteps';
|
||||
import AgentChain from './AgentChain';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import AgentChain from './AgentChain';
|
||||
import { Panel } from '~/common';
|
||||
|
||||
export default function AdvancedPanel({
|
||||
agentsConfig,
|
||||
setActivePanel,
|
||||
}: Pick<AgentPanelProps, 'setActivePanel' | 'agentsConfig'>) {
|
||||
export default function AdvancedPanel() {
|
||||
const localize = useLocalize();
|
||||
const methods = useFormContext<AgentForm>();
|
||||
const { control, watch } = methods;
|
||||
const currentAgentId = watch('id');
|
||||
|
||||
const { agentsConfig, setActivePanel } = useAgentPanelContext();
|
||||
const chainEnabled = useMemo(
|
||||
() => agentsConfig?.capabilities.includes(AgentCapabilities.chain) ?? false,
|
||||
[agentsConfig],
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { Controller, useWatch, useFormContext } from 'react-hook-form';
|
||||
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
|
||||
import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common';
|
||||
import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils';
|
||||
import { useToastContext, useFileMapContext, useAgentPanelContext } from '~/Providers';
|
||||
import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities';
|
||||
import Action from '~/components/SidePanel/Builder/Action';
|
||||
import { ToolSelectDialog } from '~/components/Tools';
|
||||
import { icons } from '~/hooks/Endpoint/Icons';
|
||||
|
|
@ -26,17 +27,20 @@ const inputClass = cn(
|
|||
removeFocusOutlines,
|
||||
);
|
||||
|
||||
export default function AgentConfig({
|
||||
agentsConfig,
|
||||
createMutation,
|
||||
endpointsConfig,
|
||||
}: Pick<AgentPanelProps, 'agentsConfig' | 'createMutation' | 'endpointsConfig'>) {
|
||||
export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'createMutation'>) {
|
||||
const localize = useLocalize();
|
||||
const fileMap = useFileMapContext();
|
||||
const { showToast } = useToastContext();
|
||||
const methods = useFormContext<AgentForm>();
|
||||
const [showToolDialog, setShowToolDialog] = useState(false);
|
||||
const { actions, setAction, groupedTools: allTools, setActivePanel } = useAgentPanelContext();
|
||||
const {
|
||||
actions,
|
||||
setAction,
|
||||
agentsConfig,
|
||||
setActivePanel,
|
||||
endpointsConfig,
|
||||
groupedTools: allTools,
|
||||
} = useAgentPanelContext();
|
||||
|
||||
const { control } = methods;
|
||||
const provider = useWatch({ control, name: 'provider' });
|
||||
|
|
@ -45,34 +49,15 @@ export default function AgentConfig({
|
|||
const tools = useWatch({ control, name: 'tools' });
|
||||
const agent_id = useWatch({ control, name: 'id' });
|
||||
|
||||
const toolsEnabled = useMemo(
|
||||
() => agentsConfig?.capabilities?.includes(AgentCapabilities.tools) ?? false,
|
||||
[agentsConfig],
|
||||
);
|
||||
const actionsEnabled = useMemo(
|
||||
() => agentsConfig?.capabilities?.includes(AgentCapabilities.actions) ?? false,
|
||||
[agentsConfig],
|
||||
);
|
||||
const artifactsEnabled = useMemo(
|
||||
() => agentsConfig?.capabilities?.includes(AgentCapabilities.artifacts) ?? false,
|
||||
[agentsConfig],
|
||||
);
|
||||
const ocrEnabled = useMemo(
|
||||
() => agentsConfig?.capabilities?.includes(AgentCapabilities.ocr) ?? false,
|
||||
[agentsConfig],
|
||||
);
|
||||
const fileSearchEnabled = useMemo(
|
||||
() => agentsConfig?.capabilities?.includes(AgentCapabilities.file_search) ?? false,
|
||||
[agentsConfig],
|
||||
);
|
||||
const webSearchEnabled = useMemo(
|
||||
() => agentsConfig?.capabilities?.includes(AgentCapabilities.web_search) ?? false,
|
||||
[agentsConfig],
|
||||
);
|
||||
const codeEnabled = useMemo(
|
||||
() => agentsConfig?.capabilities?.includes(AgentCapabilities.execute_code) ?? false,
|
||||
[agentsConfig],
|
||||
);
|
||||
const {
|
||||
ocrEnabled,
|
||||
codeEnabled,
|
||||
toolsEnabled,
|
||||
actionsEnabled,
|
||||
artifactsEnabled,
|
||||
webSearchEnabled,
|
||||
fileSearchEnabled,
|
||||
} = useAgentCapabilities(agentsConfig?.capabilities);
|
||||
|
||||
const context_files = useMemo(() => {
|
||||
if (typeof agent === 'string') {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import {
|
|||
Constants,
|
||||
SystemRoles,
|
||||
EModelEndpoint,
|
||||
TAgentsEndpoint,
|
||||
TEndpointsConfig,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AgentForm, StringOption } from '~/common';
|
||||
|
|
@ -30,19 +28,15 @@ import { Button } from '~/components';
|
|||
import ModelPanel from './ModelPanel';
|
||||
import { Panel } from '~/common';
|
||||
|
||||
export default function AgentPanel({
|
||||
agentsConfig,
|
||||
endpointsConfig,
|
||||
}: {
|
||||
agentsConfig: TAgentsEndpoint | null;
|
||||
endpointsConfig: TEndpointsConfig;
|
||||
}) {
|
||||
export default function AgentPanel() {
|
||||
const localize = useLocalize();
|
||||
const { user } = useAuthContext();
|
||||
const { showToast } = useToastContext();
|
||||
const {
|
||||
activePanel,
|
||||
agentsConfig,
|
||||
setActivePanel,
|
||||
endpointsConfig,
|
||||
setCurrentAgentId,
|
||||
agent_id: current_agent_id,
|
||||
} = useAgentPanelContext();
|
||||
|
|
@ -323,14 +317,10 @@ export default function AgentPanel({
|
|||
<ModelPanel models={models} providers={providers} setActivePanel={setActivePanel} />
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && (
|
||||
<AgentConfig
|
||||
createMutation={create}
|
||||
agentsConfig={agentsConfig}
|
||||
endpointsConfig={endpointsConfig}
|
||||
/>
|
||||
<AgentConfig createMutation={create} />
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.advanced && (
|
||||
<AdvancedPanel setActivePanel={setActivePanel} agentsConfig={agentsConfig} />
|
||||
<AdvancedPanel />
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && (
|
||||
<AgentFooter
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
|
||||
import type { TConfig, TEndpointsConfig, TAgentsEndpoint } from 'librechat-data-provider';
|
||||
import { useEffect } from 'react';
|
||||
import { AgentPanelProvider, useAgentPanelContext } from '~/Providers/AgentPanelContext';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import VersionPanel from './Version/VersionPanel';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import ActionsPanel from './ActionsPanel';
|
||||
|
|
@ -22,21 +19,6 @@ function AgentPanelSwitchWithContext() {
|
|||
const { conversation } = useChatContext();
|
||||
const { activePanel, setCurrentAgentId } = useAgentPanelContext();
|
||||
|
||||
// TODO: Implement MCP endpoint
|
||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
|
||||
const agentsConfig = useMemo<TAgentsEndpoint | null>(() => {
|
||||
const config = endpointsConfig?.[EModelEndpoint.agents] ?? null;
|
||||
if (!config) return null;
|
||||
|
||||
return {
|
||||
...(config as TConfig),
|
||||
capabilities: Array.isArray(config.capabilities)
|
||||
? config.capabilities.map((cap) => cap as unknown as AgentCapabilities)
|
||||
: ([] as AgentCapabilities[]),
|
||||
} as TAgentsEndpoint;
|
||||
}, [endpointsConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
const agent_id = conversation?.agent_id ?? '';
|
||||
if (agent_id) {
|
||||
|
|
@ -57,5 +39,5 @@ function AgentPanelSwitchWithContext() {
|
|||
if (activePanel === Panel.mcp) {
|
||||
return <MCPPanel />;
|
||||
}
|
||||
return <AgentPanel agentsConfig={agentsConfig} endpointsConfig={endpointsConfig} />;
|
||||
return <AgentPanel />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
export { default as useAgentsMap } from './useAgentsMap';
|
||||
export { default as useSelectAgent } from './useSelectAgent';
|
||||
export { default as useAgentCapabilities } from './useAgentCapabilities';
|
||||
export { default as useGetAgentsConfig } from './useGetAgentsConfig';
|
||||
|
|
|
|||
61
client/src/hooks/Agents/useAgentCapabilities.ts
Normal file
61
client/src/hooks/Agents/useAgentCapabilities.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { useMemo } from 'react';
|
||||
import { AgentCapabilities } from 'librechat-data-provider';
|
||||
|
||||
interface AgentCapabilitiesResult {
|
||||
toolsEnabled: boolean;
|
||||
actionsEnabled: boolean;
|
||||
artifactsEnabled: boolean;
|
||||
ocrEnabled: boolean;
|
||||
fileSearchEnabled: boolean;
|
||||
webSearchEnabled: boolean;
|
||||
codeEnabled: boolean;
|
||||
}
|
||||
|
||||
export default function useAgentCapabilities(
|
||||
capabilities: AgentCapabilities[] | undefined,
|
||||
): AgentCapabilitiesResult {
|
||||
const toolsEnabled = useMemo(
|
||||
() => capabilities?.includes(AgentCapabilities.tools) ?? false,
|
||||
[capabilities],
|
||||
);
|
||||
|
||||
const actionsEnabled = useMemo(
|
||||
() => capabilities?.includes(AgentCapabilities.actions) ?? false,
|
||||
[capabilities],
|
||||
);
|
||||
|
||||
const artifactsEnabled = useMemo(
|
||||
() => capabilities?.includes(AgentCapabilities.artifacts) ?? false,
|
||||
[capabilities],
|
||||
);
|
||||
|
||||
const ocrEnabled = useMemo(
|
||||
() => capabilities?.includes(AgentCapabilities.ocr) ?? false,
|
||||
[capabilities],
|
||||
);
|
||||
|
||||
const fileSearchEnabled = useMemo(
|
||||
() => capabilities?.includes(AgentCapabilities.file_search) ?? false,
|
||||
[capabilities],
|
||||
);
|
||||
|
||||
const webSearchEnabled = useMemo(
|
||||
() => capabilities?.includes(AgentCapabilities.web_search) ?? false,
|
||||
[capabilities],
|
||||
);
|
||||
|
||||
const codeEnabled = useMemo(
|
||||
() => capabilities?.includes(AgentCapabilities.execute_code) ?? false,
|
||||
[capabilities],
|
||||
);
|
||||
|
||||
return {
|
||||
ocrEnabled,
|
||||
codeEnabled,
|
||||
toolsEnabled,
|
||||
actionsEnabled,
|
||||
artifactsEnabled,
|
||||
webSearchEnabled,
|
||||
fileSearchEnabled,
|
||||
};
|
||||
}
|
||||
35
client/src/hooks/Agents/useGetAgentsConfig.ts
Normal file
35
client/src/hooks/Agents/useGetAgentsConfig.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { useMemo } from 'react';
|
||||
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
|
||||
import type { TAgentsEndpoint, TEndpointsConfig, TConfig } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
|
||||
interface UseGetAgentsConfigOptions {
|
||||
endpointsConfig?: TEndpointsConfig;
|
||||
}
|
||||
|
||||
export default function useGetAgentsConfig(options?: UseGetAgentsConfigOptions): {
|
||||
agentsConfig?: TAgentsEndpoint | null;
|
||||
endpointsConfig?: TEndpointsConfig | null;
|
||||
} {
|
||||
const { endpointsConfig: providedConfig } = options || {};
|
||||
|
||||
const { data: queriedConfig } = useGetEndpointsQuery({
|
||||
enabled: !providedConfig,
|
||||
});
|
||||
|
||||
const endpointsConfig = providedConfig || queriedConfig;
|
||||
|
||||
const agentsConfig = useMemo<TAgentsEndpoint | null>(() => {
|
||||
const config = endpointsConfig?.[EModelEndpoint.agents] ?? null;
|
||||
if (!config) return null;
|
||||
|
||||
return {
|
||||
...(config as TConfig),
|
||||
capabilities: Array.isArray(config.capabilities)
|
||||
? config.capabilities.map((cap) => cap as unknown as AgentCapabilities)
|
||||
: ([] as AgentCapabilities[]),
|
||||
} as TAgentsEndpoint;
|
||||
}, [endpointsConfig]);
|
||||
|
||||
return { agentsConfig, endpointsConfig };
|
||||
}
|
||||
|
|
@ -25,10 +25,10 @@ import useUpdateFiles from './useUpdateFiles';
|
|||
|
||||
type UseFileHandling = {
|
||||
fileSetter?: FileSetter;
|
||||
fileFilter?: (file: File) => boolean;
|
||||
additionalMetadata?: Record<string, string | undefined>;
|
||||
overrideEndpoint?: EModelEndpoint;
|
||||
fileFilter?: (file: File) => boolean;
|
||||
overrideEndpointFileConfig?: EndpointFileConfig;
|
||||
additionalMetadata?: Record<string, string | undefined>;
|
||||
};
|
||||
|
||||
const useFileHandling = (params?: UseFileHandling) => {
|
||||
|
|
@ -151,6 +151,10 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
|
||||
const formData = new FormData();
|
||||
formData.append('endpoint', endpoint);
|
||||
formData.append(
|
||||
'original_endpoint',
|
||||
conversation?.endpointType || conversation?.endpoint || '',
|
||||
);
|
||||
formData.append('file', extendedFile.file as File, encodeURIComponent(filename));
|
||||
formData.append('file_id', extendedFile.file_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export const getAvailableEndpoints = (
|
|||
|
||||
/** Get the specified field from the endpoint config */
|
||||
export function getEndpointField<K extends keyof t.TConfig>(
|
||||
endpointsConfig: t.TEndpointsConfig | undefined,
|
||||
endpointsConfig: t.TEndpointsConfig | undefined | null,
|
||||
endpoint: EModelEndpoint | string | null | undefined,
|
||||
property: K,
|
||||
): t.TConfig[K] | undefined {
|
||||
|
|
@ -246,7 +246,7 @@ export function getIconKey({
|
|||
endpointIconURL: iconURL,
|
||||
}: {
|
||||
endpoint?: string | null;
|
||||
endpointsConfig?: t.TEndpointsConfig;
|
||||
endpointsConfig?: t.TEndpointsConfig | null;
|
||||
endpointType?: string | null;
|
||||
endpointIconURL?: string;
|
||||
}): keyof IconsRecord {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue