Compare commits
8 Commits
fix/back-t
...
fix/stt-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1abf37af5a | ||
|
|
b75b799e34 | ||
|
|
43add11b05 | ||
|
|
1764de53a5 | ||
|
|
c0511b9a5f | ||
|
|
2483623c88 | ||
|
|
229d6f2dfe | ||
|
|
d5ec838218 |
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class STTService {
|
||||
*/
|
||||
async getProviderSchema(req) {
|
||||
const appConfig =
|
||||
req.config ??
|
||||
req?.config ??
|
||||
(await getAppConfig({
|
||||
role: req?.user?.role,
|
||||
}));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
31
client/src/Providers/MCPPanelContext.tsx
Normal file
31
client/src/Providers/MCPPanelContext.tsx
Normal 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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -368,7 +368,7 @@ function BadgeRow({
|
||||
<CodeInterpreter />
|
||||
<FileSearch />
|
||||
<Artifacts />
|
||||
<MCPSelect />
|
||||
<MCPSelect conversationId={conversationId} />
|
||||
</>
|
||||
)}
|
||||
{ghostBadge && (
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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} />
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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" />
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './useMCPSelect';
|
||||
export * from './useGetMCPTools';
|
||||
export { useMCPServerManager } from './useMCPServerManager';
|
||||
|
||||
43
client/src/hooks/MCP/useGetMCPTools.ts
Normal file
43
client/src/hooks/MCP/useGetMCPTools.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
72
client/src/hooks/MCP/useMCPSelect.ts
Normal file
72
client/src/hooks/MCP/useMCPSelect.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './useMCPSelect';
|
||||
export * from './useToolToggle';
|
||||
export { default as useAuthCodeTool } from './useAuthCodeTool';
|
||||
export { default as usePluginInstall } from './usePluginInstall';
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
2
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user