Compare commits

...

8 Commits

Author SHA1 Message Date
Dustin Healy
1abf37af5a fix: optional req not provided, so req.config fails 2025-08-28 09:58:27 -07:00
Arthur Barrett
b75b799e34 🔧 fix: Handle Web API Streams in File Download Route for OpenAI Assistants (#9200) 2025-08-28 12:39:35 -04:00
Danny Avila
43add11b05 🎯 refactor: Custom Endpoint Request-based Header Resolution (#9344)
* refactor: resolve request-based headers for custom endpoints right before LLM request

* ci: clarify request-based header resolution in initializeClient test
2025-08-28 12:33:08 -04:00
Danny Avila
1764de53a5 fix: use appConfig correctly in getVoices 2025-08-28 00:51:22 -04:00
Danny Avila
c0511b9a5f 🔧 fix: MCP Selection Persist and UI Flicker Issues (#9324)
* refactor: useMCPSelect

    - Add useGetMCPTools to use in useMCPSelect and elsewhere hooks for fetching MCP tools
    - remove memoized key
    - remove use of `useChatContext` and require conversationId as prop

* feat: Add MCPPanelContext and integrate conversationId as prop for useMCPSelect across components

- Introduced MCPPanelContext to manage conversationId state.
- Updated MCPSelect, MCPSubMenu, and MCPConfigDialog to accept conversationId as a prop.
- Modified ToolsDropdown and BadgeRow to pass conversationId to relevant components.
- Refactored MCPPanel to utilize MCPPanelProvider for context management.

* fix: remove nested ternary in ServerInitializationSection

- Replaced conditional operator with if-else statements for better readability in determining button text based on server initialization state and reinitialization status.

* refactor: wrap setValueWrap in useCallback for performance optimization

* refactor: streamline useMCPSelect by consolidating storageKey definition

* fix: prevent clearing selections on page refresh by tracking initial load completion

* refactor: simplify concern of useMCPSelect hook

* refactor: move ConfigFieldDetail interface to common types for better reusability, isolate usage of `useGetMCPTools`

* refactor: integrate mcpServerNames into BadgeRowContext and update ToolsDropdown and MCPSelect components
2025-08-28 00:44:49 -04:00
Danny Avila
2483623c88 🔧 fix: type checking for process.browser in api-endpoints.ts 2025-08-27 20:27:57 -04:00
Danny Avila
229d6f2dfe 📦 chore: Update librechat-data-provider to v0.8.006 2025-08-27 20:23:18 -04:00
github-actions[bot]
d5ec838218 🌍 i18n: Update translation.json with latest translations (#9321)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-27 20:15:38 -04:00
31 changed files with 389 additions and 247 deletions

View File

@@ -7,6 +7,7 @@ const {
createRun,
Tokenizer,
checkAccess,
resolveHeaders,
getBalanceConfig,
memoryInstructions,
formatContentStrings,
@@ -879,6 +880,16 @@ class AgentClient extends BaseClient {
memoryPromise = this.runMemory(messages);
}
/** Resolve request-based headers for Custom Endpoints. Note: if this is added to
* non-custom endpoints, needs consideration of varying provider header configs.
*/
if (agent.model_parameters?.configuration?.defaultHeaders != null) {
agent.model_parameters.configuration.defaultHeaders = resolveHeaders({
headers: agent.model_parameters.configuration.defaultHeaders,
body: config.configurable.requestBody,
});
}
run = await createRun({
agent,
req: this.options.req,
@@ -1181,6 +1192,20 @@ class AgentClient extends BaseClient {
clientOptions.json = true;
}
/** Resolve request-based headers for Custom Endpoints. Note: if this is added to
* non-custom endpoints, needs consideration of varying provider header configs.
*/
if (clientOptions?.configuration?.defaultHeaders != null) {
clientOptions.configuration.defaultHeaders = resolveHeaders({
headers: clientOptions.configuration.defaultHeaders,
body: {
messageId: this.responseMessageId,
conversationId: this.conversationId,
parentMessageId: this.parentMessageId,
},
});
}
try {
const titleResult = await this.run.generateTitle({
provider,

View File

@@ -31,6 +31,7 @@ const { getAssistant } = require('~/models/Assistant');
const { getAgent } = require('~/models/Agent');
const { getLogStores } = require('~/cache');
const { logger } = require('~/config');
const { Readable } = require('stream');
const router = express.Router();
@@ -325,11 +326,6 @@ router.get('/download/:userId/:file_id', fileAccess, async (req, res) => {
res.setHeader('X-File-Metadata', JSON.stringify(file));
};
/** @type {{ body: import('stream').PassThrough } | undefined} */
let passThrough;
/** @type {ReadableStream | undefined} */
let fileStream;
if (checkOpenAIStorage(file.source)) {
req.body = { model: file.model };
const endpointMap = {
@@ -342,12 +338,19 @@ router.get('/download/:userId/:file_id', fileAccess, async (req, res) => {
overrideEndpoint: endpointMap[file.source],
});
logger.debug(`Downloading file ${file_id} from OpenAI`);
passThrough = await getDownloadStream(file_id, openai);
const passThrough = await getDownloadStream(file_id, openai);
setHeaders();
logger.debug(`File ${file_id} downloaded from OpenAI`);
passThrough.body.pipe(res);
// Handle both Node.js and Web streams
const stream =
passThrough.body && typeof passThrough.body.getReader === 'function'
? Readable.fromWeb(passThrough.body)
: passThrough.body;
stream.pipe(res);
} else {
fileStream = await getDownloadStream(req, file.filepath);
const fileStream = await getDownloadStream(req, file.filepath);
fileStream.on('error', (streamError) => {
logger.error('[DOWNLOAD ROUTE] Stream error:', streamError);

View File

@@ -36,10 +36,12 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
const CUSTOM_API_KEY = extractEnvVariable(endpointConfig.apiKey);
const CUSTOM_BASE_URL = extractEnvVariable(endpointConfig.baseURL);
/** Intentionally excludes passing `body`, i.e. `req.body`, as
* values may not be accurate until `AgentClient` is initialized
*/
let resolvedHeaders = resolveHeaders({
headers: endpointConfig.headers,
user: req.user,
body: req.body,
});
if (CUSTOM_API_KEY.match(envVarRegex)) {

View File

@@ -76,7 +76,10 @@ describe('custom/initializeClient', () => {
expect(resolveHeaders).toHaveBeenCalledWith({
headers: { 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
user: { id: 'user-123', email: 'test@example.com', role: 'user' },
/**
* Note: Request-based Header Resolution is deferred until right before LLM request is made
body: { endpoint: 'test-endpoint' }, // body - supports {{LIBRECHAT_BODY_*}} placeholders
*/
});
});

View File

@@ -110,7 +110,7 @@ class STTService {
*/
async getProviderSchema(req) {
const appConfig =
req.config ??
req?.config ??
(await getAppConfig({
role: req?.user?.role,
}));

View File

@@ -14,16 +14,18 @@ const { getProvider } = require('./TTSService');
*/
async function getVoices(req, res) {
try {
const appConfig = await getAppConfig({
role: req.user?.role,
});
const appConfig =
req.config ??
(await getAppConfig({
role: req.user?.role,
}));
if (!appConfig || !appConfig?.speech?.tts) {
const ttsSchema = appConfig?.speech?.tts;
if (!ttsSchema) {
throw new Error('Configuration or TTS schema is missing');
}
const ttsSchema = appConfig?.speech?.tts;
const provider = await getProvider(ttsSchema);
const provider = await getProvider(appConfig);
let voices;
switch (provider) {

View File

@@ -1,12 +1,19 @@
import React, { createContext, useContext, useEffect, useRef } from 'react';
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
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 } from '~/hooks';
import {
useSearchApiKeyForm,
useGetAgentsConfig,
useCodeApiKeyForm,
useGetMCPTools,
useToolToggle,
} from '~/hooks';
import { ephemeralAgentByConvoId } from '~/store';
interface BadgeRowContextType {
conversationId?: string | null;
mcpServerNames?: string[] | null;
agentsConfig?: TAgentsEndpoint | null;
webSearch: ReturnType<typeof useToolToggle>;
artifacts: ReturnType<typeof useToolToggle>;
@@ -37,10 +44,12 @@ export default function BadgeRowProvider({
isSubmitting,
conversationId,
}: BadgeRowProviderProps) {
const hasInitializedRef = useRef(false);
const lastKeyRef = useRef<string>('');
const hasInitializedRef = useRef(false);
const { mcpToolDetails } = useGetMCPTools();
const { agentsConfig } = useGetAgentsConfig();
const key = conversationId ?? Constants.NEW_CONVO;
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(key));
/** Initialize ephemeralAgent from localStorage on mount and when conversation changes */
@@ -156,11 +165,16 @@ export default function BadgeRowProvider({
isAuthenticated: true,
});
const mcpServerNames = useMemo(() => {
return (mcpToolDetails ?? []).map((tool) => tool.name);
}, [mcpToolDetails]);
const value: BadgeRowContextType = {
webSearch,
artifacts,
fileSearch,
agentsConfig,
mcpServerNames,
conversationId,
codeApiKeyForm,
codeInterpreter,

View File

@@ -0,0 +1,31 @@
import React, { createContext, useContext, useMemo } from 'react';
import { Constants } from 'librechat-data-provider';
import { useChatContext } from './ChatContext';
interface MCPPanelContextValue {
conversationId: string;
}
const MCPPanelContext = createContext<MCPPanelContextValue | undefined>(undefined);
export function MCPPanelProvider({ children }: { children: React.ReactNode }) {
const { conversation } = useChatContext();
/** Context value only created when conversationId changes */
const contextValue = useMemo<MCPPanelContextValue>(
() => ({
conversationId: conversation?.conversationId ?? Constants.NEW_CONVO,
}),
[conversation?.conversationId],
);
return <MCPPanelContext.Provider value={contextValue}>{children}</MCPPanelContext.Provider>;
}
export function useMCPPanelContext() {
const context = useContext(MCPPanelContext);
if (!context) {
throw new Error('useMCPPanelContext must be used within MCPPanelProvider');
}
return context;
}

View File

@@ -23,6 +23,7 @@ export * from './SetConvoContext';
export * from './SearchContext';
export * from './BadgeRowContext';
export * from './SidePanelContext';
export * from './MCPPanelContext';
export * from './ArtifactsContext';
export * from './PromptGroupsContext';
export { default as BadgeRowProvider } from './BadgeRowContext';

View File

@@ -8,6 +8,11 @@ import type * as t from 'librechat-data-provider';
import type { LucideIcon } from 'lucide-react';
import type { TranslationKeys } from '~/hooks';
export interface ConfigFieldDetail {
title: string;
description: string;
}
export type CodeBarProps = {
lang: string;
error?: boolean;

View File

@@ -368,7 +368,7 @@ function BadgeRow({
<CodeInterpreter />
<FileSearch />
<Artifacts />
<MCPSelect />
<MCPSelect conversationId={conversationId} />
</>
)}
{ghostBadge && (

View File

@@ -1,13 +1,9 @@
import React, { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { Button, Input, Label, OGDialog, OGDialogTemplate } from '@librechat/client';
import type { ConfigFieldDetail } from '~/common';
import { useLocalize } from '~/hooks';
export interface ConfigFieldDetail {
title: string;
description: string;
}
interface MCPConfigDialogProps {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
@@ -34,7 +30,7 @@ export default function MCPConfigDialog({
control,
handleSubmit,
reset,
formState: { errors, _ },
formState: { errors },
} = useForm<Record<string, string>>({
defaultValues: initialValues,
});
@@ -56,14 +52,12 @@ export default function MCPConfigDialog({
};
const dialogTitle = localize('com_ui_configure_mcp_variables_for', { 0: serverName });
const dialogDescription = localize('com_ui_mcp_dialog_desc');
return (
<OGDialog open={isOpen} onOpenChange={onOpenChange}>
<OGDialogTemplate
className="sm:max-w-lg"
title={dialogTitle}
description={dialogDescription}
headerClassName="px-6 pt-6 pb-4"
main={
<form onSubmit={handleSubmit(onFormSubmit)} className="space-y-4 px-6 pb-2">

View File

@@ -3,8 +3,11 @@ import { MultiSelect, MCPIcon } from '@librechat/client';
import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon';
import { useMCPServerManager } from '~/hooks/MCP/useMCPServerManager';
import MCPConfigDialog from '~/components/MCP/MCPConfigDialog';
import { useBadgeRowContext } from '~/Providers';
function MCPSelect() {
type MCPSelectProps = { conversationId?: string | null };
function MCPSelectContent({ conversationId }: MCPSelectProps) {
const {
configuredServers,
mcpValues,
@@ -15,7 +18,7 @@ function MCPSelect() {
getConfigDialogProps,
isInitializing,
localize,
} = useMCPServerManager();
} = useMCPServerManager({ conversationId });
const renderSelectedValues = useCallback(
(values: string[], placeholder?: string) => {
@@ -93,9 +96,17 @@ function MCPSelect() {
selectItemsClassName="border border-blue-600/50 bg-blue-500/10 hover:bg-blue-700/10"
selectClassName="group relative inline-flex items-center justify-center md:justify-start gap-1.5 rounded-full border border-border-medium text-sm font-medium transition-all md:w-full size-9 p-2 md:p-3 bg-transparent shadow-sm hover:bg-surface-hover hover:shadow-md active:shadow-inner"
/>
{configDialogProps && <MCPConfigDialog {...configDialogProps} />}
{configDialogProps && (
<MCPConfigDialog {...configDialogProps} conversationId={conversationId} />
)}
</>
);
}
function MCPSelect(props: MCPSelectProps) {
const { mcpServerNames } = useBadgeRowContext();
if ((mcpServerNames?.length ?? 0) === 0) return null;
return <MCPSelectContent {...props} />;
}
export default memo(MCPSelect);

View File

@@ -9,10 +9,11 @@ import { cn } from '~/utils';
interface MCPSubMenuProps {
placeholder?: string;
conversationId?: string | null;
}
const MCPSubMenu = React.forwardRef<HTMLDivElement, MCPSubMenuProps>(
({ placeholder, ...props }, ref) => {
({ placeholder, conversationId, ...props }, ref) => {
const {
configuredServers,
mcpValues,
@@ -23,7 +24,7 @@ const MCPSubMenu = React.forwardRef<HTMLDivElement, MCPSubMenuProps>(
getServerStatusIconProps,
getConfigDialogProps,
isInitializing,
} = useMCPServerManager();
} = useMCPServerManager({ conversationId });
const menuStore = Ariakit.useMenuStore({
focusLoop: true,

View File

@@ -10,12 +10,12 @@ import {
PermissionTypes,
defaultAgentCapabilities,
} from 'librechat-data-provider';
import { useLocalize, useHasAccess, useAgentCapabilities, useMCPSelect } from '~/hooks';
import { useLocalize, useHasAccess, useAgentCapabilities } from '~/hooks';
import ArtifactsSubMenu from '~/components/Chat/Input/ArtifactsSubMenu';
import MCPSubMenu from '~/components/Chat/Input/MCPSubMenu';
import { useGetStartupConfig } from '~/data-provider';
import { useBadgeRowContext } from '~/Providers';
import { cn } from '~/utils';
import { useGetStartupConfig } from '~/data-provider';
interface ToolsDropdownProps {
disabled?: boolean;
@@ -30,11 +30,12 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
artifacts,
fileSearch,
agentsConfig,
mcpServerNames,
conversationId,
codeApiKeyForm,
codeInterpreter,
searchApiKeyForm,
} = useBadgeRowContext();
const mcpSelect = useMCPSelect();
const { data: startupConfig } = useGetStartupConfig();
const { codeEnabled, webSearchEnabled, artifactsEnabled, fileSearchEnabled } =
@@ -56,7 +57,6 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
} = codeInterpreter;
const { isPinned: isFileSearchPinned, setIsPinned: setIsFileSearchPinned } = fileSearch;
const { isPinned: isArtifactsPinned, setIsPinned: setIsArtifactsPinned } = artifacts;
const { mcpServerNames } = mcpSelect;
const canUseWebSearch = useHasAccess({
permissionType: PermissionTypes.WEB_SEARCH,
@@ -290,7 +290,9 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
if (mcpServerNames && mcpServerNames.length > 0) {
dropdownItems.push({
hideOnClick: false,
render: (props) => <MCPSubMenu {...props} placeholder={mcpPlaceholder} />,
render: (props) => (
<MCPSubMenu {...props} placeholder={mcpPlaceholder} conversationId={conversationId} />
),
});
}

View File

@@ -8,15 +8,11 @@ import {
OGDialogContent,
} from '@librechat/client';
import type { MCPServerStatus } from 'librechat-data-provider';
import type { ConfigFieldDetail } from '~/common';
import ServerInitializationSection from './ServerInitializationSection';
import CustomUserVarsSection from './CustomUserVarsSection';
import { useLocalize } from '~/hooks';
export interface ConfigFieldDetail {
title: string;
description: string;
}
interface MCPConfigDialogProps {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
@@ -27,6 +23,7 @@ interface MCPConfigDialogProps {
onRevoke?: () => void;
serverName: string;
serverStatus?: MCPServerStatus;
conversationId?: string | null;
}
export default function MCPConfigDialog({
@@ -38,6 +35,7 @@ export default function MCPConfigDialog({
onRevoke,
serverName,
serverStatus,
conversationId,
}: MCPConfigDialogProps) {
const localize = useLocalize();
@@ -126,6 +124,7 @@ export default function MCPConfigDialog({
{/* Server Initialization Section */}
<ServerInitializationSection
serverName={serverName}
conversationId={conversationId}
requiresOAuth={serverStatus?.requiresOAuth || false}
hasCustomUserVars={fieldsSchema && Object.keys(fieldsSchema).length > 0}
/>

View File

@@ -9,12 +9,14 @@ interface ServerInitializationSectionProps {
serverName: string;
requiresOAuth: boolean;
hasCustomUserVars?: boolean;
conversationId?: string | null;
}
export default function ServerInitializationSection({
sidePanel = false,
serverName,
requiresOAuth,
conversationId,
sidePanel = false,
hasCustomUserVars = false,
}: ServerInitializationSectionProps) {
const localize = useLocalize();
@@ -26,7 +28,7 @@ export default function ServerInitializationSection({
isInitializing,
isCancellable,
getOAuthUrl,
} = useMCPServerManager();
} = useMCPServerManager({ conversationId });
const serverStatus = connectionStatus[serverName];
const isConnected = serverStatus?.connectionState === 'connected';
@@ -69,13 +71,18 @@ export default function ServerInitializationSection({
const isReinit = shouldShowReinit;
const outerClass = isReinit ? 'flex justify-start' : 'flex justify-end';
const buttonVariant = isReinit ? undefined : 'default';
const buttonText = isServerInitializing
? localize('com_ui_loading')
: isReinit
? localize('com_ui_reinitialize')
: requiresOAuth
? localize('com_ui_authenticate')
: localize('com_ui_mcp_initialize');
let buttonText = '';
if (isServerInitializing) {
buttonText = localize('com_ui_loading');
} else if (isReinit) {
buttonText = localize('com_ui_reinitialize');
} else if (requiresOAuth) {
buttonText = localize('com_ui_authenticate');
} else {
buttonText = localize('com_ui_mcp_initialize');
}
const icon = isServerInitializing ? (
<Spinner className="h-4 w-4" />
) : (

View File

@@ -8,15 +8,16 @@ import type { TUpdateUserPlugins } from 'librechat-data-provider';
import ServerInitializationSection from '~/components/MCP/ServerInitializationSection';
import { useMCPConnectionStatusQuery } from '~/data-provider/Tools/queries';
import CustomUserVarsSection from '~/components/MCP/CustomUserVarsSection';
import BadgeRowProvider from '~/Providers/BadgeRowContext';
import { MCPPanelProvider, useMCPPanelContext } from '~/Providers';
import { useGetStartupConfig } from '~/data-provider';
import MCPPanelSkeleton from './MCPPanelSkeleton';
import { useLocalize } from '~/hooks';
function MCPPanelContent() {
const localize = useLocalize();
const { showToast } = useToastContext();
const queryClient = useQueryClient();
const { showToast } = useToastContext();
const { conversationId } = useMCPPanelContext();
const { data: startupConfig, isLoading: startupConfigLoading } = useGetStartupConfig();
const { data: connectionStatusData } = useMCPConnectionStatusQuery();
const [selectedServerNameForEditing, setSelectedServerNameForEditing] = useState<string | null>(
@@ -153,6 +154,7 @@ function MCPPanelContent() {
<ServerInitializationSection
sidePanel={true}
conversationId={conversationId}
serverName={selectedServerNameForEditing}
requiresOAuth={serverStatus?.requiresOAuth || false}
hasCustomUserVars={
@@ -204,8 +206,8 @@ function MCPPanelContent() {
export default function MCPPanel() {
return (
<BadgeRowProvider>
<MCPPanelProvider>
<MCPPanelContent />
</BadgeRowProvider>
</MCPPanelProvider>
);
}

View File

@@ -1 +1,3 @@
export * from './useMCPSelect';
export * from './useGetMCPTools';
export { useMCPServerManager } from './useMCPServerManager';

View File

@@ -0,0 +1,43 @@
import { useMemo } from 'react';
import { Constants, EModelEndpoint } from 'librechat-data-provider';
import type { TPlugin } from 'librechat-data-provider';
import { useAvailableToolsQuery, useGetStartupConfig } from '~/data-provider';
export function useGetMCPTools() {
const { data: startupConfig } = useGetStartupConfig();
const { data: rawMcpTools } = useAvailableToolsQuery(EModelEndpoint.agents, {
select: (data: TPlugin[]) => {
const mcpToolsMap = new Map<string, TPlugin>();
data.forEach((tool) => {
const isMCP = tool.pluginKey.includes(Constants.mcp_delimiter);
if (isMCP) {
const parts = tool.pluginKey.split(Constants.mcp_delimiter);
const serverName = parts[parts.length - 1];
if (!mcpToolsMap.has(serverName)) {
mcpToolsMap.set(serverName, {
name: serverName,
pluginKey: tool.pluginKey,
authConfig: tool.authConfig,
authenticated: tool.authenticated,
});
}
}
});
return Array.from(mcpToolsMap.values());
},
});
const mcpToolDetails = useMemo(() => {
if (!rawMcpTools || !startupConfig?.mcpServers) {
return rawMcpTools;
}
return rawMcpTools.filter((tool) => {
const serverConfig = startupConfig?.mcpServers?.[tool.name];
return serverConfig?.chatMenu !== false;
});
}, [rawMcpTools, startupConfig?.mcpServers]);
return {
mcpToolDetails,
};
}

View File

@@ -0,0 +1,72 @@
import { useRef, useCallback, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { Constants, LocalStorageKeys } from 'librechat-data-provider';
import useLocalStorage from '~/hooks/useLocalStorageAlt';
import { ephemeralAgentByConvoId } from '~/store';
const storageCondition = (value: unknown, rawCurrentValue?: string | null) => {
if (rawCurrentValue) {
try {
const currentValue = rawCurrentValue?.trim() ?? '';
if (currentValue.length > 2) {
return true;
}
} catch (e) {
console.error(e);
}
}
return Array.isArray(value) && value.length > 0;
};
export function useMCPSelect({ conversationId }: { conversationId?: string | null }) {
const key = conversationId ?? Constants.NEW_CONVO;
const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key));
const storageKey = `${LocalStorageKeys.LAST_MCP_}${key}`;
const mcpState = useMemo(() => {
return ephemeralAgent?.mcp ?? [];
}, [ephemeralAgent?.mcp]);
const setSelectedValues = useCallback(
(values: string[] | null | undefined) => {
if (!values) {
return;
}
if (!Array.isArray(values)) {
return;
}
setEphemeralAgent((prev) => ({
...prev,
mcp: values,
}));
},
[setEphemeralAgent],
);
const [mcpValues, setMCPValuesRaw] = useLocalStorage<string[]>(
storageKey,
mcpState,
setSelectedValues,
storageCondition,
);
const setMCPValuesRawRef = useRef(setMCPValuesRaw);
setMCPValuesRawRef.current = setMCPValuesRaw;
/** Create a stable memoized setter to avoid re-creating it on every render and causing an infinite render loop */
const setMCPValues = useCallback((value: string[]) => {
setMCPValuesRawRef.current(value);
}, []);
const [isPinned, setIsPinned] = useLocalStorage<boolean>(
`${LocalStorageKeys.PIN_MCP_}${key}`,
true,
);
return {
isPinned,
mcpValues,
setIsPinned,
setMCPValues,
};
}

View File

@@ -8,10 +8,10 @@ import {
useReinitializeMCPServerMutation,
} from 'librechat-data-provider/react-query';
import type { TUpdateUserPlugins, TPlugin } from 'librechat-data-provider';
import type { ConfigFieldDetail } from '~/components/MCP/MCPConfigDialog';
import type { ConfigFieldDetail } from '~/common';
import { useMCPConnectionStatusQuery } from '~/data-provider/Tools/queries';
import { useLocalize, useMCPSelect, useGetMCPTools } from '~/hooks';
import { useGetStartupConfig } from '~/data-provider';
import { useLocalize, useMCPSelect } from '~/hooks';
interface ServerState {
isInitializing: boolean;
@@ -21,13 +21,14 @@ interface ServerState {
pollInterval: NodeJS.Timeout | null;
}
export function useMCPServerManager() {
export function useMCPServerManager({ conversationId }: { conversationId?: string | null }) {
const localize = useLocalize();
const { showToast } = useToastContext();
const mcpSelect = useMCPSelect();
const { data: startupConfig } = useGetStartupConfig();
const { mcpValues, setMCPValues, mcpToolDetails, isPinned, setIsPinned } = mcpSelect;
const queryClient = useQueryClient();
const { showToast } = useToastContext();
const { mcpToolDetails } = useGetMCPTools();
const mcpSelect = useMCPSelect({ conversationId });
const { data: startupConfig } = useGetStartupConfig();
const { mcpValues, setMCPValues, isPinned, setIsPinned } = mcpSelect;
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
const [selectedToolForConfig, setSelectedToolForConfig] = useState<TPlugin | null>(null);
@@ -90,7 +91,21 @@ export function useMCPServerManager() {
[connectionStatusData?.connectionStatus],
);
/** Filter disconnected servers when values change, but only after initial load
This prevents clearing selections on page refresh when servers haven't connected yet
*/
const hasInitialLoadCompleted = useRef(false);
useEffect(() => {
if (!connectionStatusData || Object.keys(connectionStatus).length === 0) {
return;
}
if (!hasInitialLoadCompleted.current) {
hasInitialLoadCompleted.current = true;
return;
}
if (!mcpValues?.length) return;
const connectedSelected = mcpValues.filter(
@@ -100,7 +115,7 @@ export function useMCPServerManager() {
if (connectedSelected.length !== mcpValues.length) {
setMCPValues(connectedSelected);
}
}, [connectionStatus, mcpValues, setMCPValues]);
}, [connectionStatus, connectionStatusData, mcpValues, setMCPValues]);
const updateServerState = useCallback((serverName: string, updates: Partial<ServerState>) => {
setServerStates((prev) => {
@@ -486,12 +501,12 @@ export function useMCPServerManager() {
};
},
[
isCancellable,
mcpToolDetails,
isInitializing,
cancelOAuthFlow,
connectionStatus,
startupConfig?.mcpServers,
isInitializing,
isCancellable,
cancelOAuthFlow,
],
);
@@ -547,7 +562,6 @@ export function useMCPServerManager() {
mcpValues,
setMCPValues,
mcpToolDetails,
isPinned,
setIsPinned,
placeholderText,

View File

@@ -1,4 +1,3 @@
export * from './useMCPSelect';
export * from './useToolToggle';
export { default as useAuthCodeTool } from './useAuthCodeTool';
export { default as usePluginInstall } from './usePluginInstall';

View File

@@ -1,136 +0,0 @@
import { useRef, useEffect, useCallback, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { Constants, LocalStorageKeys, EModelEndpoint } from 'librechat-data-provider';
import type { TPlugin } from 'librechat-data-provider';
import { useAvailableToolsQuery, useGetStartupConfig } from '~/data-provider';
import useLocalStorage from '~/hooks/useLocalStorageAlt';
import { ephemeralAgentByConvoId } from '~/store';
import { useChatContext } from '~/Providers';
const storageCondition = (value: unknown, rawCurrentValue?: string | null) => {
if (rawCurrentValue) {
try {
const currentValue = rawCurrentValue?.trim() ?? '';
if (currentValue.length > 2) {
return true;
}
} catch (e) {
console.error(e);
}
}
return Array.isArray(value) && value.length > 0;
};
export function useMCPSelect() {
const { conversation } = useChatContext();
const key = useMemo(
() => conversation?.conversationId ?? Constants.NEW_CONVO,
[conversation?.conversationId],
);
const hasSetFetched = useRef<string | null>(null);
const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key));
const { data: startupConfig } = useGetStartupConfig();
const { data: rawMcpTools, isFetched } = useAvailableToolsQuery(EModelEndpoint.agents, {
select: (data: TPlugin[]) => {
const mcpToolsMap = new Map<string, TPlugin>();
data.forEach((tool) => {
const isMCP = tool.pluginKey.includes(Constants.mcp_delimiter);
if (isMCP) {
const parts = tool.pluginKey.split(Constants.mcp_delimiter);
const serverName = parts[parts.length - 1];
if (!mcpToolsMap.has(serverName)) {
mcpToolsMap.set(serverName, {
name: serverName,
pluginKey: tool.pluginKey,
authConfig: tool.authConfig,
authenticated: tool.authenticated,
});
}
}
});
return Array.from(mcpToolsMap.values());
},
});
const mcpToolDetails = useMemo(() => {
if (!rawMcpTools || !startupConfig?.mcpServers) {
return rawMcpTools;
}
return rawMcpTools.filter((tool) => {
const serverConfig = startupConfig?.mcpServers?.[tool.name];
return serverConfig?.chatMenu !== false;
});
}, [rawMcpTools, startupConfig?.mcpServers]);
const mcpState = useMemo(() => {
return ephemeralAgent?.mcp ?? [];
}, [ephemeralAgent?.mcp]);
const setSelectedValues = useCallback(
(values: string[] | null | undefined) => {
if (!values) {
return;
}
if (!Array.isArray(values)) {
return;
}
setEphemeralAgent((prev) => ({
...prev,
mcp: values,
}));
},
[setEphemeralAgent],
);
const [mcpValues, setMCPValuesRaw] = useLocalStorage<string[]>(
`${LocalStorageKeys.LAST_MCP_}${key}`,
mcpState,
setSelectedValues,
storageCondition,
);
const setMCPValuesRawRef = useRef(setMCPValuesRaw);
setMCPValuesRawRef.current = setMCPValuesRaw;
// Create a stable memoized setter to avoid re-creating it on every render and causing an infinite render loop
const setMCPValues = useCallback((value: string[]) => {
setMCPValuesRawRef.current(value);
}, []);
const [isPinned, setIsPinned] = useLocalStorage<boolean>(
`${LocalStorageKeys.PIN_MCP_}${key}`,
true,
);
useEffect(() => {
if (hasSetFetched.current === key) {
return;
}
if (!isFetched) {
return;
}
hasSetFetched.current = key;
if ((mcpToolDetails?.length ?? 0) > 0) {
setMCPValues(mcpValues.filter((mcp) => mcpToolDetails?.some((tool) => tool.name === mcp)));
return;
}
setMCPValues([]);
}, [isFetched, setMCPValues, mcpToolDetails, key, mcpValues]);
const mcpServerNames = useMemo(() => {
return (mcpToolDetails ?? []).map((tool) => tool.name);
}, [mcpToolDetails]);
return {
isPinned,
mcpValues,
setIsPinned,
setMCPValues,
mcpServerNames,
ephemeralAgent,
mcpToolDetails,
setEphemeralAgent,
};
}

View File

@@ -5,7 +5,7 @@
* - Also value will be updated everywhere, when value updated (via `storage` event)
*/
import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
export default function useLocalStorage<T>(
key: string,
@@ -47,23 +47,26 @@ export default function useLocalStorage<T>(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, globalSetState]);
const setValueWrap = (value: T) => {
try {
setValue(value);
const storeLocal = () => {
localStorage.setItem(key, JSON.stringify(value));
window?.dispatchEvent(new StorageEvent('storage', { key }));
};
if (!storageCondition) {
storeLocal();
} else if (storageCondition(value, localStorage.getItem(key))) {
storeLocal();
const setValueWrap = useCallback(
(value: T) => {
try {
setValue(value);
const storeLocal = () => {
localStorage.setItem(key, JSON.stringify(value));
window?.dispatchEvent(new StorageEvent('storage', { key }));
};
if (!storageCondition) {
storeLocal();
} else if (storageCondition(value, localStorage.getItem(key))) {
storeLocal();
}
globalSetState?.(value);
} catch (e) {
console.error(e);
}
globalSetState?.(value);
} catch (e) {
console.error(e);
}
};
},
[key, globalSetState, storageCondition],
);
return [value, setValueWrap];
}

View File

@@ -519,9 +519,9 @@
"com_nav_lang_polish": "Polski",
"com_nav_lang_portuguese": "Português",
"com_nav_lang_russian": "Русский",
"com_nav_lang_slovenian": "Slovenščina",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_slovenian": "Slovenščina",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_tibetan": "བོད་སྐད་",
"com_nav_lang_traditional_chinese": "繁體中文",
@@ -889,9 +889,9 @@
"com_ui_field_max_length": "{{field}} must be less than {{length}} characters",
"com_ui_field_required": "This field is required",
"com_ui_file_size": "File Size",
"com_ui_files": "Files",
"com_ui_file_token_limit": "File Token Limit",
"com_ui_file_token_limit_desc": "Set maximum token limit for file processing to control costs and resource usage",
"com_ui_files": "Files",
"com_ui_filter_prompts": "Filter Prompts",
"com_ui_filter_prompts_name": "Filter prompts by name",
"com_ui_final_touch": "Final touch",

View File

@@ -1,13 +1,29 @@
{
"chat_direction_left_to_right": "kaut kam šeit ir jābūt. bija tukšs",
"chat_direction_right_to_left": "kaut kam šeit ir jābūt. bija tukšs",
"chat_direction_left_to_right": "Nav rezultātu",
"chat_direction_right_to_left": "Nav rezultātu",
"com_a11y_ai_composing": "Mākslīgais intelekts joprojām veido.",
"com_a11y_end": "Mākslīgais intelekts ir pabeidzis atbildi.",
"com_a11y_start": "Mākslīgais intelekts ir sācis savu atbildi.",
"com_agents_agent_card_label": "{{name}} aģents. {{description}}",
"com_agents_all": "Visi aģenti",
"com_agents_all_category": "Viss",
"com_agents_all_description": "Pārlūkot visus koplietotos aģentus visās kategorijās",
"com_agents_by_librechat": "no LibreChat",
"com_agents_category_aftersales": "Pēcpārdošanas",
"com_agents_category_aftersales_description": "Aģenti, kas specializējas pēcpārdošanas atbalstā, apkopē un klientu apkalpošanā",
"com_agents_category_empty": "Nav atrasts neviens aģents {{category}} kategorijā",
"com_agents_category_finance": "Finanses",
"com_agents_category_finance_description": "Aģenti, kas specializējas finanšu analīzē, budžeta plānošanā un grāmatvedībā",
"com_agents_category_general": "Vispārīgi",
"com_agents_category_general_description": "Vispārējas nozīmes aģenti bieži uzdotiem uzdevumiem un jautājumiem",
"com_agents_category_hr": "Cilvēkresursi",
"com_agents_category_hr_description": "Aģenti, kas specializējas HR procesos, politikās un darbinieku atbalstā",
"com_agents_category_it": "IT",
"com_agents_category_it_description": "IT atbalsta, tehnisko problēmu novēršanas un sistēmas administrēšanas aģenti",
"com_agents_category_rd": "Pētniecība un izstrāde",
"com_agents_category_rd_description": "Aģenti, kas koncentrējas uz pētniecības un attīstības procesiem, inovācijām un tehnisko pētniecību",
"com_agents_category_sales": "Pārdošana",
"com_agents_category_sales_description": "Aģenti, kas koncentrējas uz pārdošanas procesiem un klientu attiecībām",
"com_agents_category_tab_label": "{{category}} kategorija, {{position}} no {{total}}",
"com_agents_category_tabs_label": "Aģentu kategorijas",
"com_agents_clear_search": "Notīrīt meklēšanu",
@@ -278,7 +294,7 @@
"com_endpoint_openai_detail": "Vision pieprasījumu izšķirtspēja. “Zema” ir lētāka un ātrāka, “Augsta” ir detalizētāka un dārgāka, un “Automātiska” automātiski izvēlēsies vienu no abām, pamatojoties uz attēla izšķirtspēju.",
"com_endpoint_openai_freq": "Skaitlis no -2,0 līdz 2,0. Pozitīvas vērtības soda jaunus tokenus, pamatojoties uz to esošo biežumu tekstā līdz šim, samazinot modeļa iespējamību atkārtot vienu un to pašu rindu burtiski.",
"com_endpoint_openai_max": "Maksimālais ģenerējamo tokenu skaits. Ievades tokenu un ģenerēto tokenu kopējo garumu ierobežo modeļa konteksta garums.",
"com_endpoint_openai_max_tokens": "Neobligāts lauks “max_tokens”, kas norāda maksimālo tokenu skaitu, ko var ģenerēt sarunas pabeigšanas laikā. Ievades tokenu un ģenerēto tokenu kopējo garumu ierobežo modeļa konteksta garums. Ja šis skaitlis pārsniedz maksimālo konteksta tokenu skaitu, var rasties kļūdas.",
"com_endpoint_openai_max_tokens": "Pēc izvēles “max_tokens” lauks, kas norāda maksimālo tokenu skaitu, ko var ģenerēt sarunas pabeigšanas laikā. Ievades tokenu un ģenerēto tokenu kopējo garumu ierobežo modeļa konteksta garums. Ja šis skaitlis pārsniedz maksimālo konteksta tokenu skaitu, var rasties kļūdas.",
"com_endpoint_openai_pres": "Skaitlis no -2,0 līdz 2,0. Pozitīvas vērtības soda jaunus tokenus, pamatojoties uz to, vai tie līdz šim parādās tekstā, palielinot modeļa iespējamību runāt par jaunām tēmām.",
"com_endpoint_openai_prompt_prefix_placeholder": "Iestatiet pielāgotas instrukcijas, kas jāiekļauj sistēmas ziņā. Noklusējuma vērtība: nav",
"com_endpoint_openai_reasoning_effort": "Tikai o1 un o3 modeļi: ierobežo spriešanas modeļu spriešanas piepūli. Spriešanas piepūles samazināšana var nodrošināt ātrākas atbildes un mazāk spriešanas tokenus izmantošanas atbildē.",
@@ -299,7 +315,7 @@
"com_endpoint_plug_use_functions": "Izmantot funkcijas",
"com_endpoint_presence_penalty": "Klātbūtnes sods",
"com_endpoint_preset": "iepriekš iestatīts",
"com_endpoint_preset_custom_name_placeholder": "kaut kam šeit ir jānotiek. bija tukšs",
"com_endpoint_preset_custom_name_placeholder": "Nav rezultātu",
"com_endpoint_preset_default": "tagad ir noklusējuma iestatījums.",
"com_endpoint_preset_default_item": "Noklusējums:",
"com_endpoint_preset_default_none": "Nav aktīvu noklusējuma iestatījumu.",
@@ -372,7 +388,7 @@
"com_files_number_selected": "{{0}} no {{1}} atlasīti faili",
"com_files_preparing_download": "Sagatavošanās lejupielādei...",
"com_files_sharepoint_picker_title": "Izvēlieties failus",
"com_files_table": "kaut kam šeit ir jānotiek. bija tukšs",
"com_files_table": "Nav rezultātu",
"com_files_upload_local_machine": "No lokālā datora",
"com_files_upload_sharepoint": "No SharePoint",
"com_generated_files": "Ģenerētie faili:",
@@ -410,7 +426,7 @@
"com_nav_balance_month": "mēnesis",
"com_nav_balance_months": "mēneši",
"com_nav_balance_next_refill": "Nākamā bilances papildināšana:",
"com_nav_balance_next_refill_info": "Nākamā bilances papildināšana notiks automātiski tikai tad, ja būs izpildīti abi nosacījumi: kopš pēdējās bilances papildināšanas ir pagājis norādītais laika atjaunošanas biežums un vaicājuma nosūtīšana izraisītu jūsu atlikuma samazināšanos zem nulles.",
"com_nav_balance_next_refill_info": "Nākamā bilances papildināšana notiks automātiski tikai tad, ja būs izpildīti abi nosacījumi: kopš pēdējās bilances papildināšanas ir pagājis norādītais laika atjaunošanas biežums un jauna vaicājuma nosūtīšana izraisītu jūsu atlikuma samazināšanos zem nulles.",
"com_nav_balance_refill_amount": "Bilances papildināšanas apjoms:",
"com_nav_balance_second": "otrais",
"com_nav_balance_seconds": "sekundes",
@@ -620,7 +636,7 @@
"com_ui_admin": "Administrators",
"com_ui_admin_access_warning": "Administratora piekļuves atspējošana šai funkcijai var izraisīt neparedzētas lietotāja saskarnes problēmas, kurām nepieciešama atsvaidzināšana. Ja izmaiņas ir saglabātas, vienīgais veids, kā tās atjaunot, ir, izmantojot saskarnes iestatījumu librechat.yaml konfigurācijā, kas ietekmē visas lomas.",
"com_ui_admin_settings": "Administratora iestatījumi",
"com_ui_advanced": "Advancēts",
"com_ui_advanced": "Paplašinātie uzstādījumi",
"com_ui_advanced_settings": "Advancētie iestatījumi",
"com_ui_agent": "Aģents",
"com_ui_agent_category_aftersales": "Pēcpārdošanas pakalpojumi",
@@ -724,6 +740,7 @@
"com_ui_bookmarks_edit": "Rediģēt grāmatzīmi",
"com_ui_bookmarks_filter": "Filtrēt grāmatzīmes...",
"com_ui_bookmarks_new": "Jauna grāmatzīme",
"com_ui_bookmarks_tag_exists": "Grāmatzīme ar šādu nosaukumu jau pastāv",
"com_ui_bookmarks_title": "Nosaukums",
"com_ui_bookmarks_update_error": "Atjauninot grāmatzīmi, radās kļūda.",
"com_ui_bookmarks_update_success": "Grāmatzīme veiksmīgi atjaunināta",
@@ -743,7 +760,7 @@
"com_ui_close_window": "Aizvērt logu",
"com_ui_code": "Kods",
"com_ui_collapse_chat": "Sakļaut sarunas logu",
"com_ui_command_placeholder": "Pēc izvēles: ievadiet komandu uzvednei vai tiks izmantots nosaukums",
"com_ui_command_placeholder": "Pēc izvēles: Ja tiks izmantota komanda uzvednei vai nosaukums, lūdzu ievadiet",
"com_ui_command_usage_placeholder": "Atlasiet uzvedni pēc komandas vai nosaukuma",
"com_ui_complete_setup": "Pabeigt iestatīšanu",
"com_ui_concise": "Īss",
@@ -791,7 +808,7 @@
"com_ui_date_september": "Septembris",
"com_ui_date_today": "Šodien",
"com_ui_date_yesterday": "Vakar",
"com_ui_decline": "Es nepieņemu",
"com_ui_decline": "Es nepiekrītu",
"com_ui_default_post_request": "Noklusējums (POST pieprasījums)",
"com_ui_delete": "Dzēst",
"com_ui_delete_action": "Dzēst darbību",
@@ -825,9 +842,9 @@
"com_ui_download_backup": "Lejupielādēt rezerves kodus",
"com_ui_download_backup_tooltip": "Pirms turpināt, lejupielādējiet rezerves kodus. Tie būs nepieciešami, lai atgūtu piekļuvi, ja pazaudēsiet autentifikatora ierīci.",
"com_ui_download_error": "Kļūda, lejupielādējot failu. Iespējams, fails ir izdzēsts.",
"com_ui_drag_drop": "kaut kam šeit ir jānotiek. bija tukšs",
"com_ui_drag_drop": "Nav rezultātu",
"com_ui_dropdown_variables": "Nolaižamās izvēlnes mainīgie:",
"com_ui_dropdown_variables_info": "Izveidojiet pielāgotas nolaižamās izvēlnes savām uzvednēm:{{variable_name:option1|option2|option3}}`",
"com_ui_dropdown_variables_info": "Izveidojiet pielāgotas nolaižamās izvēlnes savām uzvednēm:{{variable_name:option1|option2|option3}}` (mainīgā_nosakums:opcija1|opcija2|opcija3)",
"com_ui_duplicate": "Dublikāts",
"com_ui_duplication_error": "Sarunas dublēšanas laikā radās kļūda.",
"com_ui_duplication_processing": "Dublēju sarunu...",
@@ -870,6 +887,7 @@
"com_ui_feedback_tag_other": "Cita problēma",
"com_ui_feedback_tag_unjustified_refusal": "Atteicās bez iemesla",
"com_ui_feedback_tag_zero": "Cita problēma",
"com_ui_field_max_length": "{{field}} jābūt mazākam par {{length}} rakstzīmēm",
"com_ui_field_required": "Šis lauks ir obligāts",
"com_ui_file_size": "Faila lielums",
"com_ui_files": "Faili",
@@ -907,7 +925,7 @@
"com_ui_generating": "Ģenerē...",
"com_ui_generation_settings": "Ģenerēšanas iestatījumi",
"com_ui_getting_started": "Darba sākšana",
"com_ui_global_group": "kaut kam šeit ir jānotiek. bija tukšs",
"com_ui_global_group": "Nav rezultātu",
"com_ui_go_back": "Atgriezties",
"com_ui_go_to_conversation": "Doties uz sarunu",
"com_ui_good_afternoon": "Labdien",
@@ -940,7 +958,7 @@
"com_ui_latest_production_version": "Jaunākā produkcijas versija",
"com_ui_latest_version": "Jaunākā versija",
"com_ui_librechat_code_api_key": "Iegūstiet savu LibreChat koda interpretatora API atslēgu",
"com_ui_librechat_code_api_subtitle": "Droši. Daudzvalodu. Ievades/izvades faili.",
"com_ui_librechat_code_api_subtitle": "Drošs. Daudzas valodas. Ievades/izvades faili.",
"com_ui_librechat_code_api_title": "Palaist mākslīgā intelekta kodu",
"com_ui_loading": "Notiek ielāde...",
"com_ui_locked": "Bloķēts",
@@ -998,13 +1016,14 @@
"com_ui_no_bookmarks": "Šķiet, ka jums vēl nav grāmatzīmju. Noklikšķiniet uz sarunas un pievienojiet jaunu.",
"com_ui_no_categories": "Nav pieejamas nevienas kategorijas",
"com_ui_no_category": "Nav kategorijas",
"com_ui_no_data": "kaut kam šeit ir jānotiek. bija tukšs",
"com_ui_no_changes": "Izmaiņas netika veiktas",
"com_ui_no_data": "Nav rezultātu",
"com_ui_no_individual_access": "Aatsevišķiem lietotājiem vai grupām nav pieejas pie šī aģenta",
"com_ui_no_personalization_available": "Pašlaik nav pieejamas personalizācijas opcijas",
"com_ui_no_read_access": "Jums nav atļaujas skatīt atmiņas",
"com_ui_no_results_found": "Nav atrastu rezultātu",
"com_ui_no_terms_content": "Nav noteikumu un nosacījumu satura, ko parādīt",
"com_ui_no_valid_items": "kaut kam šeit ir jānotiek. bija tukšs",
"com_ui_no_valid_items": "Nav rezultātu",
"com_ui_none": "Neviens",
"com_ui_not_used": "Nav izmantots",
"com_ui_nothing_found": "Nekas nav atrasts",
@@ -1113,6 +1132,7 @@
"com_ui_select_file": "Atlasiet failu",
"com_ui_select_model": "Izvēlieties modeli",
"com_ui_select_options": "Izvēlieties opcijas...",
"com_ui_select_or_create_prompt": "Izvēlieties vai izveidot uzvedni",
"com_ui_select_provider": "Izvēlieties pakalpojumu sniedzēju",
"com_ui_select_provider_first": "Vispirms izvēlieties pakalpojumu sniedzēju",
"com_ui_select_region": "Izvēlieties reģionu",
@@ -1147,7 +1167,7 @@
"com_ui_special_var_current_user": "Pašreizējais lietotājs",
"com_ui_special_var_iso_datetime": "UTC ISO datums un laiks",
"com_ui_special_variables": "Īpašie mainīgie:",
"com_ui_special_variables_more_info": "Nolaižamajā izvēlnē varat atlasīt īpašos mainīgos:{{current_date}}` (šodienas datums un nedēļas diena), `{{current_datetime}}` (vietējais datums un laiks), `{{utc_iso_datetime}}` (UTC ISO datums/laiks) un `{{current_user}} (jūsu konta nosaukums).",
"com_ui_special_variables_more_info": "Nolaižamajā izvēlnē varat atlasīt īpašos mainīgos:{{current_date}}` (šodienas datums un nedēļas diena), `{{current_datetime}}` (vietējais datums un laiks), `{{utc_iso_datetime}}` (UTC ISO datums/laiks) un `{{current_user}} (jūsu lietotāja vārds).",
"com_ui_speech_while_submitting": "Nevar iesniegt runu, kamēr tiek ģenerēta atbilde.",
"com_ui_sr_actions_menu": "Atvērt darbību izvēlni priekš \"{{0}}\"",
"com_ui_stop": "Apstāties",
@@ -1210,7 +1230,7 @@
"com_ui_user_group_permissions": "Lietotāju un grupu atļaujas",
"com_ui_value": "Vērtība",
"com_ui_variables": "Mainīgie",
"com_ui_variables_info": "Mainīgo veidošanai tekstā izmantot dubultās iekavas, piemēram, `{{example variable}}`, lai vēlāk aizpildītu, izmantojot uzvedni.",
"com_ui_variables_info": "Mainīgo veidošanai tekstā izmantot dubultās iekavas, piemēram, `{{example variable}}` (mainīgā piemērs), lai vēlāk aizpildītu, izmantojot uzvedni.",
"com_ui_verify": "Pārbaudīt",
"com_ui_version_var": "Versija {{0}}",
"com_ui_versions": "Versijas",

View File

@@ -6,8 +6,24 @@
"com_a11y_start": "AI 已开始回复。",
"com_agents_agent_card_label": "智能体 {{name}}{{description}}",
"com_agents_all": "全部智能体",
"com_agents_all_category": "全部",
"com_agents_all_description": "浏览所有类别中的全部共享智能体",
"com_agents_by_librechat": "由 LibreChat 提供",
"com_agents_category_aftersales": "售后",
"com_agents_category_aftersales_description": "专注于售后支持、维护与客户服务的专用智能体",
"com_agents_category_empty": "在 {{category}} 类别中未找到智能体",
"com_agents_category_finance": "财务",
"com_agents_category_finance_description": "专注于财务分析、预算编制和会计的专用智能体",
"com_agents_category_general": "通用",
"com_agents_category_general_description": "用于处理常见任务和咨询的通用智能体",
"com_agents_category_hr": "人事",
"com_agents_category_hr_description": "专注于人事流程、政策及员工支持的专用智能体",
"com_agents_category_it": "IT",
"com_agents_category_it_description": "专注于 IT 支持、技术排障及系统管理的专用智能体",
"com_agents_category_rd": "研发",
"com_agents_category_rd_description": "专注于研发流程、创新及技术研究的专用智能体",
"com_agents_category_sales": "销售",
"com_agents_category_sales_description": "专注于销售流程和客户关系的专用智能体",
"com_agents_category_tab_label": "分类 {{category}}{{position}} / {{total}}",
"com_agents_category_tabs_label": "智能体类别",
"com_agents_clear_search": "清除搜索",
@@ -624,10 +640,10 @@
"com_ui_advanced_settings": "进阶设置",
"com_ui_agent": "智能体",
"com_ui_agent_category_aftersales": "售后",
"com_ui_agent_category_finance": "金融",
"com_ui_agent_category_finance": "财务",
"com_ui_agent_category_general": "通用",
"com_ui_agent_category_hr": "人力资源",
"com_ui_agent_category_it": "信息技术",
"com_ui_agent_category_hr": "人",
"com_ui_agent_category_it": "IT",
"com_ui_agent_category_rd": "研发",
"com_ui_agent_category_sales": "销售",
"com_ui_agent_category_selector_aria": "智能体类别选择器",
@@ -724,6 +740,7 @@
"com_ui_bookmarks_edit": "编辑书签",
"com_ui_bookmarks_filter": "筛选书签...",
"com_ui_bookmarks_new": "新书签",
"com_ui_bookmarks_tag_exists": "已存在使用此标题的书签",
"com_ui_bookmarks_title": "标题",
"com_ui_bookmarks_update_error": "更新书签时出现错误",
"com_ui_bookmarks_update_success": "书签更新成功",
@@ -868,6 +885,7 @@
"com_ui_feedback_tag_not_matched": "不符合我的要求",
"com_ui_feedback_tag_other": "其他问题",
"com_ui_feedback_tag_unjustified_refusal": "无故拒绝回答",
"com_ui_field_max_length": "{{field}} 最多 {{length}} 个字符",
"com_ui_field_required": "此字段为必填项",
"com_ui_file_size": "文件大小",
"com_ui_files": "文件",
@@ -996,6 +1014,7 @@
"com_ui_no_bookmarks": "似乎您还没有书签,点击一个对话并添加一个新的书签",
"com_ui_no_categories": "无可用类别",
"com_ui_no_category": "无类别",
"com_ui_no_changes": "未做任何修改",
"com_ui_no_data": "这里需要放点东西,当前是空的",
"com_ui_no_individual_access": "任何个人用户或群组都无法访问该智能体",
"com_ui_no_personalization_available": "当前没有可用的个性化选项",
@@ -1111,6 +1130,7 @@
"com_ui_select_file": "选择文件",
"com_ui_select_model": "模型选择",
"com_ui_select_options": "选择选项...",
"com_ui_select_or_create_prompt": "选择或创建提示词",
"com_ui_select_provider": "选择提供商",
"com_ui_select_provider_first": "请先选择提供商",
"com_ui_select_region": "选择地区",

2
package-lock.json generated
View File

@@ -52227,7 +52227,7 @@
},
"packages/data-provider": {
"name": "librechat-data-provider",
"version": "0.8.005",
"version": "0.8.006",
"license": "ISC",
"dependencies": {
"axios": "^1.8.2",

View File

@@ -1,6 +1,6 @@
{
"name": "librechat-data-provider",
"version": "0.8.005",
"version": "0.8.006",
"description": "data services for librechat apps",
"main": "dist/index.js",
"module": "dist/index.es.js",

View File

@@ -3,7 +3,10 @@ import * as q from './types/queries';
import { ResourceType } from './accessPermissions';
let BASE_URL = '';
if (typeof process === 'undefined' || process.browser === true) {
if (
typeof process === 'undefined' ||
(process as typeof process & { browser?: boolean }).browser === true
) {
// process is only available in node context, or process.browser is true in client-side code
// This is to ensure that the BASE_URL is set correctly based on the <base>
// element in the HTML document, if it exists.