mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
🥷 fix: Correct Agents Handling for Marketplace Users (#9065)
* refactor: Introduce ModelSelectorChatContext and integrate with ModelSelector * fix: agents handling in ModelSelector to show expected agents if user has marketplace access
This commit is contained in:
parent
e4e25aaf2b
commit
d57e7aec73
8 changed files with 124 additions and 57 deletions
|
@ -1,6 +1,7 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import type { ModelSelectorProps } from '~/common';
|
||||
import { ModelSelectorProvider, useModelSelectorContext } from './ModelSelectorContext';
|
||||
import { ModelSelectorChatProvider } from './ModelSelectorChatContext';
|
||||
import { renderModelSpecs, renderEndpoints, renderSearchResults } from './components';
|
||||
import { getSelectedIcon, getDisplayValue } from './utils';
|
||||
import { CustomMenu as Menu } from './CustomMenu';
|
||||
|
@ -12,6 +13,7 @@ function ModelSelectorContent() {
|
|||
|
||||
const {
|
||||
// LibreChat
|
||||
agentsMap,
|
||||
modelSpecs,
|
||||
mappedEndpoints,
|
||||
endpointsConfig,
|
||||
|
@ -43,11 +45,12 @@ function ModelSelectorContent() {
|
|||
() =>
|
||||
getDisplayValue({
|
||||
localize,
|
||||
agentsMap,
|
||||
modelSpecs,
|
||||
selectedValues,
|
||||
mappedEndpoints,
|
||||
}),
|
||||
[localize, modelSpecs, selectedValues, mappedEndpoints],
|
||||
[localize, agentsMap, modelSpecs, selectedValues, mappedEndpoints],
|
||||
);
|
||||
|
||||
const trigger = (
|
||||
|
@ -100,8 +103,10 @@ function ModelSelectorContent() {
|
|||
|
||||
export default function ModelSelector({ startupConfig }: ModelSelectorProps) {
|
||||
return (
|
||||
<ModelSelectorProvider startupConfig={startupConfig}>
|
||||
<ModelSelectorContent />
|
||||
</ModelSelectorProvider>
|
||||
<ModelSelectorChatProvider>
|
||||
<ModelSelectorProvider startupConfig={startupConfig}>
|
||||
<ModelSelectorContent />
|
||||
</ModelSelectorProvider>
|
||||
</ModelSelectorChatProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
import type { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { useChatContext } from '~/Providers/ChatContext';
|
||||
|
||||
interface ModelSelectorChatContextValue {
|
||||
endpoint?: EModelEndpoint | null;
|
||||
model?: string | null;
|
||||
spec?: string | null;
|
||||
agent_id?: string | null;
|
||||
assistant_id?: string | null;
|
||||
newConversation: ReturnType<typeof useChatContext>['newConversation'];
|
||||
}
|
||||
|
||||
const ModelSelectorChatContext = createContext<ModelSelectorChatContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export function ModelSelectorChatProvider({ children }: { children: React.ReactNode }) {
|
||||
const { conversation, newConversation } = useChatContext();
|
||||
|
||||
/** Context value only created when relevant conversation properties change */
|
||||
const contextValue = useMemo<ModelSelectorChatContextValue>(
|
||||
() => ({
|
||||
endpoint: conversation?.endpoint,
|
||||
model: conversation?.model,
|
||||
spec: conversation?.spec,
|
||||
agent_id: conversation?.agent_id,
|
||||
assistant_id: conversation?.assistant_id,
|
||||
newConversation,
|
||||
}),
|
||||
[
|
||||
conversation?.endpoint,
|
||||
conversation?.model,
|
||||
conversation?.spec,
|
||||
conversation?.agent_id,
|
||||
conversation?.assistant_id,
|
||||
newConversation,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<ModelSelectorChatContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</ModelSelectorChatContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useModelSelectorChatContext() {
|
||||
const context = useContext(ModelSelectorChatContext);
|
||||
if (!context) {
|
||||
throw new Error('useModelSelectorChatContext must be used within ModelSelectorChatProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
|
@ -3,10 +3,16 @@ import React, { createContext, useContext, useState, useMemo } from 'react';
|
|||
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type * as t from 'librechat-data-provider';
|
||||
import type { Endpoint, SelectedValues } from '~/common';
|
||||
import { useAgentsMapContext, useAssistantsMapContext, useChatContext } from '~/Providers';
|
||||
import { useEndpoints, useSelectorEffects, useKeyDialog } from '~/hooks';
|
||||
import {
|
||||
useAgentDefaultPermissionLevel,
|
||||
useSelectorEffects,
|
||||
useKeyDialog,
|
||||
useEndpoints,
|
||||
} from '~/hooks';
|
||||
import { useAgentsMapContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { useGetEndpointsQuery, useListAgentsQuery } from '~/data-provider';
|
||||
import { useModelSelectorChatContext } from './ModelSelectorChatContext';
|
||||
import useSelectMention from '~/hooks/Input/useSelectMention';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { filterItems } from './utils';
|
||||
|
||||
type ModelSelectorContextType = {
|
||||
|
@ -51,14 +57,24 @@ export function ModelSelectorProvider({ children, startupConfig }: ModelSelector
|
|||
const agentsMap = useAgentsMapContext();
|
||||
const assistantsMap = useAssistantsMapContext();
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
const { conversation, newConversation } = useChatContext();
|
||||
const { endpoint, model, spec, agent_id, assistant_id, newConversation } =
|
||||
useModelSelectorChatContext();
|
||||
const modelSpecs = useMemo(() => startupConfig?.modelSpecs?.list ?? [], [startupConfig]);
|
||||
const permissionLevel = useAgentDefaultPermissionLevel();
|
||||
const { data: agents = null } = useListAgentsQuery(
|
||||
{ requiredPermission: permissionLevel },
|
||||
{
|
||||
select: (data) => data?.data,
|
||||
},
|
||||
);
|
||||
|
||||
const { mappedEndpoints, endpointRequiresUserKey } = useEndpoints({
|
||||
agentsMap,
|
||||
agents,
|
||||
assistantsMap,
|
||||
startupConfig,
|
||||
endpointsConfig,
|
||||
});
|
||||
|
||||
const { onSelectEndpoint, onSelectSpec } = useSelectMention({
|
||||
// presets,
|
||||
modelSpecs,
|
||||
|
@ -70,13 +86,21 @@ export function ModelSelectorProvider({ children, startupConfig }: ModelSelector
|
|||
|
||||
// State
|
||||
const [selectedValues, setSelectedValues] = useState<SelectedValues>({
|
||||
endpoint: conversation?.endpoint || '',
|
||||
model: conversation?.model || '',
|
||||
modelSpec: conversation?.spec || '',
|
||||
endpoint: endpoint || '',
|
||||
model: model || '',
|
||||
modelSpec: spec || '',
|
||||
});
|
||||
useSelectorEffects({
|
||||
agentsMap,
|
||||
conversation,
|
||||
conversation: endpoint
|
||||
? ({
|
||||
endpoint: endpoint ?? null,
|
||||
model: model ?? null,
|
||||
spec: spec ?? null,
|
||||
agent_id: agent_id ?? null,
|
||||
assistant_id: assistant_id ?? null,
|
||||
} as any)
|
||||
: null,
|
||||
assistantsMap,
|
||||
setSelectedValues,
|
||||
});
|
||||
|
@ -86,7 +110,7 @@ export function ModelSelectorProvider({ children, startupConfig }: ModelSelector
|
|||
|
||||
const keyProps = useKeyDialog();
|
||||
|
||||
// Memoized search results
|
||||
/** Memoized search results */
|
||||
const searchResults = useMemo(() => {
|
||||
if (!searchValue) {
|
||||
return null;
|
||||
|
@ -95,7 +119,6 @@ export function ModelSelectorProvider({ children, startupConfig }: ModelSelector
|
|||
return filterItems(allItems, searchValue, agentsMap, assistantsMap || {});
|
||||
}, [searchValue, modelSpecs, mappedEndpoints, agentsMap, assistantsMap]);
|
||||
|
||||
// Functions
|
||||
const setDebouncedSearchValue = useMemo(
|
||||
() =>
|
||||
debounce((value: string) => {
|
||||
|
|
|
@ -167,11 +167,13 @@ export const getDisplayValue = ({
|
|||
mappedEndpoints,
|
||||
selectedValues,
|
||||
modelSpecs,
|
||||
agentsMap,
|
||||
}: {
|
||||
localize: ReturnType<typeof useLocalize>;
|
||||
selectedValues: SelectedValues;
|
||||
mappedEndpoints: Endpoint[];
|
||||
modelSpecs: TModelSpec[];
|
||||
agentsMap?: TAgentsMap;
|
||||
}) => {
|
||||
if (selectedValues.modelSpec) {
|
||||
const spec = modelSpecs.find((s) => s.name === selectedValues.modelSpec);
|
||||
|
@ -190,6 +192,9 @@ export const getDisplayValue = ({
|
|||
endpoint.agentNames[selectedValues.model]
|
||||
) {
|
||||
return endpoint.agentNames[selectedValues.model];
|
||||
} else if (isAgentsEndpoint(endpoint.value) && agentsMap) {
|
||||
const agent = agentsMap[selectedValues.model];
|
||||
return agent?.name || selectedValues.model;
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
|
@ -7,8 +7,8 @@ import type { UseMutationResult, QueryObserverResult } from '@tanstack/react-que
|
|||
import type { Agent, AgentCreateParams } from 'librechat-data-provider';
|
||||
import type { TAgentCapabilities, AgentForm } from '~/common';
|
||||
import { cn, createProviderOption, processAgentOption, getDefaultAgentFormValues } from '~/utils';
|
||||
import { useGetStartupConfig, useListAgentsQuery } from '~/data-provider';
|
||||
import { useLocalize, useAgentDefaultPermissionLevel } from '~/hooks';
|
||||
import { useListAgentsQuery } from '~/data-provider';
|
||||
|
||||
const keys = new Set(Object.keys(defaultAgentFormValues));
|
||||
|
||||
|
@ -26,8 +26,6 @@ export default function AgentSelect({
|
|||
const localize = useLocalize();
|
||||
const lastSelectedAgent = useRef<string | null>(null);
|
||||
const { control, reset } = useFormContext();
|
||||
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const permissionLevel = useAgentDefaultPermissionLevel();
|
||||
|
||||
const { data: agents = null } = useListAgentsQuery(
|
||||
|
@ -40,7 +38,6 @@ export default function AgentSelect({
|
|||
...agent,
|
||||
name: agent.name || agent.id,
|
||||
},
|
||||
instanceProjectId: startupConfig?.instanceProjectId,
|
||||
}),
|
||||
),
|
||||
},
|
||||
|
@ -122,7 +119,7 @@ export default function AgentSelect({
|
|||
|
||||
reset(formValues);
|
||||
},
|
||||
[reset, startupConfig],
|
||||
[reset],
|
||||
);
|
||||
|
||||
const onSelect = useCallback(
|
||||
|
|
|
@ -9,7 +9,7 @@ export default function useAgentsMap({
|
|||
}: {
|
||||
isAuthenticated: boolean;
|
||||
}): TAgentsMap | undefined {
|
||||
const { data: agentsList = null } = useListAgentsQuery(
|
||||
const { data: mappedAgents = null } = useListAgentsQuery(
|
||||
{ requiredPermission: PermissionBits.VIEW },
|
||||
{
|
||||
select: (res) => mapAgents(res.data),
|
||||
|
@ -17,9 +17,9 @@ export default function useAgentsMap({
|
|||
},
|
||||
);
|
||||
|
||||
const agents = useMemo<TAgentsMap | undefined>(() => {
|
||||
return agentsList !== null ? agentsList : undefined;
|
||||
}, [agentsList]);
|
||||
const agentsMap = useMemo<TAgentsMap | undefined>(() => {
|
||||
return mappedAgents !== null ? mappedAgents : undefined;
|
||||
}, [mappedAgents]);
|
||||
|
||||
return agents;
|
||||
return agentsMap;
|
||||
}
|
||||
|
|
|
@ -1,71 +1,56 @@
|
|||
import React, { useMemo, useCallback } from 'react';
|
||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
EModelEndpoint,
|
||||
PermissionTypes,
|
||||
Permissions,
|
||||
alternateName,
|
||||
EModelEndpoint,
|
||||
PermissionTypes,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
Agent,
|
||||
Assistant,
|
||||
TEndpointsConfig,
|
||||
TAgentsMap,
|
||||
TAssistantsMap,
|
||||
TStartupConfig,
|
||||
Assistant,
|
||||
Agent,
|
||||
} from 'librechat-data-provider';
|
||||
import type { Endpoint } from '~/common';
|
||||
import { mapEndpoints, getIconKey, getEndpointField } from '~/utils';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useHasAccess } from '~/hooks';
|
||||
import { icons } from './Icons';
|
||||
|
||||
export const useEndpoints = ({
|
||||
agentsMap,
|
||||
agents,
|
||||
assistantsMap,
|
||||
endpointsConfig,
|
||||
startupConfig,
|
||||
}: {
|
||||
agentsMap?: TAgentsMap;
|
||||
agents?: Agent[] | null;
|
||||
assistantsMap?: TAssistantsMap;
|
||||
endpointsConfig: TEndpointsConfig;
|
||||
startupConfig: TStartupConfig | undefined;
|
||||
}) => {
|
||||
const modelsQuery = useGetModelsQuery();
|
||||
const { conversation } = useChatContext();
|
||||
const { data: endpoints = [] } = useGetEndpointsQuery({ select: mapEndpoints });
|
||||
const { instanceProjectId } = startupConfig ?? {};
|
||||
const interfaceConfig = startupConfig?.interface ?? {};
|
||||
const includedEndpoints = useMemo(
|
||||
() => new Set(startupConfig?.modelSpecs?.addedEndpoints ?? []),
|
||||
[startupConfig?.modelSpecs?.addedEndpoints],
|
||||
);
|
||||
|
||||
const { endpoint } = conversation ?? {};
|
||||
|
||||
const hasAgentAccess = useHasAccess({
|
||||
permissionType: PermissionTypes.AGENTS,
|
||||
permission: Permissions.USE,
|
||||
});
|
||||
|
||||
const agents = useMemo(
|
||||
() =>
|
||||
Object.values(agentsMap ?? {}).filter(
|
||||
(agent): agent is Agent & { name: string } =>
|
||||
agent !== undefined && 'id' in agent && 'name' in agent && agent.name !== null,
|
||||
),
|
||||
[agentsMap],
|
||||
);
|
||||
|
||||
const assistants: Assistant[] = useMemo(
|
||||
() => Object.values(assistantsMap?.[EModelEndpoint.assistants] ?? {}),
|
||||
[endpoint, assistantsMap],
|
||||
[assistantsMap],
|
||||
);
|
||||
|
||||
const azureAssistants: Assistant[] = useMemo(
|
||||
() => Object.values(assistantsMap?.[EModelEndpoint.azureAssistants] ?? {}),
|
||||
[endpoint, assistantsMap],
|
||||
[assistantsMap],
|
||||
);
|
||||
|
||||
const filteredEndpoints = useMemo(() => {
|
||||
|
@ -84,7 +69,7 @@ export const useEndpoints = ({
|
|||
}
|
||||
|
||||
return result;
|
||||
}, [endpoints, hasAgentAccess, includedEndpoints]);
|
||||
}, [endpoints, hasAgentAccess, includedEndpoints, interfaceConfig.modelSelect]);
|
||||
|
||||
const endpointRequiresUserKey = useCallback(
|
||||
(ep: string) => {
|
||||
|
@ -100,7 +85,7 @@ export const useEndpoints = ({
|
|||
const Icon = icons[iconKey];
|
||||
const endpointIconURL = getEndpointField(endpointsConfig, ep, 'iconURL');
|
||||
const hasModels =
|
||||
(ep === EModelEndpoint.agents && agents?.length > 0) ||
|
||||
(ep === EModelEndpoint.agents && (agents?.length ?? 0) > 0) ||
|
||||
(ep === EModelEndpoint.assistants && assistants?.length > 0) ||
|
||||
(ep !== EModelEndpoint.assistants &&
|
||||
ep !== EModelEndpoint.agents &&
|
||||
|
@ -122,16 +107,16 @@ export const useEndpoints = ({
|
|||
};
|
||||
|
||||
// Handle agents case
|
||||
if (ep === EModelEndpoint.agents && agents.length > 0) {
|
||||
result.models = agents.map((agent) => ({
|
||||
if (ep === EModelEndpoint.agents && (agents?.length ?? 0) > 0) {
|
||||
result.models = agents?.map((agent) => ({
|
||||
name: agent.id,
|
||||
isGlobal: agent.isPublic ?? false,
|
||||
}));
|
||||
result.agentNames = agents.reduce((acc, agent) => {
|
||||
result.agentNames = agents?.reduce((acc, agent) => {
|
||||
acc[agent.id] = agent.name || '';
|
||||
return acc;
|
||||
}, {});
|
||||
result.modelIcons = agents.reduce((acc, agent) => {
|
||||
result.modelIcons = agents?.reduce((acc, agent) => {
|
||||
acc[agent.id] = agent?.avatar?.filepath;
|
||||
return acc;
|
||||
}, {});
|
||||
|
@ -192,7 +177,7 @@ export const useEndpoints = ({
|
|||
|
||||
return result;
|
||||
});
|
||||
}, [filteredEndpoints, endpointsConfig, modelsQuery.data, agents, assistants]);
|
||||
}, [filteredEndpoints, endpointsConfig, modelsQuery.data, agents, assistants, azureAssistants]);
|
||||
|
||||
return {
|
||||
mappedEndpoints,
|
||||
|
|
|
@ -57,11 +57,9 @@ export const getDefaultAgentFormValues = () => ({
|
|||
export const processAgentOption = ({
|
||||
agent: _agent,
|
||||
fileMap,
|
||||
instanceProjectId,
|
||||
}: {
|
||||
agent?: Agent;
|
||||
fileMap?: Record<string, TFile | undefined>;
|
||||
instanceProjectId?: string;
|
||||
}): TAgentOption => {
|
||||
const isGlobal = _agent?.isPublic ?? false;
|
||||
const agent: TAgentOption = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue