Compare commits
1 Commits
feat/agent
...
fix/stt-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1abf37af5a |
@@ -312,16 +312,6 @@ Current Date & Time: ${replaceSpecialVars({ text: '{{iso_datetime}}' })}
|
||||
continue;
|
||||
} else if (tool && cachedTools && mcpToolPattern.test(tool)) {
|
||||
const [toolName, serverName] = tool.split(Constants.mcp_delimiter);
|
||||
if (toolName === Constants.mcp_server) {
|
||||
/** Placeholder used for UI purposes */
|
||||
continue;
|
||||
}
|
||||
if (serverName && options.req?.config?.mcpConfig?.[serverName] == null) {
|
||||
logger.warn(
|
||||
`MCP server "${serverName}" for "${toolName}" tool is not configured${agent?.id != null && agent.id ? ` but attached to "${agent.id}"` : ''}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (toolName === Constants.mcp_all) {
|
||||
const currentMCPGenerator = async (index) =>
|
||||
createMCPTools({
|
||||
|
||||
@@ -211,7 +211,7 @@ describe('File Access Control', () => {
|
||||
expect(accessMap.get(fileIds[1])).toBe(false);
|
||||
});
|
||||
|
||||
it('should deny access when user only has VIEW permission and needs access for deletion', async () => {
|
||||
it('should deny access when user only has VIEW permission', async () => {
|
||||
const userId = new mongoose.Types.ObjectId();
|
||||
const authorId = new mongoose.Types.ObjectId();
|
||||
const agentId = uuidv4();
|
||||
@@ -263,71 +263,12 @@ describe('File Access Control', () => {
|
||||
role: SystemRoles.USER,
|
||||
fileIds,
|
||||
agentId,
|
||||
isDelete: true,
|
||||
});
|
||||
|
||||
// Should have no access to any files when only VIEW permission
|
||||
expect(accessMap.get(fileIds[0])).toBe(false);
|
||||
expect(accessMap.get(fileIds[1])).toBe(false);
|
||||
});
|
||||
|
||||
it('should grant access when user has VIEW permission', async () => {
|
||||
const userId = new mongoose.Types.ObjectId();
|
||||
const authorId = new mongoose.Types.ObjectId();
|
||||
const agentId = uuidv4();
|
||||
const fileIds = [uuidv4(), uuidv4()];
|
||||
|
||||
// Create users
|
||||
await User.create({
|
||||
_id: userId,
|
||||
email: 'user@example.com',
|
||||
emailVerified: true,
|
||||
provider: 'local',
|
||||
});
|
||||
|
||||
await User.create({
|
||||
_id: authorId,
|
||||
email: 'author@example.com',
|
||||
emailVerified: true,
|
||||
provider: 'local',
|
||||
});
|
||||
|
||||
// Create agent with files
|
||||
const agent = await createAgent({
|
||||
id: agentId,
|
||||
name: 'View-Only Agent',
|
||||
author: authorId,
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
tool_resources: {
|
||||
file_search: {
|
||||
file_ids: fileIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Grant only VIEW permission to user on the agent
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: userId,
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
// Check access for files
|
||||
const { hasAccessToFilesViaAgent } = require('~/server/services/Files/permissions');
|
||||
const accessMap = await hasAccessToFilesViaAgent({
|
||||
userId: userId,
|
||||
role: SystemRoles.USER,
|
||||
fileIds,
|
||||
agentId,
|
||||
});
|
||||
|
||||
expect(accessMap.get(fileIds[0])).toBe(true);
|
||||
expect(accessMap.get(fileIds[1])).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFiles with agent access control', () => {
|
||||
|
||||
@@ -269,7 +269,7 @@ async function getListPromptGroupsByAccess({
|
||||
const baseQuery = { ...otherParams, _id: { $in: accessibleIds } };
|
||||
|
||||
// Add cursor condition
|
||||
if (after && typeof after === 'string' && after !== 'undefined' && after !== 'null') {
|
||||
if (after) {
|
||||
try {
|
||||
const cursor = JSON.parse(Buffer.from(after, 'base64').toString('utf8'));
|
||||
const { updatedAt, _id } = cursor;
|
||||
|
||||
@@ -44,12 +44,12 @@
|
||||
"@googleapis/youtube": "^20.0.0",
|
||||
"@keyv/redis": "^4.3.3",
|
||||
"@langchain/community": "^0.3.47",
|
||||
"@langchain/core": "^0.3.72",
|
||||
"@langchain/core": "^0.3.62",
|
||||
"@langchain/google-genai": "^0.2.13",
|
||||
"@langchain/google-vertexai": "^0.2.13",
|
||||
"@langchain/openai": "^0.5.18",
|
||||
"@langchain/textsplitters": "^0.1.0",
|
||||
"@librechat/agents": "^3.0.0-rc10",
|
||||
"@librechat/agents": "^2.4.76",
|
||||
"@librechat/api": "*",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||
|
||||
@@ -74,23 +74,14 @@ const getAvailableTools = async (req, res) => {
|
||||
const cachedToolsArray = await cache.get(CacheKeys.TOOLS);
|
||||
const cachedUserTools = await getCachedTools({ userId });
|
||||
|
||||
const appConfig = req.config ?? (await getAppConfig({ role: req.user?.role }));
|
||||
const mcpManager = getMCPManager();
|
||||
const userPlugins =
|
||||
cachedUserTools != null
|
||||
? convertMCPToolsToPlugins({ functionTools: cachedUserTools, mcpManager })
|
||||
: undefined;
|
||||
|
||||
/** @type {TPlugin[]} */
|
||||
let mcpPlugins;
|
||||
if (appConfig?.mcpConfig) {
|
||||
const mcpManager = getMCPManager();
|
||||
mcpPlugins =
|
||||
cachedUserTools != null
|
||||
? convertMCPToolsToPlugins({ functionTools: cachedUserTools, mcpManager })
|
||||
: undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
cachedToolsArray != null &&
|
||||
(appConfig?.mcpConfig != null ? mcpPlugins != null && mcpPlugins.length > 0 : true)
|
||||
) {
|
||||
const dedupedTools = filterUniquePlugins([...(mcpPlugins ?? []), ...cachedToolsArray]);
|
||||
if (cachedToolsArray != null && userPlugins != null) {
|
||||
const dedupedTools = filterUniquePlugins([...userPlugins, ...cachedToolsArray]);
|
||||
res.status(200).json(dedupedTools);
|
||||
return;
|
||||
}
|
||||
@@ -102,9 +93,9 @@ const getAvailableTools = async (req, res) => {
|
||||
/** @type {import('@librechat/api').LCManifestTool[]} */
|
||||
let pluginManifest = availableTools;
|
||||
|
||||
const appConfig = req.config ?? (await getAppConfig({ role: req.user?.role }));
|
||||
if (appConfig?.mcpConfig != null) {
|
||||
try {
|
||||
const mcpManager = getMCPManager();
|
||||
const mcpTools = await mcpManager.getAllToolFunctions(userId);
|
||||
prelimCachedTools = prelimCachedTools ?? {};
|
||||
for (const [toolKey, toolData] of Object.entries(mcpTools)) {
|
||||
@@ -184,7 +175,7 @@ const getAvailableTools = async (req, res) => {
|
||||
const finalTools = filterUniquePlugins(toolsOutput);
|
||||
await cache.set(CacheKeys.TOOLS, finalTools);
|
||||
|
||||
const dedupedTools = filterUniquePlugins([...(mcpPlugins ?? []), ...finalTools]);
|
||||
const dedupedTools = filterUniquePlugins([...(userPlugins ?? []), ...finalTools]);
|
||||
res.status(200).json(dedupedTools);
|
||||
} catch (error) {
|
||||
logger.error('[getAvailableTools]', error);
|
||||
|
||||
@@ -174,19 +174,10 @@ describe('PluginController', () => {
|
||||
mockCache.get.mockResolvedValue(null);
|
||||
getCachedTools.mockResolvedValueOnce(mockUserTools);
|
||||
mockReq.config = {
|
||||
mcpConfig: {
|
||||
server1: {},
|
||||
},
|
||||
mcpConfig: null,
|
||||
paths: { structuredTools: '/mock/path' },
|
||||
};
|
||||
|
||||
// Mock MCP manager to return empty tools initially (since getAllToolFunctions is called)
|
||||
const mockMCPManager = {
|
||||
getAllToolFunctions: jest.fn().mockResolvedValue({}),
|
||||
getRawConfig: jest.fn().mockReturnValue({}),
|
||||
};
|
||||
require('~/config').getMCPManager.mockReturnValue(mockMCPManager);
|
||||
|
||||
// Mock second call to return tool definitions (includeGlobal: true)
|
||||
getCachedTools.mockResolvedValueOnce(mockUserTools);
|
||||
|
||||
@@ -514,7 +505,7 @@ describe('PluginController', () => {
|
||||
expect(mockRes.json).toHaveBeenCalledWith([]);
|
||||
});
|
||||
|
||||
it('should handle `cachedToolsArray` and `mcpPlugins` both being defined', async () => {
|
||||
it('should handle cachedToolsArray and userPlugins both being defined', async () => {
|
||||
const cachedTools = [{ name: 'CachedTool', pluginKey: 'cached-tool', description: 'Cached' }];
|
||||
// Use MCP delimiter for the user tool so convertMCPToolsToPlugins works
|
||||
const userTools = {
|
||||
@@ -531,19 +522,10 @@ describe('PluginController', () => {
|
||||
mockCache.get.mockResolvedValue(cachedTools);
|
||||
getCachedTools.mockResolvedValueOnce(userTools);
|
||||
mockReq.config = {
|
||||
mcpConfig: {
|
||||
server1: {},
|
||||
},
|
||||
mcpConfig: null,
|
||||
paths: { structuredTools: '/mock/path' },
|
||||
};
|
||||
|
||||
// Mock MCP manager to return empty tools initially
|
||||
const mockMCPManager = {
|
||||
getAllToolFunctions: jest.fn().mockResolvedValue({}),
|
||||
getRawConfig: jest.fn().mockReturnValue({}),
|
||||
};
|
||||
require('~/config').getMCPManager.mockReturnValue(mockMCPManager);
|
||||
|
||||
// The controller expects a second call to getCachedTools
|
||||
getCachedTools.mockResolvedValueOnce({
|
||||
'cached-tool': { type: 'function', function: { name: 'cached-tool' } },
|
||||
|
||||
@@ -187,7 +187,7 @@ const updateUserPluginsController = async (req, res) => {
|
||||
// Extract server name from pluginKey (format: "mcp_<serverName>")
|
||||
const serverName = pluginKey.replace(Constants.mcp_prefix, '');
|
||||
logger.info(
|
||||
`[updateUserPluginsController] Attempting disconnect of MCP server "${serverName}" for user ${user.id} after plugin auth update.`,
|
||||
`[updateUserPluginsController] Disconnecting MCP server ${serverName} for user ${user.id} after plugin auth update for ${pluginKey}.`,
|
||||
);
|
||||
await mcpManager.disconnectUserConnection(user.id, serverName);
|
||||
}
|
||||
|
||||
@@ -95,19 +95,6 @@ class ModelEndHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Agent Chain helper
|
||||
* @param {string | undefined} [last_agent_id]
|
||||
* @param {string | undefined} [langgraph_node]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkIfLastAgent(last_agent_id, langgraph_node) {
|
||||
if (!last_agent_id || !langgraph_node) {
|
||||
return false;
|
||||
}
|
||||
return langgraph_node?.endsWith(last_agent_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default handlers for stream events.
|
||||
* @param {Object} options - The options object.
|
||||
@@ -138,7 +125,7 @@ function getDefaultHandlers({ res, aggregateContent, toolEndCallback, collectedU
|
||||
handle: (event, data, metadata) => {
|
||||
if (data?.stepDetails.type === StepTypes.TOOL_CALLS) {
|
||||
sendEvent(res, { event, data });
|
||||
} else if (checkIfLastAgent(metadata?.last_agent_id, metadata?.langgraph_node)) {
|
||||
} else if (metadata?.last_agent_index === metadata?.agent_index) {
|
||||
sendEvent(res, { event, data });
|
||||
} else if (!metadata?.hide_sequential_outputs) {
|
||||
sendEvent(res, { event, data });
|
||||
@@ -167,7 +154,7 @@ function getDefaultHandlers({ res, aggregateContent, toolEndCallback, collectedU
|
||||
handle: (event, data, metadata) => {
|
||||
if (data?.delta.type === StepTypes.TOOL_CALLS) {
|
||||
sendEvent(res, { event, data });
|
||||
} else if (checkIfLastAgent(metadata?.last_agent_id, metadata?.langgraph_node)) {
|
||||
} else if (metadata?.last_agent_index === metadata?.agent_index) {
|
||||
sendEvent(res, { event, data });
|
||||
} else if (!metadata?.hide_sequential_outputs) {
|
||||
sendEvent(res, { event, data });
|
||||
@@ -185,7 +172,7 @@ function getDefaultHandlers({ res, aggregateContent, toolEndCallback, collectedU
|
||||
handle: (event, data, metadata) => {
|
||||
if (data?.result != null) {
|
||||
sendEvent(res, { event, data });
|
||||
} else if (checkIfLastAgent(metadata?.last_agent_id, metadata?.langgraph_node)) {
|
||||
} else if (metadata?.last_agent_index === metadata?.agent_index) {
|
||||
sendEvent(res, { event, data });
|
||||
} else if (!metadata?.hide_sequential_outputs) {
|
||||
sendEvent(res, { event, data });
|
||||
@@ -201,7 +188,7 @@ function getDefaultHandlers({ res, aggregateContent, toolEndCallback, collectedU
|
||||
* @param {GraphRunnableConfig['configurable']} [metadata] The runnable metadata.
|
||||
*/
|
||||
handle: (event, data, metadata) => {
|
||||
if (checkIfLastAgent(metadata?.last_agent_id, metadata?.langgraph_node)) {
|
||||
if (metadata?.last_agent_index === metadata?.agent_index) {
|
||||
sendEvent(res, { event, data });
|
||||
} else if (!metadata?.hide_sequential_outputs) {
|
||||
sendEvent(res, { event, data });
|
||||
@@ -217,7 +204,7 @@ function getDefaultHandlers({ res, aggregateContent, toolEndCallback, collectedU
|
||||
* @param {GraphRunnableConfig['configurable']} [metadata] The runnable metadata.
|
||||
*/
|
||||
handle: (event, data, metadata) => {
|
||||
if (checkIfLastAgent(metadata?.last_agent_id, metadata?.langgraph_node)) {
|
||||
if (metadata?.last_agent_index === metadata?.agent_index) {
|
||||
sendEvent(res, { event, data });
|
||||
} else if (!metadata?.hide_sequential_outputs) {
|
||||
sendEvent(res, { event, data });
|
||||
|
||||
@@ -3,17 +3,20 @@ const { logger } = require('@librechat/data-schemas');
|
||||
const { DynamicStructuredTool } = require('@langchain/core/tools');
|
||||
const { getBufferString, HumanMessage } = require('@langchain/core/messages');
|
||||
const {
|
||||
sendEvent,
|
||||
createRun,
|
||||
Tokenizer,
|
||||
checkAccess,
|
||||
resolveHeaders,
|
||||
getBalanceConfig,
|
||||
memoryInstructions,
|
||||
formatContentStrings,
|
||||
createMemoryProcessor,
|
||||
} = require('@librechat/api');
|
||||
const {
|
||||
Callback,
|
||||
Providers,
|
||||
GraphEvents,
|
||||
TitleMethod,
|
||||
formatMessage,
|
||||
formatAgentMessages,
|
||||
@@ -32,12 +35,12 @@ const {
|
||||
bedrockInputSchema,
|
||||
removeNullishValues,
|
||||
} = require('librechat-data-provider');
|
||||
const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts');
|
||||
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
|
||||
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
|
||||
const { getFormattedMemories, deleteMemory, setMemory } = require('~/models');
|
||||
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
|
||||
const { getProviderConfig } = require('~/server/services/Endpoints');
|
||||
const { createContextHandlers } = require('~/app/clients/prompts');
|
||||
const { checkCapability } = require('~/server/services/Config');
|
||||
const BaseClient = require('~/app/clients/BaseClient');
|
||||
const { getRoleByName } = require('~/models/Role');
|
||||
@@ -74,6 +77,8 @@ const payloadParser = ({ req, agent, endpoint }) => {
|
||||
return req.body.endpointOption.model_parameters;
|
||||
};
|
||||
|
||||
const noSystemModelRegex = [/\b(o1-preview|o1-mini|amazon\.titan-text)\b/gi];
|
||||
|
||||
function createTokenCounter(encoding) {
|
||||
return function (message) {
|
||||
const countTokens = (text) => Tokenizer.getTokenCount(text, encoding);
|
||||
@@ -796,81 +801,138 @@ class AgentClient extends BaseClient {
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Agent} agent
|
||||
* @param {BaseMessage[]} messages
|
||||
* @param {number} [i]
|
||||
* @param {TMessageContentParts[]} [contentData]
|
||||
* @param {Record<string, number>} [currentIndexCountMap]
|
||||
*/
|
||||
const runAgents = async (messages) => {
|
||||
const agents = [this.options.agent];
|
||||
if (
|
||||
this.agentConfigs &&
|
||||
this.agentConfigs.size > 0 &&
|
||||
((this.options.agent.edges?.length ?? 0) > 0 ||
|
||||
(await checkCapability(this.options.req, AgentCapabilities.chain)))
|
||||
) {
|
||||
agents.push(...this.agentConfigs.values());
|
||||
const runAgent = async (agent, _messages, i = 0, contentData = [], _currentIndexCountMap) => {
|
||||
config.configurable.model = agent.model_parameters.model;
|
||||
const currentIndexCountMap = _currentIndexCountMap ?? indexTokenCountMap;
|
||||
if (i > 0) {
|
||||
this.model = agent.model_parameters.model;
|
||||
}
|
||||
|
||||
if (agents[0].recursion_limit && typeof agents[0].recursion_limit === 'number') {
|
||||
config.recursionLimit = agents[0].recursion_limit;
|
||||
if (i > 0 && config.signal == null) {
|
||||
config.signal = abortController.signal;
|
||||
}
|
||||
if (agent.recursion_limit && typeof agent.recursion_limit === 'number') {
|
||||
config.recursionLimit = agent.recursion_limit;
|
||||
}
|
||||
|
||||
if (
|
||||
agentsEConfig?.maxRecursionLimit &&
|
||||
config.recursionLimit > agentsEConfig?.maxRecursionLimit
|
||||
) {
|
||||
config.recursionLimit = agentsEConfig?.maxRecursionLimit;
|
||||
}
|
||||
config.configurable.agent_id = agent.id;
|
||||
config.configurable.name = agent.name;
|
||||
config.configurable.agent_index = i;
|
||||
const noSystemMessages = noSystemModelRegex.some((regex) =>
|
||||
agent.model_parameters.model.match(regex),
|
||||
);
|
||||
|
||||
// TODO: needs to be added as part of AgentContext initialization
|
||||
// const noSystemModelRegex = [/\b(o1-preview|o1-mini|amazon\.titan-text)\b/gi];
|
||||
// const noSystemMessages = noSystemModelRegex.some((regex) =>
|
||||
// agent.model_parameters.model.match(regex),
|
||||
// );
|
||||
// if (noSystemMessages === true && systemContent?.length) {
|
||||
// const latestMessageContent = _messages.pop().content;
|
||||
// if (typeof latestMessageContent !== 'string') {
|
||||
// latestMessageContent[0].text = [systemContent, latestMessageContent[0].text].join('\n');
|
||||
// _messages.push(new HumanMessage({ content: latestMessageContent }));
|
||||
// } else {
|
||||
// const text = [systemContent, latestMessageContent].join('\n');
|
||||
// _messages.push(new HumanMessage(text));
|
||||
// }
|
||||
// }
|
||||
// let messages = _messages;
|
||||
// if (agent.useLegacyContent === true) {
|
||||
// messages = formatContentStrings(messages);
|
||||
// }
|
||||
// if (
|
||||
// agent.model_parameters?.clientOptions?.defaultHeaders?.['anthropic-beta']?.includes(
|
||||
// 'prompt-caching',
|
||||
// )
|
||||
// ) {
|
||||
// messages = addCacheControl(messages);
|
||||
// }
|
||||
const systemMessage = Object.values(agent.toolContextMap ?? {})
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
memoryPromise = this.runMemory(messages);
|
||||
let systemContent = [
|
||||
systemMessage,
|
||||
agent.instructions ?? '',
|
||||
i !== 0 ? (agent.additional_instructions ?? '') : '',
|
||||
]
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
if (noSystemMessages === true) {
|
||||
agent.instructions = undefined;
|
||||
agent.additional_instructions = undefined;
|
||||
} else {
|
||||
agent.instructions = systemContent;
|
||||
agent.additional_instructions = undefined;
|
||||
}
|
||||
|
||||
if (noSystemMessages === true && systemContent?.length) {
|
||||
const latestMessageContent = _messages.pop().content;
|
||||
if (typeof latestMessageContent !== 'string') {
|
||||
latestMessageContent[0].text = [systemContent, latestMessageContent[0].text].join('\n');
|
||||
_messages.push(new HumanMessage({ content: latestMessageContent }));
|
||||
} else {
|
||||
const text = [systemContent, latestMessageContent].join('\n');
|
||||
_messages.push(new HumanMessage(text));
|
||||
}
|
||||
}
|
||||
|
||||
let messages = _messages;
|
||||
if (agent.useLegacyContent === true) {
|
||||
messages = formatContentStrings(messages);
|
||||
}
|
||||
if (
|
||||
agent.model_parameters?.clientOptions?.defaultHeaders?.['anthropic-beta']?.includes(
|
||||
'prompt-caching',
|
||||
)
|
||||
) {
|
||||
messages = addCacheControl(messages);
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
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({
|
||||
agents,
|
||||
indexTokenCountMap,
|
||||
agent,
|
||||
req: this.options.req,
|
||||
runId: this.responseMessageId,
|
||||
signal: abortController.signal,
|
||||
customHandlers: this.options.eventHandlers,
|
||||
requestBody: config.configurable.requestBody,
|
||||
tokenCounter: createTokenCounter(this.getEncoding()),
|
||||
});
|
||||
|
||||
if (!run) {
|
||||
throw new Error('Failed to create run');
|
||||
}
|
||||
|
||||
this.run = run;
|
||||
if (i === 0) {
|
||||
this.run = run;
|
||||
}
|
||||
|
||||
if (contentData.length) {
|
||||
const agentUpdate = {
|
||||
type: ContentTypes.AGENT_UPDATE,
|
||||
[ContentTypes.AGENT_UPDATE]: {
|
||||
index: contentData.length,
|
||||
runId: this.responseMessageId,
|
||||
agentId: agent.id,
|
||||
},
|
||||
};
|
||||
const streamData = {
|
||||
event: GraphEvents.ON_AGENT_UPDATE,
|
||||
data: agentUpdate,
|
||||
};
|
||||
this.options.aggregateContent(streamData);
|
||||
sendEvent(this.options.res, streamData);
|
||||
contentData.push(agentUpdate);
|
||||
run.Graph.contentData = contentData;
|
||||
}
|
||||
|
||||
if (userMCPAuthMap != null) {
|
||||
config.configurable.userMCPAuthMap = userMCPAuthMap;
|
||||
}
|
||||
|
||||
/** @deprecated Agent Chain */
|
||||
config.configurable.last_agent_id = agents[agents.length - 1].id;
|
||||
await run.processStream({ messages }, config, {
|
||||
keepContent: i !== 0,
|
||||
tokenCounter: createTokenCounter(this.getEncoding()),
|
||||
indexTokenCountMap: currentIndexCountMap,
|
||||
maxContextTokens: agent.maxContextTokens,
|
||||
callbacks: {
|
||||
[Callback.TOOL_ERROR]: logToolError,
|
||||
},
|
||||
@@ -879,22 +941,109 @@ class AgentClient extends BaseClient {
|
||||
config.signal = null;
|
||||
};
|
||||
|
||||
await runAgents(initialMessages);
|
||||
/** @deprecated Agent Chain */
|
||||
if (config.configurable.hide_sequential_outputs) {
|
||||
this.contentParts = this.contentParts.filter((part, index) => {
|
||||
// Include parts that are either:
|
||||
// 1. At or after the finalContentStart index
|
||||
// 2. Of type tool_call
|
||||
// 3. Have tool_call_ids property
|
||||
return (
|
||||
index >= this.contentParts.length - 1 ||
|
||||
part.type === ContentTypes.TOOL_CALL ||
|
||||
part.tool_call_ids
|
||||
);
|
||||
});
|
||||
await runAgent(this.options.agent, initialMessages);
|
||||
let finalContentStart = 0;
|
||||
if (
|
||||
this.agentConfigs &&
|
||||
this.agentConfigs.size > 0 &&
|
||||
(await checkCapability(this.options.req, AgentCapabilities.chain))
|
||||
) {
|
||||
const windowSize = 5;
|
||||
let latestMessage = initialMessages.pop().content;
|
||||
if (typeof latestMessage !== 'string') {
|
||||
latestMessage = latestMessage[0].text;
|
||||
}
|
||||
let i = 1;
|
||||
let runMessages = [];
|
||||
|
||||
const windowIndexCountMap = {};
|
||||
const windowMessages = initialMessages.slice(-windowSize);
|
||||
let currentIndex = 4;
|
||||
for (let i = initialMessages.length - 1; i >= 0; i--) {
|
||||
windowIndexCountMap[currentIndex] = indexTokenCountMap[i];
|
||||
currentIndex--;
|
||||
if (currentIndex < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const encoding = this.getEncoding();
|
||||
const tokenCounter = createTokenCounter(encoding);
|
||||
for (const [agentId, agent] of this.agentConfigs) {
|
||||
if (abortController.signal.aborted === true) {
|
||||
break;
|
||||
}
|
||||
const currentRun = await run;
|
||||
|
||||
if (
|
||||
i === this.agentConfigs.size &&
|
||||
config.configurable.hide_sequential_outputs === true
|
||||
) {
|
||||
const content = this.contentParts.filter(
|
||||
(part) => part.type === ContentTypes.TOOL_CALL,
|
||||
);
|
||||
|
||||
this.options.res.write(
|
||||
`event: message\ndata: ${JSON.stringify({
|
||||
event: 'on_content_update',
|
||||
data: {
|
||||
runId: this.responseMessageId,
|
||||
content,
|
||||
},
|
||||
})}\n\n`,
|
||||
);
|
||||
}
|
||||
const _runMessages = currentRun.Graph.getRunMessages();
|
||||
finalContentStart = this.contentParts.length;
|
||||
runMessages = runMessages.concat(_runMessages);
|
||||
const contentData = currentRun.Graph.contentData.slice();
|
||||
const bufferString = getBufferString([new HumanMessage(latestMessage), ...runMessages]);
|
||||
if (i === this.agentConfigs.size) {
|
||||
logger.debug(`SEQUENTIAL AGENTS: Last buffer string:\n${bufferString}`);
|
||||
}
|
||||
try {
|
||||
const contextMessages = [];
|
||||
const runIndexCountMap = {};
|
||||
for (let i = 0; i < windowMessages.length; i++) {
|
||||
const message = windowMessages[i];
|
||||
const messageType = message._getType();
|
||||
if (
|
||||
(!agent.tools || agent.tools.length === 0) &&
|
||||
(messageType === 'tool' || (message.tool_calls?.length ?? 0) > 0)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
runIndexCountMap[contextMessages.length] = windowIndexCountMap[i];
|
||||
contextMessages.push(message);
|
||||
}
|
||||
const bufferMessage = new HumanMessage(bufferString);
|
||||
runIndexCountMap[contextMessages.length] = tokenCounter(bufferMessage);
|
||||
const currentMessages = [...contextMessages, bufferMessage];
|
||||
await runAgent(agent, currentMessages, i, contentData, runIndexCountMap);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`[api/server/controllers/agents/client.js #chatCompletion] Error running agent ${agentId} (${i})`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/** Note: not implemented */
|
||||
if (config.configurable.hide_sequential_outputs !== true) {
|
||||
finalContentStart = 0;
|
||||
}
|
||||
|
||||
this.contentParts = this.contentParts.filter((part, index) => {
|
||||
// Include parts that are either:
|
||||
// 1. At or after the finalContentStart index
|
||||
// 2. Of type tool_call
|
||||
// 3. Have tool_call_ids property
|
||||
return (
|
||||
index >= finalContentStart || part.type === ContentTypes.TOOL_CALL || part.tool_call_ids
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
const attachments = await this.awaitMemoryWithTimeout(memoryPromise);
|
||||
if (attachments && attachments.length > 0) {
|
||||
|
||||
@@ -5,7 +5,6 @@ const { logger } = require('@librechat/data-schemas');
|
||||
const { agentCreateSchema, agentUpdateSchema } = require('@librechat/api');
|
||||
const {
|
||||
Tools,
|
||||
Constants,
|
||||
SystemRoles,
|
||||
FileSources,
|
||||
ResourceType,
|
||||
@@ -70,9 +69,9 @@ const createAgentHandler = async (req, res) => {
|
||||
for (const tool of tools) {
|
||||
if (availableTools[tool]) {
|
||||
agentData.tools.push(tool);
|
||||
} else if (systemTools[tool]) {
|
||||
agentData.tools.push(tool);
|
||||
} else if (tool.includes(Constants.mcp_delimiter)) {
|
||||
}
|
||||
|
||||
if (systemTools[tool]) {
|
||||
agentData.tools.push(tool);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,9 +125,6 @@ router.get('/', async function (req, res) {
|
||||
payload.mcpServers = {};
|
||||
const getMCPServers = () => {
|
||||
try {
|
||||
if (appConfig?.mcpConfig == null) {
|
||||
return;
|
||||
}
|
||||
const mcpManager = getMCPManager();
|
||||
if (!mcpManager) {
|
||||
return;
|
||||
|
||||
@@ -4,13 +4,9 @@ const { sleep } = require('@librechat/agents');
|
||||
const { isEnabled } = require('@librechat/api');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { CacheKeys, EModelEndpoint } = require('librechat-data-provider');
|
||||
const {
|
||||
createImportLimiters,
|
||||
createForkLimiters,
|
||||
configMiddleware,
|
||||
} = require('~/server/middleware');
|
||||
const { getConvosByCursor, deleteConvos, getConvo, saveConvo } = require('~/models/Conversation');
|
||||
const { forkConversation, duplicateConversation } = require('~/server/utils/import/fork');
|
||||
const { createImportLimiters, createForkLimiters } = require('~/server/middleware');
|
||||
const { storage, importFileFilter } = require('~/server/routes/files/multer');
|
||||
const requireJwtAuth = require('~/server/middleware/requireJwtAuth');
|
||||
const { importConversations } = require('~/server/utils/import');
|
||||
@@ -175,7 +171,6 @@ router.post(
|
||||
'/import',
|
||||
importIpLimiter,
|
||||
importUserLimiter,
|
||||
configMiddleware,
|
||||
upload.single('file'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -185,7 +185,6 @@ router.delete('/', async (req, res) => {
|
||||
role: req.user.role,
|
||||
fileIds: nonOwnedFileIds,
|
||||
agentId: req.body.agent_id,
|
||||
isDelete: true,
|
||||
});
|
||||
|
||||
for (const file of nonOwnedFiles) {
|
||||
|
||||
@@ -156,7 +156,7 @@ router.get('/all', async (req, res) => {
|
||||
router.get('/groups', async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { pageSize, limit, cursor, name, category, ...otherFilters } = req.query;
|
||||
const { pageSize, pageNumber, limit, cursor, name, category, ...otherFilters } = req.query;
|
||||
|
||||
const { filter, searchShared, searchSharedOnly } = buildPromptGroupFilter({
|
||||
name,
|
||||
@@ -171,13 +171,6 @@ router.get('/groups', async (req, res) => {
|
||||
actualLimit = parseInt(pageSize, 10);
|
||||
}
|
||||
|
||||
if (
|
||||
actualCursor &&
|
||||
(actualCursor === 'undefined' || actualCursor === 'null' || actualCursor.length === 0)
|
||||
) {
|
||||
actualCursor = null;
|
||||
}
|
||||
|
||||
let accessibleIds = await findAccessibleResources({
|
||||
userId,
|
||||
role: req.user.role,
|
||||
@@ -197,7 +190,6 @@ router.get('/groups', async (req, res) => {
|
||||
publicPromptGroupIds: publiclyAccessibleIds,
|
||||
});
|
||||
|
||||
// Cursor-based pagination only
|
||||
const result = await getListPromptGroupsByAccess({
|
||||
accessibleIds: filteredAccessibleIds,
|
||||
otherParams: filter,
|
||||
@@ -206,21 +198,19 @@ router.get('/groups', async (req, res) => {
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
const emptyResponse = createEmptyPromptGroupsResponse({
|
||||
pageNumber: '1',
|
||||
pageSize: actualLimit,
|
||||
actualLimit,
|
||||
});
|
||||
const emptyResponse = createEmptyPromptGroupsResponse({ pageNumber, pageSize, actualLimit });
|
||||
return res.status(200).send(emptyResponse);
|
||||
}
|
||||
|
||||
const { data: promptGroups = [], has_more = false, after = null } = result;
|
||||
|
||||
const groupsWithPublicFlag = markPublicPromptGroups(promptGroups, publiclyAccessibleIds);
|
||||
|
||||
const response = formatPromptGroupsResponse({
|
||||
promptGroups: groupsWithPublicFlag,
|
||||
pageNumber: '1', // Always 1 for cursor-based pagination
|
||||
pageSize: actualLimit.toString(),
|
||||
pageNumber,
|
||||
pageSize,
|
||||
actualLimit,
|
||||
hasMore: has_more,
|
||||
after,
|
||||
});
|
||||
|
||||
@@ -33,11 +33,22 @@ let promptRoutes;
|
||||
let Prompt, PromptGroup, AclEntry, AccessRole, User;
|
||||
let testUsers, testRoles;
|
||||
let grantPermission;
|
||||
let currentTestUser; // Track current user for middleware
|
||||
|
||||
// Helper function to set user in middleware
|
||||
function setTestUser(app, user) {
|
||||
currentTestUser = user;
|
||||
app.use((req, res, next) => {
|
||||
req.user = {
|
||||
...(user.toObject ? user.toObject() : user),
|
||||
id: user.id || user._id.toString(),
|
||||
_id: user._id,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
};
|
||||
if (user.role === SystemRoles.ADMIN) {
|
||||
console.log('Setting admin user with role:', req.user.role);
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -64,35 +75,14 @@ beforeAll(async () => {
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Add user middleware before routes
|
||||
app.use((req, res, next) => {
|
||||
if (currentTestUser) {
|
||||
req.user = {
|
||||
...(currentTestUser.toObject ? currentTestUser.toObject() : currentTestUser),
|
||||
id: currentTestUser._id.toString(),
|
||||
_id: currentTestUser._id,
|
||||
name: currentTestUser.name,
|
||||
role: currentTestUser.role,
|
||||
};
|
||||
}
|
||||
next();
|
||||
});
|
||||
// Mock authentication middleware - default to owner
|
||||
setTestUser(app, testUsers.owner);
|
||||
|
||||
// Set default user
|
||||
currentTestUser = testUsers.owner;
|
||||
|
||||
// Import routes after middleware is set up
|
||||
// Import routes after mocks are set up
|
||||
promptRoutes = require('./prompts');
|
||||
app.use('/api/prompts', promptRoutes);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Always reset to owner user after each test for isolation
|
||||
if (currentTestUser !== testUsers.owner) {
|
||||
currentTestUser = testUsers.owner;
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await mongoose.disconnect();
|
||||
await mongoServer.stop();
|
||||
@@ -126,26 +116,36 @@ async function setupTestData() {
|
||||
// Create test users
|
||||
testUsers = {
|
||||
owner: await User.create({
|
||||
id: new ObjectId().toString(),
|
||||
_id: new ObjectId(),
|
||||
name: 'Prompt Owner',
|
||||
email: 'owner@example.com',
|
||||
role: SystemRoles.USER,
|
||||
}),
|
||||
viewer: await User.create({
|
||||
id: new ObjectId().toString(),
|
||||
_id: new ObjectId(),
|
||||
name: 'Prompt Viewer',
|
||||
email: 'viewer@example.com',
|
||||
role: SystemRoles.USER,
|
||||
}),
|
||||
editor: await User.create({
|
||||
id: new ObjectId().toString(),
|
||||
_id: new ObjectId(),
|
||||
name: 'Prompt Editor',
|
||||
email: 'editor@example.com',
|
||||
role: SystemRoles.USER,
|
||||
}),
|
||||
noAccess: await User.create({
|
||||
id: new ObjectId().toString(),
|
||||
_id: new ObjectId(),
|
||||
name: 'No Access',
|
||||
email: 'noaccess@example.com',
|
||||
role: SystemRoles.USER,
|
||||
}),
|
||||
admin: await User.create({
|
||||
id: new ObjectId().toString(),
|
||||
_id: new ObjectId(),
|
||||
name: 'Admin',
|
||||
email: 'admin@example.com',
|
||||
role: SystemRoles.ADMIN,
|
||||
@@ -181,7 +181,8 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||
it('should have routes loaded', async () => {
|
||||
// This should at least not crash
|
||||
const response = await request(app).get('/api/prompts/test-404');
|
||||
|
||||
console.log('Test 404 response status:', response.status);
|
||||
console.log('Test 404 response body:', response.body);
|
||||
// We expect a 401 or 404, not 500
|
||||
expect(response.status).not.toBe(500);
|
||||
});
|
||||
@@ -206,6 +207,12 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||
|
||||
const response = await request(app).post('/api/prompts').send(promptData);
|
||||
|
||||
if (response.status !== 200) {
|
||||
console.log('POST /api/prompts error status:', response.status);
|
||||
console.log('POST /api/prompts error body:', response.body);
|
||||
console.log('Console errors:', consoleErrorSpy.mock.calls);
|
||||
}
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.prompt).toBeDefined();
|
||||
expect(response.body.prompt.prompt).toBe(promptData.prompt.prompt);
|
||||
@@ -311,8 +318,29 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||
});
|
||||
|
||||
it('should allow admin access without explicit permissions', async () => {
|
||||
// Set admin user
|
||||
setTestUser(app, testUsers.admin);
|
||||
// First, reset the app to remove previous middleware
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Set admin user BEFORE adding routes
|
||||
app.use((req, res, next) => {
|
||||
req.user = {
|
||||
...testUsers.admin.toObject(),
|
||||
id: testUsers.admin._id.toString(),
|
||||
_id: testUsers.admin._id,
|
||||
name: testUsers.admin.name,
|
||||
role: testUsers.admin.role,
|
||||
};
|
||||
next();
|
||||
});
|
||||
|
||||
// Now add the routes
|
||||
const promptRoutes = require('./prompts');
|
||||
app.use('/api/prompts', promptRoutes);
|
||||
|
||||
console.log('Admin user:', testUsers.admin);
|
||||
console.log('Admin role:', testUsers.admin.role);
|
||||
console.log('SystemRoles.ADMIN:', SystemRoles.ADMIN);
|
||||
|
||||
const response = await request(app).get(`/api/prompts/${testPrompt._id}`).expect(200);
|
||||
|
||||
@@ -404,8 +432,21 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||
grantedBy: testUsers.editor._id,
|
||||
});
|
||||
|
||||
// Set viewer user
|
||||
setTestUser(app, testUsers.viewer);
|
||||
// Recreate app with viewer user
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, res, next) => {
|
||||
req.user = {
|
||||
...testUsers.viewer.toObject(),
|
||||
id: testUsers.viewer._id.toString(),
|
||||
_id: testUsers.viewer._id,
|
||||
name: testUsers.viewer.name,
|
||||
role: testUsers.viewer.role,
|
||||
};
|
||||
next();
|
||||
});
|
||||
const promptRoutes = require('./prompts');
|
||||
app.use('/api/prompts', promptRoutes);
|
||||
|
||||
await request(app)
|
||||
.delete(`/api/prompts/${authorPrompt._id}`)
|
||||
@@ -458,8 +499,21 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
// Ensure owner user
|
||||
setTestUser(app, testUsers.owner);
|
||||
// Recreate app to ensure fresh middleware
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, res, next) => {
|
||||
req.user = {
|
||||
...testUsers.owner.toObject(),
|
||||
id: testUsers.owner._id.toString(),
|
||||
_id: testUsers.owner._id,
|
||||
name: testUsers.owner.name,
|
||||
role: testUsers.owner.role,
|
||||
};
|
||||
next();
|
||||
});
|
||||
const promptRoutes = require('./prompts');
|
||||
app.use('/api/prompts', promptRoutes);
|
||||
|
||||
const response = await request(app)
|
||||
.patch(`/api/prompts/${testPrompt._id}/tags/production`)
|
||||
@@ -483,8 +537,21 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
// Set viewer user
|
||||
setTestUser(app, testUsers.viewer);
|
||||
// Recreate app with viewer user
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, res, next) => {
|
||||
req.user = {
|
||||
...testUsers.viewer.toObject(),
|
||||
id: testUsers.viewer._id.toString(),
|
||||
_id: testUsers.viewer._id,
|
||||
name: testUsers.viewer.name,
|
||||
role: testUsers.viewer.role,
|
||||
};
|
||||
next();
|
||||
});
|
||||
const promptRoutes = require('./prompts');
|
||||
app.use('/api/prompts', promptRoutes);
|
||||
|
||||
await request(app).patch(`/api/prompts/${testPrompt._id}/tags/production`).expect(403);
|
||||
|
||||
@@ -543,305 +610,4 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||
expect(response.body._id).toBe(publicPrompt._id.toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pagination', () => {
|
||||
beforeEach(async () => {
|
||||
// Create multiple prompt groups for pagination testing
|
||||
const groups = [];
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const group = await PromptGroup.create({
|
||||
name: `Test Group ${i + 1}`,
|
||||
category: 'pagination-test',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
updatedAt: new Date(Date.now() - i * 1000), // Stagger updatedAt for consistent ordering
|
||||
});
|
||||
groups.push(group);
|
||||
|
||||
// Grant owner permissions on each group
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await PromptGroup.deleteMany({});
|
||||
await AclEntry.deleteMany({});
|
||||
});
|
||||
|
||||
it('should correctly indicate hasMore when there are more pages', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '10' })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.promptGroups).toHaveLength(10);
|
||||
expect(response.body.has_more).toBe(true);
|
||||
expect(response.body.after).toBeTruthy();
|
||||
// Since has_more is true, pages should be a high number (9999 in our fix)
|
||||
expect(parseInt(response.body.pages)).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should correctly indicate no more pages on the last page', async () => {
|
||||
// First get the cursor for page 2
|
||||
const firstPage = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '10' })
|
||||
.expect(200);
|
||||
|
||||
expect(firstPage.body.has_more).toBe(true);
|
||||
expect(firstPage.body.after).toBeTruthy();
|
||||
|
||||
// Now fetch the second page using the cursor
|
||||
const response = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '10', cursor: firstPage.body.after })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.promptGroups).toHaveLength(5); // 15 total, 10 on page 1, 5 on page 2
|
||||
expect(response.body.has_more).toBe(false);
|
||||
});
|
||||
|
||||
it('should support cursor-based pagination', async () => {
|
||||
// First page
|
||||
const firstPage = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '5' })
|
||||
.expect(200);
|
||||
|
||||
expect(firstPage.body.promptGroups).toHaveLength(5);
|
||||
expect(firstPage.body.has_more).toBe(true);
|
||||
expect(firstPage.body.after).toBeTruthy();
|
||||
|
||||
// Second page using cursor
|
||||
const secondPage = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '5', cursor: firstPage.body.after })
|
||||
.expect(200);
|
||||
|
||||
expect(secondPage.body.promptGroups).toHaveLength(5);
|
||||
expect(secondPage.body.has_more).toBe(true);
|
||||
expect(secondPage.body.after).toBeTruthy();
|
||||
|
||||
// Verify different groups
|
||||
const firstPageIds = firstPage.body.promptGroups.map((g) => g._id);
|
||||
const secondPageIds = secondPage.body.promptGroups.map((g) => g._id);
|
||||
expect(firstPageIds).not.toEqual(secondPageIds);
|
||||
});
|
||||
|
||||
it('should paginate correctly with category filtering', async () => {
|
||||
// Create groups with different categories
|
||||
await PromptGroup.deleteMany({}); // Clear existing groups
|
||||
await AclEntry.deleteMany({});
|
||||
|
||||
// Create 8 groups with category 'test-cat-1'
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const group = await PromptGroup.create({
|
||||
name: `Category 1 Group ${i + 1}`,
|
||||
category: 'test-cat-1',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
updatedAt: new Date(Date.now() - i * 1000),
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create 7 groups with category 'test-cat-2'
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const group = await PromptGroup.create({
|
||||
name: `Category 2 Group ${i + 1}`,
|
||||
category: 'test-cat-2',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
updatedAt: new Date(Date.now() - (i + 8) * 1000),
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
}
|
||||
|
||||
// Test pagination with category filter
|
||||
const firstPage = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '5', category: 'test-cat-1' })
|
||||
.expect(200);
|
||||
|
||||
expect(firstPage.body.promptGroups).toHaveLength(5);
|
||||
expect(firstPage.body.promptGroups.every((g) => g.category === 'test-cat-1')).toBe(true);
|
||||
expect(firstPage.body.has_more).toBe(true);
|
||||
expect(firstPage.body.after).toBeTruthy();
|
||||
|
||||
const secondPage = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '5', cursor: firstPage.body.after, category: 'test-cat-1' })
|
||||
.expect(200);
|
||||
|
||||
expect(secondPage.body.promptGroups).toHaveLength(3); // 8 total, 5 on page 1, 3 on page 2
|
||||
expect(secondPage.body.promptGroups.every((g) => g.category === 'test-cat-1')).toBe(true);
|
||||
expect(secondPage.body.has_more).toBe(false);
|
||||
});
|
||||
|
||||
it('should paginate correctly with name/keyword filtering', async () => {
|
||||
// Create groups with specific names
|
||||
await PromptGroup.deleteMany({}); // Clear existing groups
|
||||
await AclEntry.deleteMany({});
|
||||
|
||||
// Create 12 groups with 'Search' in the name
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const group = await PromptGroup.create({
|
||||
name: `Search Test Group ${i + 1}`,
|
||||
category: 'search-test',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
updatedAt: new Date(Date.now() - i * 1000),
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create 5 groups without 'Search' in the name
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const group = await PromptGroup.create({
|
||||
name: `Other Group ${i + 1}`,
|
||||
category: 'other-test',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
updatedAt: new Date(Date.now() - (i + 12) * 1000),
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
}
|
||||
|
||||
// Test pagination with name filter
|
||||
const firstPage = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '10', name: 'Search' })
|
||||
.expect(200);
|
||||
|
||||
expect(firstPage.body.promptGroups).toHaveLength(10);
|
||||
expect(firstPage.body.promptGroups.every((g) => g.name.includes('Search'))).toBe(true);
|
||||
expect(firstPage.body.has_more).toBe(true);
|
||||
expect(firstPage.body.after).toBeTruthy();
|
||||
|
||||
const secondPage = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '10', cursor: firstPage.body.after, name: 'Search' })
|
||||
.expect(200);
|
||||
|
||||
expect(secondPage.body.promptGroups).toHaveLength(2); // 12 total, 10 on page 1, 2 on page 2
|
||||
expect(secondPage.body.promptGroups.every((g) => g.name.includes('Search'))).toBe(true);
|
||||
expect(secondPage.body.has_more).toBe(false);
|
||||
});
|
||||
|
||||
it('should paginate correctly with combined filters', async () => {
|
||||
// Create groups with various combinations
|
||||
await PromptGroup.deleteMany({}); // Clear existing groups
|
||||
await AclEntry.deleteMany({});
|
||||
|
||||
// Create 6 groups matching both category and name filters
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const group = await PromptGroup.create({
|
||||
name: `API Test Group ${i + 1}`,
|
||||
category: 'api-category',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
updatedAt: new Date(Date.now() - i * 1000),
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create groups that only match one filter
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const group = await PromptGroup.create({
|
||||
name: `API Other Group ${i + 1}`,
|
||||
category: 'other-category',
|
||||
author: testUsers.owner._id,
|
||||
authorName: testUsers.owner.name,
|
||||
productionId: new ObjectId(),
|
||||
updatedAt: new Date(Date.now() - (i + 6) * 1000),
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: PrincipalType.USER,
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
}
|
||||
|
||||
// Test pagination with both filters
|
||||
const response = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '5', name: 'API', category: 'api-category' })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.promptGroups).toHaveLength(5);
|
||||
expect(
|
||||
response.body.promptGroups.every(
|
||||
(g) => g.name.includes('API') && g.category === 'api-category',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(response.body.has_more).toBe(true);
|
||||
expect(response.body.after).toBeTruthy();
|
||||
|
||||
// Page 2
|
||||
const page2 = await request(app)
|
||||
.get('/api/prompts/groups')
|
||||
.query({ limit: '5', cursor: response.body.after, name: 'API', category: 'api-category' })
|
||||
.expect(200);
|
||||
|
||||
expect(page2.body.promptGroups).toHaveLength(1); // 6 total, 5 on page 1, 1 on page 2
|
||||
expect(page2.body.has_more).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { createContentAggregator } = require('@librechat/agents');
|
||||
const {
|
||||
validateAgentModel,
|
||||
getCustomEndpointConfig,
|
||||
createSequentialChainEdges,
|
||||
} = require('@librechat/api');
|
||||
const { validateAgentModel, getCustomEndpointConfig } = require('@librechat/api');
|
||||
const {
|
||||
Constants,
|
||||
EModelEndpoint,
|
||||
@@ -123,90 +119,44 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => {
|
||||
|
||||
const agent_ids = primaryConfig.agent_ids;
|
||||
let userMCPAuthMap = primaryConfig.userMCPAuthMap;
|
||||
|
||||
async function processAgent(agentId) {
|
||||
const agent = await getAgent({ id: agentId });
|
||||
if (!agent) {
|
||||
throw new Error(`Agent ${agentId} not found`);
|
||||
}
|
||||
|
||||
const validationResult = await validateAgentModel({
|
||||
req,
|
||||
res,
|
||||
agent,
|
||||
modelsConfig,
|
||||
logViolation,
|
||||
});
|
||||
|
||||
if (!validationResult.isValid) {
|
||||
throw new Error(validationResult.error?.message);
|
||||
}
|
||||
|
||||
const config = await initializeAgent({
|
||||
req,
|
||||
res,
|
||||
agent,
|
||||
loadTools,
|
||||
requestFiles,
|
||||
conversationId,
|
||||
endpointOption,
|
||||
allowedProviders,
|
||||
});
|
||||
if (userMCPAuthMap != null) {
|
||||
Object.assign(userMCPAuthMap, config.userMCPAuthMap ?? {});
|
||||
} else {
|
||||
userMCPAuthMap = config.userMCPAuthMap;
|
||||
}
|
||||
agentConfigs.set(agentId, config);
|
||||
}
|
||||
|
||||
let edges = primaryConfig.edges;
|
||||
const checkAgentInit = (agentId) => agentId === primaryConfig.id || agentConfigs.has(agentId);
|
||||
if ((edges?.length ?? 0) > 0) {
|
||||
for (const edge of edges) {
|
||||
if (Array.isArray(edge.to)) {
|
||||
for (const to of edge.to) {
|
||||
if (checkAgentInit(to)) {
|
||||
continue;
|
||||
}
|
||||
await processAgent(to);
|
||||
}
|
||||
} else if (typeof edge.to === 'string' && checkAgentInit(edge.to)) {
|
||||
continue;
|
||||
} else if (typeof edge.to === 'string') {
|
||||
await processAgent(edge.to);
|
||||
}
|
||||
|
||||
if (Array.isArray(edge.from)) {
|
||||
for (const from of edge.from) {
|
||||
if (checkAgentInit(from)) {
|
||||
continue;
|
||||
}
|
||||
await processAgent(from);
|
||||
}
|
||||
} else if (typeof edge.from === 'string' && checkAgentInit(edge.from)) {
|
||||
continue;
|
||||
} else if (typeof edge.from === 'string') {
|
||||
await processAgent(edge.from);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Agent Chain */
|
||||
if (agent_ids?.length) {
|
||||
for (const agentId of agent_ids) {
|
||||
if (checkAgentInit(agentId)) {
|
||||
continue;
|
||||
const agent = await getAgent({ id: agentId });
|
||||
if (!agent) {
|
||||
throw new Error(`Agent ${agentId} not found`);
|
||||
}
|
||||
await processAgent(agentId);
|
||||
|
||||
const validationResult = await validateAgentModel({
|
||||
req,
|
||||
res,
|
||||
agent,
|
||||
modelsConfig,
|
||||
logViolation,
|
||||
});
|
||||
|
||||
if (!validationResult.isValid) {
|
||||
throw new Error(validationResult.error?.message);
|
||||
}
|
||||
|
||||
const config = await initializeAgent({
|
||||
req,
|
||||
res,
|
||||
agent,
|
||||
loadTools,
|
||||
requestFiles,
|
||||
conversationId,
|
||||
endpointOption,
|
||||
allowedProviders,
|
||||
});
|
||||
if (userMCPAuthMap != null) {
|
||||
Object.assign(userMCPAuthMap, config.userMCPAuthMap ?? {});
|
||||
} else {
|
||||
userMCPAuthMap = config.userMCPAuthMap;
|
||||
}
|
||||
agentConfigs.set(agentId, config);
|
||||
}
|
||||
|
||||
const chain = await createSequentialChainEdges([primaryConfig.id].concat(agent_ids), '{convo}');
|
||||
edges = edges ? edges.concat(chain) : chain;
|
||||
}
|
||||
|
||||
primaryConfig.edges = edges;
|
||||
|
||||
let endpointConfig = appConfig.endpoints?.[primaryConfig.endpoint];
|
||||
if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) {
|
||||
try {
|
||||
|
||||
@@ -27,13 +27,13 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
|
||||
const anthropicConfig = appConfig.endpoints?.[EModelEndpoint.anthropic];
|
||||
|
||||
if (anthropicConfig) {
|
||||
clientOptions._lc_stream_delay = anthropicConfig.streamRate;
|
||||
clientOptions.streamRate = anthropicConfig.streamRate;
|
||||
clientOptions.titleModel = anthropicConfig.titleModel;
|
||||
}
|
||||
|
||||
const allConfig = appConfig.endpoints?.all;
|
||||
if (allConfig) {
|
||||
clientOptions._lc_stream_delay = allConfig.streamRate;
|
||||
clientOptions.streamRate = allConfig.streamRate;
|
||||
}
|
||||
|
||||
if (optionsOnly) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
const { createHandleLLMNewToken } = require('@librechat/api');
|
||||
const {
|
||||
AuthType,
|
||||
Constants,
|
||||
EModelEndpoint,
|
||||
bedrockInputParser,
|
||||
bedrockOutputParser,
|
||||
@@ -9,6 +11,7 @@ const {
|
||||
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||
|
||||
const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
||||
const appConfig = req.config;
|
||||
const {
|
||||
BEDROCK_AWS_SECRET_ACCESS_KEY,
|
||||
BEDROCK_AWS_ACCESS_KEY_ID,
|
||||
@@ -44,12 +47,10 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
||||
checkUserKeyExpiry(expiresAt, EModelEndpoint.bedrock);
|
||||
}
|
||||
|
||||
/*
|
||||
Callback for stream rate no longer awaits and may end the stream prematurely
|
||||
/** @type {number}
|
||||
/** @type {number} */
|
||||
let streamRate = Constants.DEFAULT_STREAM_RATE;
|
||||
|
||||
/** @type {undefined | TBaseEndpoint}
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const bedrockConfig = appConfig.endpoints?.[EModelEndpoint.bedrock];
|
||||
|
||||
if (bedrockConfig && bedrockConfig.streamRate) {
|
||||
@@ -60,7 +61,6 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
||||
if (allConfig && allConfig.streamRate) {
|
||||
streamRate = allConfig.streamRate;
|
||||
}
|
||||
*/
|
||||
|
||||
/** @type {BedrockClientOptions} */
|
||||
const requestOptions = {
|
||||
@@ -88,6 +88,12 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
||||
llmConfig.endpointHost = BEDROCK_REVERSE_PROXY;
|
||||
}
|
||||
|
||||
llmConfig.callbacks = [
|
||||
{
|
||||
handleLLMNewToken: createHandleLLMNewToken(streamRate),
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
/** @type {BedrockClientOptions} */
|
||||
llmConfig,
|
||||
|
||||
@@ -4,6 +4,7 @@ const {
|
||||
isUserProvided,
|
||||
getOpenAIConfig,
|
||||
getCustomEndpointConfig,
|
||||
createHandleLLMNewToken,
|
||||
} = require('@librechat/api');
|
||||
const {
|
||||
CacheKeys,
|
||||
@@ -158,7 +159,11 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
|
||||
if (!clientOptions.streamRate) {
|
||||
return options;
|
||||
}
|
||||
options.llmConfig._lc_stream_delay = clientOptions.streamRate;
|
||||
options.llmConfig.callbacks = [
|
||||
{
|
||||
handleLLMNewToken: createHandleLLMNewToken(clientOptions.streamRate),
|
||||
},
|
||||
];
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ jest.mock('@librechat/api', () => ({
|
||||
...jest.requireActual('@librechat/api'),
|
||||
resolveHeaders: jest.fn(),
|
||||
getOpenAIConfig: jest.fn(),
|
||||
createHandleLLMNewToken: jest.fn(),
|
||||
getCustomEndpointConfig: jest.fn().mockReturnValue({
|
||||
apiKey: 'test-key',
|
||||
baseURL: 'https://test.com',
|
||||
|
||||
@@ -5,6 +5,7 @@ const {
|
||||
isUserProvided,
|
||||
getOpenAIConfig,
|
||||
getAzureCredentials,
|
||||
createHandleLLMNewToken,
|
||||
} = require('@librechat/api');
|
||||
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||
const OpenAIClient = require('~/app/clients/OpenAIClient');
|
||||
@@ -150,7 +151,11 @@ const initializeClient = async ({
|
||||
if (!streamRate) {
|
||||
return options;
|
||||
}
|
||||
options.llmConfig._lc_stream_delay = streamRate;
|
||||
options.llmConfig.callbacks = [
|
||||
{
|
||||
handleLLMNewToken: createHandleLLMNewToken(streamRate),
|
||||
},
|
||||
];
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class STTService {
|
||||
*/
|
||||
async getProviderSchema(req) {
|
||||
const appConfig =
|
||||
req.config ??
|
||||
req?.config ??
|
||||
(await getAppConfig({
|
||||
role: req?.user?.role,
|
||||
}));
|
||||
|
||||
@@ -10,10 +10,9 @@ const { getAgent } = require('~/models/Agent');
|
||||
* @param {string} [params.role] - Optional user role to avoid DB query
|
||||
* @param {string[]} params.fileIds - Array of file IDs to check
|
||||
* @param {string} params.agentId - The agent ID that might grant access
|
||||
* @param {boolean} [params.isDelete] - Whether the operation is a delete operation
|
||||
* @returns {Promise<Map<string, boolean>>} Map of fileId to access status
|
||||
*/
|
||||
const hasAccessToFilesViaAgent = async ({ userId, role, fileIds, agentId, isDelete }) => {
|
||||
const hasAccessToFilesViaAgent = async ({ userId, role, fileIds, agentId }) => {
|
||||
const accessMap = new Map();
|
||||
|
||||
// Initialize all files as no access
|
||||
@@ -45,23 +44,22 @@ const hasAccessToFilesViaAgent = async ({ userId, role, fileIds, agentId, isDele
|
||||
return accessMap;
|
||||
}
|
||||
|
||||
if (isDelete) {
|
||||
// Check if user has EDIT permission (which would indicate collaborative access)
|
||||
const hasEditPermission = await checkPermission({
|
||||
userId,
|
||||
role,
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
// Check if user has EDIT permission (which would indicate collaborative access)
|
||||
const hasEditPermission = await checkPermission({
|
||||
userId,
|
||||
role,
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
// If user only has VIEW permission, they can't access files
|
||||
// Only users with EDIT permission or higher can access agent files
|
||||
if (!hasEditPermission) {
|
||||
return accessMap;
|
||||
}
|
||||
// If user only has VIEW permission, they can't access files
|
||||
// Only users with EDIT permission or higher can access agent files
|
||||
if (!hasEditPermission) {
|
||||
return accessMap;
|
||||
}
|
||||
|
||||
// User has edit permissions - check which files are actually attached
|
||||
const attachedFileIds = new Set();
|
||||
if (agent.tool_resources) {
|
||||
for (const [_resourceType, resource] of Object.entries(agent.tool_resources)) {
|
||||
|
||||
@@ -616,7 +616,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||
|
||||
if (shouldUseSTT) {
|
||||
const sttService = await STTService.getInstance();
|
||||
const { text, bytes } = await processAudioFile({ req, file, sttService });
|
||||
const { text, bytes } = await processAudioFile({ file, sttService });
|
||||
return await createTextFile({ text, bytes });
|
||||
}
|
||||
|
||||
@@ -646,8 +646,8 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||
req,
|
||||
file,
|
||||
file_id,
|
||||
basePath,
|
||||
entity_id,
|
||||
basePath,
|
||||
});
|
||||
|
||||
// SECOND: Upload to Vector DB
|
||||
@@ -670,18 +670,17 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||
req,
|
||||
file,
|
||||
file_id,
|
||||
basePath,
|
||||
entity_id,
|
||||
basePath,
|
||||
});
|
||||
}
|
||||
|
||||
let { bytes, filename, filepath: _filepath, height, width } = storageResult;
|
||||
const { bytes, filename, filepath: _filepath, height, width } = storageResult;
|
||||
// For RAG files, use embedding result; for others, use storage result
|
||||
let embedded = storageResult.embedded;
|
||||
if (tool_resource === EToolResources.file_search) {
|
||||
embedded = embeddingResult?.embedded;
|
||||
filename = embeddingResult?.filename || filename;
|
||||
}
|
||||
const embedded =
|
||||
tool_resource === EToolResources.file_search
|
||||
? embeddingResult?.embedded
|
||||
: storageResult.embedded;
|
||||
|
||||
let filepath = _filepath;
|
||||
|
||||
@@ -930,7 +929,6 @@ async function saveBase64Image(
|
||||
url,
|
||||
{ req, file_id: _file_id, filename: _filename, endpoint, context, resolution },
|
||||
) {
|
||||
const appConfig = req.config;
|
||||
const effectiveResolution = resolution ?? appConfig.fileConfig?.imageGeneration ?? 'high';
|
||||
const file_id = _file_id ?? v4();
|
||||
let filename = `${file_id}-${_filename}`;
|
||||
@@ -945,6 +943,7 @@ async function saveBase64Image(
|
||||
}
|
||||
|
||||
const image = await resizeImageBuffer(inputBuffer, effectiveResolution, endpoint);
|
||||
const appConfig = req.config;
|
||||
const source = getFileStrategy(appConfig, { isImage: true });
|
||||
const { saveBuffer } = getStrategyFunctions(source);
|
||||
const filepath = await saveBuffer({
|
||||
|
||||
@@ -271,7 +271,6 @@ async function createMCPTool({
|
||||
availableTools: tools,
|
||||
}) {
|
||||
const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter);
|
||||
|
||||
const availableTools =
|
||||
tools ?? (await getCachedTools({ userId: req.user?.id, includeGlobal: true }));
|
||||
/** @type {LCTool | undefined} */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const fs = require('fs').promises;
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { getImporter } = require('./importers');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
/**
|
||||
* Job definition for importing a conversation.
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"@headlessui/react": "^2.1.2",
|
||||
"@librechat/client": "*",
|
||||
"@marsidev/react-turnstile": "^1.1.0",
|
||||
"@mcp-ui/client": "^5.7.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.3",
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import React, { createContext, useContext, useState, useMemo } from 'react';
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { Constants, EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { MCP, Action, TPlugin, AgentToolType } from 'librechat-data-provider';
|
||||
import type { AgentPanelContextType, MCPServerInfo } from '~/common';
|
||||
import { useAvailableToolsQuery, useGetActionsQuery, useGetStartupConfig } from '~/data-provider';
|
||||
import { useLocalize, useGetAgentsConfig, useMCPConnectionStatus } from '~/hooks';
|
||||
import type { AgentPanelContextType } from '~/common';
|
||||
import { useAvailableToolsQuery, useGetActionsQuery } from '~/data-provider';
|
||||
import { useLocalize, useGetAgentsConfig } from '~/hooks';
|
||||
import { Panel } from '~/common';
|
||||
|
||||
type GroupedToolType = AgentToolType & { tools?: AgentToolType[] };
|
||||
type GroupedToolsRecord = Record<string, GroupedToolType>;
|
||||
|
||||
const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined);
|
||||
|
||||
export function useAgentPanelContext() {
|
||||
@@ -36,116 +33,67 @@ export function AgentPanelProvider({ children }: { children: React.ReactNode })
|
||||
enabled: !!agent_id,
|
||||
});
|
||||
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const mcpServerNames = useMemo(
|
||||
() => Object.keys(startupConfig?.mcpServers ?? {}),
|
||||
[startupConfig],
|
||||
);
|
||||
|
||||
const { connectionStatus } = useMCPConnectionStatus({
|
||||
enabled: !!agent_id && mcpServerNames.length > 0,
|
||||
});
|
||||
|
||||
const processedData = useMemo(() => {
|
||||
if (!pluginTools) {
|
||||
return {
|
||||
tools: [],
|
||||
groupedTools: {},
|
||||
mcpServersMap: new Map<string, MCPServerInfo>(),
|
||||
};
|
||||
}
|
||||
|
||||
const tools: AgentToolType[] = [];
|
||||
const groupedTools: GroupedToolsRecord = {};
|
||||
|
||||
const configuredServers = new Set(mcpServerNames);
|
||||
const mcpServersMap = new Map<string, MCPServerInfo>();
|
||||
|
||||
for (const pluginTool of pluginTools) {
|
||||
const tool: AgentToolType = {
|
||||
tool_id: pluginTool.pluginKey,
|
||||
metadata: pluginTool as TPlugin,
|
||||
};
|
||||
|
||||
tools.push(tool);
|
||||
const tools =
|
||||
pluginTools?.map((tool) => ({
|
||||
tool_id: tool.pluginKey,
|
||||
metadata: tool as TPlugin,
|
||||
agent_id: agent_id || '',
|
||||
})) || [];
|
||||
|
||||
const groupedTools = tools?.reduce(
|
||||
(acc, tool) => {
|
||||
if (tool.tool_id.includes(Constants.mcp_delimiter)) {
|
||||
const [_toolName, serverName] = tool.tool_id.split(Constants.mcp_delimiter);
|
||||
|
||||
if (!mcpServersMap.has(serverName)) {
|
||||
const metadata = {
|
||||
name: serverName,
|
||||
pluginKey: serverName,
|
||||
description: `${localize('com_ui_tool_collection_prefix')} ${serverName}`,
|
||||
icon: pluginTool.icon || '',
|
||||
} as TPlugin;
|
||||
|
||||
mcpServersMap.set(serverName, {
|
||||
serverName,
|
||||
const groupKey = `${serverName.toLowerCase()}`;
|
||||
if (!acc[groupKey]) {
|
||||
acc[groupKey] = {
|
||||
tool_id: groupKey,
|
||||
metadata: {
|
||||
name: `${serverName}`,
|
||||
pluginKey: groupKey,
|
||||
description: `${localize('com_ui_tool_collection_prefix')} ${serverName}`,
|
||||
icon: tool.metadata.icon || '',
|
||||
} as TPlugin,
|
||||
agent_id: agent_id || '',
|
||||
tools: [],
|
||||
isConfigured: configuredServers.has(serverName),
|
||||
isConnected: connectionStatus?.[serverName]?.connectionState === 'connected',
|
||||
metadata,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
mcpServersMap.get(serverName)!.tools.push(tool);
|
||||
} else {
|
||||
// Non-MCP tool
|
||||
groupedTools[tool.tool_id] = {
|
||||
acc[groupKey].tools?.push({
|
||||
tool_id: tool.tool_id,
|
||||
metadata: tool.metadata,
|
||||
agent_id: agent_id || '',
|
||||
});
|
||||
} else {
|
||||
acc[tool.tool_id] = {
|
||||
tool_id: tool.tool_id,
|
||||
metadata: tool.metadata,
|
||||
agent_id: agent_id || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (const mcpServerName of mcpServerNames) {
|
||||
if (mcpServersMap.has(mcpServerName)) {
|
||||
continue;
|
||||
}
|
||||
const metadata = {
|
||||
icon: '',
|
||||
name: mcpServerName,
|
||||
pluginKey: mcpServerName,
|
||||
description: `${localize('com_ui_tool_collection_prefix')} ${mcpServerName}`,
|
||||
} as TPlugin;
|
||||
|
||||
mcpServersMap.set(mcpServerName, {
|
||||
tools: [],
|
||||
metadata,
|
||||
isConfigured: true,
|
||||
serverName: mcpServerName,
|
||||
isConnected: connectionStatus?.[mcpServerName]?.connectionState === 'connected',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tools,
|
||||
groupedTools,
|
||||
mcpServersMap,
|
||||
};
|
||||
}, [pluginTools, localize, mcpServerNames, connectionStatus]);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, AgentToolType & { tools?: AgentToolType[] }>,
|
||||
);
|
||||
|
||||
const { agentsConfig, endpointsConfig } = useGetAgentsConfig();
|
||||
|
||||
const value: AgentPanelContextType = {
|
||||
mcp,
|
||||
mcps,
|
||||
/** Query data for actions and tools */
|
||||
tools,
|
||||
action,
|
||||
setMcp,
|
||||
actions,
|
||||
setMcps,
|
||||
agent_id,
|
||||
setAction,
|
||||
pluginTools,
|
||||
activePanel,
|
||||
groupedTools,
|
||||
agentsConfig,
|
||||
setActivePanel,
|
||||
endpointsConfig,
|
||||
setCurrentAgentId,
|
||||
tools: processedData.tools,
|
||||
groupedTools: processedData.groupedTools,
|
||||
mcpServersMap: processedData.mcpServersMap,
|
||||
};
|
||||
|
||||
return <AgentPanelContext.Provider value={value}>{children}</AgentPanelContext.Provider>;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { AgentCapabilities, ArtifactModes } from 'librechat-data-provider';
|
||||
import type {
|
||||
Agent,
|
||||
AgentProvider,
|
||||
AgentModelParameters,
|
||||
SupportContact,
|
||||
AgentProvider,
|
||||
GraphEdge,
|
||||
Agent,
|
||||
} from 'librechat-data-provider';
|
||||
import type { OptionWithIcon, ExtendedFile } from './types';
|
||||
|
||||
@@ -34,9 +33,7 @@ export type AgentForm = {
|
||||
model_parameters: AgentModelParameters;
|
||||
tools?: string[];
|
||||
provider?: AgentProvider | OptionWithIcon;
|
||||
/** @deprecated Use edges instead */
|
||||
agent_ids?: string[];
|
||||
edges?: GraphEdge[];
|
||||
[AgentCapabilities.artifacts]?: ArtifactModes | string;
|
||||
recursion_limit?: number;
|
||||
support_contact?: SupportContact;
|
||||
|
||||
@@ -216,14 +216,6 @@ export type AgentPanelProps = {
|
||||
agentsConfig?: t.TAgentsEndpoint | null;
|
||||
};
|
||||
|
||||
export interface MCPServerInfo {
|
||||
serverName: string;
|
||||
tools: t.AgentToolType[];
|
||||
isConfigured: boolean;
|
||||
isConnected: boolean;
|
||||
metadata: t.TPlugin;
|
||||
}
|
||||
|
||||
export type AgentPanelContextType = {
|
||||
action?: t.Action;
|
||||
actions?: t.Action[];
|
||||
@@ -233,16 +225,13 @@ export type AgentPanelContextType = {
|
||||
setMcp: React.Dispatch<React.SetStateAction<t.MCP | undefined>>;
|
||||
setMcps: React.Dispatch<React.SetStateAction<t.MCP[] | undefined>>;
|
||||
groupedTools: Record<string, t.AgentToolType & { tools?: t.AgentToolType[] }>;
|
||||
activePanel?: string;
|
||||
tools: t.AgentToolType[];
|
||||
pluginTools?: t.TPlugin[];
|
||||
activePanel?: string;
|
||||
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
|
||||
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
agent_id?: string;
|
||||
agentsConfig?: t.TAgentsEndpoint | null;
|
||||
endpointsConfig?: t.TEndpointsConfig | null;
|
||||
/** Pre-computed MCP server information indexed by server key */
|
||||
mcpServersMap: Map<string, MCPServerInfo>;
|
||||
};
|
||||
|
||||
export type AgentModelPanelProps = {
|
||||
@@ -641,10 +630,3 @@ declare global {
|
||||
google_tag_manager?: unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export type UIResource = {
|
||||
uri: string;
|
||||
mimeType: string;
|
||||
text: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Turnstile } from '@marsidev/react-turnstile';
|
||||
import { ThemeContext, Spinner, Button, isDark } from '@librechat/client';
|
||||
import { ThemeContext, Spinner, Button } from '@librechat/client';
|
||||
import type { TLoginUser, TStartupConfig } from 'librechat-data-provider';
|
||||
import type { TAuthContext } from '~/common';
|
||||
import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider';
|
||||
@@ -28,7 +28,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||
|
||||
const { data: config } = useGetStartupConfig();
|
||||
const useUsernameLogin = config?.ldap?.username;
|
||||
const validTheme = isDark(theme) ? 'dark' : 'light';
|
||||
const validTheme = theme === 'dark' ? 'dark' : 'light';
|
||||
const requireCaptcha = Boolean(startupConfig.turnstile?.siteKey);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Turnstile } from '@marsidev/react-turnstile';
|
||||
import { ThemeContext, Spinner, Button, isDark } from '@librechat/client';
|
||||
import { ThemeContext, Spinner, Button } from '@librechat/client';
|
||||
import { useNavigate, useOutletContext, useLocation } from 'react-router-dom';
|
||||
import { useRegisterUserMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TRegisterUser, TError } from 'librechat-data-provider';
|
||||
@@ -31,7 +31,7 @@ const Registration: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const token = queryParams.get('token');
|
||||
const validTheme = isDark(theme) ? 'dark' : 'light';
|
||||
const validTheme = theme === 'dark' ? 'dark' : 'light';
|
||||
|
||||
// only require captcha if we have a siteKey
|
||||
const requireCaptcha = Boolean(startupConfig?.turnstile?.siteKey);
|
||||
|
||||
@@ -253,7 +253,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
|
||||
handleSaveBadges={handleSaveBadges}
|
||||
setBadges={setBadges}
|
||||
/>
|
||||
<FileFormChat conversation={conversation} />
|
||||
<FileFormChat disableInputs={disableInputs} />
|
||||
{endpoint && (
|
||||
<div className={cn('flex', isRTL ? 'flex-row-reverse' : 'flex-row')}>
|
||||
<TextareaAutosize
|
||||
@@ -301,7 +301,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
|
||||
)}
|
||||
>
|
||||
<div className={`${isRTL ? 'mr-2' : 'ml-2'}`}>
|
||||
<AttachFileChat conversation={conversation} disableInputs={disableInputs} />
|
||||
<AttachFileChat disableInputs={disableInputs} />
|
||||
</div>
|
||||
<BadgeRow
|
||||
showEphemeralBadges={!isAgentsEndpoint(endpoint) && !isAssistantsEndpoint(endpoint)}
|
||||
|
||||
@@ -7,18 +7,14 @@ import {
|
||||
isAssistantsEndpoint,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { EndpointFileConfig, TConversation } from 'librechat-data-provider';
|
||||
import type { EndpointFileConfig } from 'librechat-data-provider';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
import AttachFileMenu from './AttachFileMenu';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import AttachFile from './AttachFile';
|
||||
|
||||
function AttachFileChat({
|
||||
disableInputs,
|
||||
conversation,
|
||||
}: {
|
||||
disableInputs: boolean;
|
||||
conversation: TConversation | null;
|
||||
}) {
|
||||
function AttachFileChat({ disableInputs }: { disableInputs: boolean }) {
|
||||
const { conversation } = useChatContext();
|
||||
const conversationId = conversation?.conversationId ?? Constants.NEW_CONVO;
|
||||
const { endpoint, endpointType } = conversation ?? { endpoint: null };
|
||||
const isAgents = useMemo(() => isAgentsEndpoint(endpoint), [endpoint]);
|
||||
|
||||
@@ -91,10 +91,6 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
|
||||
label: localize('com_ui_upload_file_search'),
|
||||
onClick: () => {
|
||||
setToolResource(EToolResources.file_search);
|
||||
setEphemeralAgent((prev) => ({
|
||||
...prev,
|
||||
[EToolResources.file_search]: true,
|
||||
}));
|
||||
onAction();
|
||||
},
|
||||
icon: <FileSearch className="icon-md" />,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { memo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useFileHandling } from '~/hooks';
|
||||
import FileRow from './FileRow';
|
||||
import store from '~/store';
|
||||
|
||||
function FileFormChat({ conversation }: { conversation: TConversation | null }) {
|
||||
const { files, setFiles, setFilesLoading } = useChatContext();
|
||||
function FileFormChat({ disableInputs }: { disableInputs: boolean }) {
|
||||
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
|
||||
const { files, setFiles, conversation, setFilesLoading } = useChatContext();
|
||||
const { endpoint: _endpoint } = conversation ?? { endpoint: null };
|
||||
const { abortUpload } = useFileHandling();
|
||||
|
||||
|
||||
@@ -59,12 +59,10 @@ export default function FileRow({
|
||||
|
||||
useEffect(() => {
|
||||
if (files.length === 0) {
|
||||
setFilesLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (files.some((file) => file.progress < 1)) {
|
||||
setFilesLoading(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { memo, useCallback } from 'react';
|
||||
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';
|
||||
import { useMCPServerManager } from '~/hooks';
|
||||
|
||||
type MCPSelectProps = { conversationId?: string | null };
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import * as Ariakit from '@ariakit/react';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { PinIcon, MCPIcon } from '@librechat/client';
|
||||
import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon';
|
||||
import { useMCPServerManager } from '~/hooks/MCP/useMCPServerManager';
|
||||
import MCPConfigDialog from '~/components/MCP/MCPConfigDialog';
|
||||
import { useMCPServerManager } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
interface MCPSubMenuProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { createContext, useContext, useState, useMemo } from 'react';
|
||||
import { EModelEndpoint, isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type * as t from 'librechat-data-provider';
|
||||
import type { Endpoint, SelectedValues } from '~/common';
|
||||
import {
|
||||
@@ -59,25 +59,7 @@ export function ModelSelectorProvider({ children, startupConfig }: ModelSelector
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
const { endpoint, model, spec, agent_id, assistant_id, newConversation } =
|
||||
useModelSelectorChatContext();
|
||||
const modelSpecs = useMemo(() => {
|
||||
const specs = startupConfig?.modelSpecs?.list ?? [];
|
||||
if (!agentsMap) {
|
||||
return specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter modelSpecs to only include agents the user has access to.
|
||||
* Use agentsMap which already contains permission-filtered agents (consistent with other components).
|
||||
*/
|
||||
return specs.filter((spec) => {
|
||||
if (spec.preset?.endpoint === EModelEndpoint.agents && spec.preset?.agent_id) {
|
||||
return spec.preset.agent_id in agentsMap;
|
||||
}
|
||||
/** Keep non-agent modelSpecs */
|
||||
return true;
|
||||
});
|
||||
}, [startupConfig, agentsMap]);
|
||||
|
||||
const modelSpecs = useMemo(() => startupConfig?.modelSpecs?.list ?? [], [startupConfig]);
|
||||
const permissionLevel = useAgentDefaultPermissionLevel();
|
||||
const { data: agents = null } = useListAgentsQuery(
|
||||
{ requiredPermission: permissionLevel },
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { EModelEndpoint, Constants } from 'librechat-data-provider';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import MessageIcon from '~/components/Share/MessageIcon';
|
||||
import { useAgentsMapContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
interface AgentHandoffProps {
|
||||
name: string;
|
||||
args: string | Record<string, unknown>;
|
||||
output?: string | null;
|
||||
}
|
||||
|
||||
const AgentHandoff: React.FC<AgentHandoffProps> = ({ name, args: _args = '' }) => {
|
||||
const localize = useLocalize();
|
||||
const agentsMap = useAgentsMapContext();
|
||||
const [showInfo, setShowInfo] = useState(false);
|
||||
|
||||
/** Extracted agent ID from tool name (e.g., "lc_transfer_to_agent_gUV0wMb7zHt3y3Xjz-8_4" -> "agent_gUV0wMb7zHt3y3Xjz-8_4") */
|
||||
const targetAgentId = useMemo(() => {
|
||||
if (typeof name !== 'string' || !name.startsWith(Constants.LC_TRANSFER_TO_)) {
|
||||
return null;
|
||||
}
|
||||
return name.replace(Constants.LC_TRANSFER_TO_, '');
|
||||
}, [name]);
|
||||
|
||||
const targetAgent = useMemo(() => {
|
||||
if (!targetAgentId || !agentsMap) {
|
||||
return null;
|
||||
}
|
||||
return agentsMap[targetAgentId];
|
||||
}, [agentsMap, targetAgentId]);
|
||||
|
||||
const args = useMemo(() => {
|
||||
if (typeof _args === 'string') {
|
||||
return _args;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(_args, null, 2);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}, [_args]) as string;
|
||||
|
||||
/** Requires more than 2 characters as can be an empty object: `{}` */
|
||||
const hasInfo = useMemo(() => (args?.trim()?.length ?? 0) > 2, [args]);
|
||||
|
||||
return (
|
||||
<div className="my-3">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-2.5 text-sm text-text-secondary',
|
||||
hasInfo && 'cursor-pointer transition-colors hover:text-text-primary',
|
||||
)}
|
||||
onClick={() => hasInfo && setShowInfo(!showInfo)}
|
||||
>
|
||||
<div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
|
||||
<MessageIcon
|
||||
message={
|
||||
{
|
||||
endpoint: EModelEndpoint.agents,
|
||||
isCreatedByUser: false,
|
||||
} as TMessage
|
||||
}
|
||||
agent={targetAgent || undefined}
|
||||
/>
|
||||
</div>
|
||||
<span className="select-none">{localize('com_ui_transferred_to')}</span>
|
||||
<span className="select-none font-medium text-text-primary">
|
||||
{targetAgent?.name || localize('com_ui_agent')}
|
||||
</span>
|
||||
{hasInfo && (
|
||||
<ChevronDown
|
||||
className={cn('ml-1 h-3 w-3 transition-transform', showInfo && 'rotate-180')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{hasInfo && showInfo && (
|
||||
<div className="ml-8 mt-2 rounded-md bg-surface-secondary p-3 text-xs">
|
||||
<div className="mb-1 font-medium text-text-secondary">
|
||||
{localize('com_ui_handoff_instructions')}:
|
||||
</div>
|
||||
<pre className="overflow-x-auto whitespace-pre-wrap text-text-primary">{args}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentHandoff;
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
Tools,
|
||||
Constants,
|
||||
ContentTypes,
|
||||
ToolCallTypes,
|
||||
imageGenTools,
|
||||
@@ -11,7 +10,6 @@ import type { TMessageContentParts, TAttachment } from 'librechat-data-provider'
|
||||
import { OpenAIImageGen, EmptyText, Reasoning, ExecuteCode, AgentUpdate, Text } from './Parts';
|
||||
import { ErrorMessage } from './MessageContent';
|
||||
import RetrievalCall from './RetrievalCall';
|
||||
import AgentHandoff from './AgentHandoff';
|
||||
import CodeAnalyze from './CodeAnalyze';
|
||||
import Container from './Container';
|
||||
import WebSearch from './WebSearch';
|
||||
@@ -120,14 +118,6 @@ const Part = memo(
|
||||
isLast={isLast}
|
||||
/>
|
||||
);
|
||||
} else if (isToolCall && toolCall.name?.startsWith(Constants.LC_TRANSFER_TO_)) {
|
||||
return (
|
||||
<AgentHandoff
|
||||
args={toolCall.args ?? ''}
|
||||
name={toolCall.name || ''}
|
||||
output={toolCall.output ?? ''}
|
||||
/>
|
||||
);
|
||||
} else if (isToolCall) {
|
||||
return (
|
||||
<ToolCall
|
||||
|
||||
@@ -11,8 +11,8 @@ interface AgentUpdateProps {
|
||||
|
||||
const AgentUpdate: React.FC<AgentUpdateProps> = ({ currentAgentId }) => {
|
||||
const localize = useLocalize();
|
||||
const agentsMap = useAgentsMapContext();
|
||||
const currentAgent = useMemo(() => agentsMap?.[currentAgentId], [agentsMap, currentAgentId]);
|
||||
const agentsMap = useAgentsMapContext() || {};
|
||||
const currentAgent = useMemo(() => agentsMap[currentAgentId], [agentsMap, currentAgentId]);
|
||||
if (!currentAgentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -88,10 +88,6 @@ export default function ToolCall({
|
||||
const url = new URL(authURL);
|
||||
return url.hostname;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'client/src/components/Chat/Messages/Content/ToolCall.tsx - Failed to parse auth URL',
|
||||
e,
|
||||
);
|
||||
return '';
|
||||
}
|
||||
}, [auth]);
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { UIResourceRenderer } from '@mcp-ui/client';
|
||||
import UIResourceCarousel from './UIResourceCarousel';
|
||||
import type { UIResource } from '~/common';
|
||||
|
||||
function OptimizedCodeBlock({ text, maxHeight = 320 }: { text: string; maxHeight?: number }) {
|
||||
return (
|
||||
@@ -54,26 +51,6 @@ export default function ToolCallInfo({
|
||||
: localize('com_assistants_attempt_info');
|
||||
}
|
||||
|
||||
// Extract ui_resources from the output to display them in the UI
|
||||
let uiResources: UIResource[] = [];
|
||||
if (output?.includes('ui_resources')) {
|
||||
try {
|
||||
const parsedOutput = JSON.parse(output);
|
||||
const uiResourcesItem = parsedOutput.find(
|
||||
(contentItem) => contentItem.metadata?.type === 'ui_resources',
|
||||
);
|
||||
if (uiResourcesItem?.metadata?.data) {
|
||||
uiResources = uiResourcesItem.metadata.data;
|
||||
output = JSON.stringify(
|
||||
parsedOutput.filter((contentItem) => contentItem.metadata?.type !== 'ui_resources'),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// If JSON parsing fails, keep original output
|
||||
console.error('Failed to parse output:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full p-2">
|
||||
<div style={{ opacity: 1 }}>
|
||||
@@ -89,26 +66,6 @@ export default function ToolCallInfo({
|
||||
<div>
|
||||
<OptimizedCodeBlock text={formatText(output)} maxHeight={250} />
|
||||
</div>
|
||||
{uiResources.length > 0 && (
|
||||
<div className="my-2 text-sm font-medium text-text-primary">
|
||||
{localize('com_ui_ui_resources')}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
{uiResources.length > 1 && <UIResourceCarousel uiResources={uiResources} />}
|
||||
|
||||
{uiResources.length === 1 && (
|
||||
<UIResourceRenderer
|
||||
resource={uiResources[0]}
|
||||
onUIAction={async (result) => {
|
||||
console.log('Action:', result);
|
||||
}}
|
||||
htmlProps={{
|
||||
autoResizeIframe: { width: true, height: true },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
import { UIResourceRenderer } from '@mcp-ui/client';
|
||||
import type { UIResource } from '~/common';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface UIResourceCarouselProps {
|
||||
uiResources: UIResource[];
|
||||
}
|
||||
|
||||
const UIResourceCarousel: React.FC<UIResourceCarouselProps> = React.memo(({ uiResources }) => {
|
||||
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
||||
const [showRightArrow, setShowRightArrow] = useState(true);
|
||||
const [isContainerHovered, setIsContainerHovered] = useState(false);
|
||||
const scrollContainerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleScroll = React.useCallback(() => {
|
||||
if (!scrollContainerRef.current) return;
|
||||
|
||||
const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
|
||||
setShowLeftArrow(scrollLeft > 0);
|
||||
setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 10);
|
||||
}, []);
|
||||
|
||||
const scroll = React.useCallback((direction: 'left' | 'right') => {
|
||||
if (!scrollContainerRef.current) return;
|
||||
|
||||
const viewportWidth = scrollContainerRef.current.clientWidth;
|
||||
const scrollAmount = Math.floor(viewportWidth * 0.9);
|
||||
const currentScroll = scrollContainerRef.current.scrollLeft;
|
||||
const newScroll =
|
||||
direction === 'left' ? currentScroll - scrollAmount : currentScroll + scrollAmount;
|
||||
|
||||
scrollContainerRef.current.scrollTo({
|
||||
left: newScroll,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const container = scrollContainerRef.current;
|
||||
if (container) {
|
||||
container.addEventListener('scroll', handleScroll);
|
||||
handleScroll();
|
||||
return () => container.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
}, [handleScroll]);
|
||||
|
||||
if (uiResources.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative mb-4 pt-3"
|
||||
onMouseEnter={() => setIsContainerHovered(true)}
|
||||
onMouseLeave={() => setIsContainerHovered(false)}
|
||||
>
|
||||
<div
|
||||
className={`pointer-events-none absolute left-0 top-0 z-10 h-full w-24 bg-gradient-to-r from-surface-primary to-transparent transition-opacity duration-500 ease-in-out ${
|
||||
showLeftArrow ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`pointer-events-none absolute right-0 top-0 z-10 h-full w-24 bg-gradient-to-l from-surface-primary to-transparent transition-opacity duration-500 ease-in-out ${
|
||||
showRightArrow ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
/>
|
||||
|
||||
{showLeftArrow && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => scroll('left')}
|
||||
className={`absolute left-2 top-1/2 z-20 -translate-y-1/2 rounded-xl bg-white p-2 text-gray-800 shadow-lg transition-all duration-200 hover:scale-110 hover:bg-gray-100 hover:shadow-xl active:scale-95 dark:bg-gray-200 dark:text-gray-800 dark:hover:bg-gray-300 ${
|
||||
isContainerHovered ? 'opacity-100' : 'pointer-events-none opacity-0'
|
||||
}`}
|
||||
aria-label="Scroll left"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className="hide-scrollbar flex gap-4 overflow-x-auto scroll-smooth"
|
||||
>
|
||||
{uiResources.map((uiResource, index) => {
|
||||
const height = 360;
|
||||
const width = 230;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex-shrink-0 transform-gpu transition-all duration-300 ease-out animate-in fade-in-0 slide-in-from-bottom-5"
|
||||
style={{
|
||||
width: `${width}px`,
|
||||
minHeight: `${height}px`,
|
||||
animationDelay: `${index * 100}ms`,
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<UIResourceRenderer
|
||||
resource={{
|
||||
uri: uiResource.uri,
|
||||
mimeType: uiResource.mimeType,
|
||||
text: uiResource.text,
|
||||
}}
|
||||
onUIAction={async (result) => {
|
||||
console.log('Action:', result);
|
||||
}}
|
||||
htmlProps={{
|
||||
autoResizeIframe: { width: true, height: true },
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{showRightArrow && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => scroll('right')}
|
||||
className={`absolute right-2 top-1/2 z-20 -translate-y-1/2 rounded-xl bg-white p-2 text-gray-800 shadow-lg transition-all duration-200 hover:scale-110 hover:bg-gray-100 hover:shadow-xl active:scale-95 dark:bg-gray-200 dark:text-gray-800 dark:hover:bg-gray-300 ${
|
||||
isContainerHovered ? 'opacity-100' : 'pointer-events-none opacity-0'
|
||||
}`}
|
||||
aria-label="Scroll right"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default UIResourceCarousel;
|
||||
@@ -1,273 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import ToolCallInfo from '../ToolCallInfo';
|
||||
import { UIResourceRenderer } from '@mcp-ui/client';
|
||||
import UIResourceCarousel from '../UIResourceCarousel';
|
||||
|
||||
// Mock the dependencies
|
||||
jest.mock('~/hooks', () => ({
|
||||
useLocalize: () => (key: string, values?: any) => {
|
||||
const translations: Record<string, string> = {
|
||||
com_assistants_domain_info: `Used ${values?.[0]}`,
|
||||
com_assistants_function_use: `Used ${values?.[0]}`,
|
||||
com_assistants_action_attempt: `Attempted to use ${values?.[0]}`,
|
||||
com_assistants_attempt_info: 'Attempted to use function',
|
||||
com_ui_result: 'Result',
|
||||
com_ui_ui_resources: 'UI Resources',
|
||||
};
|
||||
return translations[key] || key;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@mcp-ui/client', () => ({
|
||||
UIResourceRenderer: jest.fn(() => null),
|
||||
}));
|
||||
|
||||
jest.mock('../UIResourceCarousel', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => null),
|
||||
}));
|
||||
|
||||
// Add TextEncoder/TextDecoder polyfill for Jest environment
|
||||
import { TextEncoder, TextDecoder } from 'util';
|
||||
|
||||
if (typeof global.TextEncoder === 'undefined') {
|
||||
global.TextEncoder = TextEncoder as any;
|
||||
global.TextDecoder = TextDecoder as any;
|
||||
}
|
||||
|
||||
describe('ToolCallInfo', () => {
|
||||
const mockProps = {
|
||||
input: '{"test": "input"}',
|
||||
function_name: 'testFunction',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('ui_resources extraction', () => {
|
||||
it('should extract single ui_resource from output', () => {
|
||||
const uiResource = {
|
||||
type: 'text',
|
||||
data: 'Test resource',
|
||||
};
|
||||
|
||||
const output = JSON.stringify([
|
||||
{ type: 'text', text: 'Regular output' },
|
||||
{
|
||||
metadata: {
|
||||
type: 'ui_resources',
|
||||
data: [uiResource],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
// Should render UIResourceRenderer for single resource
|
||||
expect(UIResourceRenderer).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
resource: uiResource,
|
||||
onUIAction: expect.any(Function),
|
||||
htmlProps: {
|
||||
autoResizeIframe: { width: true, height: true },
|
||||
},
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
// Should not render carousel for single resource
|
||||
expect(UIResourceCarousel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should extract multiple ui_resources from output', () => {
|
||||
const uiResources = [
|
||||
{ type: 'text', data: 'Resource 1' },
|
||||
{ type: 'text', data: 'Resource 2' },
|
||||
{ type: 'text', data: 'Resource 3' },
|
||||
];
|
||||
|
||||
const output = JSON.stringify([
|
||||
{ type: 'text', text: 'Regular output' },
|
||||
{
|
||||
metadata: {
|
||||
type: 'ui_resources',
|
||||
data: uiResources,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
// Should render carousel for multiple resources
|
||||
expect(UIResourceCarousel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
uiResources,
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
// Should not render individual UIResourceRenderer
|
||||
expect(UIResourceRenderer).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should filter out ui_resources from displayed output', () => {
|
||||
const regularContent = [
|
||||
{ type: 'text', text: 'Regular output 1' },
|
||||
{ type: 'text', text: 'Regular output 2' },
|
||||
];
|
||||
|
||||
const output = JSON.stringify([
|
||||
...regularContent,
|
||||
{
|
||||
metadata: {
|
||||
type: 'ui_resources',
|
||||
data: [{ type: 'text', data: 'UI Resource' }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const { container } = render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
// Check that the displayed output doesn't contain ui_resources
|
||||
const codeBlocks = container.querySelectorAll('code');
|
||||
const outputCode = codeBlocks[1]?.textContent; // Second code block is the output
|
||||
|
||||
expect(outputCode).toContain('Regular output 1');
|
||||
expect(outputCode).toContain('Regular output 2');
|
||||
expect(outputCode).not.toContain('ui_resources');
|
||||
});
|
||||
|
||||
it('should handle output without ui_resources', () => {
|
||||
const output = JSON.stringify([{ type: 'text', text: 'Regular output' }]);
|
||||
|
||||
render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
expect(UIResourceRenderer).not.toHaveBeenCalled();
|
||||
expect(UIResourceCarousel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle malformed ui_resources gracefully', () => {
|
||||
const output = JSON.stringify([
|
||||
{
|
||||
metadata: 'ui_resources', // metadata should be an object, not a string
|
||||
text: 'some text content',
|
||||
},
|
||||
]);
|
||||
|
||||
// Component should not throw error and should render without UI resources
|
||||
const { container } = render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
// Should render the component without crashing
|
||||
expect(container).toBeTruthy();
|
||||
|
||||
// UIResourceCarousel should not be called since the metadata structure is invalid
|
||||
expect(UIResourceCarousel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle ui_resources as plain text without breaking', () => {
|
||||
const outputWithTextOnly =
|
||||
'This output contains ui_resources as plain text but not as a proper structure';
|
||||
|
||||
render(<ToolCallInfo {...mockProps} output={outputWithTextOnly} />);
|
||||
|
||||
// Should render normally without errors
|
||||
expect(screen.getByText(`Used ${mockProps.function_name}`)).toBeInTheDocument();
|
||||
expect(screen.getByText('Result')).toBeInTheDocument();
|
||||
|
||||
// The output text should be displayed in a code block
|
||||
const codeBlocks = screen.getAllByText((content, element) => {
|
||||
return element?.tagName === 'CODE' && content.includes(outputWithTextOnly);
|
||||
});
|
||||
expect(codeBlocks.length).toBeGreaterThan(0);
|
||||
|
||||
// Should not render UI resources components
|
||||
expect(UIResourceRenderer).not.toHaveBeenCalled();
|
||||
expect(UIResourceCarousel).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering logic', () => {
|
||||
it('should render UI Resources heading when ui_resources exist', () => {
|
||||
const output = JSON.stringify([
|
||||
{
|
||||
metadata: {
|
||||
type: 'ui_resources',
|
||||
data: [{ type: 'text', data: 'Test' }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
expect(screen.getByText('UI Resources')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render UI Resources heading when no ui_resources', () => {
|
||||
const output = JSON.stringify([{ type: 'text', text: 'Regular output' }]);
|
||||
|
||||
render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
expect(screen.queryByText('UI Resources')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should pass correct props to UIResourceRenderer', () => {
|
||||
const uiResource = {
|
||||
type: 'form',
|
||||
data: { fields: [{ name: 'test', type: 'text' }] },
|
||||
};
|
||||
|
||||
const output = JSON.stringify([
|
||||
{
|
||||
metadata: {
|
||||
type: 'ui_resources',
|
||||
data: [uiResource],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
expect(UIResourceRenderer).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
resource: uiResource,
|
||||
onUIAction: expect.any(Function),
|
||||
htmlProps: {
|
||||
autoResizeIframe: { width: true, height: true },
|
||||
},
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it('should console.log when UIAction is triggered', async () => {
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
const output = JSON.stringify([
|
||||
{
|
||||
metadata: {
|
||||
type: 'ui_resources',
|
||||
data: [{ type: 'text', data: 'Test' }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
render(<ToolCallInfo {...mockProps} output={output} />);
|
||||
|
||||
const mockUIResourceRenderer = UIResourceRenderer as jest.MockedFunction<
|
||||
typeof UIResourceRenderer
|
||||
>;
|
||||
const onUIAction = mockUIResourceRenderer.mock.calls[0]?.[0]?.onUIAction;
|
||||
const testResult = { action: 'submit', data: { test: 'value' } };
|
||||
|
||||
if (onUIAction) {
|
||||
await onUIAction(testResult as any);
|
||||
}
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Action:', testResult);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,219 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import UIResourceCarousel from '../UIResourceCarousel';
|
||||
import type { UIResource } from '~/common';
|
||||
|
||||
// Mock the UIResourceRenderer component
|
||||
jest.mock('@mcp-ui/client', () => ({
|
||||
UIResourceRenderer: ({ resource, onUIAction }: any) => (
|
||||
<div data-testid="ui-resource-renderer" onClick={() => onUIAction({ action: 'test' })}>
|
||||
{resource.text || 'UI Resource'}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock scrollTo
|
||||
const mockScrollTo = jest.fn();
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollTo', {
|
||||
configurable: true,
|
||||
value: mockScrollTo,
|
||||
});
|
||||
|
||||
describe('UIResourceCarousel', () => {
|
||||
const mockUIResources: UIResource[] = [
|
||||
{ uri: 'resource1', mimeType: 'text/html', text: 'Resource 1' },
|
||||
{ uri: 'resource2', mimeType: 'text/html', text: 'Resource 2' },
|
||||
{ uri: 'resource3', mimeType: 'text/html', text: 'Resource 3' },
|
||||
{ uri: 'resource4', mimeType: 'text/html', text: 'Resource 4' },
|
||||
{ uri: 'resource5', mimeType: 'text/html', text: 'Resource 5' },
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Reset scroll properties
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollLeft', {
|
||||
configurable: true,
|
||||
value: 0,
|
||||
});
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', {
|
||||
configurable: true,
|
||||
value: 1000,
|
||||
});
|
||||
Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
|
||||
configurable: true,
|
||||
value: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders nothing when no resources provided', () => {
|
||||
const { container } = render(<UIResourceCarousel uiResources={[]} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('renders all UI resources', () => {
|
||||
render(<UIResourceCarousel uiResources={mockUIResources} />);
|
||||
const renderers = screen.getAllByTestId('ui-resource-renderer');
|
||||
expect(renderers).toHaveLength(5);
|
||||
expect(screen.getByText('Resource 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Resource 5')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows/hides navigation arrows on hover', async () => {
|
||||
const { container } = render(<UIResourceCarousel uiResources={mockUIResources} />);
|
||||
const carouselContainer = container.querySelector('.relative.mb-4.pt-3');
|
||||
|
||||
// Initially arrows should be hidden (opacity-0)
|
||||
const leftArrow = screen.queryByLabelText('Scroll left');
|
||||
const rightArrow = screen.queryByLabelText('Scroll right');
|
||||
|
||||
// Right arrow should exist but left should not (at start)
|
||||
expect(leftArrow).not.toBeInTheDocument();
|
||||
expect(rightArrow).toBeInTheDocument();
|
||||
expect(rightArrow).toHaveClass('opacity-0');
|
||||
|
||||
// Hover over container
|
||||
fireEvent.mouseEnter(carouselContainer!);
|
||||
await waitFor(() => {
|
||||
expect(rightArrow).toHaveClass('opacity-100');
|
||||
});
|
||||
|
||||
// Leave hover
|
||||
fireEvent.mouseLeave(carouselContainer!);
|
||||
await waitFor(() => {
|
||||
expect(rightArrow).toHaveClass('opacity-0');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles scroll navigation', async () => {
|
||||
const { container } = render(<UIResourceCarousel uiResources={mockUIResources} />);
|
||||
const scrollContainer = container.querySelector('.hide-scrollbar');
|
||||
|
||||
// Simulate being scrolled to show left arrow
|
||||
Object.defineProperty(scrollContainer, 'scrollLeft', {
|
||||
configurable: true,
|
||||
value: 200,
|
||||
});
|
||||
|
||||
// Trigger scroll event
|
||||
fireEvent.scroll(scrollContainer!);
|
||||
|
||||
// Both arrows should now be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('Scroll left')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Scroll right')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Hover to make arrows interactive
|
||||
const carouselContainer = container.querySelector('.relative.mb-4.pt-3');
|
||||
fireEvent.mouseEnter(carouselContainer!);
|
||||
|
||||
// Click right arrow
|
||||
fireEvent.click(screen.getByLabelText('Scroll right'));
|
||||
expect(mockScrollTo).toHaveBeenCalledWith({
|
||||
left: 650, // 200 + (500 * 0.9)
|
||||
behavior: 'smooth',
|
||||
});
|
||||
|
||||
// Click left arrow
|
||||
fireEvent.click(screen.getByLabelText('Scroll left'));
|
||||
expect(mockScrollTo).toHaveBeenCalledWith({
|
||||
left: -250, // 200 - (500 * 0.9)
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
|
||||
it('hides right arrow when scrolled to end', async () => {
|
||||
const { container } = render(<UIResourceCarousel uiResources={mockUIResources} />);
|
||||
const scrollContainer = container.querySelector('.hide-scrollbar');
|
||||
|
||||
// Simulate scrolled to end
|
||||
Object.defineProperty(scrollContainer, 'scrollLeft', {
|
||||
configurable: true,
|
||||
value: 490, // scrollWidth - clientWidth - 10
|
||||
});
|
||||
|
||||
fireEvent.scroll(scrollContainer!);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('Scroll left')).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('Scroll right')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles UIResource actions', async () => {
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
render(<UIResourceCarousel uiResources={mockUIResources.slice(0, 1)} />);
|
||||
|
||||
const renderer = screen.getByTestId('ui-resource-renderer');
|
||||
fireEvent.click(renderer);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Action:', { action: 'test' });
|
||||
});
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('applies correct dimensions to resource containers', () => {
|
||||
render(<UIResourceCarousel uiResources={mockUIResources.slice(0, 2)} />);
|
||||
const containers = screen
|
||||
.getAllByTestId('ui-resource-renderer')
|
||||
.map((el) => el.parentElement?.parentElement);
|
||||
|
||||
containers.forEach((container, index) => {
|
||||
expect(container).toHaveStyle({
|
||||
width: '230px',
|
||||
minHeight: '360px',
|
||||
animationDelay: `${index * 100}ms`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows correct gradient overlays based on scroll position', () => {
|
||||
const { container } = render(<UIResourceCarousel uiResources={mockUIResources} />);
|
||||
|
||||
// At start, left gradient should be hidden, right should be visible
|
||||
const leftGradient = container.querySelector('.bg-gradient-to-r');
|
||||
const rightGradient = container.querySelector('.bg-gradient-to-l');
|
||||
|
||||
expect(leftGradient).toHaveClass('opacity-0');
|
||||
expect(rightGradient).toHaveClass('opacity-100');
|
||||
});
|
||||
|
||||
it('cleans up event listeners on unmount', () => {
|
||||
const { container, unmount } = render(<UIResourceCarousel uiResources={mockUIResources} />);
|
||||
const scrollContainer = container.querySelector('.hide-scrollbar');
|
||||
|
||||
const removeEventListenerSpy = jest.spyOn(scrollContainer!, 'removeEventListener');
|
||||
|
||||
unmount();
|
||||
|
||||
expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
|
||||
});
|
||||
|
||||
it('renders with animation delays for each resource', () => {
|
||||
render(<UIResourceCarousel uiResources={mockUIResources.slice(0, 3)} />);
|
||||
const resourceContainers = screen
|
||||
.getAllByTestId('ui-resource-renderer')
|
||||
.map((el) => el.parentElement?.parentElement);
|
||||
|
||||
resourceContainers.forEach((container, index) => {
|
||||
expect(container).toHaveStyle({
|
||||
animationDelay: `${index * 100}ms`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('memoizes component properly', () => {
|
||||
const { rerender } = render(<UIResourceCarousel uiResources={mockUIResources} />);
|
||||
const firstRender = screen.getAllByTestId('ui-resource-renderer');
|
||||
|
||||
// Re-render with same props
|
||||
rerender(<UIResourceCarousel uiResources={mockUIResources} />);
|
||||
const secondRender = screen.getAllByTestId('ui-resource-renderer');
|
||||
|
||||
// Component should not re-render with same props (React.memo)
|
||||
expect(firstRender.length).toBe(secondRender.length);
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ interface CustomUserVarsSectionProps {
|
||||
onRevoke: () => void;
|
||||
isSubmitting?: boolean;
|
||||
}
|
||||
|
||||
interface AuthFieldProps {
|
||||
name: string;
|
||||
config: CustomUserVarConfig;
|
||||
@@ -68,7 +69,7 @@ function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps)
|
||||
? localize('com_ui_mcp_update_var', { 0: config.title })
|
||||
: localize('com_ui_mcp_enter_var', { 0: config.title })
|
||||
}
|
||||
className="w-full rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary placeholder:text-text-secondary focus:outline-none sm:text-sm"
|
||||
className="w-full shadow-sm sm:text-sm"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -78,22 +79,23 @@ function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps)
|
||||
}
|
||||
|
||||
export default function CustomUserVarsSection({
|
||||
serverName,
|
||||
fields,
|
||||
onSave,
|
||||
onRevoke,
|
||||
serverName,
|
||||
isSubmitting = false,
|
||||
}: CustomUserVarsSectionProps) {
|
||||
const localize = useLocalize();
|
||||
|
||||
// Fetch auth value flags for the server
|
||||
const { data: authValuesData } = useMCPAuthValuesQuery(serverName, {
|
||||
enabled: !!serverName,
|
||||
});
|
||||
|
||||
const {
|
||||
reset,
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm<Record<string, string>>({
|
||||
defaultValues: useMemo(() => {
|
||||
@@ -138,20 +140,10 @@ export default function CustomUserVarsSection({
|
||||
</form>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
disabled={isSubmitting}
|
||||
onClick={handleRevokeClick}
|
||||
>
|
||||
<Button onClick={handleRevokeClick} variant="destructive" disabled={isSubmitting}>
|
||||
{localize('com_ui_revoke')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="submit"
|
||||
disabled={isSubmitting}
|
||||
onClick={handleSubmit(onFormSubmit)}
|
||||
>
|
||||
<Button onClick={handleSubmit(onFormSubmit)} variant="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? localize('com_ui_saving') : localize('com_ui_save')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { Button, Spinner } from '@librechat/client';
|
||||
import { useLocalize, useMCPServerManager, useMCPConnectionStatus } from '~/hooks';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
import { useMCPServerManager } from '~/hooks/MCP/useMCPServerManager';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface ServerInitializationSectionProps {
|
||||
sidePanel?: boolean;
|
||||
@@ -21,15 +21,16 @@ export default function ServerInitializationSection({
|
||||
}: ServerInitializationSectionProps) {
|
||||
const localize = useLocalize();
|
||||
|
||||
const { initializeServer, cancelOAuthFlow, isInitializing, isCancellable, getOAuthUrl } =
|
||||
useMCPServerManager({ conversationId });
|
||||
const {
|
||||
initializeServer,
|
||||
connectionStatus,
|
||||
cancelOAuthFlow,
|
||||
isInitializing,
|
||||
isCancellable,
|
||||
getOAuthUrl,
|
||||
} = useMCPServerManager({ conversationId });
|
||||
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { connectionStatus } = useMCPConnectionStatus({
|
||||
enabled: !!startupConfig?.mcpServers && Object.keys(startupConfig.mcpServers).length > 0,
|
||||
});
|
||||
|
||||
const serverStatus = connectionStatus?.[serverName];
|
||||
const serverStatus = connectionStatus[serverName];
|
||||
const isConnected = serverStatus?.connectionState === 'connected';
|
||||
const canCancel = isCancellable(serverName);
|
||||
const isServerInitializing = isInitializing(serverName);
|
||||
|
||||
@@ -11,9 +11,9 @@ import store from '~/store';
|
||||
|
||||
export default function FilterPrompts({ className = '' }: { className?: string }) {
|
||||
const localize = useLocalize();
|
||||
const { name, setName } = usePromptGroupsContext();
|
||||
const { setName } = usePromptGroupsContext();
|
||||
const { categories } = useCategories('h-4 w-4');
|
||||
const [displayName, setDisplayName] = useState(name || '');
|
||||
const [displayName, setDisplayName] = useState('');
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [categoryFilter, setCategory] = useRecoilState(store.promptsCategory);
|
||||
|
||||
@@ -60,26 +60,13 @@ export default function FilterPrompts({ className = '' }: { className?: string }
|
||||
[setCategory],
|
||||
);
|
||||
|
||||
// Sync displayName with name prop when it changes externally
|
||||
useEffect(() => {
|
||||
setDisplayName(name || '');
|
||||
}, [name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (displayName === '') {
|
||||
// Clear immediately when empty
|
||||
setName('');
|
||||
setIsSearching(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSearching(true);
|
||||
const timeout = setTimeout(() => {
|
||||
setIsSearching(false);
|
||||
setName(displayName); // Debounced setName call
|
||||
}, 500);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [displayName, setName]);
|
||||
}, [displayName]);
|
||||
|
||||
return (
|
||||
<div className={cn('flex w-full gap-2 text-text-primary', className)}>
|
||||
@@ -97,6 +84,7 @@ export default function FilterPrompts({ className = '' }: { className?: string }
|
||||
value={displayName}
|
||||
onChange={(e) => {
|
||||
setDisplayName(e.target.value);
|
||||
setName(e.target.value);
|
||||
}}
|
||||
isSearching={isSearching}
|
||||
placeholder={localize('com_ui_filter_prompts_name')}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useMediaQuery } from '@librechat/client';
|
||||
import PanelNavigation from '~/components/Prompts/Groups/PanelNavigation';
|
||||
import ManagePrompts from '~/components/Prompts/ManagePrompts';
|
||||
import { usePromptGroupsContext } from '~/Providers';
|
||||
import List from '~/components/Prompts/Groups/List';
|
||||
import PanelNavigation from './PanelNavigation';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export default function GroupSidePanel({
|
||||
@@ -19,33 +19,38 @@ export default function GroupSidePanel({
|
||||
const location = useLocation();
|
||||
const isSmallerScreen = useMediaQuery('(max-width: 1024px)');
|
||||
const isChatRoute = useMemo(() => location.pathname?.startsWith('/c/'), [location.pathname]);
|
||||
|
||||
const { promptGroups, groupsQuery, nextPage, prevPage, hasNextPage, hasPreviousPage } =
|
||||
usePromptGroupsContext();
|
||||
const {
|
||||
nextPage,
|
||||
prevPage,
|
||||
isFetching,
|
||||
hasNextPage,
|
||||
groupsQuery,
|
||||
promptGroups,
|
||||
hasPreviousPage,
|
||||
} = usePromptGroupsContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-full w-full flex-col gap-2 md:mr-2 md:w-auto md:min-w-72 lg:w-1/4 xl:w-1/4',
|
||||
'mr-2 flex h-auto w-auto min-w-72 flex-col gap-2 lg:w-1/4 xl:w-1/4',
|
||||
isDetailView === true && isSmallerScreen ? 'hidden' : '',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<div className={cn('flex-grow overflow-y-auto', isChatRoute ? '' : 'px-2 md:px-0')}>
|
||||
<div className="flex-grow overflow-y-auto">
|
||||
<List groups={promptGroups} isChatRoute={isChatRoute} isLoading={!!groupsQuery.isLoading} />
|
||||
</div>
|
||||
<div className={cn(isChatRoute ? '' : 'px-2 pb-3 pt-2 md:px-0')}>
|
||||
<div className="flex items-center justify-between">
|
||||
{isChatRoute && <ManagePrompts className="select-none" />}
|
||||
<PanelNavigation
|
||||
onPrevious={prevPage}
|
||||
onNext={nextPage}
|
||||
nextPage={nextPage}
|
||||
prevPage={prevPage}
|
||||
isFetching={isFetching}
|
||||
hasNextPage={hasNextPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
isLoading={groupsQuery.isFetching}
|
||||
isChatRoute={isChatRoute}
|
||||
>
|
||||
{isChatRoute && <ManagePrompts className="select-none" />}
|
||||
</PanelNavigation>
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,51 +3,42 @@ import { Button, ThemeSelector } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
function PanelNavigation({
|
||||
onPrevious,
|
||||
onNext,
|
||||
hasNextPage,
|
||||
prevPage,
|
||||
nextPage,
|
||||
hasPreviousPage,
|
||||
isLoading,
|
||||
hasNextPage,
|
||||
isFetching,
|
||||
isChatRoute,
|
||||
children,
|
||||
}: {
|
||||
onPrevious: () => void;
|
||||
onNext: () => void;
|
||||
prevPage: () => void;
|
||||
nextPage: () => void;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
isLoading?: boolean;
|
||||
isFetching: boolean;
|
||||
isChatRoute: boolean;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
{!isChatRoute && <ThemeSelector returnThemeOnly={true} />}
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex items-center gap-2" role="navigation" aria-label="Pagination">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onPrevious}
|
||||
disabled={!hasPreviousPage || isLoading}
|
||||
aria-label={localize('com_ui_prev')}
|
||||
>
|
||||
<>
|
||||
<div className="flex gap-2">{!isChatRoute && <ThemeSelector returnThemeOnly={true} />}</div>
|
||||
<div
|
||||
className="flex items-center justify-between gap-2"
|
||||
role="navigation"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={() => prevPage()} disabled={!hasPreviousPage}>
|
||||
{localize('com_ui_prev')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onNext}
|
||||
disabled={!hasNextPage || isLoading}
|
||||
aria-label={localize('com_ui_next')}
|
||||
onClick={() => nextPage()}
|
||||
disabled={!hasNextPage || isFetching}
|
||||
>
|
||||
{localize('com_ui_next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function PromptsAccordion() {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<PromptSidePanel className="mt-2 space-y-2 lg:w-full xl:w-full" {...groupsNav}>
|
||||
<FilterPrompts className="items-center justify-center" />
|
||||
<FilterPrompts setName={groupsNav.setName} className="items-center justify-center" />
|
||||
<div className="flex w-full flex-row items-center justify-end">
|
||||
<AutoSendPrompt className="text-xs dark:text-white" />
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function PromptsView() {
|
||||
<DashBreadcrumb />
|
||||
<div className="flex w-full flex-grow flex-row divide-x overflow-hidden dark:divide-gray-600">
|
||||
<GroupSidePanel isDetailView={isDetailView}>
|
||||
<div className="mt-1 flex flex-row items-center justify-between px-2 md:px-2">
|
||||
<div className="mx-2 mt-1 flex flex-row items-center justify-between">
|
||||
<FilterPrompts />
|
||||
</div>
|
||||
</GroupSidePanel>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useFormContext, Controller } from 'react-hook-form';
|
||||
import type { AgentForm } from '~/common';
|
||||
import { useAgentPanelContext } from '~/Providers';
|
||||
import MaxAgentSteps from './MaxAgentSteps';
|
||||
import AgentHandoffs from './AgentHandoffs';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import AgentChain from './AgentChain';
|
||||
import { Panel } from '~/common';
|
||||
@@ -42,12 +41,6 @@ export default function AdvancedPanel() {
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 px-2">
|
||||
<MaxAgentSteps />
|
||||
<Controller
|
||||
name="edges"
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
render={({ field }) => <AgentHandoffs field={field} currentAgentId={currentAgentId} />}
|
||||
/>
|
||||
{chainEnabled && (
|
||||
<Controller
|
||||
name="agent_ids"
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { X, Waypoints, PlusCircle, ChevronDown } from 'lucide-react';
|
||||
import {
|
||||
Label,
|
||||
Input,
|
||||
Textarea,
|
||||
HoverCard,
|
||||
CircleHelpIcon,
|
||||
HoverCardPortal,
|
||||
ControlCombobox,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@librechat/client';
|
||||
import type { TMessage, GraphEdge } from 'librechat-data-provider';
|
||||
import type { ControllerRenderProps } from 'react-hook-form';
|
||||
import type { AgentForm, OptionWithIcon } from '~/common';
|
||||
import MessageIcon from '~/components/Share/MessageIcon';
|
||||
import { useAgentsMapContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
interface AgentHandoffsProps {
|
||||
field: ControllerRenderProps<AgentForm, 'edges'>;
|
||||
currentAgentId: string;
|
||||
}
|
||||
|
||||
/** TODO: make configurable */
|
||||
const MAX_HANDOFFS = 10;
|
||||
|
||||
const AgentHandoffs: React.FC<AgentHandoffsProps> = ({ field, currentAgentId }) => {
|
||||
const localize = useLocalize();
|
||||
const [newAgentId, setNewAgentId] = useState('');
|
||||
const [expandedIndices, setExpandedIndices] = useState<Set<number>>(new Set());
|
||||
const agentsMap = useAgentsMapContext();
|
||||
const edgesValue = field.value;
|
||||
const edges = useMemo(() => edgesValue || [], [edgesValue]);
|
||||
|
||||
const agents = useMemo(() => (agentsMap ? Object.values(agentsMap) : []), [agentsMap]);
|
||||
|
||||
const selectableAgents = useMemo(
|
||||
() =>
|
||||
agents
|
||||
.filter((agent) => agent?.id !== currentAgentId)
|
||||
.map(
|
||||
(agent) =>
|
||||
({
|
||||
label: agent?.name || '',
|
||||
value: agent?.id || '',
|
||||
icon: (
|
||||
<MessageIcon
|
||||
message={
|
||||
{
|
||||
endpoint: EModelEndpoint.agents,
|
||||
isCreatedByUser: false,
|
||||
} as TMessage
|
||||
}
|
||||
agent={agent}
|
||||
/>
|
||||
),
|
||||
}) as OptionWithIcon,
|
||||
),
|
||||
[agents, currentAgentId],
|
||||
);
|
||||
|
||||
const getAgentDetails = useCallback((id: string) => agentsMap?.[id], [agentsMap]);
|
||||
|
||||
useEffect(() => {
|
||||
if (newAgentId && edges.length < MAX_HANDOFFS) {
|
||||
const newEdge: GraphEdge = {
|
||||
from: currentAgentId,
|
||||
to: newAgentId,
|
||||
edgeType: 'handoff',
|
||||
};
|
||||
field.onChange([...edges, newEdge]);
|
||||
setNewAgentId('');
|
||||
}
|
||||
}, [newAgentId, edges, field, currentAgentId]);
|
||||
|
||||
const removeHandoffAt = (index: number) => {
|
||||
field.onChange(edges.filter((_, i) => i !== index));
|
||||
// Also remove from expanded set
|
||||
setExpandedIndices((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(index);
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const updateHandoffAt = (index: number, agentId: string) => {
|
||||
const updated = [...edges];
|
||||
updated[index] = { ...updated[index], to: agentId };
|
||||
field.onChange(updated);
|
||||
};
|
||||
|
||||
const updateHandoffDetailsAt = (index: number, updates: Partial<GraphEdge>) => {
|
||||
const updated = [...edges];
|
||||
updated[index] = { ...updated[index], ...updates };
|
||||
field.onChange(updated);
|
||||
};
|
||||
|
||||
const toggleExpanded = (index: number) => {
|
||||
setExpandedIndices((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(index)) {
|
||||
newSet.delete(index);
|
||||
} else {
|
||||
newSet.add(index);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const getTargetAgentId = (to: string | string[]): string => {
|
||||
return Array.isArray(to) ? to[0] : to;
|
||||
};
|
||||
|
||||
return (
|
||||
<HoverCard openDelay={50}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="font-semibold text-text-primary">
|
||||
{localize('com_ui_agent_handoffs')}
|
||||
</label>
|
||||
<HoverCardTrigger>
|
||||
<CircleHelpIcon className="h-4 w-4 text-text-tertiary" />
|
||||
</HoverCardTrigger>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-full border border-purple-600/40 bg-purple-500/10 px-2 py-0.5 text-xs font-medium text-purple-700 hover:bg-purple-700/10 dark:text-purple-400">
|
||||
{localize('com_ui_beta')}
|
||||
</div>
|
||||
<div className="text-xs text-text-secondary">
|
||||
{edges.length} / {MAX_HANDOFFS}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{edges.map((edge, idx) => {
|
||||
const targetAgentId = getTargetAgentId(edge.to);
|
||||
const isExpanded = expandedIndices.has(idx);
|
||||
|
||||
return (
|
||||
<React.Fragment key={idx}>
|
||||
<div className="space-y-1">
|
||||
<div className="flex h-10 items-center gap-2 rounded-md border border-border-medium bg-surface-tertiary pr-2">
|
||||
<ControlCombobox
|
||||
isCollapsed={false}
|
||||
ariaLabel={localize('com_ui_agent_var', { 0: localize('com_ui_select') })}
|
||||
selectedValue={targetAgentId}
|
||||
setValue={(id) => updateHandoffAt(idx, id)}
|
||||
selectPlaceholder={localize('com_ui_agent_var', {
|
||||
0: localize('com_ui_select'),
|
||||
})}
|
||||
searchPlaceholder={localize('com_ui_agent_var', {
|
||||
0: localize('com_ui_search'),
|
||||
})}
|
||||
items={selectableAgents}
|
||||
displayValue={getAgentDetails(targetAgentId)?.name ?? ''}
|
||||
SelectIcon={
|
||||
<MessageIcon
|
||||
message={
|
||||
{
|
||||
endpoint: EModelEndpoint.agents,
|
||||
isCreatedByUser: false,
|
||||
} as TMessage
|
||||
}
|
||||
agent={targetAgentId && agentsMap ? agentsMap[targetAgentId] : undefined}
|
||||
/>
|
||||
}
|
||||
className="flex-1 border-border-heavy"
|
||||
containerClassName="px-0"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded p-1 transition hover:bg-surface-hover"
|
||||
onClick={() => toggleExpanded(idx)}
|
||||
>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className={`text-text-secondary transition-transform ${
|
||||
isExpanded ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-xl p-1 transition hover:bg-surface-hover"
|
||||
onClick={() => removeHandoffAt(idx)}
|
||||
>
|
||||
<X size={18} className="text-text-secondary" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="space-y-3 rounded-md border border-border-light bg-surface-primary p-3">
|
||||
<div>
|
||||
<Label
|
||||
htmlFor={`handoff-desc-${idx}`}
|
||||
className="text-xs text-text-secondary"
|
||||
>
|
||||
{localize('com_ui_agent_handoff_description')}
|
||||
</Label>
|
||||
<Input
|
||||
id={`handoff-desc-${idx}`}
|
||||
placeholder={localize('com_ui_agent_handoff_description_placeholder')}
|
||||
value={edge.description || ''}
|
||||
onChange={(e) =>
|
||||
updateHandoffDetailsAt(idx, { description: e.target.value })
|
||||
}
|
||||
className="mt-1 h-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label
|
||||
htmlFor={`handoff-prompt-${idx}`}
|
||||
className="text-xs text-text-secondary"
|
||||
>
|
||||
{localize('com_ui_agent_handoff_prompt')}
|
||||
</Label>
|
||||
<Textarea
|
||||
id={`handoff-prompt-${idx}`}
|
||||
placeholder={localize('com_ui_agent_handoff_prompt_placeholder')}
|
||||
value={typeof edge.prompt === 'string' ? edge.prompt : ''}
|
||||
onChange={(e) => updateHandoffDetailsAt(idx, { prompt: e.target.value })}
|
||||
className="mt-1 h-20 resize-none text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{edge.prompt && (
|
||||
<div>
|
||||
<Label
|
||||
htmlFor={`handoff-promptkey-${idx}`}
|
||||
className="text-xs text-text-secondary"
|
||||
>
|
||||
{localize('com_ui_agent_handoff_prompt_key')}
|
||||
</Label>
|
||||
<Input
|
||||
id={`handoff-promptkey-${idx}`}
|
||||
placeholder={localize('com_ui_agent_handoff_prompt_key_placeholder')}
|
||||
value={edge.promptKey || ''}
|
||||
onChange={(e) =>
|
||||
updateHandoffDetailsAt(idx, { promptKey: e.target.value })
|
||||
}
|
||||
className="mt-1 h-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{idx < edges.length - 1 && (
|
||||
<Waypoints className="mx-auto text-text-secondary" size={14} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
{edges.length < MAX_HANDOFFS && (
|
||||
<>
|
||||
{edges.length > 0 && <Waypoints className="mx-auto text-text-secondary" size={14} />}
|
||||
<ControlCombobox
|
||||
isCollapsed={false}
|
||||
ariaLabel={localize('com_ui_agent_var', { 0: localize('com_ui_add') })}
|
||||
selectedValue=""
|
||||
setValue={setNewAgentId}
|
||||
selectPlaceholder={localize('com_ui_agent_handoff_add')}
|
||||
searchPlaceholder={localize('com_ui_agent_var', { 0: localize('com_ui_search') })}
|
||||
items={selectableAgents}
|
||||
className="h-10 w-full border-dashed border-border-heavy text-center text-text-secondary hover:text-text-primary"
|
||||
containerClassName="px-0"
|
||||
SelectIcon={<PlusCircle size={16} className="text-text-secondary" />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{edges.length >= MAX_HANDOFFS && (
|
||||
<p className="pt-1 text-center text-xs italic text-text-tertiary">
|
||||
{localize('com_ui_agent_handoff_max', { 0: MAX_HANDOFFS })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<HoverCardPortal>
|
||||
<HoverCardContent side={ESide.Top} className="w-80">
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-text-secondary">{localize('com_ui_agent_handoff_info')}</p>
|
||||
<p className="text-sm text-text-secondary">{localize('com_ui_agent_handoff_info_2')}</p>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCardPortal>
|
||||
</HoverCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentHandoffs;
|
||||
@@ -12,23 +12,22 @@ import {
|
||||
getIconKey,
|
||||
cn,
|
||||
} from '~/utils';
|
||||
import { ToolSelectDialog, MCPToolSelectDialog } from '~/components/Tools';
|
||||
import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities';
|
||||
import { useFileMapContext, useAgentPanelContext } from '~/Providers';
|
||||
import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities';
|
||||
import AgentCategorySelector from './AgentCategorySelector';
|
||||
import Action from '~/components/SidePanel/Builder/Action';
|
||||
import { useLocalize, useVisibleTools } from '~/hooks';
|
||||
import { ToolSelectDialog } from '~/components/Tools';
|
||||
import { useGetAgentFiles } from '~/data-provider';
|
||||
import { icons } from '~/hooks/Endpoint/Icons';
|
||||
import Instructions from './Instructions';
|
||||
import AgentAvatar from './AgentAvatar';
|
||||
import FileContext from './FileContext';
|
||||
import SearchForm from './Search/Form';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import FileSearch from './FileSearch';
|
||||
import Artifacts from './Artifacts';
|
||||
import AgentTool from './AgentTool';
|
||||
import CodeForm from './Code/Form';
|
||||
import MCPTools from './MCPTools';
|
||||
import { Panel } from '~/common';
|
||||
|
||||
const labelClass = 'mb-2 text-token-text-primary block font-medium';
|
||||
@@ -44,12 +43,10 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
||||
const { showToast } = useToastContext();
|
||||
const methods = useFormContext<AgentForm>();
|
||||
const [showToolDialog, setShowToolDialog] = useState(false);
|
||||
const [showMCPToolDialog, setShowMCPToolDialog] = useState(false);
|
||||
const {
|
||||
actions,
|
||||
setAction,
|
||||
agentsConfig,
|
||||
mcpServersMap,
|
||||
setActivePanel,
|
||||
endpointsConfig,
|
||||
groupedTools: allTools,
|
||||
@@ -176,7 +173,19 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
||||
Icon = icons[iconKey];
|
||||
}
|
||||
|
||||
const { toolIds, mcpServerNames } = useVisibleTools(tools, allTools, mcpServersMap);
|
||||
// Determine what to show
|
||||
const selectedToolIds = tools ?? [];
|
||||
const visibleToolIds = new Set(selectedToolIds);
|
||||
|
||||
// Check what group parent tools should be shown if any subtool is present
|
||||
Object.entries(allTools ?? {}).forEach(([toolId, toolObj]) => {
|
||||
if (toolObj.tools?.length) {
|
||||
// if any subtool of this group is selected, ensure group parent tool rendered
|
||||
if (toolObj.tools.some((st) => selectedToolIds.includes(st.tool_id))) {
|
||||
visibleToolIds.add(toolId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -317,8 +326,8 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
||||
</label>
|
||||
<div>
|
||||
<div className="mb-1">
|
||||
{/* Render all visible IDs (including groups with subtools selected) */}
|
||||
{toolIds.map((toolId, i) => {
|
||||
{/* // Render all visible IDs (including groups with subtools selected) */}
|
||||
{[...visibleToolIds].map((toolId, i) => {
|
||||
if (!allTools) return null;
|
||||
const tool = allTools[toolId];
|
||||
if (!tool) return null;
|
||||
@@ -376,11 +385,8 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
||||
</div>
|
||||
</div>
|
||||
{/* MCP Section */}
|
||||
<MCPTools
|
||||
agentId={agent_id}
|
||||
mcpServerNames={mcpServerNames}
|
||||
setShowMCPToolDialog={setShowMCPToolDialog}
|
||||
/>
|
||||
{/* <MCPSection /> */}
|
||||
|
||||
{/* Support Contact (Optional) */}
|
||||
<div className="mb-4">
|
||||
<div className="mb-1.5 flex items-center gap-2">
|
||||
@@ -471,13 +477,6 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
||||
setIsOpen={setShowToolDialog}
|
||||
endpoint={EModelEndpoint.agents}
|
||||
/>
|
||||
<MCPToolSelectDialog
|
||||
agentId={agent_id}
|
||||
isOpen={showMCPToolDialog}
|
||||
mcpServerNames={mcpServerNames}
|
||||
setIsOpen={setShowMCPToolDialog}
|
||||
endpoint={EModelEndpoint.agents}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Tools,
|
||||
Constants,
|
||||
SystemRoles,
|
||||
ResourceType,
|
||||
EModelEndpoint,
|
||||
PermissionBits,
|
||||
isAssistantsEndpoint,
|
||||
@@ -54,7 +53,7 @@ export default function AgentPanel() {
|
||||
});
|
||||
|
||||
const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions(
|
||||
ResourceType.AGENT,
|
||||
'agent',
|
||||
basicAgentQuery.data?._id || '',
|
||||
);
|
||||
|
||||
@@ -177,7 +176,6 @@ export default function AgentPanel() {
|
||||
model_parameters,
|
||||
provider: _provider,
|
||||
agent_ids,
|
||||
edges,
|
||||
end_after_tools,
|
||||
hide_sequential_outputs,
|
||||
recursion_limit,
|
||||
@@ -202,7 +200,6 @@ export default function AgentPanel() {
|
||||
provider,
|
||||
model_parameters,
|
||||
agent_ids,
|
||||
edges,
|
||||
end_after_tools,
|
||||
hide_sequential_outputs,
|
||||
recursion_limit,
|
||||
@@ -236,7 +233,6 @@ export default function AgentPanel() {
|
||||
provider,
|
||||
model_parameters,
|
||||
agent_ids,
|
||||
edges,
|
||||
end_after_tools,
|
||||
hide_sequential_outputs,
|
||||
recursion_limit,
|
||||
|
||||
@@ -103,11 +103,6 @@ export default function AgentSelect({
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'edges' && Array.isArray(value)) {
|
||||
formValues[name] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keys.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Label,
|
||||
Checkbox,
|
||||
OGDialog,
|
||||
Accordion,
|
||||
TrashIcon,
|
||||
AccordionItem,
|
||||
CircleHelpIcon,
|
||||
OGDialogTrigger,
|
||||
useToastContext,
|
||||
AccordionContent,
|
||||
OGDialogTemplate,
|
||||
} from '@librechat/client';
|
||||
import type { AgentForm, MCPServerInfo } from '~/common';
|
||||
import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon';
|
||||
import MCPConfigDialog from '~/components/MCP/MCPConfigDialog';
|
||||
import { useLocalize, useMCPServerManager } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const updateUserPlugins = useUpdateUserPluginsMutation();
|
||||
const { getValues, setValue } = useFormContext<AgentForm>();
|
||||
const { getServerStatusIconProps, getConfigDialogProps } = useMCPServerManager();
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const [accordionValue, setAccordionValue] = useState<string>('');
|
||||
const [hoveredToolId, setHoveredToolId] = useState<string | null>(null);
|
||||
|
||||
if (!serverInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentServerName = serverInfo.serverName;
|
||||
|
||||
const getSelectedTools = () => {
|
||||
if (!serverInfo?.tools) return [];
|
||||
const formTools = getValues('tools') || [];
|
||||
return serverInfo.tools.filter((t) => formTools.includes(t.tool_id)).map((t) => t.tool_id);
|
||||
};
|
||||
|
||||
const updateFormTools = (newSelectedTools: string[]) => {
|
||||
const currentTools = getValues('tools') || [];
|
||||
const otherTools = currentTools.filter(
|
||||
(t: string) => !serverInfo?.tools?.some((st) => st.tool_id === t),
|
||||
);
|
||||
setValue('tools', [...otherTools, ...newSelectedTools]);
|
||||
};
|
||||
|
||||
const removeTool = (serverName: string) => {
|
||||
if (!serverName) {
|
||||
return;
|
||||
}
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'uninstall',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => {
|
||||
showToast({ message: `Error while deleting the tool: ${error}`, status: 'error' });
|
||||
},
|
||||
onSuccess: () => {
|
||||
const currentTools = getValues('tools');
|
||||
const remainingToolIds =
|
||||
currentTools?.filter(
|
||||
(currentToolId) =>
|
||||
currentToolId !== serverName &&
|
||||
!currentToolId.endsWith(`${Constants.mcp_delimiter}${serverName}`),
|
||||
) || [];
|
||||
setValue('tools', remainingToolIds);
|
||||
showToast({ message: 'Tool deleted successfully', status: 'success' });
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const selectedTools = getSelectedTools();
|
||||
const isExpanded = accordionValue === currentServerName;
|
||||
|
||||
const statusIconProps = getServerStatusIconProps(currentServerName);
|
||||
const configDialogProps = getConfigDialogProps();
|
||||
|
||||
const statusIcon = statusIconProps && (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="cursor-pointer rounded p-0.5 hover:bg-surface-secondary"
|
||||
>
|
||||
<MCPServerStatusIcon {...statusIconProps} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<OGDialog>
|
||||
<Accordion type="single" value={accordionValue} onValueChange={setAccordionValue} collapsible>
|
||||
<AccordionItem value={currentServerName} className="group relative w-full border-none">
|
||||
<div
|
||||
className="relative flex w-full items-center gap-1 rounded-lg p-1 hover:bg-surface-primary-alt"
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={(e) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AccordionPrimitive.Header asChild>
|
||||
<div
|
||||
className="flex grow cursor-pointer select-none items-center gap-1 rounded bg-transparent p-0 text-left transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1"
|
||||
onClick={() =>
|
||||
setAccordionValue((prev) => {
|
||||
if (prev) {
|
||||
return '';
|
||||
}
|
||||
return currentServerName;
|
||||
})
|
||||
}
|
||||
>
|
||||
{statusIcon && <div className="flex items-center">{statusIcon}</div>}
|
||||
|
||||
{serverInfo.metadata.icon && (
|
||||
<div className="flex h-8 w-8 items-center justify-center overflow-hidden rounded-full">
|
||||
<div
|
||||
className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full bg-center bg-no-repeat dark:bg-white/20"
|
||||
style={{
|
||||
backgroundImage: `url(${serverInfo.metadata.icon})`,
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="grow px-2 py-1.5"
|
||||
style={{ textOverflow: 'ellipsis', wordBreak: 'break-all', overflow: 'hidden' }}
|
||||
>
|
||||
{currentServerName}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="relative flex items-center">
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 transition-all duration-300',
|
||||
isHovering || isFocused
|
||||
? 'translate-x-0 opacity-100'
|
||||
: 'translate-x-8 opacity-0',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
data-checkbox-container
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="mt-1"
|
||||
>
|
||||
<Checkbox
|
||||
id={`select-all-${currentServerName}`}
|
||||
checked={
|
||||
selectedTools.length === serverInfo.tools?.length &&
|
||||
selectedTools.length > 0
|
||||
}
|
||||
onCheckedChange={(checked) => {
|
||||
if (serverInfo.tools) {
|
||||
const newSelectedTools = checked
|
||||
? serverInfo.tools.map((t) => t.tool_id)
|
||||
: [
|
||||
`${Constants.mcp_server}${Constants.mcp_delimiter}${currentServerName}`,
|
||||
];
|
||||
updateFormTools(newSelectedTools);
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'h-4 w-4 rounded border border-border-medium transition-all duration-200 hover:border-border-heavy',
|
||||
isExpanded ? 'visible' : 'pointer-events-none invisible',
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const checkbox = e.currentTarget as HTMLButtonElement;
|
||||
checkbox.click();
|
||||
}
|
||||
}}
|
||||
tabIndex={isExpanded ? 0 : -1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{/* Caret button for accordion */}
|
||||
<AccordionPrimitive.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={cn(
|
||||
'flex h-7 w-7 items-center justify-center rounded transition-colors duration-200 hover:bg-surface-active-alt focus:translate-x-0 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
|
||||
isExpanded && 'bg-surface-active-alt',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
tabIndex={0}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-4 w-4 transition-transform duration-200',
|
||||
isExpanded && 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</AccordionPrimitive.Trigger>
|
||||
|
||||
<OGDialogTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex h-7 w-7 items-center justify-center rounded transition-colors duration-200',
|
||||
'hover:bg-surface-active-alt focus:translate-x-0 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={`Delete ${currentServerName}`}
|
||||
tabIndex={0}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</OGDialogTrigger>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionPrimitive.Header>
|
||||
</div>
|
||||
|
||||
<AccordionContent className="relative ml-1 pt-1 before:absolute before:bottom-2 before:left-0 before:top-0 before:w-0.5 before:bg-border-medium">
|
||||
<div className="space-y-1">
|
||||
{serverInfo.tools?.map((subTool) => (
|
||||
<label
|
||||
key={subTool.tool_id}
|
||||
htmlFor={subTool.tool_id}
|
||||
className={cn(
|
||||
'border-token-border-light hover:bg-token-surface-secondary flex cursor-pointer items-center rounded-lg border p-2',
|
||||
'ml-2 mr-1 focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background',
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onMouseEnter={() => setHoveredToolId(subTool.tool_id)}
|
||||
onMouseLeave={() => setHoveredToolId(null)}
|
||||
>
|
||||
<Checkbox
|
||||
id={subTool.tool_id}
|
||||
checked={selectedTools.includes(subTool.tool_id)}
|
||||
onCheckedChange={(_checked) => {
|
||||
const newSelectedTools = selectedTools.includes(subTool.tool_id)
|
||||
? selectedTools.filter((t) => t !== subTool.tool_id)
|
||||
: [...selectedTools, subTool.tool_id];
|
||||
updateFormTools(newSelectedTools);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
const checkbox = e.currentTarget as HTMLButtonElement;
|
||||
checkbox.click();
|
||||
}
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className={cn(
|
||||
'relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer rounded border border-border-medium transition-[border-color] duration-200 hover:border-border-heavy focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background',
|
||||
)}
|
||||
/>
|
||||
<span className="text-token-text-primary select-none">
|
||||
{subTool.metadata.name}
|
||||
</span>
|
||||
{subTool.metadata.description && (
|
||||
<Ariakit.HovercardProvider placement="left-start">
|
||||
<div className="ml-auto flex h-6 w-6 items-center justify-center">
|
||||
<Ariakit.HovercardAnchor
|
||||
render={
|
||||
<Ariakit.Button
|
||||
className={cn(
|
||||
'flex h-5 w-5 cursor-help items-center rounded-full text-text-secondary transition-opacity duration-200',
|
||||
hoveredToolId === subTool.tool_id ? 'opacity-100' : 'opacity-0',
|
||||
)}
|
||||
aria-label={localize('com_ui_tool_info')}
|
||||
>
|
||||
<CircleHelpIcon className="h-4 w-4" />
|
||||
<Ariakit.VisuallyHidden>
|
||||
{localize('com_ui_tool_info')}
|
||||
</Ariakit.VisuallyHidden>
|
||||
</Ariakit.Button>
|
||||
}
|
||||
/>
|
||||
<Ariakit.HovercardDisclosure
|
||||
className="rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
aria-label={localize('com_ui_tool_more_info')}
|
||||
aria-expanded={hoveredToolId === subTool.tool_id}
|
||||
aria-controls={`tool-description-${subTool.tool_id}`}
|
||||
>
|
||||
<Ariakit.VisuallyHidden>
|
||||
{localize('com_ui_tool_more_info')}
|
||||
</Ariakit.VisuallyHidden>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Ariakit.HovercardDisclosure>
|
||||
</div>
|
||||
<Ariakit.Hovercard
|
||||
id={`tool-description-${subTool.tool_id}`}
|
||||
gutter={14}
|
||||
shift={40}
|
||||
flip={false}
|
||||
className="z-[999] w-80 scale-95 rounded-2xl border border-border-medium bg-surface-secondary p-4 text-text-primary opacity-0 shadow-md transition-all duration-200 data-[enter]:scale-100 data-[leave]:scale-95 data-[enter]:opacity-100 data-[leave]:opacity-0"
|
||||
portal={true}
|
||||
unmountOnHide={true}
|
||||
role="tooltip"
|
||||
aria-label={subTool.metadata.description}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-text-secondary">
|
||||
{subTool.metadata.description}
|
||||
</p>
|
||||
</div>
|
||||
</Ariakit.Hovercard>
|
||||
</Ariakit.HovercardProvider>
|
||||
)}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_tool')}
|
||||
mainClassName="px-0"
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_ui_delete_tool_confirm')}
|
||||
</Label>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: () => removeTool(currentServerName),
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
{configDialogProps && <MCPConfigDialog {...configDialogProps} />}
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import UninitializedMCPTool from './UninitializedMCPTool';
|
||||
import UnconfiguredMCPTool from './UnconfiguredMCPTool';
|
||||
import { useAgentPanelContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import MCPTool from './MCPTool';
|
||||
|
||||
export default function MCPTools({
|
||||
agentId,
|
||||
mcpServerNames,
|
||||
setShowMCPToolDialog,
|
||||
}: {
|
||||
agentId: string;
|
||||
mcpServerNames?: string[];
|
||||
setShowMCPToolDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { mcpServersMap } = useAgentPanelContext();
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<label className="text-token-text-primary mb-2 block font-medium">
|
||||
{localize('com_ui_mcp_servers')}
|
||||
</label>
|
||||
<div>
|
||||
<div className="mb-1">
|
||||
{/* Render servers with selected tools */}
|
||||
{mcpServerNames?.map((mcpServerName) => {
|
||||
const serverInfo = mcpServersMap.get(mcpServerName);
|
||||
if (!serverInfo?.isConfigured) {
|
||||
return (
|
||||
<UnconfiguredMCPTool
|
||||
key={`${mcpServerName}-${agentId}`}
|
||||
serverName={mcpServerName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!serverInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (serverInfo.isConnected) {
|
||||
return (
|
||||
<MCPTool key={`${serverInfo.serverName}-${agentId}`} serverInfo={serverInfo} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<UninitializedMCPTool
|
||||
key={`${serverInfo.serverName}-${agentId}`}
|
||||
serverInfo={serverInfo}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowMCPToolDialog(true)}
|
||||
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
|
||||
aria-haspopup="dialog"
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{localize('com_assistants_add_mcp_server_tools')}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { CircleX } from 'lucide-react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Label,
|
||||
OGDialog,
|
||||
TrashIcon,
|
||||
useToastContext,
|
||||
OGDialogTrigger,
|
||||
OGDialogTemplate,
|
||||
} from '@librechat/client';
|
||||
import type { AgentForm } from '~/common';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export default function UnconfiguredMCPTool({ serverName }: { serverName?: string }) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const updateUserPlugins = useUpdateUserPluginsMutation();
|
||||
const { getValues, setValue } = useFormContext<AgentForm>();
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
if (!serverName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const removeTool = () => {
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'uninstall',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => {
|
||||
showToast({
|
||||
message: localize('com_ui_delete_tool_error', { error: String(error) }),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
const currentTools = getValues('tools');
|
||||
const remainingToolIds =
|
||||
currentTools?.filter(
|
||||
(currentToolId) =>
|
||||
currentToolId !== serverName &&
|
||||
!currentToolId.endsWith(`${Constants.mcp_delimiter}${serverName}`),
|
||||
) || [];
|
||||
setValue('tools', remainingToolIds);
|
||||
showToast({ message: localize('com_ui_delete_tool_success'), status: 'success' });
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<OGDialog>
|
||||
<div
|
||||
className="group relative flex w-full items-center gap-1 rounded-lg p-1 text-sm hover:bg-surface-primary-alt"
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={(e) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded p-1">
|
||||
<CircleX className="h-4 w-4 text-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex grow cursor-not-allowed items-center gap-1 rounded bg-transparent p-0 text-left transition-colors">
|
||||
<div
|
||||
className="grow select-none px-2 py-1.5"
|
||||
style={{ textOverflow: 'ellipsis', wordBreak: 'break-all', overflow: 'hidden' }}
|
||||
>
|
||||
{serverName}
|
||||
<span className="ml-2 text-xs text-text-secondary">
|
||||
{' - '}
|
||||
{localize('com_ui_unavailable')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OGDialogTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex h-7 w-7 items-center justify-center rounded transition-all duration-200 hover:bg-surface-active-alt focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
|
||||
isHovering || isFocused ? 'opacity-100' : 'pointer-events-none opacity-0',
|
||||
)}
|
||||
aria-label={`Delete ${serverName}`}
|
||||
tabIndex={0}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</OGDialogTrigger>
|
||||
</div>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_tool')}
|
||||
mainClassName="px-0"
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_ui_delete_tool_confirm')}
|
||||
</Label>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: () => removeTool(),
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Label,
|
||||
OGDialog,
|
||||
TrashIcon,
|
||||
OGDialogTrigger,
|
||||
useToastContext,
|
||||
OGDialogTemplate,
|
||||
} from '@librechat/client';
|
||||
import type { AgentForm, MCPServerInfo } from '~/common';
|
||||
import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon';
|
||||
import MCPConfigDialog from '~/components/MCP/MCPConfigDialog';
|
||||
import { useLocalize, useMCPServerManager } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export default function UninitializedMCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const updateUserPlugins = useUpdateUserPluginsMutation();
|
||||
const { getValues, setValue } = useFormContext<AgentForm>();
|
||||
const { initializeServer, isInitializing, getServerStatusIconProps, getConfigDialogProps } =
|
||||
useMCPServerManager();
|
||||
|
||||
if (!serverInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const removeTool = (serverName: string) => {
|
||||
if (!serverName) {
|
||||
return;
|
||||
}
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'uninstall',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => {
|
||||
showToast({
|
||||
message: localize('com_ui_delete_tool_error', { error: String(error) }),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
const currentTools = getValues('tools');
|
||||
const remainingToolIds =
|
||||
currentTools?.filter(
|
||||
(currentToolId) =>
|
||||
currentToolId !== serverName &&
|
||||
!currentToolId.endsWith(`${Constants.mcp_delimiter}${serverName}`),
|
||||
) || [];
|
||||
setValue('tools', remainingToolIds);
|
||||
showToast({ message: localize('com_ui_delete_tool_success'), status: 'success' });
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const serverName = serverInfo.serverName;
|
||||
const isServerInitializing = isInitializing(serverName);
|
||||
const statusIconProps = getServerStatusIconProps(serverName);
|
||||
const configDialogProps = getConfigDialogProps();
|
||||
|
||||
const statusIcon = statusIconProps && (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="cursor-pointer rounded p-0.5 hover:bg-surface-secondary"
|
||||
>
|
||||
<MCPServerStatusIcon {...statusIconProps} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<OGDialog>
|
||||
<div
|
||||
className="group relative flex w-full items-center gap-1 rounded-lg p-1 text-sm hover:bg-surface-primary-alt"
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={(e) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex grow cursor-pointer items-center gap-1 rounded bg-transparent p-0 text-left transition-colors"
|
||||
onClick={(e) => {
|
||||
if ((e.target as HTMLElement).closest('[data-status-icon]')) {
|
||||
return;
|
||||
}
|
||||
if (!isServerInitializing) {
|
||||
initializeServer(serverName);
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
if (!isServerInitializing) {
|
||||
initializeServer(serverName);
|
||||
}
|
||||
}
|
||||
}}
|
||||
aria-disabled={isServerInitializing}
|
||||
>
|
||||
{statusIcon && (
|
||||
<div className="flex items-center" data-status-icon>
|
||||
{statusIcon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{serverInfo.metadata.icon && (
|
||||
<div className="flex h-8 w-8 items-center justify-center overflow-hidden rounded-full">
|
||||
<div
|
||||
className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full bg-center bg-no-repeat dark:bg-white/20"
|
||||
style={{
|
||||
backgroundImage: `url(${serverInfo.metadata.icon})`,
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="grow px-2 py-1.5"
|
||||
style={{ textOverflow: 'ellipsis', wordBreak: 'break-all', overflow: 'hidden' }}
|
||||
>
|
||||
{serverName}
|
||||
{isServerInitializing && (
|
||||
<span className="ml-2 text-xs text-text-secondary">
|
||||
{localize('com_ui_initializing')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OGDialogTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex h-7 w-7 items-center justify-center rounded transition-all duration-200 hover:bg-surface-active-alt focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
|
||||
isHovering || isFocused ? 'opacity-100' : 'pointer-events-none opacity-0',
|
||||
)}
|
||||
aria-label={`Delete ${serverName}`}
|
||||
tabIndex={0}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</OGDialogTrigger>
|
||||
</div>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_tool')}
|
||||
mainClassName="px-0"
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_ui_delete_tool_confirm')}
|
||||
</Label>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: () => removeTool(serverName),
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
{configDialogProps && <MCPConfigDialog {...configDialogProps} />}
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
||||
@@ -6,11 +6,12 @@ import { Constants, QueryKeys } from 'librechat-data-provider';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
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 { MCPPanelProvider, useMCPPanelContext } from '~/Providers';
|
||||
import { useLocalize, useMCPConnectionStatus } from '~/hooks';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
import MCPPanelSkeleton from './MCPPanelSkeleton';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
function MCPPanelContent() {
|
||||
const localize = useLocalize();
|
||||
@@ -18,10 +19,7 @@ function MCPPanelContent() {
|
||||
const { showToast } = useToastContext();
|
||||
const { conversationId } = useMCPPanelContext();
|
||||
const { data: startupConfig, isLoading: startupConfigLoading } = useGetStartupConfig();
|
||||
const { connectionStatus } = useMCPConnectionStatus({
|
||||
enabled: !!startupConfig?.mcpServers && Object.keys(startupConfig.mcpServers).length > 0,
|
||||
});
|
||||
|
||||
const { data: connectionStatusData } = useMCPConnectionStatusQuery();
|
||||
const [selectedServerNameForEditing, setSelectedServerNameForEditing] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
@@ -59,6 +57,11 @@ function MCPPanelContent() {
|
||||
}));
|
||||
}, [startupConfig?.mcpServers]);
|
||||
|
||||
const connectionStatus = useMemo(
|
||||
() => connectionStatusData?.connectionStatus || {},
|
||||
[connectionStatusData?.connectionStatus],
|
||||
);
|
||||
|
||||
const handleServerClickToEdit = (serverName: string) => {
|
||||
setSelectedServerNameForEditing(serverName);
|
||||
};
|
||||
@@ -122,7 +125,7 @@ function MCPPanelContent() {
|
||||
);
|
||||
}
|
||||
|
||||
const serverStatus = connectionStatus?.[selectedServerNameForEditing];
|
||||
const serverStatus = connectionStatus[selectedServerNameForEditing];
|
||||
|
||||
return (
|
||||
<div className="h-auto max-w-full space-y-4 overflow-x-hidden py-2">
|
||||
@@ -167,7 +170,7 @@ function MCPPanelContent() {
|
||||
<div className="h-auto max-w-full overflow-x-hidden py-2">
|
||||
<div className="space-y-2">
|
||||
{mcpServerDefinitions.map((server) => {
|
||||
const serverStatus = connectionStatus?.[server.serverName];
|
||||
const serverStatus = connectionStatus[server.serverName];
|
||||
const isConnected = serverStatus?.connectionState === 'connected';
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import { XCircle, PlusCircleIcon, Wrench } from 'lucide-react';
|
||||
import type { AgentToolType } from 'librechat-data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type MCPToolItemProps = {
|
||||
tool: AgentToolType;
|
||||
onAddTool: () => void;
|
||||
onRemoveTool: () => void;
|
||||
isInstalled?: boolean;
|
||||
isConfiguring?: boolean;
|
||||
isInitializing?: boolean;
|
||||
};
|
||||
|
||||
function MCPToolItem({
|
||||
tool,
|
||||
onAddTool,
|
||||
onRemoveTool,
|
||||
isInstalled = false,
|
||||
isConfiguring = false,
|
||||
isInitializing = false,
|
||||
}: MCPToolItemProps) {
|
||||
const localize = useLocalize();
|
||||
const handleClick = () => {
|
||||
if (isInstalled) {
|
||||
onRemoveTool();
|
||||
} else {
|
||||
onAddTool();
|
||||
}
|
||||
};
|
||||
|
||||
const name = tool.metadata?.name || tool.tool_id;
|
||||
const description = tool.metadata?.description || '';
|
||||
const icon = tool.metadata?.icon;
|
||||
|
||||
// Determine button state and text
|
||||
const getButtonState = () => {
|
||||
if (isInstalled) {
|
||||
return {
|
||||
text: localize('com_nav_tool_remove'),
|
||||
icon: <XCircle className="flex h-4 w-4 items-center stroke-2" />,
|
||||
className:
|
||||
'btn relative bg-gray-300 hover:bg-gray-400 dark:bg-gray-50 dark:hover:bg-gray-200',
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (isConfiguring) {
|
||||
return {
|
||||
text: localize('com_ui_confirm'),
|
||||
icon: <PlusCircleIcon className="flex h-4 w-4 items-center stroke-2" />,
|
||||
className: 'btn btn-primary relative',
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (isInitializing) {
|
||||
return {
|
||||
text: localize('com_ui_initializing'),
|
||||
icon: <Wrench className="flex h-4 w-4 items-center stroke-2" />,
|
||||
className: 'btn btn-primary relative opacity-75 cursor-not-allowed',
|
||||
disabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
text: localize('com_ui_add'),
|
||||
icon: <PlusCircleIcon className="flex h-4 w-4 items-center stroke-2" />,
|
||||
className: 'btn btn-primary relative',
|
||||
disabled: false,
|
||||
};
|
||||
};
|
||||
|
||||
const buttonState = getButtonState();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 rounded border border-border-medium bg-transparent p-6">
|
||||
<div className="flex gap-4">
|
||||
<div className="h-[70px] w-[70px] shrink-0">
|
||||
<div className="relative h-full w-full">
|
||||
{icon ? (
|
||||
<img
|
||||
src={icon}
|
||||
alt={localize('com_ui_logo', { 0: name })}
|
||||
className="h-full w-full rounded-[5px] bg-white"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center rounded-[5px] border border-border-medium bg-transparent">
|
||||
<Wrench className="h-8 w-8 text-text-secondary" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 rounded-[5px] ring-1 ring-inset ring-black/10"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-col items-start justify-between">
|
||||
<div className="mb-2 line-clamp-1 max-w-full text-lg leading-5 text-text-primary">
|
||||
{name}
|
||||
</div>
|
||||
<button
|
||||
className={buttonState.className}
|
||||
aria-label={`${buttonState.text} ${name}`}
|
||||
onClick={handleClick}
|
||||
disabled={buttonState.disabled}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{buttonState.text}
|
||||
{buttonState.icon}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="line-clamp-3 h-[60px] text-sm text-text-secondary">{description}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MCPToolItem;
|
||||
@@ -1,370 +0,0 @@
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Constants, EModelEndpoint } from 'librechat-data-provider';
|
||||
import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TError, AgentToolType } from 'librechat-data-provider';
|
||||
import type { AgentForm, TPluginStoreDialogProps } from '~/common';
|
||||
import { useLocalize, usePluginDialogHelpers, useMCPServerManager } from '~/hooks';
|
||||
import { useGetStartupConfig, useAvailableToolsQuery } from '~/data-provider';
|
||||
import CustomUserVarsSection from '~/components/MCP/CustomUserVarsSection';
|
||||
import { PluginPagination } from '~/components/Plugins/Store';
|
||||
import { useAgentPanelContext } from '~/Providers';
|
||||
import MCPToolItem from './MCPToolItem';
|
||||
|
||||
function MCPToolSelectDialog({
|
||||
isOpen,
|
||||
agentId,
|
||||
setIsOpen,
|
||||
mcpServerNames,
|
||||
}: TPluginStoreDialogProps & {
|
||||
agentId: string;
|
||||
mcpServerNames?: string[];
|
||||
endpoint: EModelEndpoint.agents;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { mcpServersMap } = useAgentPanelContext();
|
||||
const { initializeServer } = useMCPServerManager();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { getValues, setValue } = useFormContext<AgentForm>();
|
||||
const { refetch: refetchAvailableTools } = useAvailableToolsQuery(EModelEndpoint.agents);
|
||||
|
||||
const [isInitializing, setIsInitializing] = useState<string | null>(null);
|
||||
const [configuringServer, setConfiguringServer] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
maxPage,
|
||||
setMaxPage,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
itemsPerPage,
|
||||
searchChanged,
|
||||
setSearchChanged,
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
gridRef,
|
||||
handleSearch,
|
||||
handleChangePage,
|
||||
error,
|
||||
setError,
|
||||
errorMessage,
|
||||
setErrorMessage,
|
||||
} = usePluginDialogHelpers();
|
||||
|
||||
const updateUserPlugins = useUpdateUserPluginsMutation();
|
||||
|
||||
const handleInstallError = (error: TError) => {
|
||||
setError(true);
|
||||
const errorMessage = error.response?.data?.message ?? '';
|
||||
if (errorMessage) {
|
||||
setErrorMessage(errorMessage);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setError(false);
|
||||
setErrorMessage('');
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
const handleDirectAdd = async (serverName: string) => {
|
||||
try {
|
||||
setIsInitializing(serverName);
|
||||
const serverInfo = mcpServersMap.get(serverName);
|
||||
if (!serverInfo?.isConnected) {
|
||||
const result = await initializeServer(serverName);
|
||||
if (result?.success && result.oauthRequired && result.oauthUrl) {
|
||||
setIsInitializing(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'install',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => {
|
||||
handleInstallError(error as TError);
|
||||
setIsInitializing(null);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
const { data: updatedAvailableTools } = await refetchAvailableTools();
|
||||
|
||||
const currentTools = getValues('tools') || [];
|
||||
const toolsToAdd: string[] = [
|
||||
`${Constants.mcp_server}${Constants.mcp_delimiter}${serverName}`,
|
||||
];
|
||||
|
||||
if (updatedAvailableTools) {
|
||||
updatedAvailableTools.forEach((tool) => {
|
||||
if (tool.pluginKey.endsWith(`${Constants.mcp_delimiter}${serverName}`)) {
|
||||
toolsToAdd.push(tool.pluginKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const newTools = toolsToAdd.filter((tool) => !currentTools.includes(tool));
|
||||
if (newTools.length > 0) {
|
||||
setValue('tools', [...currentTools, ...newTools]);
|
||||
}
|
||||
setIsInitializing(null);
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error adding MCP server:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveCustomVars = async (serverName: string, authData: Record<string, string>) => {
|
||||
try {
|
||||
await updateUserPlugins.mutateAsync({
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'install',
|
||||
auth: authData,
|
||||
isEntityTool: true,
|
||||
});
|
||||
|
||||
await handleDirectAdd(serverName);
|
||||
|
||||
setConfiguringServer(null);
|
||||
} catch (error) {
|
||||
console.error('Error saving custom vars:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRevokeCustomVars = (serverName: string) => {
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'uninstall',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => handleInstallError(error as TError),
|
||||
onSuccess: () => {
|
||||
setConfiguringServer(null);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const onAddTool = async (serverName: string) => {
|
||||
if (configuringServer === serverName) {
|
||||
setConfiguringServer(null);
|
||||
await handleDirectAdd(serverName);
|
||||
return;
|
||||
}
|
||||
|
||||
const serverConfig = startupConfig?.mcpServers?.[serverName];
|
||||
const hasCustomUserVars =
|
||||
serverConfig?.customUserVars && Object.keys(serverConfig.customUserVars).length > 0;
|
||||
|
||||
if (hasCustomUserVars) {
|
||||
setConfiguringServer(serverName);
|
||||
} else {
|
||||
await handleDirectAdd(serverName);
|
||||
}
|
||||
};
|
||||
|
||||
const onRemoveTool = (serverName: string) => {
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'uninstall',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => handleInstallError(error as TError),
|
||||
onSuccess: () => {
|
||||
const currentTools = getValues('tools') || [];
|
||||
const remainingTools = currentTools.filter(
|
||||
(tool) =>
|
||||
tool !== serverName && !tool.endsWith(`${Constants.mcp_delimiter}${serverName}`),
|
||||
);
|
||||
setValue('tools', remainingTools);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const installedToolsSet = useMemo(() => {
|
||||
return new Set(mcpServerNames);
|
||||
}, [mcpServerNames]);
|
||||
|
||||
const mcpServers = useMemo(() => {
|
||||
const servers = Array.from(mcpServersMap.values());
|
||||
return servers.sort((a, b) => a.serverName.localeCompare(b.serverName));
|
||||
}, [mcpServersMap]);
|
||||
|
||||
const filteredServers = useMemo(() => {
|
||||
if (!searchValue) {
|
||||
return mcpServers;
|
||||
}
|
||||
return mcpServers.filter((serverInfo) =>
|
||||
serverInfo.serverName.toLowerCase().includes(searchValue.toLowerCase()),
|
||||
);
|
||||
}, [mcpServers, searchValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setMaxPage(Math.ceil(filteredServers.length / itemsPerPage));
|
||||
if (searchChanged) {
|
||||
setCurrentPage(1);
|
||||
setSearchChanged(false);
|
||||
}
|
||||
}, [
|
||||
setMaxPage,
|
||||
itemsPerPage,
|
||||
searchChanged,
|
||||
setCurrentPage,
|
||||
setSearchChanged,
|
||||
filteredServers.length,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
setSearchValue('');
|
||||
setConfiguringServer(null);
|
||||
setIsInitializing(null);
|
||||
}}
|
||||
className="relative z-[102]"
|
||||
>
|
||||
<div className="fixed inset-0 bg-surface-primary opacity-60 transition-opacity dark:opacity-80" />
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<DialogPanel
|
||||
className="relative max-h-[90vh] w-full transform overflow-hidden overflow-y-auto rounded-lg bg-surface-secondary text-left shadow-xl transition-all max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
||||
style={{ minHeight: '610px' }}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b-[1px] border-border-medium px-4 pb-4 pt-5 sm:p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="text-center sm:text-left">
|
||||
<DialogTitle className="text-lg font-medium leading-6 text-text-primary">
|
||||
{localize('com_nav_tool_dialog_mcp_server_tools')}
|
||||
</DialogTitle>
|
||||
<Description className="text-sm text-text-secondary">
|
||||
{localize('com_nav_tool_dialog_description')}
|
||||
</Description>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
setConfiguringServer(null);
|
||||
setIsInitializing(null);
|
||||
}}
|
||||
className="inline-block rounded-full text-text-secondary transition-colors hover:text-text-primary"
|
||||
aria-label="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<X aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
className="relative m-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
|
||||
role="alert"
|
||||
>
|
||||
{localize('com_nav_plugin_auth_error')} {errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{configuringServer && (
|
||||
<div className="p-4 sm:p-6 sm:pt-4">
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-text-secondary">
|
||||
{localize('com_ui_mcp_configure_server_description', { 0: configuringServer })}
|
||||
</p>
|
||||
</div>
|
||||
<CustomUserVarsSection
|
||||
serverName={configuringServer}
|
||||
fields={startupConfig?.mcpServers?.[configuringServer]?.customUserVars || {}}
|
||||
onSave={(authData) => handleSaveCustomVars(configuringServer, authData)}
|
||||
onRevoke={() => handleRevokeCustomVars(configuringServer)}
|
||||
isSubmitting={updateUserPlugins.isLoading}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-4 sm:p-6 sm:pt-4">
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
<div
|
||||
className="flex items-center justify-center space-x-4"
|
||||
onClick={() => setConfiguringServer(null)}
|
||||
>
|
||||
<Search className="h-6 w-6 text-text-tertiary" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
placeholder={localize('com_nav_tool_search')}
|
||||
className="w-64 rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={gridRef}
|
||||
className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||
style={{ minHeight: '410px' }}
|
||||
>
|
||||
{filteredServers
|
||||
.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
|
||||
.map((serverInfo) => {
|
||||
const isInstalled = installedToolsSet.has(serverInfo.serverName);
|
||||
const isConfiguring = configuringServer === serverInfo.serverName;
|
||||
const isServerInitializing = isInitializing === serverInfo.serverName;
|
||||
|
||||
const tool: AgentToolType = {
|
||||
agent_id: agentId,
|
||||
tool_id: serverInfo.serverName,
|
||||
metadata: {
|
||||
...serverInfo.metadata,
|
||||
description: `${localize('com_ui_tool_collection_prefix')} ${serverInfo.serverName}`,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<MCPToolItem
|
||||
tool={tool}
|
||||
isInstalled={isInstalled}
|
||||
key={serverInfo.serverName}
|
||||
isConfiguring={isConfiguring}
|
||||
isInitializing={isServerInitializing}
|
||||
onAddTool={() => onAddTool(serverInfo.serverName)}
|
||||
onRemoveTool={() => onRemoveTool(serverInfo.serverName)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex flex-col items-center gap-2 sm:flex-row sm:justify-between">
|
||||
{maxPage > 0 ? (
|
||||
<PluginPagination
|
||||
currentPage={currentPage}
|
||||
maxPage={maxPage}
|
||||
onChangePage={handleChangePage}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ height: '21px' }}></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default MCPToolSelectDialog;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import { Constants, isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import type {
|
||||
@@ -15,6 +15,7 @@ import type { AgentForm, TPluginStoreDialogProps } from '~/common';
|
||||
import { PluginPagination, PluginAuthForm } from '~/components/Plugins/Store';
|
||||
import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
|
||||
import { useLocalize, usePluginDialogHelpers } from '~/hooks';
|
||||
import { useAvailableToolsQuery } from '~/data-provider';
|
||||
import ToolItem from './ToolItem';
|
||||
|
||||
function ToolSelectDialog({
|
||||
@@ -25,9 +26,10 @@ function ToolSelectDialog({
|
||||
endpoint: AssistantsEndpoint | EModelEndpoint.agents;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const isAgentTools = isAgentsEndpoint(endpoint);
|
||||
const { getValues, setValue } = useFormContext<AgentForm>();
|
||||
const { groupedTools, pluginTools } = useAgentPanelContext();
|
||||
const { data: tools } = useAvailableToolsQuery(endpoint);
|
||||
const { groupedTools } = useAgentPanelContext();
|
||||
const isAgentTools = isAgentsEndpoint(endpoint);
|
||||
|
||||
const {
|
||||
maxPage,
|
||||
@@ -119,28 +121,38 @@ function ToolSelectDialog({
|
||||
|
||||
const onAddTool = (pluginKey: string) => {
|
||||
setShowPluginAuthForm(false);
|
||||
const availablePluginFromKey = pluginTools?.find((p) => p.pluginKey === pluginKey);
|
||||
setSelectedPlugin(availablePluginFromKey);
|
||||
const getAvailablePluginFromKey = tools?.find((p) => p.pluginKey === pluginKey);
|
||||
setSelectedPlugin(getAvailablePluginFromKey);
|
||||
|
||||
const { authConfig, authenticated = false } = availablePluginFromKey ?? {};
|
||||
if (authConfig && authConfig.length > 0 && !authenticated) {
|
||||
setShowPluginAuthForm(true);
|
||||
const isMCPTool = pluginKey.includes(Constants.mcp_delimiter);
|
||||
|
||||
if (isMCPTool) {
|
||||
// MCP tools have their variables configured elsewhere (e.g., MCPPanel or MCPSelect),
|
||||
// so we directly proceed to install without showing the auth form.
|
||||
handleInstall({ pluginKey, action: 'install', auth: {} });
|
||||
} else {
|
||||
handleInstall({
|
||||
pluginKey,
|
||||
action: 'install',
|
||||
auth: {},
|
||||
});
|
||||
const { authConfig, authenticated = false } = getAvailablePluginFromKey ?? {};
|
||||
if (authConfig && authConfig.length > 0 && !authenticated) {
|
||||
setShowPluginAuthForm(true);
|
||||
} else {
|
||||
handleInstall({
|
||||
pluginKey,
|
||||
action: 'install',
|
||||
auth: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const filteredTools = Object.values(groupedTools || {}).filter(
|
||||
(currentTool: AgentToolType & { tools?: AgentToolType[] }) => {
|
||||
if (currentTool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase())) {
|
||||
(tool: AgentToolType & { tools?: AgentToolType[] }) => {
|
||||
// Check if the parent tool matches
|
||||
if (tool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
if (currentTool.tools) {
|
||||
return currentTool.tools.some((childTool) =>
|
||||
// Check if any child tools match
|
||||
if (tool.tools) {
|
||||
return tool.tools.some((childTool) =>
|
||||
childTool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase()),
|
||||
);
|
||||
}
|
||||
@@ -157,9 +169,9 @@ function ToolSelectDialog({
|
||||
}
|
||||
}
|
||||
}, [
|
||||
pluginTools,
|
||||
searchValue,
|
||||
tools,
|
||||
itemsPerPage,
|
||||
searchValue,
|
||||
filteredTools,
|
||||
searchChanged,
|
||||
setMaxPage,
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default as MCPToolSelectDialog } from './MCPToolSelectDialog';
|
||||
export { default as ToolSelectDialog } from './ToolSelectDialog';
|
||||
export { default as ToolItem } from './ToolItem';
|
||||
|
||||
@@ -400,27 +400,22 @@ export const usePromptGroupsInfiniteQuery = (
|
||||
params?: t.TPromptGroupsWithFilterRequest,
|
||||
config?: UseInfiniteQueryOptions<t.PromptGroupListResponse, unknown>,
|
||||
) => {
|
||||
const { name, pageSize, category } = params || {};
|
||||
const { name, pageSize, category, ...rest } = params || {};
|
||||
return useInfiniteQuery<t.PromptGroupListResponse, unknown>(
|
||||
[QueryKeys.promptGroups, name, category, pageSize],
|
||||
({ pageParam }) => {
|
||||
const queryParams: t.TPromptGroupsWithFilterRequest = {
|
||||
({ pageParam = '1' }) =>
|
||||
dataService.getPromptGroups({
|
||||
...rest,
|
||||
name,
|
||||
category: category || '',
|
||||
limit: (pageSize || 10).toString(),
|
||||
};
|
||||
|
||||
// Only add cursor if it's a valid string
|
||||
if (pageParam && typeof pageParam === 'string') {
|
||||
queryParams.cursor = pageParam;
|
||||
}
|
||||
|
||||
return dataService.getPromptGroups(queryParams);
|
||||
},
|
||||
pageNumber: pageParam?.toString(),
|
||||
pageSize: (pageSize || 10).toString(),
|
||||
}),
|
||||
{
|
||||
getNextPageParam: (lastPage) => {
|
||||
// Use cursor-based pagination - ensure we return a valid cursor or undefined
|
||||
return lastPage.has_more && lastPage.after ? lastPage.after : undefined;
|
||||
const currentPageNumber = Number(lastPage.pageNumber);
|
||||
const totalPages = Number(lastPage.pages);
|
||||
return currentPageNumber < totalPages ? currentPageNumber + 1 : undefined;
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { useToastContext } from '@librechat/client';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
QueryKeys,
|
||||
Constants,
|
||||
EModelEndpoint,
|
||||
EToolResources,
|
||||
mergeFileConfig,
|
||||
isAgentsEndpoint,
|
||||
isAssistantsEndpoint,
|
||||
@@ -22,7 +19,6 @@ import useLocalize, { TranslationKeys } from '~/hooks/useLocalize';
|
||||
import { useDelayedUploadToast } from './useDelayedUploadToast';
|
||||
import { processFileForUpload } from '~/utils/heicConverter';
|
||||
import { useChatContext } from '~/Providers/ChatContext';
|
||||
import { ephemeralAgentByConvoId } from '~/store';
|
||||
import { logger, validateFiles } from '~/utils';
|
||||
import useClientResize from './useClientResize';
|
||||
import useUpdateFiles from './useUpdateFiles';
|
||||
@@ -43,9 +39,6 @@ const useFileHandling = (params?: UseFileHandling) => {
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const { startUploadTimer, clearUploadTimer } = useDelayedUploadToast();
|
||||
const { files, setFiles, setFilesLoading, conversation } = useChatContext();
|
||||
const setEphemeralAgent = useSetRecoilState(
|
||||
ephemeralAgentByConvoId(conversation?.conversationId ?? Constants.NEW_CONVO),
|
||||
);
|
||||
const setError = (error: string) => setErrors((prevErrors) => [...prevErrors, error]);
|
||||
const { addFile, replaceFile, updateFileById, deleteFileById } = useUpdateFiles(
|
||||
params?.fileSetter ?? setFiles,
|
||||
@@ -140,13 +133,6 @@ const useFileHandling = (params?: UseFileHandling) => {
|
||||
const error = _error as TError | undefined;
|
||||
console.log('upload error', error);
|
||||
const file_id = body.get('file_id');
|
||||
const tool_resource = body.get('tool_resource');
|
||||
if (tool_resource === EToolResources.execute_code) {
|
||||
setEphemeralAgent((prev) => ({
|
||||
...prev,
|
||||
[EToolResources.execute_code]: false,
|
||||
}));
|
||||
}
|
||||
clearUploadTimer(file_id as string);
|
||||
deleteFileById(file_id as string);
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Permissions,
|
||||
alternateName,
|
||||
PermissionBits,
|
||||
EModelEndpoint,
|
||||
PermissionTypes,
|
||||
isAgentsEndpoint,
|
||||
getConfigDefaults,
|
||||
isAssistantsEndpoint,
|
||||
PermissionBits,
|
||||
} from 'librechat-data-provider';
|
||||
import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import type { MentionOption } from '~/common';
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
useGetStartupConfig,
|
||||
} from '~/data-provider';
|
||||
import useAssistantListMap from '~/hooks/Assistants/useAssistantListMap';
|
||||
import { useAgentsMapContext } from '~/Providers/AgentsMapContext';
|
||||
import { mapEndpoints, getPresetTitle } from '~/utils';
|
||||
import { EndpointIcon } from '~/components/Endpoints';
|
||||
import useHasAccess from '~/hooks/Roles/useHasAccess';
|
||||
@@ -63,7 +62,6 @@ export default function useMentions({
|
||||
permission: Permissions.USE,
|
||||
});
|
||||
|
||||
const agentsMap = useAgentsMapContext();
|
||||
const { data: presets } = useGetPresetsQuery();
|
||||
const { data: modelsConfig } = useGetModelsQuery();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
@@ -131,24 +129,7 @@ export default function useMentions({
|
||||
[listMap, assistantMap, endpointsConfig],
|
||||
);
|
||||
|
||||
const modelSpecs = useMemo(() => {
|
||||
const specs = startupConfig?.modelSpecs?.list ?? [];
|
||||
if (!agentsMap) {
|
||||
return specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter modelSpecs to only include agents the user has access to.
|
||||
* Use agentsMap which already contains permission-filtered agents (consistent with other components).
|
||||
*/
|
||||
return specs.filter((spec) => {
|
||||
if (spec.preset?.endpoint === EModelEndpoint.agents && spec.preset?.agent_id) {
|
||||
return spec.preset.agent_id in agentsMap;
|
||||
}
|
||||
/** Keep non-agent modelSpecs */
|
||||
return true;
|
||||
});
|
||||
}, [startupConfig, agentsMap]);
|
||||
const modelSpecs = useMemo(() => startupConfig?.modelSpecs?.list ?? [], [startupConfig]);
|
||||
|
||||
const options: MentionOption[] = useMemo(() => {
|
||||
let validEndpoints = endpoints;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export * from './useGetMCPTools';
|
||||
export * from './useMCPConnectionStatus';
|
||||
export * from './useMCPSelect';
|
||||
export * from './useVisibleTools';
|
||||
export * from './useGetMCPTools';
|
||||
export { useMCPServerManager } from './useMCPServerManager';
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { useMCPConnectionStatusQuery } from '~/data-provider/Tools/queries';
|
||||
|
||||
export function useMCPConnectionStatus({ enabled }: { enabled?: boolean } = {}) {
|
||||
const { data } = useMCPConnectionStatusQuery({
|
||||
enabled,
|
||||
});
|
||||
|
||||
return {
|
||||
connectionStatus: data?.connectionStatus,
|
||||
};
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import type { TUpdateUserPlugins, TPlugin } from 'librechat-data-provider';
|
||||
import type { ConfigFieldDetail } from '~/common';
|
||||
import { useLocalize, useMCPSelect, useGetMCPTools, useMCPConnectionStatus } from '~/hooks';
|
||||
import { useMCPConnectionStatusQuery } from '~/data-provider/Tools/queries';
|
||||
import { useLocalize, useMCPSelect, useGetMCPTools } from '~/hooks';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
|
||||
interface ServerState {
|
||||
@@ -20,7 +21,7 @@ interface ServerState {
|
||||
pollInterval: NodeJS.Timeout | null;
|
||||
}
|
||||
|
||||
export function useMCPServerManager({ conversationId }: { conversationId?: string | null } = {}) {
|
||||
export function useMCPServerManager({ conversationId }: { conversationId?: string | null }) {
|
||||
const localize = useLocalize();
|
||||
const queryClient = useQueryClient();
|
||||
const { showToast } = useToastContext();
|
||||
@@ -82,9 +83,13 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
return initialStates;
|
||||
});
|
||||
|
||||
const { connectionStatus } = useMCPConnectionStatus({
|
||||
const { data: connectionStatusData } = useMCPConnectionStatusQuery({
|
||||
enabled: !!startupConfig?.mcpServers && Object.keys(startupConfig.mcpServers).length > 0,
|
||||
});
|
||||
const connectionStatus = useMemo(
|
||||
() => connectionStatusData?.connectionStatus || {},
|
||||
[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
|
||||
@@ -92,7 +97,7 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
const hasInitialLoadCompleted = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connectionStatus || Object.keys(connectionStatus).length === 0) {
|
||||
if (!connectionStatusData || Object.keys(connectionStatus).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,7 +115,7 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
if (connectedSelected.length !== mcpValues.length) {
|
||||
setMCPValues(connectedSelected);
|
||||
}
|
||||
}, [connectionStatus, mcpValues, setMCPValues]);
|
||||
}, [connectionStatus, connectionStatusData, mcpValues, setMCPValues]);
|
||||
|
||||
const updateServerState = useCallback((serverName: string, updates: Partial<ServerState>) => {
|
||||
setServerStates((prev) => {
|
||||
@@ -224,46 +229,46 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
const initializeServer = useCallback(
|
||||
async (serverName: string, autoOpenOAuth: boolean = true) => {
|
||||
updateServerState(serverName, { isInitializing: true });
|
||||
|
||||
try {
|
||||
const response = await reinitializeMutation.mutateAsync(serverName);
|
||||
if (!response.success) {
|
||||
|
||||
if (response.success) {
|
||||
if (response.oauthRequired && response.oauthUrl) {
|
||||
updateServerState(serverName, {
|
||||
oauthUrl: response.oauthUrl,
|
||||
oauthStartTime: Date.now(),
|
||||
isCancellable: true,
|
||||
isInitializing: true,
|
||||
});
|
||||
|
||||
if (autoOpenOAuth) {
|
||||
window.open(response.oauthUrl, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
|
||||
startServerPolling(serverName);
|
||||
} else {
|
||||
await queryClient.refetchQueries([QueryKeys.mcpConnectionStatus]);
|
||||
|
||||
showToast({
|
||||
message: localize('com_ui_mcp_initialized_success', { 0: serverName }),
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
const currentValues = mcpValues ?? [];
|
||||
if (!currentValues.includes(serverName)) {
|
||||
setMCPValues([...currentValues, serverName]);
|
||||
}
|
||||
|
||||
cleanupServerState(serverName);
|
||||
}
|
||||
} else {
|
||||
showToast({
|
||||
message: localize('com_ui_mcp_init_failed', { 0: serverName }),
|
||||
status: 'error',
|
||||
});
|
||||
cleanupServerState(serverName);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (response.oauthRequired && response.oauthUrl) {
|
||||
updateServerState(serverName, {
|
||||
oauthUrl: response.oauthUrl,
|
||||
oauthStartTime: Date.now(),
|
||||
isCancellable: true,
|
||||
isInitializing: true,
|
||||
});
|
||||
|
||||
if (autoOpenOAuth) {
|
||||
window.open(response.oauthUrl, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
|
||||
startServerPolling(serverName);
|
||||
} else {
|
||||
await queryClient.invalidateQueries([QueryKeys.mcpConnectionStatus]);
|
||||
|
||||
showToast({
|
||||
message: localize('com_ui_mcp_initialized_success', { 0: serverName }),
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
const currentValues = mcpValues ?? [];
|
||||
if (!currentValues.includes(serverName)) {
|
||||
setMCPValues([...currentValues, serverName]);
|
||||
}
|
||||
|
||||
cleanupServerState(serverName);
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`[MCP Manager] Failed to initialize ${serverName}:`, error);
|
||||
showToast({
|
||||
@@ -346,7 +351,7 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
return;
|
||||
}
|
||||
|
||||
const serverStatus = connectionStatus?.[serverName];
|
||||
const serverStatus = connectionStatus[serverName];
|
||||
if (serverStatus?.connectionState === 'connected') {
|
||||
connectedServers.push(serverName);
|
||||
} else {
|
||||
@@ -376,7 +381,7 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
const filteredValues = currentValues.filter((name) => name !== serverName);
|
||||
setMCPValues(filteredValues);
|
||||
} else {
|
||||
const serverStatus = connectionStatus?.[serverName];
|
||||
const serverStatus = connectionStatus[serverName];
|
||||
if (serverStatus?.connectionState === 'connected') {
|
||||
setMCPValues([...currentValues, serverName]);
|
||||
} else {
|
||||
@@ -450,7 +455,7 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
const getServerStatusIconProps = useCallback(
|
||||
(serverName: string) => {
|
||||
const tool = mcpToolDetails?.find((t) => t.name === serverName);
|
||||
const serverStatus = connectionStatus?.[serverName];
|
||||
const serverStatus = connectionStatus[serverName];
|
||||
const serverConfig = startupConfig?.mcpServers?.[serverName];
|
||||
|
||||
const handleConfigClick = (e: React.MouseEvent) => {
|
||||
@@ -527,7 +532,7 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
|
||||
return {
|
||||
serverName: selectedToolForConfig.name,
|
||||
serverStatus: connectionStatus?.[selectedToolForConfig.name],
|
||||
serverStatus: connectionStatus[selectedToolForConfig.name],
|
||||
isOpen: isConfigModalOpen,
|
||||
onOpenChange: handleDialogOpenChange,
|
||||
fieldsSchema,
|
||||
@@ -548,6 +553,7 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
|
||||
|
||||
return {
|
||||
configuredServers,
|
||||
connectionStatus,
|
||||
initializeServer,
|
||||
cancelOAuthFlow,
|
||||
isInitializing,
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import type { AgentToolType } from 'librechat-data-provider';
|
||||
import type { MCPServerInfo } from '~/common';
|
||||
|
||||
type GroupedToolType = AgentToolType & { tools?: AgentToolType[] };
|
||||
type GroupedToolsRecord = Record<string, GroupedToolType>;
|
||||
|
||||
interface VisibleToolsResult {
|
||||
toolIds: string[];
|
||||
mcpServerNames: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook to calculate visible tool IDs based on selected tools and their parent groups.
|
||||
* If any subtool of a group is selected, the parent group tool is also made visible.
|
||||
*
|
||||
* @param selectedToolIds - Array of selected tool IDs
|
||||
* @param allTools - Record of all available tools
|
||||
* @param mcpServersMap - Map of all MCP servers
|
||||
* @returns Object containing separate arrays of visible tool IDs for regular and MCP tools
|
||||
*/
|
||||
export function useVisibleTools(
|
||||
selectedToolIds: string[] | undefined,
|
||||
allTools: GroupedToolsRecord | undefined,
|
||||
mcpServersMap: Map<string, MCPServerInfo>,
|
||||
): VisibleToolsResult {
|
||||
return useMemo(() => {
|
||||
const mcpServers = new Set<string>();
|
||||
const selectedSet = new Set<string>();
|
||||
const regularToolIds = new Set<string>();
|
||||
|
||||
for (const toolId of selectedToolIds ?? []) {
|
||||
if (!toolId.includes(Constants.mcp_delimiter)) {
|
||||
selectedSet.add(toolId);
|
||||
continue;
|
||||
}
|
||||
const serverName = toolId.split(Constants.mcp_delimiter)[1];
|
||||
if (!serverName) {
|
||||
continue;
|
||||
}
|
||||
mcpServers.add(serverName);
|
||||
}
|
||||
|
||||
if (allTools) {
|
||||
for (const [toolId, toolObj] of Object.entries(allTools)) {
|
||||
if (selectedSet.has(toolId)) {
|
||||
regularToolIds.add(toolId);
|
||||
}
|
||||
|
||||
if (toolObj.tools?.length) {
|
||||
for (const subtool of toolObj.tools) {
|
||||
if (selectedSet.has(subtool.tool_id)) {
|
||||
regularToolIds.add(toolId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mcpServersMap) {
|
||||
for (const [mcpServerName] of mcpServersMap) {
|
||||
if (mcpServers.has(mcpServerName)) {
|
||||
continue;
|
||||
}
|
||||
/** Legacy check */
|
||||
if (selectedSet.has(mcpServerName)) {
|
||||
mcpServers.add(mcpServerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
toolIds: Array.from(regularToolIds).sort((a, b) => a.localeCompare(b)),
|
||||
mcpServerNames: Array.from(mcpServers).sort((a, b) => a.localeCompare(b)),
|
||||
};
|
||||
}, [allTools, mcpServersMap, selectedToolIds]);
|
||||
}
|
||||
@@ -98,10 +98,6 @@ export function useToolToggle({
|
||||
if (isAuthenticated !== undefined && !isAuthenticated && setIsDialogOpen) {
|
||||
setIsDialogOpen(true);
|
||||
e?.preventDefault?.();
|
||||
setEphemeralAgent((prev) => ({
|
||||
...(prev || {}),
|
||||
[toolKey]: false,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,108 +1,92 @@
|
||||
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useMemo, useRef, useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { usePromptGroupsInfiniteQuery } from '~/data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import debounce from 'lodash/debounce';
|
||||
import store from '~/store';
|
||||
|
||||
export default function usePromptGroupsNav() {
|
||||
const [pageSize] = useRecoilState(store.promptsPageSize);
|
||||
const [category] = useRecoilState(store.promptsCategory);
|
||||
const queryClient = useQueryClient();
|
||||
const category = useRecoilValue(store.promptsCategory);
|
||||
const [name, setName] = useRecoilState(store.promptsName);
|
||||
const [pageSize, setPageSize] = useRecoilState(store.promptsPageSize);
|
||||
const [pageNumber, setPageNumber] = useRecoilState(store.promptsPageNumber);
|
||||
|
||||
// Track current page index and cursor history
|
||||
const [currentPageIndex, setCurrentPageIndex] = useState(0);
|
||||
const cursorHistoryRef = useRef<Array<string | null>>([null]); // Start with null for first page
|
||||
const maxPageNumberReached = useRef(1);
|
||||
const prevFiltersRef = useRef({ name, category, pageSize });
|
||||
|
||||
const prevFiltersRef = useRef({ name, category });
|
||||
useEffect(() => {
|
||||
if (pageNumber > 1 && pageNumber > maxPageNumberReached.current) {
|
||||
maxPageNumberReached.current = pageNumber;
|
||||
}
|
||||
}, [pageNumber]);
|
||||
|
||||
const groupsQuery = usePromptGroupsInfiniteQuery({
|
||||
name,
|
||||
pageSize,
|
||||
category,
|
||||
pageNumber: pageNumber + '',
|
||||
});
|
||||
|
||||
// Get the current page data
|
||||
const currentPageData = useMemo(() => {
|
||||
if (!groupsQuery.data?.pages || groupsQuery.data.pages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// Ensure we don't go out of bounds
|
||||
const pageIndex = Math.min(currentPageIndex, groupsQuery.data.pages.length - 1);
|
||||
return groupsQuery.data.pages[pageIndex];
|
||||
}, [groupsQuery.data?.pages, currentPageIndex]);
|
||||
|
||||
// Get prompt groups for current page
|
||||
const promptGroups = useMemo(() => {
|
||||
return currentPageData?.promptGroups || [];
|
||||
}, [currentPageData]);
|
||||
|
||||
// Calculate pagination state
|
||||
const hasNextPage = useMemo(() => {
|
||||
if (!currentPageData) return false;
|
||||
|
||||
// If we're not on the last loaded page, we have a next page
|
||||
if (currentPageIndex < (groupsQuery.data?.pages?.length || 0) - 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we're on the last loaded page, check if there are more from backend
|
||||
return currentPageData.has_more || false;
|
||||
}, [currentPageData, currentPageIndex, groupsQuery.data?.pages?.length]);
|
||||
|
||||
const hasPreviousPage = currentPageIndex > 0;
|
||||
const currentPage = currentPageIndex + 1;
|
||||
const totalPages = hasNextPage ? currentPage + 1 : currentPage;
|
||||
|
||||
// Navigate to next page
|
||||
const nextPage = useCallback(async () => {
|
||||
if (!hasNextPage) return;
|
||||
|
||||
const nextPageIndex = currentPageIndex + 1;
|
||||
|
||||
// Check if we need to load more data
|
||||
if (nextPageIndex >= (groupsQuery.data?.pages?.length || 0)) {
|
||||
// We need to fetch the next page
|
||||
const result = await groupsQuery.fetchNextPage();
|
||||
if (result.isSuccess && result.data?.pages) {
|
||||
// Update cursor history with the cursor for the next page
|
||||
const lastPage = result.data.pages[result.data.pages.length - 2]; // Get the page before the newly fetched one
|
||||
if (lastPage?.after && !cursorHistoryRef.current.includes(lastPage.after)) {
|
||||
cursorHistoryRef.current.push(lastPage.after);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentPageIndex(nextPageIndex);
|
||||
}, [currentPageIndex, hasNextPage, groupsQuery]);
|
||||
|
||||
// Navigate to previous page
|
||||
const prevPage = useCallback(() => {
|
||||
if (!hasPreviousPage) return;
|
||||
setCurrentPageIndex(currentPageIndex - 1);
|
||||
}, [currentPageIndex, hasPreviousPage]);
|
||||
|
||||
// Reset when filters change
|
||||
useEffect(() => {
|
||||
const filtersChanged =
|
||||
prevFiltersRef.current.name !== name || prevFiltersRef.current.category !== category;
|
||||
prevFiltersRef.current.name !== name ||
|
||||
prevFiltersRef.current.category !== category ||
|
||||
prevFiltersRef.current.pageSize !== pageSize;
|
||||
|
||||
if (filtersChanged) {
|
||||
setCurrentPageIndex(0);
|
||||
cursorHistoryRef.current = [null];
|
||||
prevFiltersRef.current = { name, category };
|
||||
if (!filtersChanged) {
|
||||
return;
|
||||
}
|
||||
}, [name, category]);
|
||||
maxPageNumberReached.current = 1;
|
||||
setPageNumber(1);
|
||||
|
||||
// Only reset queries if we're not already on page 1
|
||||
// This prevents double queries when filters change
|
||||
if (pageNumber !== 1) {
|
||||
queryClient.invalidateQueries([QueryKeys.promptGroups, name, category, pageSize]);
|
||||
}
|
||||
|
||||
prevFiltersRef.current = { name, category, pageSize };
|
||||
}, [pageSize, name, category, setPageNumber, pageNumber, queryClient]);
|
||||
|
||||
const promptGroups = useMemo(() => {
|
||||
return groupsQuery.data?.pages[pageNumber - 1 + '']?.promptGroups || [];
|
||||
}, [groupsQuery.data, pageNumber]);
|
||||
|
||||
const nextPage = () => {
|
||||
setPageNumber((prev) => prev + 1);
|
||||
groupsQuery.hasNextPage && groupsQuery.fetchNextPage();
|
||||
};
|
||||
|
||||
const prevPage = () => {
|
||||
setPageNumber((prev) => prev - 1);
|
||||
groupsQuery.hasPreviousPage && groupsQuery.fetchPreviousPage();
|
||||
};
|
||||
|
||||
const isFetching = groupsQuery.isFetchingNextPage;
|
||||
const hasNextPage = !!groupsQuery.hasNextPage || maxPageNumberReached.current > pageNumber;
|
||||
const hasPreviousPage = !!groupsQuery.hasPreviousPage || pageNumber > 1;
|
||||
|
||||
const debouncedSetName = useMemo(
|
||||
() =>
|
||||
debounce((nextValue: string) => {
|
||||
setName(nextValue);
|
||||
}, 850),
|
||||
[setName],
|
||||
);
|
||||
|
||||
return {
|
||||
promptGroups,
|
||||
groupsQuery,
|
||||
currentPage,
|
||||
totalPages,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
name,
|
||||
setName: debouncedSetName,
|
||||
nextPage,
|
||||
prevPage,
|
||||
isFetching: groupsQuery.isFetching,
|
||||
name,
|
||||
setName,
|
||||
isFetching,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
hasNextPage,
|
||||
groupsQuery,
|
||||
promptGroups,
|
||||
hasPreviousPage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createContext, useRef, useContext, RefObject } from 'react';
|
||||
import { toCanvas } from 'html-to-image';
|
||||
import { ThemeContext, isDark } from '@librechat/client';
|
||||
import { ThemeContext } from '@librechat/client';
|
||||
|
||||
type ScreenshotContextType = {
|
||||
ref?: RefObject<HTMLDivElement>;
|
||||
@@ -17,7 +17,11 @@ export const useScreenshot = () => {
|
||||
throw new Error('You should provide correct html node.');
|
||||
}
|
||||
|
||||
const backgroundColor = isDark(theme) ? '#171717' : 'white';
|
||||
let isDark = theme === 'dark';
|
||||
if (theme === 'system') {
|
||||
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
const backgroundColor = isDark ? '#171717' : 'white';
|
||||
|
||||
const canvas = await toCanvas(node, {
|
||||
backgroundColor,
|
||||
|
||||
@@ -4,106 +4,37 @@
|
||||
"com_a11y_ai_composing": "Die KI erstellt noch ihre Antwort.\n",
|
||||
"com_a11y_end": "Die KI hat die Antwort fertiggestellt.",
|
||||
"com_a11y_start": "Die KI hat mit ihrer Antwort begonnen. ",
|
||||
"com_agents_agent_card_label": "{{name}} Agent. {{description}}",
|
||||
"com_agents_all": "Alle Agenten",
|
||||
"com_agents_all_category": "Alle",
|
||||
"com_agents_all_description": "Durchstöbere alle freigegebenen Agenten in allen Kategorien",
|
||||
"com_agents_by_librechat": "von LibreChat",
|
||||
"com_agents_category_aftersales": "Kundenservice",
|
||||
"com_agents_category_aftersales_description": "Agenten, die auf Support nach dem Kauf, Wartung und Kundenservice spezialisiert sind",
|
||||
"com_agents_category_empty": "Keine Agenten in der Kategorie {{category}} gefunden",
|
||||
"com_agents_category_finance": "Finanzen",
|
||||
"com_agents_category_finance_description": "Auf Finanzanalyse, Budgetierung und Buchhaltung spezialisierte Agenten",
|
||||
"com_agents_category_general": "Allgemein",
|
||||
"com_agents_category_general_description": "Allzweck-Agenten für alltägliche Aufgaben und Anfragen",
|
||||
"com_agents_category_hr": "Personalwesen",
|
||||
"com_agents_category_hr_description": "Auf HR-Prozesse, Richtlinien und Mitarbeiterbetreuung spezialisierte Agents",
|
||||
"com_agents_category_it": "IT",
|
||||
"com_agents_category_it_description": "Agenten für IT-Support, technische Fehlerbehebung und Systemadministration",
|
||||
"com_agents_category_rd": "Forschung & Entwicklung",
|
||||
"com_agents_category_rd_description": "Agenten mit Fokus auf F&E-Prozesse, Innovation und technische Forschung",
|
||||
"com_agents_category_sales": "Vertrieb",
|
||||
"com_agents_category_sales_description": "Agenten mit Fokus auf Vertriebsprozesse und Kundenbeziehungen",
|
||||
"com_agents_category_tab_label": "Kategorie {{category}}. {{position}} von {{total}}",
|
||||
"com_agents_category_tabs_label": "Agenten-Kategorien",
|
||||
"com_agents_clear_search": "Suche löschen",
|
||||
"com_agents_code_interpreter": "Wenn aktiviert, ermöglicht es deinem Agenten, die LibreChat Code Interpreter API zu nutzen, um generierten Code sicher auszuführen, einschließlich der Verarbeitung von Dateien. Erfordert einen gültigen API-Schlüssel.",
|
||||
"com_agents_code_interpreter_title": "Code-Interpreter-API",
|
||||
"com_agents_contact": "Kontakt",
|
||||
"com_agents_copy_link": "Link kopieren",
|
||||
"com_agents_create_error": "Bei der Erstellung deines Agenten ist ein Fehler aufgetreten.",
|
||||
"com_agents_created_by": "von",
|
||||
"com_agents_description_placeholder": "Optional: Beschreibe hier deinen Agenten",
|
||||
"com_agents_empty_state_heading": "Keine Agenten gefunden",
|
||||
"com_agents_enable_file_search": "Dateisuche aktivieren",
|
||||
"com_agents_error_bad_request_message": "Die Anfrage konnte nicht verarbeitet werden.",
|
||||
"com_agents_error_bad_request_suggestion": "Bitte überprüfe deine Eingabe und versuche es erneut.",
|
||||
"com_agents_error_category_title": "Kategorienfehler",
|
||||
"com_agents_error_generic": "Beim Laden des Inhalts ist ein Problem aufgetreten.",
|
||||
"com_agents_error_invalid_request": "Ungültige Anfrage",
|
||||
"com_agents_error_loading": "Fehler beim Laden der Agenten",
|
||||
"com_agents_error_network_message": "Verbindung zum Server nicht möglich.",
|
||||
"com_agents_error_network_suggestion": "Überprüfe deine Internetverbindung und versuche es erneut.",
|
||||
"com_agents_error_network_title": "Verbindungsproblem",
|
||||
"com_agents_error_not_found_message": "Der angeforderte Inhalt konnte nicht gefunden werden.",
|
||||
"com_agents_error_not_found_suggestion": "Versuche, andere Optionen zu durchsuchen, oder kehre zum Marktplatz zurück.",
|
||||
"com_agents_error_not_found_title": "Nicht gefunden",
|
||||
"com_agents_error_retry": "Erneut versuchen",
|
||||
"com_agents_error_search_title": "Suchfehler",
|
||||
"com_agents_error_searching": "Fehler beim Suchen nach Agenten",
|
||||
"com_agents_error_server_message": "Der Server ist vorübergehend nicht verfügbar.",
|
||||
"com_agents_error_server_suggestion": "Bitte versuche es in wenigen Augenblicken erneut.",
|
||||
"com_agents_error_server_title": "Serverfehler",
|
||||
"com_agents_error_suggestion_generic": "Bitte versuche, die Seite zu aktualisieren, oder versuche es später erneut.",
|
||||
"com_agents_error_timeout_message": "Die Anfrage dauerte zu lange.",
|
||||
"com_agents_error_timeout_suggestion": "Bitte überprüfe deine Internetverbindung und versuche es erneut.",
|
||||
"com_agents_error_timeout_title": "Verbindungs-Timeout",
|
||||
"com_agents_error_title": "Es ist ein Fehler aufgetreten",
|
||||
"com_agents_file_context": "Datei-Kontext (OCR)",
|
||||
"com_agents_file_context_disabled": "Der Agent muss vor dem Hochladen von Dateien für den Datei-Kontext erstellt werden.",
|
||||
"com_agents_file_context_info": "Als „Kontext“ hochgeladene Dateien werden mit OCR verarbeitet, um Text zu extrahieren, der dann den Anweisungen des Agenten hinzugefügt wird. Ideal für Dokumente, Bilder mit Text oder PDFs, wenn Sie den vollständigen Textinhalt einer Datei benötigen",
|
||||
"com_agents_file_search_disabled": "Der Agent muss erstellt werden, bevor Dateien für die Dateisuche hochgeladen werden können.",
|
||||
"com_agents_file_search_info": "Wenn aktiviert, wird der Agent über die unten aufgelisteten exakten Dateinamen informiert und kann dadurch relevante Informationen aus diesen Dateien abrufen",
|
||||
"com_agents_grid_announcement": "Zeige {{count}} Agenten in der Kategorie {{category}}",
|
||||
"com_agents_instructions_placeholder": "Die Systemanweisungen, die der Agent verwendet",
|
||||
"com_agents_link_copied": "Link kopiert",
|
||||
"com_agents_link_copy_failed": "Link konnte nicht kopiert werden",
|
||||
"com_agents_load_more_label": "Weitere Agenten aus der Kategorie {{category}} laden",
|
||||
"com_agents_loading": "Wird geladen...",
|
||||
"com_agents_marketplace": "Agenten-Marktplatz",
|
||||
"com_agents_marketplace_subtitle": "Entdecke und nutze leistungsstarke KI-Agenten, um deine Arbeitsabläufe und Produktivität zu verbessern.",
|
||||
"com_agents_mcp_description_placeholder": "Erkläre in wenigen Worten, was es tut",
|
||||
"com_agents_mcp_icon_size": "Mindestgröße 128 x 128 px",
|
||||
"com_agents_mcp_info": "Füge deinem Agenten MCP-Server hinzu, damit er Aufgaben ausführen und mit externen Diensten interagieren kann",
|
||||
"com_agents_mcp_name_placeholder": "Eigenes Tool",
|
||||
"com_agents_mcp_trust_subtext": "Benutzerdefinierte Konnektoren werden nicht von LibreChat verifiziert.",
|
||||
"com_agents_mcps_disabled": "Du musst zuerst einen Agenten erstellen, bevor du MCPs hinzufügen kannst.",
|
||||
"com_agents_missing_name": "Bitte gib einen Namen ein, bevor du einen Agenten erstellst.",
|
||||
"com_agents_missing_provider_model": "Bitte wählen Sie einen Anbieter und ein Modell aus, bevor Sie einen Agenten erstellen.",
|
||||
"com_agents_name_placeholder": "Optional: Der Name des Agenten",
|
||||
"com_agents_no_access": "Du hast keine Berechtigung, diesen Agenten zu bearbeiten.",
|
||||
"com_agents_no_agent_id_error": "Keine Agenten-ID gefunden. Bitte stelle sicher, dass der Agent zuerst erstellt wurde.",
|
||||
"com_agents_no_more_results": "Du hast das Ende der Ergebnisse erreicht.",
|
||||
"com_agents_not_available": "Agent nicht verfügbar",
|
||||
"com_agents_recommended": "Unsere empfohlenen Agenten",
|
||||
"com_agents_results_for": "Ergebnisse für '{{query}}'",
|
||||
"com_agents_search_aria": "Nach Agenten suchen",
|
||||
"com_agents_search_empty_heading": "Keine Suchergebnisse",
|
||||
"com_agents_search_info": "Wenn diese Funktion aktiviert ist, kann der Agent im Internet nach aktuellen Informationen suchen. Erfordert einen gültigen API-Schlüssel.",
|
||||
"com_agents_search_instructions": "Gib einen Namen oder eine Beschreibung ein, um nach Agenten zu suchen.",
|
||||
"com_agents_search_name": "Agenten nach Namen suchen",
|
||||
"com_agents_search_no_results": "Keine Agenten für \"{{query}}\" gefunden",
|
||||
"com_agents_search_placeholder": "Agenten suchen...",
|
||||
"com_agents_see_more": "Mehr anzeigen",
|
||||
"com_agents_start_chat": "Chat starten",
|
||||
"com_agents_top_picks": "Top-Auswahl",
|
||||
"com_agents_update_error": "Beim Aktualisieren deines Agenten ist ein Fehler aufgetreten.",
|
||||
"com_assistants_action_attempt": "Assistent möchte kommunizieren mit {{0}}",
|
||||
"com_assistants_actions": "Aktionen",
|
||||
"com_assistants_actions_disabled": "Du musst einen Agenten erstellen, bevor du Aktionen hinzufügen kannst.",
|
||||
"com_assistants_actions_info": "Lasse deinen Agenten Informationen abrufen oder Aktionen über APIs ausführen",
|
||||
"com_assistants_add_actions": "Aktionen hinzufügen",
|
||||
"com_assistants_add_mcp_server_tools": "MCP Server-Tools hinzufügen",
|
||||
"com_assistants_add_tools": "Werkzeuge hinzufügen",
|
||||
"com_assistants_allow_sites_you_trust": "Erlaube nur Webseiten, denen du vertraust.",
|
||||
"com_assistants_append_date": "Aktuelles Datum & Uhrzeit anhängen",
|
||||
@@ -377,20 +308,9 @@
|
||||
"com_error_moderation": "Es scheint, dass der eingereichte Inhalt von unserem Moderationssystem als nicht mit unseren Community-Richtlinien vereinbar gekennzeichnet wurde. Wir können mit diesem spezifischen Thema nicht fortfahren. Wenn Sie andere Fragen oder Themen haben, die Sie erkunden möchten, bearbeiten Sie bitte Ihre Nachricht oder erstellen Sie eine neue Konversation.",
|
||||
"com_error_no_base_url": "Keine Basis-URL gefunden. Bitte gebe eine ein und versuche es erneut.",
|
||||
"com_error_no_user_key": "Kein API-Key gefunden. Bitte gebe einen API-Key ein und versuche es erneut.",
|
||||
"com_file_pages": "Seiten: {{pages}}",
|
||||
"com_file_source": "Datei",
|
||||
"com_file_unknown": "Unbekannte Datei",
|
||||
"com_files_download_failed": "{{0}} Dateien fehlgeschlagen",
|
||||
"com_files_download_percent_complete": "{{0}}% abgeschlossen",
|
||||
"com_files_download_progress": "{{0}} von {{1}} Dateien",
|
||||
"com_files_downloading": "Dateien werden heruntergeladen",
|
||||
"com_files_filter": "Dateien filtern...",
|
||||
"com_files_no_results": "Keine Ergebnisse.",
|
||||
"com_files_number_selected": "{{0}} von {{1}} Datei(en) ausgewählt",
|
||||
"com_files_preparing_download": "Download wird vorbereitet...",
|
||||
"com_files_sharepoint_picker_title": "Dateien auswählen",
|
||||
"com_files_upload_local_machine": "Vom lokalen Computer",
|
||||
"com_files_upload_sharepoint": "Von SharePoint",
|
||||
"com_generated_files": "Generierte Dateien:",
|
||||
"com_hide_examples": "Beispiele ausblenden",
|
||||
"com_info_heic_converting": "HEIC-Bild wird in JPEG konventiert...",
|
||||
@@ -494,7 +414,6 @@
|
||||
"com_nav_lang_arabic": "العربية",
|
||||
"com_nav_lang_armenian": "Armenisch",
|
||||
"com_nav_lang_auto": "Automatisch erkennen",
|
||||
"com_nav_lang_bosnian": "Bosnisch",
|
||||
"com_nav_lang_brazilian_portuguese": "Português Brasileiro",
|
||||
"com_nav_lang_catalan": "Katalonisch",
|
||||
"com_nav_lang_chinese": "中文",
|
||||
@@ -514,12 +433,10 @@
|
||||
"com_nav_lang_japanese": "日本語",
|
||||
"com_nav_lang_korean": "한국어",
|
||||
"com_nav_lang_latvian": "Lettisch",
|
||||
"com_nav_lang_norwegian_bokmal": "Norwegisch Bokmål",
|
||||
"com_nav_lang_persian": "Persisch",
|
||||
"com_nav_lang_polish": "Polski",
|
||||
"com_nav_lang_portuguese": "Português",
|
||||
"com_nav_lang_russian": "Русский",
|
||||
"com_nav_lang_slovenian": "Slowenisch",
|
||||
"com_nav_lang_spanish": "Español",
|
||||
"com_nav_lang_swedish": "Svenska",
|
||||
"com_nav_lang_thai": "ไทย",
|
||||
@@ -580,7 +497,6 @@
|
||||
"com_nav_tool_dialog": "Assistenten-Werkzeuge",
|
||||
"com_nav_tool_dialog_agents": "Agent-Tools",
|
||||
"com_nav_tool_dialog_description": "Agent muss gespeichert werden, um Werkzeugauswahlen zu speichern.",
|
||||
"com_nav_tool_dialog_mcp_server_tools": "MCP Server-Tools",
|
||||
"com_nav_tool_remove": "Entfernen",
|
||||
"com_nav_tool_search": "Werkzeuge suchen",
|
||||
"com_nav_user": "BENUTZER",
|
||||
@@ -598,21 +514,9 @@
|
||||
"com_sidepanel_manage_files": "Dateien verwalten",
|
||||
"com_sidepanel_mcp_no_servers_with_vars": "Keine MCP-Server mit konfigurierbaren Variablen.",
|
||||
"com_sidepanel_parameters": "KI-Einstellungen",
|
||||
"com_sources_agent_file": "Quelldokument",
|
||||
"com_sources_agent_files": "Agenten-Dateien",
|
||||
"com_sources_download_aria_label": "Download {{filename}} {{status}}",
|
||||
"com_sources_download_failed": "Download fehlgeschlagen",
|
||||
"com_sources_download_local_unavailable": "Download nicht möglich: Datei ist nicht gespeichert",
|
||||
"com_sources_downloading_status": "(wird heruntergeladen...)",
|
||||
"com_sources_error_fallback": "Quellen konnten nicht geladen werden",
|
||||
"com_sources_image_alt": "Suchergebnis Bild\n",
|
||||
"com_sources_more_files": "+{{count}} Dateien",
|
||||
"com_sources_more_sources": "+{{count}} Quellen\n",
|
||||
"com_sources_pages": "Seiten",
|
||||
"com_sources_region_label": "Suchergebnisse und Quellen",
|
||||
"com_sources_reload_page": "Seite neu laden",
|
||||
"com_sources_tab_all": "Alles",
|
||||
"com_sources_tab_files": "Dateien",
|
||||
"com_sources_tab_images": "Bilder",
|
||||
"com_sources_tab_news": "Nachrichten",
|
||||
"com_sources_title": "Quellen\n",
|
||||
@@ -627,7 +531,7 @@
|
||||
"com_ui_2fa_setup": "2FA einrichten",
|
||||
"com_ui_2fa_verified": "Die Zwei-Faktor-Authentifizierung wurde erfolgreich verifiziert.",
|
||||
"com_ui_accept": "Ich akzeptiere",
|
||||
"com_ui_action_button": "Aktions Button",
|
||||
"com_ui_action_button": "Aktionstaste",
|
||||
"com_ui_active": "Aktiv",
|
||||
"com_ui_add": "Hinzufügen",
|
||||
"com_ui_add_mcp": "MCP hinzufügen",
|
||||
@@ -641,14 +545,6 @@
|
||||
"com_ui_advanced": "Erweitert",
|
||||
"com_ui_advanced_settings": "Erweiterte Einstellungen",
|
||||
"com_ui_agent": "Agent",
|
||||
"com_ui_agent_category_aftersales": "Kundendienst",
|
||||
"com_ui_agent_category_finance": "Finanzen",
|
||||
"com_ui_agent_category_general": "Allgemein",
|
||||
"com_ui_agent_category_hr": "Personalwesen",
|
||||
"com_ui_agent_category_it": "IT",
|
||||
"com_ui_agent_category_rd": "F&E",
|
||||
"com_ui_agent_category_sales": "Vertrieb",
|
||||
"com_ui_agent_category_selector_aria": "Agenten-Kategorieauswahl",
|
||||
"com_ui_agent_chain": "Agent-Kette",
|
||||
"com_ui_agent_chain_info": "Ermöglicht das Erstellen von Agenten-Sequenzen. Jeder Agent kann auf die Ausgaben vorheriger Agenten in der Kette zugreifen. Basiert auf der \"Mixture-of-Agents\"-Architektur, bei der Agenten vorherige Ausgaben als zusätzliche Informationen verwenden.",
|
||||
"com_ui_agent_chain_max": "Du hast die maximale Anzahl von {{0}} Agenten erreicht.",
|
||||
@@ -656,10 +552,8 @@
|
||||
"com_ui_agent_deleted": "Agent erfolgreich gelöscht",
|
||||
"com_ui_agent_duplicate_error": "Beim Duplizieren des Assistenten ist ein Fehler aufgetreten",
|
||||
"com_ui_agent_duplicated": "Agent wurde erfolgreich dupliziert",
|
||||
"com_ui_agent_name_is_required": "Ein Agentenname ist erforderlich.",
|
||||
"com_ui_agent_recursion_limit": "Maximale Agenten-Schritte",
|
||||
"com_ui_agent_recursion_limit_info": "Begrenzt, wie viele Schritte der Agent in einem Durchlauf ausführen kann, bevor er eine endgültige Antwort gibt. Der Standardwert ist 25 Schritte. Ein Schritt ist entweder eine KI-API-Anfrage oder eine Werkzeugnutzungsrunde. Eine einfache Werkzeuginteraktion umfasst beispielsweise 3 Schritte: die ursprüngliche Anfrage, die Werkzeugnutzung und die Folgeanfrage.",
|
||||
"com_ui_agent_url_copied": "Agenten-URL in die Zwischenablage kopiert",
|
||||
"com_ui_agent_var": "{{0}} Agent",
|
||||
"com_ui_agent_version": "Version",
|
||||
"com_ui_agent_version_active": "Aktive Version\n",
|
||||
@@ -676,7 +570,6 @@
|
||||
"com_ui_agent_version_unknown_date": "Unbekanntes Datum\n",
|
||||
"com_ui_agents": "Agenten",
|
||||
"com_ui_agents_allow_create": "Erlaube Agenten zu erstellen",
|
||||
"com_ui_agents_allow_share": "Teilen von Agenten erlauben",
|
||||
"com_ui_agents_allow_use": "Verwendung von Agenten erlauben",
|
||||
"com_ui_all": "alle",
|
||||
"com_ui_all_proper": "Alle",
|
||||
@@ -697,7 +590,6 @@
|
||||
"com_ui_assistant_deleted": "Assistent erfolgreich gelöscht",
|
||||
"com_ui_assistants": "Assistenten",
|
||||
"com_ui_assistants_output": "Assistenten-Ausgabe",
|
||||
"com_ui_at_least_one_owner_required": "Mindestens ein Besitzer ist erforderlich.",
|
||||
"com_ui_attach_error": "Datei kann nicht angehängt werden. Erstelle oder wähle einen Chat oder versuche, die Seite zu aktualisieren.",
|
||||
"com_ui_attach_error_openai": "Assistentendateien können nicht an andere Endpunkte angehängt werden",
|
||||
"com_ui_attach_error_size": "Dateigrößenlimit für Endpunkt überschritten:",
|
||||
@@ -714,16 +606,12 @@
|
||||
"com_ui_available_tools": "Verfügbare Tools",
|
||||
"com_ui_avatar": "Avatar",
|
||||
"com_ui_azure": "Azure",
|
||||
"com_ui_azure_ad": "Entra ID",
|
||||
"com_ui_back": "Zurück",
|
||||
"com_ui_back_to_chat": "Zurück zum Chat",
|
||||
"com_ui_back_to_prompts": "Zurück zu den Prompts",
|
||||
"com_ui_backup_code_number": "Code #{{number}}",
|
||||
"com_ui_backup_codes": "Backup-Codes",
|
||||
"com_ui_backup_codes_regenerate_error": "Beim Neuerstellen der Backup-Codes ist ein Fehler aufgetreten.",
|
||||
"com_ui_backup_codes_regenerated": "Backup-Codes wurden erfolgreich neu erstellt.",
|
||||
"com_ui_backup_codes_security_info": "Aus Sicherheitsgründen werden Backup-Codes nur einmalig bei der Erstellung angezeigt. Bitte speichere sie an einem sicheren Ort.",
|
||||
"com_ui_backup_codes_status": "Backup Codes Status",
|
||||
"com_ui_basic": "Basic",
|
||||
"com_ui_basic_auth_header": "Basic-Authentifizierungsheader",
|
||||
"com_ui_bearer": "Bearer",
|
||||
@@ -742,7 +630,6 @@
|
||||
"com_ui_bookmarks_edit": "Lesezeichen bearbeiten",
|
||||
"com_ui_bookmarks_filter": "Lesezeichen filtern...",
|
||||
"com_ui_bookmarks_new": "Neues Lesezeichen",
|
||||
"com_ui_bookmarks_tag_exists": "Ein Lesezeichen mit diesem Titel existiert bereits",
|
||||
"com_ui_bookmarks_title": "Titel",
|
||||
"com_ui_bookmarks_update_error": "Beim Aktualisieren des Lesezeichens ist ein Fehler aufgetreten",
|
||||
"com_ui_bookmarks_update_success": "Lesezeichen erfolgreich aktualisiert",
|
||||
@@ -767,7 +654,6 @@
|
||||
"com_ui_complete_setup": "Einrichtung abschließen",
|
||||
"com_ui_concise": "Prägnant",
|
||||
"com_ui_configure_mcp_variables_for": "Konfiguriere Variablen für {{0}}",
|
||||
"com_ui_confirm": "Bestätigen",
|
||||
"com_ui_confirm_action": "Aktion bestätigen",
|
||||
"com_ui_confirm_admin_use_change": "Wenn du diese Einstellung änderst, wird der Zugriff für Administratoren, einschließlich dir selbst, gesperrt. Bist du sicher, dass du fortfahren möchtest?",
|
||||
"com_ui_confirm_change": "Änderung bestätigen",
|
||||
@@ -782,7 +668,6 @@
|
||||
"com_ui_copy_code": "Code kopieren",
|
||||
"com_ui_copy_link": "Link kopieren",
|
||||
"com_ui_copy_to_clipboard": "In die Zwischenablage kopieren",
|
||||
"com_ui_copy_url_to_clipboard": "URL in die Zwischenablage kopieren",
|
||||
"com_ui_create": "Erstellen",
|
||||
"com_ui_create_link": "Link erstellen",
|
||||
"com_ui_create_memory": "Erinnerung erstellen",
|
||||
@@ -832,8 +717,6 @@
|
||||
"com_ui_delete_success": "Erfolgreich gelöscht",
|
||||
"com_ui_delete_tool": "Werkzeug löschen",
|
||||
"com_ui_delete_tool_confirm": "Bist du sicher, dass du dieses Werkzeug löschen möchtest?",
|
||||
"com_ui_delete_tool_error": "Fehler beim Löschen des Tools: {{error}}",
|
||||
"com_ui_delete_tool_success": "Tool erfolgreich gelöscht",
|
||||
"com_ui_deleted": "Gelöscht",
|
||||
"com_ui_deleting_file": "Lösche Datei...",
|
||||
"com_ui_descending": "Absteigend",
|
||||
@@ -870,7 +753,6 @@
|
||||
"com_ui_error_connection": "Verbindungsfehler zum Server. Versuche, die Seite zu aktualisieren.",
|
||||
"com_ui_error_save_admin_settings": "Beim Speichern Ihrer Admin-Einstellungen ist ein Fehler aufgetreten.",
|
||||
"com_ui_error_updating_preferences": "Fehler beim Aktualisieren der Einstellungen",
|
||||
"com_ui_everyone_permission_level": "Berechtigungsstufe für Alle",
|
||||
"com_ui_examples": "Beispiele",
|
||||
"com_ui_expand_chat": "Chat erweitern",
|
||||
"com_ui_export_convo_modal": "Konversation exportieren",
|
||||
@@ -890,11 +772,8 @@
|
||||
"com_ui_feedback_tag_not_matched": "Entspricht nicht der Anfrage",
|
||||
"com_ui_feedback_tag_other": "Anderer Fehler",
|
||||
"com_ui_feedback_tag_unjustified_refusal": "Mit anderer Begründung abgelehnt",
|
||||
"com_ui_field_max_length": "{{field}} darf maximal {{length}} Zeichen haben",
|
||||
"com_ui_field_required": "Dieses Feld ist erforderlich",
|
||||
"com_ui_file_size": "Dateigröße",
|
||||
"com_ui_file_token_limit": "Datei-Token-Limit",
|
||||
"com_ui_file_token_limit_desc": "Lege ein maximales Token-Limit für die Dateiverarbeitung fest, um Kosten und Ressourcenverbrauch zu steuern.",
|
||||
"com_ui_files": "Dateien\n",
|
||||
"com_ui_filter_prompts": "Prompts filtern",
|
||||
"com_ui_filter_prompts_name": "Prompts nach Namen filtern",
|
||||
@@ -936,7 +815,6 @@
|
||||
"com_ui_good_afternoon": "Guten Nachmittag",
|
||||
"com_ui_good_evening": "Guten Abend",
|
||||
"com_ui_good_morning": "Guten Morgen",
|
||||
"com_ui_group": "Gruppe",
|
||||
"com_ui_happy_birthday": "Es ist mein 1. Geburtstag!",
|
||||
"com_ui_hide_image_details": "Details zum Bild ausblenden",
|
||||
"com_ui_hide_password": "Passwort verbergen",
|
||||
@@ -955,7 +833,6 @@
|
||||
"com_ui_import_conversation_info": "Konversationen aus einer JSON-Datei importieren",
|
||||
"com_ui_import_conversation_success": "Konversationen erfolgreich importiert",
|
||||
"com_ui_include_shadcnui": "Anweisungen für shadcn/ui-Komponenten einschließen",
|
||||
"com_ui_initializing": "Initialisiere...",
|
||||
"com_ui_input": "Eingabe",
|
||||
"com_ui_instructions": "Anweisungen",
|
||||
"com_ui_key": "Schlüssel",
|
||||
@@ -971,12 +848,8 @@
|
||||
"com_ui_logo": "{{0}} Logo",
|
||||
"com_ui_low": "Niedrig",
|
||||
"com_ui_manage": "Verwalten",
|
||||
"com_ui_marketplace": "Marktplatz",
|
||||
"com_ui_marketplace_allow_use": "Nutzung des Marktplatzes erlauben",
|
||||
"com_ui_max_tags": "Die maximale Anzahl ist {{0}}, es werden die neuesten Werte verwendet.",
|
||||
"com_ui_mcp_authenticated_success": "MCP-Server „{{0}}“ erfolgreich authentifiziert.",
|
||||
"com_ui_mcp_configure_server": "Konfiguriere {{0}}",
|
||||
"com_ui_mcp_configure_server_description": "Konfiguriere benutzerdefinierte Variablen für {{0}}",
|
||||
"com_ui_mcp_enter_var": "Geben Sie einen Wert für {{0}} ein",
|
||||
"com_ui_mcp_init_failed": "Initialisierung des MCP-Servers fehlgeschlagen.",
|
||||
"com_ui_mcp_initialize": "Initialisieren",
|
||||
@@ -1022,14 +895,10 @@
|
||||
"com_ui_next": "Weiter",
|
||||
"com_ui_no": "Nein",
|
||||
"com_ui_no_bookmarks": "Du hast noch keine Lesezeichen. Klicke auf einen Chat und füge ein neues hinzu",
|
||||
"com_ui_no_categories": "Keine Kategorien verfügbar",
|
||||
"com_ui_no_category": "Keine Kategorie",
|
||||
"com_ui_no_changes": "Es wurden keine Änderungen vorgenommen",
|
||||
"com_ui_no_data": "Leer – etwas fehlt noch",
|
||||
"com_ui_no_individual_access": "Keine einzelnen Benutzer oder Gruppen haben Zugriff auf diesen Agenten.",
|
||||
"com_ui_no_personalization_available": "Derzeit sind keine Personalisierungsoptionen verfügbar.",
|
||||
"com_ui_no_read_access": "Du hast keine Berechtigung, Erinnerungen anzuzeigen.",
|
||||
"com_ui_no_results_found": "Keine Ergebnisse gefunden",
|
||||
"com_ui_no_terms_content": "Keine Inhalte der Allgemeinen Geschäftsbedingungen zum Anzeigen",
|
||||
"com_ui_no_valid_items": "Leer - Text fehlt noch",
|
||||
"com_ui_none": "Keine",
|
||||
@@ -1052,14 +921,6 @@
|
||||
"com_ui_openai": "OpenAI",
|
||||
"com_ui_optional": "(Optional)",
|
||||
"com_ui_page": "Seite",
|
||||
"com_ui_people": "Personen",
|
||||
"com_ui_people_picker": "Personenauswahl",
|
||||
"com_ui_people_picker_allow_view_groups": "Anzeigen von Gruppen erlauben",
|
||||
"com_ui_people_picker_allow_view_roles": "Anzeigen von Rollen erlauben",
|
||||
"com_ui_people_picker_allow_view_users": "Anzeigen von Benutzern erlauben",
|
||||
"com_ui_permissions_failed_load": "Berechtigungen konnten nicht geladen werden. Bitte versuche es erneut.",
|
||||
"com_ui_permissions_failed_update": "Berechtigungen konnten nicht aktualisiert werden. Bitte versuche es erneut.",
|
||||
"com_ui_permissions_updated_success": "Berechtigungen wurden erfolgreich aktualisiert",
|
||||
"com_ui_preferences_updated": "Einstellungen erfolgreich aktualisiert",
|
||||
"com_ui_prev": "Zurück",
|
||||
"com_ui_preview": "Vorschau",
|
||||
@@ -1074,7 +935,6 @@
|
||||
"com_ui_prompt_update_error": "Beim Aktualisieren des Prompts ist ein Fehler aufgetreten",
|
||||
"com_ui_prompts": "Prompts",
|
||||
"com_ui_prompts_allow_create": "Erstellung von Prompts erlauben",
|
||||
"com_ui_prompts_allow_share": "Teilen von Prompts erlauben",
|
||||
"com_ui_prompts_allow_use": "Verwendung von Prompts erlauben",
|
||||
"com_ui_provider": "Anbieter",
|
||||
"com_ui_quality": "Qualität",
|
||||
@@ -1082,14 +942,12 @@
|
||||
"com_ui_redirecting_to_provider": "Weiterleitung zu {{0}}, einen Moment bitte...",
|
||||
"com_ui_reference_saved_memories": "Gespeicherte Erinnerungen verwenden",
|
||||
"com_ui_reference_saved_memories_description": "Erlaube der KI bei den Antworten auf deine gespeicherten Erinnerungen zuzugreifen und sie zu verwenden.",
|
||||
"com_ui_refresh": "Aktualisieren",
|
||||
"com_ui_refresh_link": "Link aktualisieren",
|
||||
"com_ui_regenerate": "Neu generieren",
|
||||
"com_ui_regenerate_backup": "Backup-Codes neu generieren",
|
||||
"com_ui_regenerating": "Generiere neu ...",
|
||||
"com_ui_region": "Region",
|
||||
"com_ui_reinitialize": "Neu initialisieren",
|
||||
"com_ui_remove_user": "{{0}} entfernen",
|
||||
"com_ui_rename": "Umbenennen",
|
||||
"com_ui_rename_conversation": "Chat umbenennen",
|
||||
"com_ui_rename_failed": "Chat konnte nicht umbenannt werden.",
|
||||
@@ -1097,7 +955,6 @@
|
||||
"com_ui_requires_auth": "Authentifizierung erforderlich",
|
||||
"com_ui_reset_var": "{{0}} zurücksetzen",
|
||||
"com_ui_reset_zoom": "Zoom zurücksetzen",
|
||||
"com_ui_resource": "Ressource",
|
||||
"com_ui_result": "Ergebnis",
|
||||
"com_ui_revoke": "Widerrufen",
|
||||
"com_ui_revoke_info": "Benutzer-API-Keys widerrufen",
|
||||
@@ -1105,42 +962,24 @@
|
||||
"com_ui_revoke_key_endpoint": "API-Schlüssel für {{0}} widerrufen",
|
||||
"com_ui_revoke_keys": "Schlüssel widerrufen",
|
||||
"com_ui_revoke_keys_confirm": "Bist du sicher, dass du alle Schlüssel widerrufen möchtest?",
|
||||
"com_ui_role": "Rolle",
|
||||
"com_ui_role_editor": "Bearbeiter",
|
||||
"com_ui_role_editor_desc": "Kann den Agenten ansehen und bearbeiten.",
|
||||
"com_ui_role_manager": "Verwalter",
|
||||
"com_ui_role_manager_desc": "Kann den Agenten ansehen, bearbeiten und löschen.",
|
||||
"com_ui_role_owner": "Besitzer",
|
||||
"com_ui_role_owner_desc": "Hat volle Kontrolle über den Agenten inklusive Teilen",
|
||||
"com_ui_role_select": "Rolle auswählen",
|
||||
"com_ui_role_viewer": "Betrachter",
|
||||
"com_ui_role_viewer_desc": "Kann den Agenten ansehen und nutzen aber nicht bearbeiten",
|
||||
"com_ui_roleplay": "Rollenspiel",
|
||||
"com_ui_run_code": "Code ausführen",
|
||||
"com_ui_run_code_error": "Bei der Ausführung des Codes ist ein Fehler aufgetreten",
|
||||
"com_ui_save": "Speichern",
|
||||
"com_ui_save_badge_changes": "Änderungen an Badges speichern?",
|
||||
"com_ui_save_changes": "Änderungen speichern",
|
||||
"com_ui_save_submit": "Speichern & Absenden",
|
||||
"com_ui_saved": "Gespeichert!",
|
||||
"com_ui_saving": "Sicherung läuft...",
|
||||
"com_ui_schema": "Schema",
|
||||
"com_ui_scope": "Umfang",
|
||||
"com_ui_search": "Suche",
|
||||
"com_ui_search_above_to_add": "Suche oben, um Benutzer oder Gruppen hinzuzufügen",
|
||||
"com_ui_search_above_to_add_all": "Suche oben, um Benutzer, Gruppen oder Rollen hinzuzufügen",
|
||||
"com_ui_search_above_to_add_people": "Suche oben, um Personen hinzuzufügen",
|
||||
"com_ui_search_agent_category": "Kategorien suchen...",
|
||||
"com_ui_search_default_placeholder": "Suche nach Name oder E-Mail (min. 2 Zeichen)",
|
||||
"com_ui_search_people_placeholder": "Suche nach Personen oder Gruppen per Name oder E-Mail",
|
||||
"com_ui_seconds": "Sekunden",
|
||||
"com_ui_secret_key": "Geheimschlüssel",
|
||||
"com_ui_select": "Auswählen",
|
||||
"com_ui_select_all": "Alle auswählen",
|
||||
"com_ui_select_file": "Datei auswählen",
|
||||
"com_ui_select_model": "Ein KI-Modell auswählen",
|
||||
"com_ui_select_options": "Optionen auswählen",
|
||||
"com_ui_select_or_create_prompt": "Wähle oder erstelle einen Prompt",
|
||||
"com_ui_select_provider": "Wähle einen Anbieter",
|
||||
"com_ui_select_provider_first": "Wähle zuerst einen Anbieter",
|
||||
"com_ui_select_region": "Wähle eine Region",
|
||||
@@ -1153,8 +992,6 @@
|
||||
"com_ui_share_create_message": "Ihr Name und alle Nachrichten, die du nach dem Teilen hinzufügst, bleiben privat.",
|
||||
"com_ui_share_delete_error": "Beim Löschen des geteilten Links ist ein Fehler aufgetreten",
|
||||
"com_ui_share_error": "Beim Teilen des Chat-Links ist ein Fehler aufgetreten",
|
||||
"com_ui_share_everyone": "Mit allen teilen",
|
||||
"com_ui_share_everyone_description_var": "{{resource}} wird für alle verfügbar sein. Bitte stelle sicher, dass {{resource}} wirklich für alle freigegeben werden soll. Sei vorsichtig mit deinen Daten.",
|
||||
"com_ui_share_link_to_chat": "Link zum Chat teilen",
|
||||
"com_ui_share_update_message": "Ihr Name, benutzerdefinierte Anweisungen und alle Nachrichten, die du nach dem Teilen hinzufügen, bleiben privat.",
|
||||
"com_ui_share_var": "{{0}} teilen",
|
||||
@@ -1181,13 +1018,6 @@
|
||||
"com_ui_stop": "Stopp",
|
||||
"com_ui_storage": "Speicher",
|
||||
"com_ui_submit": "Absenden",
|
||||
"com_ui_support_contact": "Ansprechpartner-Kontakt",
|
||||
"com_ui_support_contact_email": "E-Mail",
|
||||
"com_ui_support_contact_email_invalid": "Bitte gib eine gültige E-Mail-Adresse ein",
|
||||
"com_ui_support_contact_email_placeholder": "support@beispiel.com",
|
||||
"com_ui_support_contact_name": "Name",
|
||||
"com_ui_support_contact_name_min_length": "Der Name muss mindestens {{minLength}} Zeichen lang sein",
|
||||
"com_ui_support_contact_name_placeholder": "Name des Ansprechpartner-Kontakts",
|
||||
"com_ui_teach_or_explain": "Lernen",
|
||||
"com_ui_temporary": "Privater Chat",
|
||||
"com_ui_terms_and_conditions": "Allgemeine Geschäftsbedingungen",
|
||||
@@ -1204,11 +1034,8 @@
|
||||
"com_ui_tools": "Werkzeuge",
|
||||
"com_ui_travel": "Reisen",
|
||||
"com_ui_trust_app": "Ich vertraue dieser Anwendung",
|
||||
"com_ui_try_adjusting_search": "Versuche, deine Suchbegriffe anzupassen",
|
||||
"com_ui_ui_resources": "UI-Ressourcen",
|
||||
"com_ui_unarchive": "Aus Archiv holen",
|
||||
"com_ui_unarchive_error": "Konversation konnte nicht aus dem Archiv geholt werden",
|
||||
"com_ui_unavailable": "Nicht verfügbar",
|
||||
"com_ui_unknown": "Unbekannt",
|
||||
"com_ui_unset": "Aufheben",
|
||||
"com_ui_untitled": "Unbenannt",
|
||||
@@ -1216,7 +1043,6 @@
|
||||
"com_ui_update_mcp_error": "Beim Erstellen oder Aktualisieren des MCP ist ein Fehler aufgetreten.",
|
||||
"com_ui_update_mcp_success": "MCP erfolgreich erstellt oder aktualisiert",
|
||||
"com_ui_upload": "Hochladen",
|
||||
"com_ui_upload_agent_avatar": "Agenten-Avatar erfolgreich aktualisiert",
|
||||
"com_ui_upload_code_files": "Hochladen für Code-Interpreter",
|
||||
"com_ui_upload_delay": "Das Hochladen von \"{{0}}\" dauert etwas länger. Bitte warte, während die Datei für den Abruf indexiert wird.",
|
||||
"com_ui_upload_error": "Beim Hochladen Ihrer Datei ist ein Fehler aufgetreten",
|
||||
@@ -1236,8 +1062,6 @@
|
||||
"com_ui_use_memory": "Erinnerung nutzen",
|
||||
"com_ui_use_micrphone": "Mikrofon verwenden",
|
||||
"com_ui_used": "Verwendet",
|
||||
"com_ui_user": "Benutzer",
|
||||
"com_ui_user_group_permissions": "Benutzer- & Gruppenberechtigungen",
|
||||
"com_ui_value": "Wert",
|
||||
"com_ui_variables": "Variablen",
|
||||
"com_ui_variables_info": "Verwende doppelte geschweifte Klammern in deinem Text wie z. B. `{{example variable}}`, um Variablen zu erstellen, die du später beim Ausführen des Prompts füllen kannst. Schreibe in die geschweiften Klammern, was die Platzhalter-Nachricht sein soll.",
|
||||
|
||||
@@ -103,7 +103,6 @@
|
||||
"com_assistants_actions_disabled": "You need to create an assistant before adding actions.",
|
||||
"com_assistants_actions_info": "Let your Assistant retrieve information or take actions via API's",
|
||||
"com_assistants_add_actions": "Add Actions",
|
||||
"com_assistants_add_mcp_server_tools": "Add MCP Server Tools",
|
||||
"com_assistants_add_tools": "Add Tools",
|
||||
"com_assistants_allow_sites_you_trust": "Only allow sites you trust.",
|
||||
"com_assistants_append_date": "Append Current Date & Time",
|
||||
@@ -581,7 +580,6 @@
|
||||
"com_nav_tool_dialog": "Assistant Tools",
|
||||
"com_nav_tool_dialog_agents": "Agent Tools",
|
||||
"com_nav_tool_dialog_description": "Assistant must be saved to persist tool selections.",
|
||||
"com_nav_tool_dialog_mcp_server_tools": "MCP Server Tools",
|
||||
"com_nav_tool_remove": "Remove",
|
||||
"com_nav_tool_search": "Search tools",
|
||||
"com_nav_user": "USER",
|
||||
@@ -653,22 +651,6 @@
|
||||
"com_ui_agent_chain": "Agent Chain (Mixture-of-Agents)",
|
||||
"com_ui_agent_chain_info": "Enables creating sequences of agents. Each agent can access outputs from previous agents in the chain. Based on the \"Mixture-of-Agents\" architecture where agents use previous outputs as auxiliary information.",
|
||||
"com_ui_agent_chain_max": "You have reached the maximum of {{0}} agents.",
|
||||
"com_ui_agent_handoffs": "Agent Handoffs",
|
||||
"com_ui_agent_handoff_add": "Add handoff agent",
|
||||
"com_ui_agent_handoff_description": "Handoff description",
|
||||
"com_ui_agent_handoff_description_placeholder": "e.g., Transfer to data analyst for statistical analysis",
|
||||
"com_ui_agent_handoff_info": "Configure agents that this agent can transfer conversations to when specific expertise is needed.",
|
||||
"com_ui_agent_handoff_info_2": "Each handoff creates a transfer tool that enables seamless routing to specialist agents with context.",
|
||||
"com_ui_agent_handoff_max": "Maximum {{0}} handoff agents reached.",
|
||||
"com_ui_agent_handoff_prompt": "Passthrough content",
|
||||
"com_ui_agent_handoff_prompt_placeholder": "Tell this agent what content to generate and pass to the handoff agent. You need to add something here to enable this feature",
|
||||
"com_ui_agent_handoff_prompt_key": "Content parameter name (default: 'instructions')",
|
||||
"com_ui_agent_handoff_prompt_key_placeholder": "Label the content passed (default: 'instructions')",
|
||||
"com_ui_transferring_to_agent": "Transferring to {{0}}",
|
||||
"com_ui_transferred_to_agent": "Transferred to {{0}}",
|
||||
"com_ui_transferred_to": "Transferred to",
|
||||
"com_ui_handoff_instructions": "Handoff instructions",
|
||||
"com_ui_beta": "Beta",
|
||||
"com_ui_agent_delete_error": "There was an error deleting the agent",
|
||||
"com_ui_agent_deleted": "Successfully deleted agent",
|
||||
"com_ui_agent_duplicate_error": "There was an error duplicating the agent",
|
||||
@@ -784,7 +766,6 @@
|
||||
"com_ui_complete_setup": "Complete Setup",
|
||||
"com_ui_concise": "Concise",
|
||||
"com_ui_configure_mcp_variables_for": "Configure Variables for {{0}}",
|
||||
"com_ui_confirm": "Confirm",
|
||||
"com_ui_confirm_action": "Confirm Action",
|
||||
"com_ui_confirm_admin_use_change": "Changing this setting will block access for admins, including yourself. Are you sure you want to proceed?",
|
||||
"com_ui_confirm_change": "Confirm Change",
|
||||
@@ -849,8 +830,6 @@
|
||||
"com_ui_delete_success": "Successfully deleted",
|
||||
"com_ui_delete_tool": "Delete Tool",
|
||||
"com_ui_delete_tool_confirm": "Are you sure you want to delete this tool?",
|
||||
"com_ui_delete_tool_error": "Error while deleting the tool: {{error}}",
|
||||
"com_ui_delete_tool_success": "Tool deleted successfully",
|
||||
"com_ui_deleted": "Deleted",
|
||||
"com_ui_deleting_file": "Deleting file...",
|
||||
"com_ui_descending": "Desc",
|
||||
@@ -972,7 +951,6 @@
|
||||
"com_ui_import_conversation_info": "Import conversations from a JSON file",
|
||||
"com_ui_import_conversation_success": "Conversations imported successfully",
|
||||
"com_ui_include_shadcnui": "Include shadcn/ui components instructions",
|
||||
"com_ui_initializing": "Initializing...",
|
||||
"com_ui_input": "Input",
|
||||
"com_ui_instructions": "Instructions",
|
||||
"com_ui_key": "Key",
|
||||
@@ -992,8 +970,6 @@
|
||||
"com_ui_marketplace_allow_use": "Allow using Marketplace",
|
||||
"com_ui_max_tags": "Maximum number allowed is {{0}}, using latest values.",
|
||||
"com_ui_mcp_authenticated_success": "MCP server '{{0}}' authenticated successfully",
|
||||
"com_ui_mcp_configure_server": "Configure {{0}}",
|
||||
"com_ui_mcp_configure_server_description": "Configure custom variables for {{0}}",
|
||||
"com_ui_mcp_enter_var": "Enter value for {{0}}",
|
||||
"com_ui_mcp_init_failed": "Failed to initialize MCP server",
|
||||
"com_ui_mcp_initialize": "Initialize",
|
||||
@@ -1222,10 +1198,8 @@
|
||||
"com_ui_travel": "Travel",
|
||||
"com_ui_trust_app": "I trust this application",
|
||||
"com_ui_try_adjusting_search": "Try adjusting your search terms",
|
||||
"com_ui_ui_resources": "UI Resources",
|
||||
"com_ui_unarchive": "Unarchive",
|
||||
"com_ui_unarchive_error": "Failed to unarchive conversation",
|
||||
"com_ui_unavailable": "Unavailable",
|
||||
"com_ui_unknown": "Unknown",
|
||||
"com_ui_unset": "Unset",
|
||||
"com_ui_untitled": "Untitled",
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
"com_agents_file_search_disabled": "Es necesario crear el Agente antes de subir archivos para la Búsqueda de Archivos.",
|
||||
"com_agents_file_search_info": "Cuando está habilitado, se informará al agente sobre los nombres exactos de los archivos listados a continuación, permitiéndole recuperar el contexto relevante de estos archivos.",
|
||||
"com_agents_instructions_placeholder": "Las instrucciones del sistema que utiliza el agente",
|
||||
"com_agents_link_copied": "Enlace copiado",
|
||||
"com_agents_link_copy_failed": "No se pudo copiar el enlace",
|
||||
"com_agents_loading": "Cargando...",
|
||||
"com_agents_marketplace_subtitle": "Descubre y usa el poder de los agentes de inteligencia artificial para mejorar tu productividad y tus flujos de trabajo",
|
||||
"com_agents_mcp_description_placeholder": "Explica que hace en pocas palabras",
|
||||
"com_agents_mcp_icon_size": "Tamaño minimo 128 x128 px",
|
||||
"com_agents_mcp_trust_subtext": "LibreChat no verifica los conectores personalizados",
|
||||
|
||||
@@ -4,97 +4,31 @@
|
||||
"com_a11y_ai_composing": "הבינה המלאכותית (AI) עדיין יוצרת",
|
||||
"com_a11y_end": "הבינה המלאכותית (AI) סיימה להשיב.",
|
||||
"com_a11y_start": "הבינה המלאכותית (AI) מתחילה להשיב.",
|
||||
"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_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": "נקה חיפוש",
|
||||
"com_agents_code_interpreter": "כאשר מופעל, מאפשר לסוכן שלך למנף את ה-API של מפענח הקוד כדי להריץ את הקוד שנוצר, כולל עיבוד קבצים, בצורה מאובטחת. דורש מפתח API חוקי.",
|
||||
"com_agents_code_interpreter_title": "מפענח קוד API",
|
||||
"com_agents_contact": "יצירת קשר",
|
||||
"com_agents_copy_link": "העתק קישור",
|
||||
"com_agents_create_error": "אירעה שגיאה ביצירת הסוכן שלך.",
|
||||
"com_agents_created_by": "מאת",
|
||||
"com_agents_description_placeholder": "אופציונלי: תאר את הסוכן שלך כאן",
|
||||
"com_agents_empty_state_heading": "לא נמצאו סוכנים",
|
||||
"com_agents_enable_file_search": "אפשר חיפוש בקבצים",
|
||||
"com_agents_error_bad_request_message": "לא היה ניתן לעבד את הבקשה",
|
||||
"com_agents_error_bad_request_suggestion": "אנא בדוק את הקלט שלך ונסה שוב",
|
||||
"com_agents_error_category_title": "שגיאת קטגוריה",
|
||||
"com_agents_error_generic": "נתקלנו בבעיה בעת טעינת התוכן",
|
||||
"com_agents_error_invalid_request": "בקשה לא חוקית",
|
||||
"com_agents_error_loading": "שגיאה בטעינת הסוכנים",
|
||||
"com_agents_error_network_message": "לא ניתן להתחבר לשרת",
|
||||
"com_agents_error_network_suggestion": "בדוק את חיבור האינטרנט שלך ונסה שוב",
|
||||
"com_agents_error_network_title": "בעיית חיבור",
|
||||
"com_agents_error_not_found_message": "לא ניתן היה למצוא את התוכן המבוקש",
|
||||
"com_agents_error_not_found_suggestion": "נסה לבחור מהאפשרויות האחרות או חזור לחנות",
|
||||
"com_agents_error_not_found_title": "לא נמצא",
|
||||
"com_agents_error_retry": "נסה שוב",
|
||||
"com_agents_error_search_title": "שגיאה בחיפוש",
|
||||
"com_agents_error_searching": "שגיאה בחיפוש הסוכנים",
|
||||
"com_agents_error_server_message": "השרת אינו זמין באופן זמני",
|
||||
"com_agents_error_server_suggestion": "אנא נסה שוב בעוד מספר דקות",
|
||||
"com_agents_error_server_title": "שגיאה בשרת",
|
||||
"com_agents_error_suggestion_generic": "אנא נסה לרענן את העמוד או נסה שוב מאוחר יותר",
|
||||
"com_agents_error_timeout_message": "הבקשה ארכה זמן רב מדי ולא היה ניתן להשלים אותה",
|
||||
"com_agents_error_timeout_suggestion": "אנא בדוק את חיבור האינטרנט שלך ונסה שוב",
|
||||
"com_agents_error_timeout_title": "זמן התפוגה של החיבור",
|
||||
"com_agents_error_title": "משהו השתבש",
|
||||
"com_agents_file_context": "קבצי הקשר (OCR)",
|
||||
"com_agents_file_context_disabled": "יש ליצור סוכן לפני שמעלים קבצים עבור הקשר קבצים",
|
||||
"com_agents_file_context_info": "קבצים שהועלו כ\"הקשר\" מעובדים באמצעות OCR (זיהוי אופטי של תווים) כדי להפיק טקסט אשר לאחר מכן מתווסף להוראות הסוכן. אידיאלי עבור מסמכים, תמונות עם טקסט או קובצי PDF בהם אתה צריך את התוכן הטקסטואלי המלא של הקובץ.",
|
||||
"com_agents_file_search_disabled": "יש ליצור את הסוכן לפני העלאת קבצים לחיפוש",
|
||||
"com_agents_file_search_info": "כאשר הסוכן מופעל הוא יקבל מידע על שמות הקבצים המפורטים להלן, כדי שהוא יוכל לאחזר את הקשר רלוונטי.",
|
||||
"com_agents_grid_announcement": "מציג {{count}} סוכנים מהקטגוריה {{category}}",
|
||||
"com_agents_instructions_placeholder": "הוראות המערכת שבהן ישתמש הסוכן",
|
||||
"com_agents_link_copied": "הקישור הועתק",
|
||||
"com_agents_link_copy_failed": "העתקת הקישור נכשלה",
|
||||
"com_agents_load_more_label": "טען סוכנים נוספים מהקטגוריה {{category}}",
|
||||
"com_agents_loading": "טוען...",
|
||||
"com_agents_marketplace": "מרכז הסוכנים",
|
||||
"com_agents_marketplace_subtitle": "גלו והשתמשו בסוכני בינה מלאכותית רבי עוצמה כדי לשפר את זרימות העבודה והפרודוקטיביות שלכם",
|
||||
"com_agents_mcp_description_placeholder": "הסבר בכמה מילים מה זה אמור לעשות",
|
||||
"com_agents_mcp_icon_size": "הגודל המינמלי הוא 128 x 128 פיקסלים",
|
||||
"com_agents_mcp_info": "הוסף לסוכן שרתי MCP כדי לאפשר לו לבצע משימות ולקיים אינטראקציות עם שירותים חיצוניים",
|
||||
"com_agents_mcp_name_placeholder": "כלי מותאם אישית",
|
||||
"com_agents_mcp_trust_subtext": "המחברים המותאמים אישית אינם מאומתים על ידי LibreChat",
|
||||
"com_agents_mcps_disabled": "עליך ליצור סוכן לפני הוספת שרתי MCP",
|
||||
"com_agents_missing_name": "אנא הזן שם לפני יצירת הסוכן",
|
||||
"com_agents_missing_provider_model": "אנא בחר את הספק ואת הדגם לפני יצירת הסוכן.",
|
||||
"com_agents_name_placeholder": "אופציונלי: שם הסוכן",
|
||||
"com_agents_no_access": "אין לך גישה לערוך את הסוכן הזה.",
|
||||
"com_agents_no_agent_id_error": "לא נמצא מזהה סוכן. אנא ודא שהסוכן נוצר תחילה",
|
||||
"com_agents_no_more_results": "הגעת לסוף התוצאות",
|
||||
"com_agents_not_available": "הסוכן לא זמין",
|
||||
"com_agents_recommended": "הסוכנים המומלצים שלנו",
|
||||
"com_agents_results_for": "תוצאות עבור '{{query}}'",
|
||||
"com_agents_search_aria": "חפש סוכנים",
|
||||
"com_agents_search_empty_heading": "אין תוצאות לחיפוש",
|
||||
"com_agents_search_info": "כאשר מופעל, מאפשר לסוכן שלך לחפש באינטרנט מידע עדכני. דורש מפתח API תקף.",
|
||||
"com_agents_search_instructions": "הקלד כדי לחפש סוכנים לפי שם או תיאור",
|
||||
"com_agents_search_info": "כאשר אפשרות זו מופעלת, היא מאפשרת לסוכן שלך לחפש מידע עדכני באינטרנט. נדרש מפתח API תקף.",
|
||||
"com_agents_search_name": "חפש סוכן לפי שם",
|
||||
"com_agents_search_no_results": "לא נמצאו סוכנים עבור \"{{query}}\"",
|
||||
"com_agents_search_placeholder": "מחפש סוכנים...",
|
||||
"com_agents_see_more": "ראה יותר",
|
||||
"com_agents_start_chat": "התחל צ'אט",
|
||||
"com_agents_top_picks": "הבחירות המובילות",
|
||||
"com_agents_update_error": "אירעה שגיאה בעדכון הסוכן שלך.",
|
||||
"com_assistants_action_attempt": "הסוכן מעוניין לתקשר עם {{0}}",
|
||||
"com_assistants_actions": "פעולות",
|
||||
@@ -172,7 +106,6 @@
|
||||
"com_auth_error_login_rl": "יותר מדי ניסיונות כניסה בזמן קצר. בבקשה נסה שוב מאוחר יותר.",
|
||||
"com_auth_error_login_server": "הייתה שגיאת שרת פנימית. אנא המתן מספר רגעים ונסה שוב.",
|
||||
"com_auth_error_login_unverified": "הדוא\"ל שלך לא אומת. אנא חפש בדוא\"ל שלך קישור לאימות.",
|
||||
"com_auth_error_oauth_failed": "האימות נכשל. אנא בדוק את שיטת ההתחברות שלך ונסה שוב",
|
||||
"com_auth_facebook_login": "המשך עם פייסבוק",
|
||||
"com_auth_full_name": "שם מלא",
|
||||
"com_auth_github_login": "המשך עם Github",
|
||||
@@ -197,7 +130,7 @@
|
||||
"com_auth_reset_password_if_email_exists": "אם קיים חשבון עם דוא\"ל זה, נשלח דוא\"ל עם הוראות לאיפוס סיסמה. אנא הקפד לבדוק גם בתיקיית הספאם שלך.",
|
||||
"com_auth_reset_password_link_sent": "אימייל (דוא\"ל) נשלח",
|
||||
"com_auth_reset_password_success": "איפוס סיסמה הצליח",
|
||||
"com_auth_saml_login": "המשך באמצעות SAML",
|
||||
"com_auth_saml_login": "המשך עם SAML",
|
||||
"com_auth_sign_in": "כניסה",
|
||||
"com_auth_sign_up": "הירשם",
|
||||
"com_auth_submit_registration": "שלח רישום",
|
||||
@@ -209,7 +142,7 @@
|
||||
"com_auth_username_min_length": "שם משתמש חייב להיות לפחות 2 תווים",
|
||||
"com_auth_verify_your_identity": "אמת את הזהות שלך",
|
||||
"com_auth_welcome_back": "ברוכים הבאים",
|
||||
"com_citation_more_details": "יותר פרטים על {{label}}",
|
||||
"com_citation_more_details": "פרטים נוספים על {{label}}",
|
||||
"com_citation_source": "מקור",
|
||||
"com_click_to_download": "(לחץ כאן להורדה)",
|
||||
"com_download_expired": "(פג תוקף ההורדה)",
|
||||
@@ -264,8 +197,6 @@
|
||||
"com_endpoint_deprecated": "לא מומלץ - בתהליך הסרה",
|
||||
"com_endpoint_deprecated_info": "נקודת קצה (endpoint) זו מיושנת ועלולה להיות מוסרת בגרסאות עתידיות, אנא השתמש בנקודת הקצה של הסוכן במקום זאת.",
|
||||
"com_endpoint_deprecated_info_a11y": "נקודת הקצה של התוסף מיושנת ועלולה להיות מוסרת בגרסאות עתידיות, אנא השתמש בנקודת הקצה של הסוכן במקום זאת.",
|
||||
"com_endpoint_disable_streaming": "השבתת תגובות סטרימינג וקבלת התגובה המלאה בבת אחת. שימושי עבור דגמים כמו o3 הדורשים ארגון מאומת כדי לאפשר סטרימינג",
|
||||
"com_endpoint_disable_streaming_label": "השבתת סטרימינג",
|
||||
"com_endpoint_examples": "הגדרות קבועות מראש",
|
||||
"com_endpoint_export": "ייצוא",
|
||||
"com_endpoint_export_share": "ייצא/שתף",
|
||||
@@ -295,7 +226,7 @@
|
||||
"com_endpoint_openai_max_tokens": "שדה 'max_tokens' אופציונלי, הוא מייצג את המספר המרבי של טוקנים שניתן ליצור בהשלמת הצ'אט. האורך הכולל של טוקני קלט והטוקנים שנוצרו מוגבל על ידי אורך ההקשר של המודל. אתה עלול להיתקל בשגיאות אם המספר הזה חורג מטוקני ההקשר המקסימליים.",
|
||||
"com_endpoint_openai_pres": "מספר בין -2.0 ל-2.0. ערכים חיוביים מענישים אסימונים חדשים על סמך האם הם מופיעים בטקסט עד כה, ומגדילים את הסבירות של המודל לדבר על נושאים חדשים.",
|
||||
"com_endpoint_openai_prompt_prefix_placeholder": "הגדר הוראות מותאמות אישית לכלול בהודעת המערכת. ברירת מחדל: אין",
|
||||
"com_endpoint_openai_reasoning_effort": "מודלי הנמקה בלבד: מגביל את המאמץ בהנמקה. הפחתת מאמץ ההנמקה יכולה להביא לתגובות מהירות יותר ולפחות טוקנים בשימוש להנמקה בתגובה. 'מינימלי' מייצר מעט מאוד טוקני הנמקה לזמן מהיר ביותר לטוקן הראשון, מתאים במיוחד לתכנות ולביצוע הוראות.",
|
||||
"com_endpoint_openai_reasoning_effort": "במודלים o1 ו-o3 בלבד: מגביל את מאמץ ההנמקה במודלים של הגיון. הפחתת מאמץ החשיבה יכולה לגרום לתגובות מהירות יותר ולפחות טוקנים בשימוש בהנמקה בתגובה.",
|
||||
"com_endpoint_openai_reasoning_summary": "Responses API בלבד: סיכום של החשיבה שבוצעה על ידי המודל. זה יכול להיות שימושי לאיתור שגיאות ולהבנת תהליך החשיבה של המודל. אפשרויות הגדרה: ללא, אוטומטי, תמציתי, מפורט.",
|
||||
"com_endpoint_openai_resend": "שלח שוב את כל התמונות שצורפו בעבר. הערה: זה יכול להגדיל משמעותית את עלות האסימונים ואתה עלול להיתקל בשגיאות עם קבצים מצורפים רבים של תמונות.",
|
||||
"com_endpoint_openai_resend_files": "שלח שוב את כל הקבצים שצורפו בעבר. הערה: זה יגדיל את עלות הטוקנים, ואתה עלול להיתקל בשגיאות עם קבצים מצורפים רבים.",
|
||||
@@ -304,7 +235,6 @@
|
||||
"com_endpoint_openai_topp": "חלופה לדגימה עם טמפרטורה, הנקראת דגימת גרעין, שבה המודל מחשיב את תוצאות האסימונים עם מסת ההסתברות top_p. אז 0.1 אומר שרק האסימונים המהווים את מסת ההסתברות העליונה של 10% נחשבים. אנו ממליצים לשנות את זה או את הטמפרטורה אבל לא את שניהם.",
|
||||
"com_endpoint_openai_use_responses_api": "השתמש ב-API של תגובות במקום השלמות צ'אט, אשר כולל תכונות מורחבות מ-OpenAI. נדרש עבור o1-pro, o3-pro, וכדי לאפשר סיכומי חשיבה.",
|
||||
"com_endpoint_openai_use_web_search": "הפעל פונקציונליות חיפוש ברשת באמצעות יכולות החיפוש המובנות של OpenAI. זה מאפשר למודל לחפש ברשת מידע עדכני ולספק תשובות מדויקות ועדכניות יותר.",
|
||||
"com_endpoint_openai_verbosity": "מגביל את רמת הפירוט של תגובת המודל. ערך נמוך יותר יביאו לתשובות תמציתיות יותר, בעוד שערכים גבוהים יותר יביאו לתשובות מפורטות יותר. הערכים הנתמכים כעת הם נמוך, בינוני, וגבוה",
|
||||
"com_endpoint_output": "פלט",
|
||||
"com_endpoint_plug_image_detail": "פרטי תמונה",
|
||||
"com_endpoint_plug_resend_files": "שלח שוב את הקובץ",
|
||||
@@ -353,8 +283,6 @@
|
||||
"com_endpoint_use_active_assistant": "השתמש ב-סייען פעיל",
|
||||
"com_endpoint_use_responses_api": "השתמש ב-API של תגובות",
|
||||
"com_endpoint_use_search_grounding": "התבססות על חיפוש גוגל",
|
||||
"com_endpoint_verbosity": "מלל",
|
||||
"com_error_endpoint_models_not_loaded": "לא ניתן היה לטעון את המודלים עבור {{0}}. אנא רענן את העמוד ונסה שוב",
|
||||
"com_error_expired_user_key": "המפתח שסופק עבור {{0}} פג ב-{{1}}. אנא ספק מפתח חדש ונסה שוב.",
|
||||
"com_error_files_dupe": "זוהה קובץ כפול",
|
||||
"com_error_files_empty": "אין אפשרות לקבצים ריקים",
|
||||
@@ -365,30 +293,16 @@
|
||||
"com_error_files_validation": "אירעה שגיאה במהלך אימות הקובץ.",
|
||||
"com_error_google_tool_conflict": "השימוש בכלים המובנים של Google אינו נתמך עם כלים חיצוניים. אנא השבת את הכלים המובנים או את הכלים החיצוניים.",
|
||||
"com_error_heic_conversion": "המרת התמונה בפורמט HEIC לפורמט JPEG נכשלה. אנא נסה להמיר את התמונה ידנית או להשתמש בפורמט אחר.",
|
||||
"com_error_illegal_model_request": "המודל \"{{0}}\" אינו זמין עבור {{1}}. אנא בחר מודל אחר",
|
||||
"com_error_input_length": "מספר הטוקנים של ההודעות האחרונות גבוה מדי, והוא חורג ממגבלת האסימונים ({{0}} בהתאמה. אנא קצר את ההודעה שלך, שנה את גודל ההקשר המקסימלי בפרמטרי השיחה, או התחל שיחה חדשה.",
|
||||
"com_error_invalid_agent_provider": "המודלים של \"{{0}}\" אינם זמינים לשימוש עם סוכנים. אנא עבור להגדרות הסוכן שלך ובחר ספק הזמין כרגע.",
|
||||
"com_error_invalid_user_key": "מפתח שסופק אינו חוקי. אנא ספק מפתח חוקי ונסה שוב.",
|
||||
"com_error_missing_model": "לא נבחר מודל עבור {{0}}. אנא בחר מודל ונסה שוב",
|
||||
"com_error_models_not_loaded": "לא ניתן היה לטעון את תצורת המודלים. אנא רענן את העמוד ונסה שוב",
|
||||
"com_error_moderation": "נראה שהתוכן שנשלח סומן על ידי מערכת הניהול שלנו בגלל שהוא אינו תואם את הנחיות הקהילה שלנו. אנחנו לא יכולים להמשיך עם הנושא הספציפי הזה. אם יש לך שאלות או נושאים אחרים שתרצה לחקור, אנא ערוך את ההודעה שלך, או צור שיחה חדשה.",
|
||||
"com_error_no_base_url": "לא נמצאה כתובת URL. אנא ספק כתובת ונסה שוב.",
|
||||
"com_error_no_user_key": "לא נמצא מפתח. אנא ספק מפתח ונסה שוב.",
|
||||
"com_file_pages": "עמודים: {{דפים}}",
|
||||
"com_file_source": "קובץ",
|
||||
"com_file_unknown": "קובץ לא ידוע",
|
||||
"com_files_download_failed": "{{0}} קבצים נכשלו",
|
||||
"com_files_download_percent_complete": "{{0}}% הושלמו",
|
||||
"com_files_download_progress": "{{0}} מתוך {{1}} קבצים",
|
||||
"com_files_downloading": "הורדת קבצים",
|
||||
"com_files_filter": "סינון קבצים...",
|
||||
"com_files_no_results": "אין תוצאות",
|
||||
"com_files_number_selected": "{{0}} מתוך {{1}} פריטים נבחרו",
|
||||
"com_files_preparing_download": "מכין את ההורדה...",
|
||||
"com_files_sharepoint_picker_title": "בחירת קבצים",
|
||||
"com_files_table": "השדה חייב להכיל תוכן, הוא אינו יכול להישאר ריק",
|
||||
"com_files_upload_local_machine": "מהמחשב המקומי",
|
||||
"com_files_upload_sharepoint": "מ-SharePoint",
|
||||
"com_generated_files": "קבצים שנוצרו:",
|
||||
"com_hide_examples": "הסתר דוגמאות",
|
||||
"com_info_heic_converting": "המרת התמונה מפורמט HEIC לפורמט JPEG...",
|
||||
@@ -521,9 +435,7 @@
|
||||
"com_nav_log_out": "צא",
|
||||
"com_nav_long_audio_warning": "העיבוד של טקסטים ארוכים ייקח יותר זמן.",
|
||||
"com_nav_maximize_chat_space": "הגדל את שטח הצ'אט",
|
||||
"com_nav_mcp_configure_server": "הגדרת {{0}}",
|
||||
"com_nav_mcp_status_connecting": "{{0}} - חיבור",
|
||||
"com_nav_mcp_vars_update_error": "שגיאה בעדכון משתנה משתמש מותאם אישית של MCP",
|
||||
"com_nav_mcp_vars_update_error": "שגיאה בעדכון משתני משתמש מותאמים אישית של MCP: {{0}}",
|
||||
"com_nav_mcp_vars_updated": "משתני משתמש מותאמים אישית של MCP עודכנו בהצלחה.",
|
||||
"com_nav_modular_chat": "אפשר החלפת נקודות קצה באמצע שיחה",
|
||||
"com_nav_my_files": "הקבצים שלי",
|
||||
@@ -584,21 +496,9 @@
|
||||
"com_sidepanel_manage_files": "נהל קבצים",
|
||||
"com_sidepanel_mcp_no_servers_with_vars": "אין שרתי MCP עם משתנים הניתנים להגדרה.",
|
||||
"com_sidepanel_parameters": "פרמטרים",
|
||||
"com_sources_agent_file": "מסמך המקור",
|
||||
"com_sources_agent_files": "קבצי הסוכן",
|
||||
"com_sources_download_aria_label": "הורדת {{filename}}{{status}}",
|
||||
"com_sources_download_failed": "ההורדה נכשלה",
|
||||
"com_sources_download_local_unavailable": "לא ניתן להוריד: הקובץ לא נשמר",
|
||||
"com_sources_downloading_status": "(מוריד...)",
|
||||
"com_sources_error_fallback": "לא ניתן לטעון את המקורות",
|
||||
"com_sources_image_alt": "תמונת תוצאות החיפוש",
|
||||
"com_sources_more_files": "+{{count}} קבצים",
|
||||
"com_sources_more_sources": "+{{count}}} מקורות",
|
||||
"com_sources_pages": "דפים",
|
||||
"com_sources_region_label": "תוצאות החיפוש ומקורות",
|
||||
"com_sources_reload_page": "טען מחדש את הדף",
|
||||
"com_sources_tab_all": "הכל",
|
||||
"com_sources_tab_files": "קבצים",
|
||||
"com_sources_tab_images": "תמונות",
|
||||
"com_sources_tab_news": "חדשות",
|
||||
"com_sources_title": "מקורות",
|
||||
@@ -614,7 +514,6 @@
|
||||
"com_ui_2fa_verified": "האימות הדו-שלבי אומת בהצלחה",
|
||||
"com_ui_accept": "אני מקבל",
|
||||
"com_ui_action_button": "לחצן פעולה",
|
||||
"com_ui_active": "פעיל",
|
||||
"com_ui_add": "הוסף",
|
||||
"com_ui_add_mcp": "הוסף MCP",
|
||||
"com_ui_add_mcp_server": "הוסף שרת MCP",
|
||||
@@ -627,14 +526,6 @@
|
||||
"com_ui_advanced": "מתקדם",
|
||||
"com_ui_advanced_settings": "הגדרות מתקדמות",
|
||||
"com_ui_agent": "סוכן",
|
||||
"com_ui_agent_category_aftersales": "אחרי המכירה",
|
||||
"com_ui_agent_category_finance": "פיננסי",
|
||||
"com_ui_agent_category_general": "כללי",
|
||||
"com_ui_agent_category_hr": "משאבי אנוש (HR)",
|
||||
"com_ui_agent_category_it": "טכנולוגיית מידע (IT)",
|
||||
"com_ui_agent_category_rd": "מחקר ופיתוח (R&D)",
|
||||
"com_ui_agent_category_sales": "מכירות",
|
||||
"com_ui_agent_category_selector_aria": "בורר הקטגוריות של הסוכנים",
|
||||
"com_ui_agent_chain": "שרשרת סוכנים (תערובת-סוכנים)",
|
||||
"com_ui_agent_chain_info": "מאפשר יצירת שרשרת סוכנים שבה כל סוכן יכול לגשת לפלטים של סוכנים קודמים בשרשרת. מבוסס על ארכיטקטורת \"תערובת-סוכנים\" שבה סוכנים משתמשים בפלטים קודמים כמידע עזר.",
|
||||
"com_ui_agent_chain_max": "הגעת למקסימום של {{0}} סוכנים.",
|
||||
@@ -642,10 +533,8 @@
|
||||
"com_ui_agent_deleted": "הסוכן נמחק בהצלחה.",
|
||||
"com_ui_agent_duplicate_error": "אירעה שגיאה בעת שכפול הסוכן",
|
||||
"com_ui_agent_duplicated": "הסוכן שוכפל בהצלחה",
|
||||
"com_ui_agent_name_is_required": "שם הסוכן הוא שדה חובה",
|
||||
"com_ui_agent_recursion_limit": "מספר מרבי של שלבי סוכן",
|
||||
"com_ui_agent_recursion_limit_info": "מגביל את מספר השלבים שהסוכן יכול לבצע בריצה לפני מתן תגובה סופית. ברירת המחדל היא 25 שלבים. שלב הוא בקשת API של בינה מלאכותית או סבב שימוש בכלי. לדוגמה, אינטראקציה בסיסית עם כלי לוקחת 3 שלבים: בקשה ראשונית, שימוש בכלי, ובקשת המשך.",
|
||||
"com_ui_agent_url_copied": "כתובת ה-URL של הסוכן הועתקה ללוח",
|
||||
"com_ui_agent_var": "{{0}} סוכנים",
|
||||
"com_ui_agent_version": "גרסה",
|
||||
"com_ui_agent_version_active": "גרסת הפעלה",
|
||||
@@ -662,7 +551,6 @@
|
||||
"com_ui_agent_version_unknown_date": "תאריך לא ידוע",
|
||||
"com_ui_agents": "סוכנים",
|
||||
"com_ui_agents_allow_create": "אפשר יצירת סוכנים",
|
||||
"com_ui_agents_allow_share": "אפשר שיתוף סוכנים",
|
||||
"com_ui_agents_allow_use": "אפשר שימוש בסוכנים",
|
||||
"com_ui_all": "הכל",
|
||||
"com_ui_all_proper": "הכל",
|
||||
@@ -683,7 +571,6 @@
|
||||
"com_ui_assistant_deleted": "הסייען נמחק בהצלחה",
|
||||
"com_ui_assistants": "סייען",
|
||||
"com_ui_assistants_output": "פלט סייענים",
|
||||
"com_ui_at_least_one_owner_required": "נדרש לפחות בעלים אחד",
|
||||
"com_ui_attach_error": "לא ניתן לצרף קובץ. צור או בחר שיחה, או נסה לרענן את הדף.",
|
||||
"com_ui_attach_error_openai": "לא ניתן לצרף את קבצי הסייען לנקודות קצה אחרות",
|
||||
"com_ui_attach_error_size": "חרגת ממגבלת גודל הקובץ עבור נקודת הקצה:",
|
||||
@@ -693,7 +580,6 @@
|
||||
"com_ui_attachment": "קובץ מצורף",
|
||||
"com_ui_auth_type": "סוג אישור",
|
||||
"com_ui_auth_url": "כתובת URL לאימות הרשאה",
|
||||
"com_ui_authenticate": "אימות",
|
||||
"com_ui_authentication": "אימות",
|
||||
"com_ui_authentication_type": "סוג אימות",
|
||||
"com_ui_auto": "אוטומטי",
|
||||
@@ -705,8 +591,6 @@
|
||||
"com_ui_backup_codes": "קודי גיבוי",
|
||||
"com_ui_backup_codes_regenerate_error": "אירעה שגיאה בעת יצירת קודי הגיבוי מחדש",
|
||||
"com_ui_backup_codes_regenerated": "קודי הגיבוי נוצרו מחדש בהצלחה",
|
||||
"com_ui_backup_codes_security_info": "מסיבות אבטחה, קודי גיבוי מוצגים פעם אחת בלבד בעת יצירתם. אנא שמרו אותם במקום מאובטח",
|
||||
"com_ui_backup_codes_status": "סטטוס קודי גיבוי",
|
||||
"com_ui_basic": "בסיסי",
|
||||
"com_ui_basic_auth_header": "כותרת אימות בסיסי",
|
||||
"com_ui_bearer": "נושא הרשאה",
|
||||
@@ -725,7 +609,6 @@
|
||||
"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": "הסימניה עודכנה בהצלחה",
|
||||
@@ -753,10 +636,8 @@
|
||||
"com_ui_confirm_action": "אשר פעולה",
|
||||
"com_ui_confirm_admin_use_change": "שינוי הגדרה זו יחסום גישה למנהלים, כולל אותך. האם אתה בטוח שברצונך להמשיך?",
|
||||
"com_ui_confirm_change": "אשר את השינוי",
|
||||
"com_ui_connecting": "חיבור",
|
||||
"com_ui_context": "הקשר",
|
||||
"com_ui_continue": "המשך",
|
||||
"com_ui_continue_oauth": "המשך עם OAuth",
|
||||
"com_ui_controls": "פקדים",
|
||||
"com_ui_convo_delete_error": "מחיקת הצ'אט נכשלה",
|
||||
"com_ui_copied": "הועתק!",
|
||||
@@ -764,7 +645,6 @@
|
||||
"com_ui_copy_code": "העתק קוד",
|
||||
"com_ui_copy_link": "העתק קישור",
|
||||
"com_ui_copy_to_clipboard": "העתק ללוח",
|
||||
"com_ui_copy_url_to_clipboard": "העתקת כתובת URL ללוח",
|
||||
"com_ui_create": "צור",
|
||||
"com_ui_create_link": "צור קישור",
|
||||
"com_ui_create_memory": "צור זכרון",
|
||||
@@ -850,7 +730,6 @@
|
||||
"com_ui_error_connection": "שגיאה בחיבור לשרת, נסה לרענן את הדף",
|
||||
"com_ui_error_save_admin_settings": "אירעה שגיאה בשמירת הגדרות הניהול שלך",
|
||||
"com_ui_error_updating_preferences": "אירעה שגיאה בעדכון העדפות",
|
||||
"com_ui_everyone_permission_level": "רמת ההרשאה של כולם",
|
||||
"com_ui_examples": "דוגמאות",
|
||||
"com_ui_expand_chat": "הרחב צ'אט",
|
||||
"com_ui_export_convo_modal": "חלון ייצוא שיחה",
|
||||
@@ -870,7 +749,6 @@
|
||||
"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": "קבצים",
|
||||
@@ -914,7 +792,6 @@
|
||||
"com_ui_good_afternoon": "צהריים טובים",
|
||||
"com_ui_good_evening": "ערב ",
|
||||
"com_ui_good_morning": "ערב טוב",
|
||||
"com_ui_group": "קבוצה",
|
||||
"com_ui_happy_birthday": "זה יום ההולדת הראשון שלי!",
|
||||
"com_ui_hide_image_details": "הסתר פרטי תמונה",
|
||||
"com_ui_hide_password": "הסתר סיסמה",
|
||||
@@ -948,18 +825,10 @@
|
||||
"com_ui_logo": "\"לוגו {{0}}\"",
|
||||
"com_ui_low": "נמוך",
|
||||
"com_ui_manage": "נהל",
|
||||
"com_ui_marketplace": "מרכז הסוכנים",
|
||||
"com_ui_marketplace_allow_use": "אפשר שימוש במרכז הסוכנים",
|
||||
"com_ui_max_tags": "המספר המקסימלי המותר על פי הערכים העדכניים הוא {{0}}.",
|
||||
"com_ui_mcp_authenticated_success": "{{0}} שרתי MCP אומתו בהצלחה",
|
||||
"com_ui_mcp_enter_var": "הזן ערך עבור {{0}}",
|
||||
"com_ui_mcp_initialize": "אתחול",
|
||||
"com_ui_mcp_initialized_success": "{{0}} שרתי MCP אותחלו בהצלחה",
|
||||
"com_ui_mcp_oauth_cancelled": "התחברות באמצעות OAuth בוטלה עבור {{0}}",
|
||||
"com_ui_mcp_oauth_timeout": "תם הזמן שהוקצב להתחברות OAuth עבור {{0}}",
|
||||
"com_ui_mcp_server_not_found": "נשרת לא נמצא",
|
||||
"com_ui_mcp_servers": "שרתי MCP",
|
||||
"com_ui_mcp_update_var": "עדכון {{0}}",
|
||||
"com_ui_mcp_url": "קישור לשרת ה-MCP",
|
||||
"com_ui_medium": "בינוני",
|
||||
"com_ui_memories": "זכרונות",
|
||||
@@ -983,7 +852,6 @@
|
||||
"com_ui_memory_would_exceed": "לא ניתן לשמור - זה יעבור את המגבלה ב-{{tokens}} אסימונים. מחק זיכרונות קיימים כדי לפנות מקום לזיכרונות חדשים.",
|
||||
"com_ui_mention": "ציין נקודת קצה, סייען, או הנחייה (פרופמט) כדי לעבור אליה במהירות",
|
||||
"com_ui_min_tags": "לא ניתן למחוק ערכים נוספים, יש צורך במינימום {{0}} ערכים.",
|
||||
"com_ui_minimal": "מינימלי",
|
||||
"com_ui_misc": "כללי",
|
||||
"com_ui_model": "דגם",
|
||||
"com_ui_model_parameters": "הגדרות המודל",
|
||||
@@ -996,14 +864,10 @@
|
||||
"com_ui_next": "הבא",
|
||||
"com_ui_no": "לא",
|
||||
"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": "אין אפשרויות התאמה אישית זמינות כרגע",
|
||||
"com_ui_no_read_access": "אין לך הרשאה לצפות בזיכרונות",
|
||||
"com_ui_no_results_found": "לא נמצאו תוצאות",
|
||||
"com_ui_no_terms_content": "אין תוכן תנאים והגבלות להצגה",
|
||||
"com_ui_no_valid_items": "השדה חייב להכיל תוכן, הוא לא יכול להישאר ריק",
|
||||
"com_ui_none": "אף אחד",
|
||||
@@ -1021,18 +885,9 @@
|
||||
"com_ui_oauth_success_title": "האימות בוצע בהצלחה",
|
||||
"com_ui_of": "של",
|
||||
"com_ui_off": "של",
|
||||
"com_ui_offline": "לא מקוון",
|
||||
"com_ui_on": "פעיל",
|
||||
"com_ui_optional": "(אופציונלי)",
|
||||
"com_ui_page": "עמוד",
|
||||
"com_ui_people": "אנשים",
|
||||
"com_ui_people_picker": "בורר אנשים",
|
||||
"com_ui_people_picker_allow_view_groups": "אפשר צפייה בקבוצות",
|
||||
"com_ui_people_picker_allow_view_roles": "אפשר צפייה בתפקידים",
|
||||
"com_ui_people_picker_allow_view_users": "אפשר צפייה במשתמשים",
|
||||
"com_ui_permissions_failed_load": "טעינת ההרשאות נכשלה. אנא נסה שוב",
|
||||
"com_ui_permissions_failed_update": "עדכון ההרשאות נכשל. אנא נסה שוב",
|
||||
"com_ui_permissions_updated_success": "ההרשאות עודכנו בהצלחה",
|
||||
"com_ui_preferences_updated": "ההעדפות עודכנו בהצלחה",
|
||||
"com_ui_prev": "הקודם",
|
||||
"com_ui_preview": "תצוגה מקדימה",
|
||||
@@ -1047,7 +902,6 @@
|
||||
"com_ui_prompt_update_error": "אירעה שגיאה בעדכון ההנחיה (פרומפט)",
|
||||
"com_ui_prompts": "הנחיות (פרומפטים)",
|
||||
"com_ui_prompts_allow_create": "אפשר יצירת הנחיות",
|
||||
"com_ui_prompts_allow_share": "אפשר שיתוף הנחיות",
|
||||
"com_ui_prompts_allow_use": "אפשר שימוש בהנחיות (פרומפטים)",
|
||||
"com_ui_provider": "ספק",
|
||||
"com_ui_quality": "איכות",
|
||||
@@ -1055,14 +909,11 @@
|
||||
"com_ui_redirecting_to_provider": "מבצע הפניה ל-{{0}}, אנא המתן...",
|
||||
"com_ui_reference_saved_memories": "הפניה לזכרונות שמורים",
|
||||
"com_ui_reference_saved_memories_description": "אפשר לסוכן להתייחס ולהשתמש בזיכרונות השמורים שלך בעת התגובה",
|
||||
"com_ui_refresh": "רענן",
|
||||
"com_ui_refresh_link": "רענון קישור",
|
||||
"com_ui_regenerate": "לחדש",
|
||||
"com_ui_regenerate_backup": "צור קודי גיבוי מחדש",
|
||||
"com_ui_regenerating": "יוצר מחדש...",
|
||||
"com_ui_region": "איזור",
|
||||
"com_ui_reinitialize": "אתחול מחדש",
|
||||
"com_ui_remove_user": "הסר את {{0}}",
|
||||
"com_ui_rename": "שנה שם",
|
||||
"com_ui_rename_conversation": "החלפת שם הצ'אט",
|
||||
"com_ui_rename_failed": "החלפת שם הצ'אט נכשלה",
|
||||
@@ -1070,7 +921,6 @@
|
||||
"com_ui_requires_auth": "נדרש אימות",
|
||||
"com_ui_reset_var": "איפוס {{0}}",
|
||||
"com_ui_reset_zoom": "איפוס זום",
|
||||
"com_ui_resource": "משאב",
|
||||
"com_ui_result": "תוצאה",
|
||||
"com_ui_revoke": "בטל",
|
||||
"com_ui_revoke_info": "בטל את כל האישורים שסופקו על ידי המשתמש",
|
||||
@@ -1078,38 +928,24 @@
|
||||
"com_ui_revoke_key_endpoint": "ביטול מפתח עבור {{0}}",
|
||||
"com_ui_revoke_keys": "ביטול מפתחות",
|
||||
"com_ui_revoke_keys_confirm": "האם אתה בטוח שברצונך לבטל את כל המפתחות?",
|
||||
"com_ui_role": "תפקיד",
|
||||
"com_ui_role_editor": "עורך",
|
||||
"com_ui_role_editor_desc": "יכול לצפות ולשנות את הסוכן",
|
||||
"com_ui_role_manager": "מנהל",
|
||||
"com_ui_role_manager_desc": "יכול לצפות, לשנות ולמחוק את הסוכן",
|
||||
"com_ui_role_owner": "בעלים",
|
||||
"com_ui_role_owner_desc": "בעל שליטה מלאה על הסוכן כולל שיתוף",
|
||||
"com_ui_role_select": "תפקיד",
|
||||
"com_ui_role_viewer": "צופה",
|
||||
"com_ui_roleplay": "משחק תפקידים",
|
||||
"com_ui_run_code": "הרץ קוד",
|
||||
"com_ui_run_code_error": "אירעה שגיאה בהרצת הקוד",
|
||||
"com_ui_save": "שמור",
|
||||
"com_ui_save_badge_changes": "האם לשמור את השינויים בתגים?",
|
||||
"com_ui_save_changes": "שמור שינויים",
|
||||
"com_ui_save_submit": "שמור ושלח",
|
||||
"com_ui_saved": "שמור!",
|
||||
"com_ui_saving": "שומר...",
|
||||
"com_ui_schema": "סכמה",
|
||||
"com_ui_scope": "תחום",
|
||||
"com_ui_search": "חיפוש",
|
||||
"com_ui_search_agent_category": "חיפוש קטגוריות...",
|
||||
"com_ui_search_default_placeholder": "חיפוש לפי שם או דוא\"ל (מינימום 2 תווים)",
|
||||
"com_ui_search_people_placeholder": "חיפוש אנשים או קבוצות לפי שם או דוא\"ל",
|
||||
"com_ui_seconds": "שניות",
|
||||
"com_ui_secret_key": "מפתח סודי",
|
||||
"com_ui_select": "בחר",
|
||||
"com_ui_select_all": "בחר הכל",
|
||||
"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": "בחר איזור",
|
||||
@@ -1117,13 +953,10 @@
|
||||
"com_ui_select_search_plugin": "חפש פאלגין לפי שם",
|
||||
"com_ui_select_search_provider": "חפש ספק לפי שם",
|
||||
"com_ui_select_search_region": "חפש איזור לפי שם",
|
||||
"com_ui_set": "הגדר",
|
||||
"com_ui_share": "שתף",
|
||||
"com_ui_share_create_message": "שמך וכל הודעה שתוסיף לאחר השיתוף יישארו פרטיים.",
|
||||
"com_ui_share_delete_error": "אירעה שגיאה בעת מחיקת הקישור המשותף.",
|
||||
"com_ui_share_error": "אירעה שגיאה בעת שיתוף קישור הצ'אט",
|
||||
"com_ui_share_everyone": "שתף עם כולם",
|
||||
"com_ui_share_everyone_description_var": "{{resource}} זה יהיה זמין לכולם. אנא ודא שה-{{resource}} באמת נועד להיות משותף עם כולם. היזהר עם הנתונים שלך.",
|
||||
"com_ui_share_link_to_chat": "שתף קישור בצ'אט",
|
||||
"com_ui_share_update_message": "השם שלך, ההוראות המותאמות אישית וכל ההודעות שתוסיף לאחר השיתוף יישארו פרטיים.",
|
||||
"com_ui_share_var": "שתף {{0}}",
|
||||
@@ -1150,10 +983,6 @@
|
||||
"com_ui_stop": "עצור",
|
||||
"com_ui_storage": "אחסון",
|
||||
"com_ui_submit": "שלח",
|
||||
"com_ui_support_contact": "פניה לתמיכה",
|
||||
"com_ui_support_contact_email": "דוא\"ל",
|
||||
"com_ui_support_contact_email_invalid": "אנא הזן כתובת דוא\"ל חוקית",
|
||||
"com_ui_support_contact_name": "שם",
|
||||
"com_ui_teach_or_explain": "למידה",
|
||||
"com_ui_temporary": "צ'אט זמני",
|
||||
"com_ui_terms_and_conditions": "תנאים והגבלות",
|
||||
@@ -1170,17 +999,14 @@
|
||||
"com_ui_tools": "כלים",
|
||||
"com_ui_travel": "מסע",
|
||||
"com_ui_trust_app": "אני סומך על האפליקציה הזו",
|
||||
"com_ui_try_adjusting_search": "נסה להתאים את מונחי החיפוש שלך",
|
||||
"com_ui_unarchive": "הוצא מהארכיון",
|
||||
"com_ui_unarchive_error": "הוצאת השיחה מהארכיון נכשלה",
|
||||
"com_ui_unknown": "לא ידוע",
|
||||
"com_ui_unset": "בטל הגדרה",
|
||||
"com_ui_untitled": "ללא כותר",
|
||||
"com_ui_update": "עדכון",
|
||||
"com_ui_update_mcp_error": "אירעה שגיאה ביצירה או עדכון של ה-MCP.",
|
||||
"com_ui_update_mcp_success": "ה-MCP נוצר או עודכן בהצלחה",
|
||||
"com_ui_upload": "העלה",
|
||||
"com_ui_upload_agent_avatar": "האווטר של הסוכן עודכן בהצלחה",
|
||||
"com_ui_upload_code_files": "העלאה עבור מפענח הקוד",
|
||||
"com_ui_upload_delay": "העלאת \"{{0}}\" לוקחת יותר זמן מהצפוי. אנא המתן בזמן שהקובץ מסיים את האינדוקס לאחזור.",
|
||||
"com_ui_upload_error": "אירעה שגיאה בהעלאת הקובץ שלך",
|
||||
@@ -1200,8 +1026,6 @@
|
||||
"com_ui_use_memory": "השתמש בזיכרון",
|
||||
"com_ui_use_micrphone": "שימוש במיקורפון",
|
||||
"com_ui_used": "נוצל",
|
||||
"com_ui_user": "משתמש",
|
||||
"com_ui_user_group_permissions": "הרשאות משתמשים וקבוצות",
|
||||
"com_ui_value": "ערך",
|
||||
"com_ui_variables": "משתנים",
|
||||
"com_ui_variables_info": "השתמש בסוגריים מסולסלות כפולות בטקסט שלך ליצירת משתנים, לדוגמא `{{example variable}}`, כדי למלא אותם מאוחר יותר בשימוש בהנחיה.",
|
||||
|
||||
@@ -103,7 +103,6 @@
|
||||
"com_assistants_actions_disabled": "Pirms darbību pievienošanas ir jāizveido asistents.",
|
||||
"com_assistants_actions_info": "Ļaujiet savam asistentam iegūt informāciju vai veikt darbības, izmantojot API.",
|
||||
"com_assistants_add_actions": "Pievienot darbības",
|
||||
"com_assistants_add_mcp_server_tools": "Pievienot MCP servera rīkus",
|
||||
"com_assistants_add_tools": "Pievienot rīkus",
|
||||
"com_assistants_allow_sites_you_trust": "Atļaujiet tikai tās vietnes, kurām uzticaties.",
|
||||
"com_assistants_append_date": "Pievienot pašreizējo datumu un laiku",
|
||||
@@ -260,7 +259,7 @@
|
||||
"com_endpoint_context_info": "Maksimālais tokenu skaits, ko var izmantot kontekstam. Izmanto to, lai kontrolētu, cik tokenu tiek nosūtīti katrā pieprasījumā. Ja tas nav norādīts, tiks izmantoti sistēmas noklusējuma iestatījumi, pamatojoties uz zināmo modeļu konteksta lielumu. Augstāku vērtību iestatīšana var izraisīt kļūdas un/vai augstākas tokenu izmaksas.",
|
||||
"com_endpoint_context_tokens": "Maksimālais konteksta tokenu skaits",
|
||||
"com_endpoint_custom_name": "Pielāgots nosaukums",
|
||||
"com_endpoint_default": "noklusējums",
|
||||
"com_endpoint_default": "noklusējuma",
|
||||
"com_endpoint_default_blank": "pēc noklusējuma: tukšs",
|
||||
"com_endpoint_default_empty": "noklusējums: tukšs",
|
||||
"com_endpoint_default_with_num": "noklusējums: {{0}}",
|
||||
@@ -269,7 +268,7 @@
|
||||
"com_endpoint_deprecated_info_a11y": "Spraudņa galapunkts ir novecojis un var tikt noņemts turpmākajās versijās; lūdzu, tā vietā izmantojiet aģenta galapunktu.",
|
||||
"com_endpoint_disable_streaming": "Izslēgt atbilžu straumēšanu un saņemt visu atbildi uzreiz. Noderīgi tādiem modeļiem kā o3, kas pieprasa organizācijas pārbaudi straumēšanai.",
|
||||
"com_endpoint_disable_streaming_label": "Atspējot straumēšanu",
|
||||
"com_endpoint_examples": "Iestatījumi",
|
||||
"com_endpoint_examples": " Iepriekšiestatījumi",
|
||||
"com_endpoint_export": "Eksportēt",
|
||||
"com_endpoint_export_share": "Eksportēt/kopīgot",
|
||||
"com_endpoint_frequency_penalty": "Biežuma sods",
|
||||
@@ -288,8 +287,8 @@
|
||||
"com_endpoint_message": "Ziņa",
|
||||
"com_endpoint_message_new": "Ziņa {{0}}",
|
||||
"com_endpoint_message_not_appendable": "Rediģējiet savu ziņu vai ģenerējiet to atkārtoti.",
|
||||
"com_endpoint_my_preset": "Mans iestatījums",
|
||||
"com_endpoint_no_presets": "Nav vēl neviena iestatījuma, lai tos izveidotu, izmantojiet iestatījumu pogu.",
|
||||
"com_endpoint_my_preset": "Mans iepriekšiestatījums",
|
||||
"com_endpoint_no_presets": "Vēl nav iepriekš iestatītu iestatījumu. Lai tos izveidotu, izmantojiet iestatījumu pogu.",
|
||||
"com_endpoint_open_menu": "Atvērt izvēlni",
|
||||
"com_endpoint_openai_custom_name_placeholder": "Iestatiet pielāgotu nosaukumu mākslīgajam intelektam",
|
||||
"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.",
|
||||
@@ -315,23 +314,23 @@
|
||||
"com_endpoint_plug_skip_completion": "Izlaist pabeigšanu",
|
||||
"com_endpoint_plug_use_functions": "Izmantot funkcijas",
|
||||
"com_endpoint_presence_penalty": "Klātbūtnes sods",
|
||||
"com_endpoint_preset": "iestatījums",
|
||||
"com_endpoint_preset": "iepriekš iestatīts",
|
||||
"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.",
|
||||
"com_endpoint_preset_default_removed": "vairs nav noklusējuma iestatījums.",
|
||||
"com_endpoint_preset_delete_confirm": "Vai tiešām vēlaties dzēst šo iestatījumu?",
|
||||
"com_endpoint_preset_delete_error": "Dzēšot jūsu iestatījumu, radās kļūda. Lūdzu, mēģiniet vēlreiz.",
|
||||
"com_endpoint_preset_import": "Iestatījums importēts!",
|
||||
"com_endpoint_preset_import_error": "Importējot jūsu iestatījumu, radās kļūda. Lūdzu, mēģiniet vēlreiz.",
|
||||
"com_endpoint_preset_name": "Iestatījuma nosaukums",
|
||||
"com_endpoint_preset_save_error": "Saglabājot jūsu iestatījumu, radās kļūda. Lūdzu, mēģiniet vēlreiz.",
|
||||
"com_endpoint_preset_selected": "Iestatījumi aktīvs!",
|
||||
"com_endpoint_preset_delete_error": "Dzēšot jūsu sākotnējo iestatījumu, radās kļūda. Lūdzu, mēģiniet vēlreiz.",
|
||||
"com_endpoint_preset_import": "Iepriekšiestatījums importēts!",
|
||||
"com_endpoint_preset_import_error": "Importējot jūsu sākotnējo iestatījumu, radās kļūda. Lūdzu, mēģiniet vēlreiz.",
|
||||
"com_endpoint_preset_name": "Iepriekšiestatījuma nosaukums",
|
||||
"com_endpoint_preset_save_error": "Saglabājot jūsu sākotnējo iestatījumu, radās kļūda. Lūdzu, mēģiniet vēlreiz.",
|
||||
"com_endpoint_preset_selected": "Iepriekšiestatījums aktīvs!",
|
||||
"com_endpoint_preset_selected_title": "Aktīvs!",
|
||||
"com_endpoint_preset_title": "iestatījums",
|
||||
"com_endpoint_presets": "iestatījumi",
|
||||
"com_endpoint_presets_clear_warning": "Vai tiešām vēlaties notīrīt visus iestatījumus? Šī darbība ir neatgriezeniska.",
|
||||
"com_endpoint_preset_title": "Iepriekšiestatījums",
|
||||
"com_endpoint_presets": "iepriekšiestatījumi",
|
||||
"com_endpoint_presets_clear_warning": "Vai tiešām vēlaties notīrīt visus iepriekšiestatījumus? Šī darbība ir neatgriezeniska.",
|
||||
"com_endpoint_prompt_cache": "Izmantojiet uzvednes kešatmiņu",
|
||||
"com_endpoint_prompt_prefix": "Pielāgotas instrukcijas",
|
||||
"com_endpoint_prompt_prefix_assistants": "Papildu instrukcijas",
|
||||
@@ -339,12 +338,12 @@
|
||||
"com_endpoint_prompt_prefix_placeholder": "Iestatiet pielāgotas instrukcijas vai kontekstu. Ja lauks ir tukšs, tas tiek ignorēts.",
|
||||
"com_endpoint_reasoning_effort": "Domāšanas grūtums",
|
||||
"com_endpoint_reasoning_summary": "Argumentācijas kopsavilkums",
|
||||
"com_endpoint_save_as_preset": "Saglabāt kā iestatījumu",
|
||||
"com_endpoint_save_as_preset": "Saglabāt kā iepriekšiestatījumu",
|
||||
"com_endpoint_search": "Meklēt galapunktu pēc nosaukuma",
|
||||
"com_endpoint_search_endpoint_models": "Meklēt {{0}} modeļos...",
|
||||
"com_endpoint_search_models": "Meklēt modeļus...",
|
||||
"com_endpoint_search_var": "Meklēt {{0}}...",
|
||||
"com_endpoint_set_custom_name": "Iestatiet pielāgotu nosaukumu, ja varat atrast šo iestatījumu",
|
||||
"com_endpoint_set_custom_name": "Iestatiet pielāgotu nosaukumu, ja varat atrast šo iepriekšiestatījumu",
|
||||
"com_endpoint_skip_hover": "Iespējot pabeigšanas soļa izlaišanu, kurā tiek pārskatīta galīgā atbilde un ģenerētie soļi",
|
||||
"com_endpoint_stop": "Apturēt secības",
|
||||
"com_endpoint_stop_placeholder": "Atdaliet vērtības, nospiežot taustiņu `Enter`",
|
||||
@@ -374,7 +373,7 @@
|
||||
"com_error_invalid_user_key": "Norādīta nederīga atslēga. Lūdzu, ievadiet derīgu atslēgu un mēģiniet vēlreiz.",
|
||||
"com_error_missing_model": "Nav izvēlēts neviens modelis {{0}}. Lūdzu, izvēlieties modeli un mēģiniet vēlreiz.",
|
||||
"com_error_models_not_loaded": "Modeļu konfigurāciju nevar ielādēt. Lūdzu, atsvaidziniet lapu un mēģiniet vēlreiz.",
|
||||
"com_error_moderation": "Šķiet, ka mūsu moderācijas sistēma ir atzīmējusi nosūtīto saturu kā neatbilstošu mūsu vadlīnijām. Mēs nevaram turpināt darbu ar šo konkrēto tēmu. Ja jums ir vēl kādi jautājumi vai tēmas, kuras vēlaties izpētīt, lūdzu, rediģējiet savu ziņu vai izveidojiet jaunu sarunu.",
|
||||
"com_error_moderation": "Šķiet, ka mūsu moderācijas sistēma ir atzīmējusi iesniegto saturu kā neatbilstošu mūsu vadlīnijām. Mēs nevaram turpināt darbu ar šo konkrēto tēmu. Ja jums ir vēl kādi jautājumi vai tēmas, kuras vēlaties izpētīt, lūdzu, rediģējiet savu ziņu vai izveidojiet jaunu sarunu.",
|
||||
"com_error_no_base_url": "Nav atrasts bāzes URL. Lūdzu, norādiet to un mēģiniet vēlreiz.",
|
||||
"com_error_no_user_key": "Atslēga nav atrasta. Lūdzu, norādiet atslēgu un mēģiniet vēlreiz.",
|
||||
"com_file_pages": "Lapas: {{pages}}",
|
||||
@@ -402,7 +401,7 @@
|
||||
"com_nav_archive_name": "Vārds",
|
||||
"com_nav_archived_chats": "Arhivētās sarunas",
|
||||
"com_nav_at_command": "@-Komanda",
|
||||
"com_nav_at_command_description": "Pārslēgšanas komanda \"@\" galapunktu, modeļu, iestatījumu u.c. pārslēgšanai.",
|
||||
"com_nav_at_command_description": "Pārslēgšanas komanda \"@\" galapunktu, modeļu, sākotnējo iestatījumu u.c. pārslēgšanai.",
|
||||
"com_nav_audio_play_error": "Kļūda, atskaņojot audio: {{0}}",
|
||||
"com_nav_audio_process_error": "Kļūda, apstrādājot audio: {{0}}",
|
||||
"com_nav_auto_scroll": "Automātiski iet uz jaunāko ziņu, atverot sarunu",
|
||||
@@ -520,7 +519,6 @@
|
||||
"com_nav_lang_polish": "Poļu",
|
||||
"com_nav_lang_portuguese": "Portugāļu",
|
||||
"com_nav_lang_russian": "Krievu",
|
||||
"com_nav_lang_slovenian": "Slovenščina",
|
||||
"com_nav_lang_spanish": "Spāņu",
|
||||
"com_nav_lang_swedish": "Zviedru",
|
||||
"com_nav_lang_thai": "ไทย",
|
||||
@@ -581,7 +579,6 @@
|
||||
"com_nav_tool_dialog": "Asistenta rīki",
|
||||
"com_nav_tool_dialog_agents": "Aģenta rīki",
|
||||
"com_nav_tool_dialog_description": "Lai saglabātu rīku atlasi, ir jāsaglabā asistents.",
|
||||
"com_nav_tool_dialog_mcp_server_tools": "MCP servera rīki",
|
||||
"com_nav_tool_remove": "Noņemt",
|
||||
"com_nav_tool_search": "Meklēšanas rīki",
|
||||
"com_nav_user": "LIETOTĀJS",
|
||||
@@ -633,7 +630,7 @@
|
||||
"com_ui_add": "Pievienot",
|
||||
"com_ui_add_mcp": "Pievienot MCP",
|
||||
"com_ui_add_mcp_server": "Pievienot MCP serveri",
|
||||
"com_ui_add_model_preset": "Pievienot modeli vai iestatījumu papildu atbildei",
|
||||
"com_ui_add_model_preset": "Pievienot modeli vai iepriekš iestatītu iestatījumu papildu atbildei",
|
||||
"com_ui_add_multi_conversation": "Pievienot vairākas sarunas",
|
||||
"com_ui_adding_details": "Detalizētas informācijas pievienošana",
|
||||
"com_ui_admin": "Administrators",
|
||||
@@ -679,7 +676,7 @@
|
||||
"com_ui_agents_allow_create": "Atļaut aģentu izveidi",
|
||||
"com_ui_agents_allow_share": "Atļaut aģentu koplietošanu",
|
||||
"com_ui_agents_allow_use": "Atļaut aģentu izmantošanu",
|
||||
"com_ui_all": "visu",
|
||||
"com_ui_all": "visi",
|
||||
"com_ui_all_proper": "Visi",
|
||||
"com_ui_analyzing": "Analīze",
|
||||
"com_ui_analyzing_finished": "Analīze pabeigta",
|
||||
@@ -768,7 +765,6 @@
|
||||
"com_ui_complete_setup": "Pabeigt iestatīšanu",
|
||||
"com_ui_concise": "Īss",
|
||||
"com_ui_configure_mcp_variables_for": "Uzstādīt parametrus {{0}}",
|
||||
"com_ui_confirm": "Apstiprināt",
|
||||
"com_ui_confirm_action": "Apstiprināt darbību",
|
||||
"com_ui_confirm_admin_use_change": "Mainot šo iestatījumu, administratoriem, tostarp jums, tiks liegta piekļuve. Vai tiešām vēlaties turpināt?",
|
||||
"com_ui_confirm_change": "Apstiprināt izmaiņas",
|
||||
@@ -833,8 +829,6 @@
|
||||
"com_ui_delete_success": "Veiksmīgi dzēsts",
|
||||
"com_ui_delete_tool": "Dzēst rīku",
|
||||
"com_ui_delete_tool_confirm": "Vai tiešām vēlaties dzēst šo rīku?",
|
||||
"com_ui_delete_tool_error": "Kļūda, dzēšot rīku: {{error}}",
|
||||
"com_ui_delete_tool_success": "Rīks veiksmīgi izdzēsts",
|
||||
"com_ui_deleted": "Dzēsts",
|
||||
"com_ui_deleting_file": "Dzēšu failu...",
|
||||
"com_ui_descending": "Dilstošs",
|
||||
@@ -851,7 +845,7 @@
|
||||
"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}}` (mainīgā_nosakums:opcija1|opcija2|opcija3)",
|
||||
"com_ui_duplicate": "Dublicēt",
|
||||
"com_ui_duplicate": "Dublikāts",
|
||||
"com_ui_duplication_error": "Sarunas dublēšanas laikā radās kļūda.",
|
||||
"com_ui_duplication_processing": "Dublēju sarunu...",
|
||||
"com_ui_duplication_success": "Saruna veiksmīgi dublēta",
|
||||
@@ -896,8 +890,6 @@
|
||||
"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_file_token_limit": "Failu tokenu ierobežojums",
|
||||
"com_ui_file_token_limit_desc": "Iestatiet maksimālo tokenu ierobežojumu failu apstrādei, lai kontrolētu izmaksas un resursu izmantošanu",
|
||||
"com_ui_files": "Faili",
|
||||
"com_ui_filter_prompts": "Filtrēt uzvednes",
|
||||
"com_ui_filter_prompts_name": "Filtrēt uzvednes pēc nosaukuma",
|
||||
@@ -958,7 +950,6 @@
|
||||
"com_ui_import_conversation_info": "Sarunu importēšana no JSON faila",
|
||||
"com_ui_import_conversation_success": "Sarunas ir veiksmīgi importētas",
|
||||
"com_ui_include_shadcnui": "Iekļaujiet shadcn/ui komponentu instrukcijas",
|
||||
"com_ui_initializing": "Inicializē...",
|
||||
"com_ui_input": "Ievade",
|
||||
"com_ui_instructions": "Instrukcijas",
|
||||
"com_ui_key": "Atslēga",
|
||||
@@ -978,8 +969,6 @@
|
||||
"com_ui_marketplace_allow_use": "Atļaut izmantot katalogu",
|
||||
"com_ui_max_tags": "Maksimālais atļautais skaits ir {{0}}, izmantojot jaunākās vērtības.",
|
||||
"com_ui_mcp_authenticated_success": "MCP serveris '{{0}}' veiksmīgi autentificēts",
|
||||
"com_ui_mcp_configure_server": "Konfigurēt {{0}}",
|
||||
"com_ui_mcp_configure_server_description": "Konfigurējiet pielāgotus mainīgos {{0}}",
|
||||
"com_ui_mcp_enter_var": "Ievadiet vērtību {{0}}",
|
||||
"com_ui_mcp_init_failed": "Neizdevās inicializēt MCP serveri",
|
||||
"com_ui_mcp_initialize": "Inicializēt",
|
||||
@@ -1010,7 +999,7 @@
|
||||
"com_ui_memory_updated": "Atjaunināta saglabātā atmiņa",
|
||||
"com_ui_memory_updated_items": "Atjauninātas atmiņas",
|
||||
"com_ui_memory_would_exceed": "Nevar saglabāt - pārsniegtu tokenu limitu par {{tokens}}. Izdzēsiet esošās atmiņas, lai atbrīvotu vietu.",
|
||||
"com_ui_mention": "Pieminiet galapunktu, assistentu vai iestatījumu, lai ātri uz to pārslēgtos",
|
||||
"com_ui_mention": "Pieminiet galapunktu, assistentu vai sākotnējo iestatījumu, lai ātri uz to pārslēgtos",
|
||||
"com_ui_min_tags": "Nevar noņemt vairāk vērtību, vismaz {{0}} ir nepieciešamas.",
|
||||
"com_ui_minimal": "Minimāla",
|
||||
"com_ui_misc": "Dažādi",
|
||||
@@ -1124,7 +1113,7 @@
|
||||
"com_ui_save": "Saglabāt",
|
||||
"com_ui_save_badge_changes": "Vai saglabāt emblēmas izmaiņas?",
|
||||
"com_ui_save_changes": "Saglabāt izmaiņas",
|
||||
"com_ui_save_submit": "Saglabāt un nosūtīt",
|
||||
"com_ui_save_submit": "Saglabāt un iesniegt",
|
||||
"com_ui_saved": "Saglabāts!",
|
||||
"com_ui_saving": "Saglabā...",
|
||||
"com_ui_schema": "Shēma",
|
||||
@@ -1179,11 +1168,11 @@
|
||||
"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 lietotāja vārds).",
|
||||
"com_ui_speech_while_submitting": "Nevar nosūtīt runu, kamēr tiek ģenerēta atbilde.",
|
||||
"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",
|
||||
"com_ui_storage": "Uzglabāšana",
|
||||
"com_ui_submit": "Nosūtīt",
|
||||
"com_ui_submit": "Iesniegt",
|
||||
"com_ui_support_contact": "Atbalsta kontaktinformācija",
|
||||
"com_ui_support_contact_email": "E-pasts",
|
||||
"com_ui_support_contact_email_invalid": "Lūdzu, ievadiet derīgu e-pasta adresi",
|
||||
@@ -1208,10 +1197,8 @@
|
||||
"com_ui_travel": "Ceļošana",
|
||||
"com_ui_trust_app": "Es uzticos šai lietotnei",
|
||||
"com_ui_try_adjusting_search": "Mēģiniet pielāgot meklēšanas vaicājumus",
|
||||
"com_ui_ui_resources": "Lietotāja saskarnes resursi",
|
||||
"com_ui_unarchive": "Atarhivēt",
|
||||
"com_ui_unarchive_error": "Neizdevās atarhivēt sarunu",
|
||||
"com_ui_unavailable": "Nav pieejams",
|
||||
"com_ui_unknown": "Nezināms",
|
||||
"com_ui_unset": "Neuzlikts",
|
||||
"com_ui_untitled": "Bez nosaukuma",
|
||||
|
||||
@@ -4,23 +4,13 @@
|
||||
"com_a11y_ai_composing": "De AI is nog bezig met het formuleren van een antwoord.",
|
||||
"com_a11y_end": "De AI is klaar met het antwoord.",
|
||||
"com_a11y_start": "De AI is begonnen met antwoorden.",
|
||||
"com_agents_all": "Alle Agents\n\n",
|
||||
"com_agents_all": "Alle Agents",
|
||||
"com_agents_by_librechat": "door LibreChat",
|
||||
"com_agents_category_empty": "Geen agents gevonden in de {{category}} categorie",
|
||||
"com_agents_category_tab_label": "{{category}} categorie, {{position}} of {{total}}",
|
||||
"com_agents_category_tabs_label": "Agentcategorieën",
|
||||
"com_agents_clear_search": "Zoekopdracht wissen",
|
||||
"com_agents_code_interpreter": "Indien ingeschakeld, kan je agent de LibreChat Code Interpreter API gebruiken om gegenereerde code, inclusief het verwerken van bestanden, veilig uit te voeren. Vereist een geldige API-sleutel.",
|
||||
"com_agents_code_interpreter_title": "Code Interpreter API",
|
||||
"com_agents_contact": "Contact",
|
||||
"com_agents_copy_link": "Kopieer link",
|
||||
"com_agents_create_error": "Er is een fout opgetreden bij het aanmaken van je agent.",
|
||||
"com_agents_description_placeholder": "Optioneel: Beschrijf hier je agent",
|
||||
"com_agents_empty_state_heading": "Geen agents gevonden",
|
||||
"com_agents_enable_file_search": "File Search inschakelen",
|
||||
"com_agents_error_bad_request_message": "De aanvraag kon niet worden verwerkt.",
|
||||
"com_agents_error_bad_request_suggestion": "Controleer uw invoer en probeer het opnieuw.",
|
||||
"com_agents_error_invalid_request": "Ongeldige aanvraag",
|
||||
"com_agents_file_context": "File Context (OCR)",
|
||||
"com_agents_file_context_disabled": "Agent moet worden aangemaakt voordat bestanden worden geüpload voor File Context",
|
||||
"com_agents_file_context_info": "Bestanden die als \"Context\" worden geüpload, worden verwerkt met OCR voor tekstherkenning. De tekst wordt daarna toegevoegd aan de instructies van de Agent. Ideaal voor documenten, afbeeldingen met tekst of PDF's waarvan je de volledige tekstinhoud nodig hebt.\"",
|
||||
@@ -110,7 +100,6 @@
|
||||
"com_auth_error_login_rl": "Te veel inlogpogingen in een korte tijd. Probeer het later nog eens.",
|
||||
"com_auth_error_login_server": "Er was een interne serverfout. Wacht een paar momenten en probeer het opnieuw.",
|
||||
"com_auth_error_login_unverified": "Je account is nog niet geverifieerd. Controleer je e-mail voor een verificatielink.",
|
||||
"com_auth_error_oauth_failed": "Authenticatie mislukt. Controleer uw inlogmethode en probeer het opnieuw.",
|
||||
"com_auth_facebook_login": "Inloggen met Facebook",
|
||||
"com_auth_full_name": "Volledige naam",
|
||||
"com_auth_github_login": "Inloggen met Github",
|
||||
@@ -215,7 +204,6 @@
|
||||
"com_endpoint_openai_max": "Het max. aantal tokens dat kan worden gegenereerd. De totale lengte van invoer-tokens en gegenereerde tokens is beperkt door de contextlengte van het model.",
|
||||
"com_endpoint_openai_pres": "Getal tussen -2,0 en 2,0. Positieve waarden straffen nieuwe tokens op basis van of ze al voorkomen in de tekst tot nu toe, waardoor de kans dat het model over nieuwe onderwerpen praat toeneemt.",
|
||||
"com_endpoint_openai_prompt_prefix_placeholder": "Stel aangepaste instructies in om op te nemen in Systeembericht. Standaard: geen",
|
||||
"com_endpoint_openai_reasoning_effort": "Alleen voor redeneringsmodellen: beperkt de inspanning voor redeneren. Het verminderen van de redeneringsinspanning kan leiden tot snellere antwoorden en minder tokens die worden gebruikt voor redeneren in een antwoord. 'Minimaal' produceert zeer weinig redeneringstokens voor de snelste tijd tot het eerste token, vooral geschikt voor coderen en instructies volgen.",
|
||||
"com_endpoint_openai_temp": "Hogere waarden = meer willekeurig, terwijl lagere waarden = meer gericht en deterministisch. We raden aan dit of Top P te wijzigen, maar niet beide.",
|
||||
"com_endpoint_openai_topp": "Een alternatief voor sampling met temperatuur, genaamd nucleus sampling, waarbij het model de resultaten van de tokens met de top_p waarschijnlijkheidsmassa in overweging neemt. Dus 0,1 betekent dat alleen de tokens die de bovenste 10% waarschijnlijkheidsmassa omvatten, in overweging worden genomen. We raden aan dit of temperatuur te wijzigen, maar niet beide.",
|
||||
"com_endpoint_output": "Uitvoer",
|
||||
@@ -302,10 +290,6 @@
|
||||
"com_nav_theme_system": "Systeem",
|
||||
"com_nav_user": "GEBRUIKER",
|
||||
"com_ui_accept": "Ik accepteer",
|
||||
"com_ui_agent_duplicate_error": "Er is een fout opgetreden bij het dupliceren van de agent",
|
||||
"com_ui_agent_duplicated": "Agent succesvol gedupliceerd",
|
||||
"com_ui_agent_name_is_required": "Agentnaam is verplicht",
|
||||
"com_ui_agent_recursion_limit": "Maximale agentstappen",
|
||||
"com_ui_all": "alle",
|
||||
"com_ui_archive": "Archiveren",
|
||||
"com_ui_archive_error": "Kan conversatie niet archiveren",
|
||||
|
||||
@@ -4,72 +4,31 @@
|
||||
"com_a11y_ai_composing": "A IA ainda está compondo.",
|
||||
"com_a11y_end": "A IA terminou de responder.",
|
||||
"com_a11y_start": "A IA começou a responder.",
|
||||
"com_agents_all": "Todos os Agentes",
|
||||
"com_agents_all_category": "Todos",
|
||||
"com_agents_all_description": "Navegar por todos os agentes compartilhados em todas as categorias",
|
||||
"com_agents_by_librechat": "por LibreChat",
|
||||
"com_agents_category_aftersales": "Pós-vendas",
|
||||
"com_agents_category_finance": "Financeiro",
|
||||
"com_agents_category_general": "Geral",
|
||||
"com_agents_category_hr": "Recursos Humanos",
|
||||
"com_agents_category_it": "TI",
|
||||
"com_agents_category_rd": "Pesquisa e Desenvolvimento",
|
||||
"com_agents_category_sales": "Vendas",
|
||||
"com_agents_category_tabs_label": "Categorias de Agentes",
|
||||
"com_agents_clear_search": "Limpar pesquisa",
|
||||
"com_agents_code_interpreter": "Quando ativado, permite que seu agente aproveite a API do interpretador de código LibreChat para executar o código gerado, incluindo o processamento de arquivos, com segurança. Requer uma chave de API válida.",
|
||||
"com_agents_code_interpreter_title": "API do Interpretador de Código",
|
||||
"com_agents_contact": "Contato",
|
||||
"com_agents_copy_link": "Copiar link",
|
||||
"com_agents_create_error": "Houve um erro ao criar seu agente.",
|
||||
"com_agents_created_by": "por",
|
||||
"com_agents_description_placeholder": "Opcional: Descreva seu Agente aqui",
|
||||
"com_agents_empty_state_heading": "Nenhum agente encontrado",
|
||||
"com_agents_enable_file_search": "Permitir Pesquisa de Ficheiros",
|
||||
"com_agents_error_category_title": "Erro na categoria",
|
||||
"com_agents_error_generic": "Houve um problema ao carregar o contexto.",
|
||||
"com_agents_error_loading": "Erro ao carregar agentes",
|
||||
"com_agents_error_network_title": "Há um problema de conexão",
|
||||
"com_agents_error_retry": "Tente novamente",
|
||||
"com_agents_error_search_title": "Erro na pesquisa",
|
||||
"com_agents_error_searching": "Erro ao procurar agentes",
|
||||
"com_agents_error_server_message": "O servidor esta temporariamente indisponível",
|
||||
"com_agents_error_server_title": "Erro no servidor",
|
||||
"com_agents_error_timeout_title": "A conexão expirou",
|
||||
"com_agents_error_title": "Algo deu errado",
|
||||
"com_agents_file_context": "Contexto de arquivo (OCR)",
|
||||
"com_agents_file_context_disabled": "O agente deve ser criado antes de carregar arquivos para o Contexto de Arquivo.",
|
||||
"com_agents_file_context_info": "Os arquivos carregados como \"Contexto\" são processados usando OCR para extrair texto, que é então adicionado às instruções do Agente. Ideal para documentos, imagens com texto ou PDFs onde você precisa do conteúdo de texto completo de um arquivo",
|
||||
"com_agents_file_search_disabled": "O agente deve ser criado antes de carregar arquivos para Pesquisa de Arquivos.",
|
||||
"com_agents_file_search_info": "Quando ativado, o agente será informado dos nomes exatos dos arquivos listados abaixo, permitindo que ele recupere o contexto relevante desses arquivos.",
|
||||
"com_agents_instructions_placeholder": "As instruções do sistema que o agente usa",
|
||||
"com_agents_link_copied": "Link copiado",
|
||||
"com_agents_link_copy_failed": "Falha ao copiar o link",
|
||||
"com_agents_loading": "Carregando...",
|
||||
"com_agents_marketplace": "Marketplace de Agentes",
|
||||
"com_agents_mcp_description_placeholder": "Explique o que ele faz em poucas palavras",
|
||||
"com_agents_mcp_icon_size": "Tamanho mínimo 128 x 128 px",
|
||||
"com_agents_mcp_info": "Adicione servidores MCP ao seu agente para permitir que ele execute tarefas e interaja com serviços externos",
|
||||
"com_agents_mcp_name_placeholder": "Ferramenta personalizada",
|
||||
"com_agents_mcp_trust_subtext": "Conectores personalizados não são verificados pelo LibreChat",
|
||||
"com_agents_mcps_disabled": "Você precisa criar um agente antes de adicionar MCPs.",
|
||||
"com_agents_missing_name": "Digite um nome para criar um agente",
|
||||
"com_agents_missing_provider_model": "Selecione um provedor e um modelo antes de criar um agente.\n",
|
||||
"com_agents_name_placeholder": "Opcional: O nome do agente",
|
||||
"com_agents_no_access": "Não tens permissões para editar este agente.",
|
||||
"com_agents_no_agent_id_error": "Nenhum ID de agente encontrado. Certifique-se de que o agente seja criado primeiro.",
|
||||
"com_agents_no_more_results": "Você chegou ao fim dos resultados",
|
||||
"com_agents_not_available": "Agente não disponível.",
|
||||
"com_agents_recommended": "Agentes recomendados",
|
||||
"com_agents_results_for": "Resultados para '{{query}}'",
|
||||
"com_agents_search_aria": "Buscar agentes",
|
||||
"com_agents_search_empty_heading": "Sem resultados na busca",
|
||||
"com_agents_search_info": "Quando ativado, permite seu agente buscar informações atualizadas na web. Requer uma chave de API válida.",
|
||||
"com_agents_search_instructions": "Utilize nome ou descrição para pesquisar agentes",
|
||||
"com_agents_search_name": "Pesquisar agentes por nome",
|
||||
"com_agents_search_placeholder": "Pesquisar agentes...",
|
||||
"com_agents_see_more": "Ver mais",
|
||||
"com_agents_start_chat": "Iniciar chat",
|
||||
"com_agents_update_error": "Houve um erro ao atualizar seu agente.",
|
||||
"com_assistants_action_attempt": "Assistente quer falar com {{0}}",
|
||||
"com_assistants_actions": "Ações",
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
"com_a11y_ai_composing": "A IA ainda está a escrever.",
|
||||
"com_a11y_end": "A IA terminou de responder.",
|
||||
"com_a11y_start": "A IA começou a responder.",
|
||||
"com_agents_agent_card_label": "{{name}} agente. {{description}}",
|
||||
"com_agents_all": "Todos os Agentes",
|
||||
"com_agents_all_category": "Todos",
|
||||
"com_agents_by_librechat": "por LibreChat",
|
||||
"com_agents_code_interpreter": "Quando ativo, permite que os seus agentes usem a API de Interpretação de código do LibreChat para correr código gerado, inclusivé processamento de ficheiros em segurança. Requer uma chave API válida.",
|
||||
"com_agents_code_interpreter_title": "API de Interpretação de Código",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"chat_direction_left_to_right": "Здесь пока пусто.",
|
||||
"com_a11y_ai_composing": "ИИ продолжает составлять ответ",
|
||||
"com_a11y_end": "ИИ закончил свой ответ",
|
||||
"com_a11y_start": "ИИ начал отвечать",
|
||||
@@ -215,8 +214,6 @@
|
||||
"com_endpoint_openai_stop": "До 4 последовательностей, после которых API прекратит генерировать дальнейшие токены.",
|
||||
"com_endpoint_openai_temp": "Более высокие значения = более случайные результаты, более низкие значения = более фокусированные и детерминированные результаты. Мы рекомендуем изменять это или Top P, но не оба значения одновременно.",
|
||||
"com_endpoint_openai_topp": "Альтернатива выбору с использованием температуры, называемая выбором по ядру, при которой модель учитывает результаты токенов с наибольшей вероятностью top_p. Таким образом, значение 0,1 означает, что рассматриваются только токены, составляющие верхние 10% вероятностной массы. Мы рекомендуем изменять это или температуру, но не оба значения одновременно.",
|
||||
"com_endpoint_openai_use_responses_api": "Используйте Responses API вместо завершения чата, который включает расширенные функции от OpenAI. Требуется для o1-pro, o3-pro и для включения обобщений рассуждений.",
|
||||
"com_endpoint_openai_use_web_search": "Включите функцию веб-поиска с помощью встроенных поисковых возможностей OpenAI. Это позволит модели искать в Интернете актуальную информацию и предоставлять более точные и свежие ответы.",
|
||||
"com_endpoint_output": "Вывод",
|
||||
"com_endpoint_plug_image_detail": "Детали изображения",
|
||||
"com_endpoint_plug_resend_files": "Повторить отправку файлов",
|
||||
@@ -246,7 +243,6 @@
|
||||
"com_endpoint_prompt_prefix_assistants_placeholder": "Задайте дополнительные инструкции или контекст сверху основных инструкций ассистента. Игнорируется, если пусто.",
|
||||
"com_endpoint_prompt_prefix_placeholder": "Задайте пользовательские инструкции или контекст. Игнорируется, если пусто.",
|
||||
"com_endpoint_reasoning_effort": "Затраты на рассуждение",
|
||||
"com_endpoint_reasoning_summary": "Саммари рассуждений",
|
||||
"com_endpoint_save_as_preset": "Сохранить как Пресет",
|
||||
"com_endpoint_search": "Поиск эндпоинта по имени",
|
||||
"com_endpoint_search_endpoint_models": "Поиск {{0}} моделей...",
|
||||
@@ -263,7 +259,6 @@
|
||||
"com_endpoint_top_k": "Top K",
|
||||
"com_endpoint_top_p": "Top P",
|
||||
"com_endpoint_use_active_assistant": "Использовать активного ассистента",
|
||||
"com_endpoint_use_responses_api": "Использовать Responses API",
|
||||
"com_error_expired_user_key": "Предоставленный ключ для {{0}} истек {{1}}. Пожалуйста, укажите новый ключ и повторите попытку.",
|
||||
"com_error_files_dupe": "Обнаружен дублирующийся файл",
|
||||
"com_error_files_empty": "Пустые файлы не допускаются",
|
||||
@@ -300,25 +295,6 @@
|
||||
"com_nav_auto_transcribe_audio": "Автоматическая транскрипция",
|
||||
"com_nav_automatic_playback": "Автовоспроизведение последнего сообщения",
|
||||
"com_nav_balance": "Баланс",
|
||||
"com_nav_balance_auto_refill_disabled": "Автоматическое пополнение отключено.",
|
||||
"com_nav_balance_auto_refill_error": "Ошибка при загрузке настроек автоматического пополнения.",
|
||||
"com_nav_balance_day": "день",
|
||||
"com_nav_balance_days": "дн.",
|
||||
"com_nav_balance_hour": "час",
|
||||
"com_nav_balance_hours": "час.",
|
||||
"com_nav_balance_interval": "Интервал:",
|
||||
"com_nav_balance_last_refill": "Последнее автопополнение:",
|
||||
"com_nav_balance_minute": "минуту",
|
||||
"com_nav_balance_minutes": "мин.",
|
||||
"com_nav_balance_month": "месяц",
|
||||
"com_nav_balance_months": "мес.",
|
||||
"com_nav_balance_next_refill": "Следующее автопополнение:",
|
||||
"com_nav_balance_next_refill_info": "Следующее пополнение произойдёт автоматически только тогда, когда одновременно выполняются два условия: прошёл установленный интервал времени с последнего пополнения, и отправка запроса привела бы к отрицательному балансу.",
|
||||
"com_nav_balance_refill_amount": "Сумма пополнения:",
|
||||
"com_nav_balance_second": "секунду",
|
||||
"com_nav_balance_seconds": "сек.",
|
||||
"com_nav_balance_week": "неделю",
|
||||
"com_nav_balance_weeks": "нед.",
|
||||
"com_nav_browser": "Браузер",
|
||||
"com_nav_center_chat_input": "Центрировать поле ввода чата на экране приветствия",
|
||||
"com_nav_change_picture": "Изменить изображение",
|
||||
@@ -431,11 +407,9 @@
|
||||
"com_nav_search_placeholder": "Поиск сообщений",
|
||||
"com_nav_send_message": "Отправить сообщение",
|
||||
"com_nav_setting_account": "Аккаунт",
|
||||
"com_nav_setting_balance": "Баланс",
|
||||
"com_nav_setting_chat": "Чат",
|
||||
"com_nav_setting_data": "Управление данными",
|
||||
"com_nav_setting_general": "Общие",
|
||||
"com_nav_setting_personalization": "Персонализация",
|
||||
"com_nav_setting_speech": "Голос",
|
||||
"com_nav_settings": "Настройки",
|
||||
"com_nav_shared_links": "Связываемые ссылки",
|
||||
@@ -533,7 +507,6 @@
|
||||
"com_ui_auth_url": "URL авторизации",
|
||||
"com_ui_authentication": "Аутентификация",
|
||||
"com_ui_authentication_type": "Тип аутентификации",
|
||||
"com_ui_auto": "Авто",
|
||||
"com_ui_avatar": "Аватар",
|
||||
"com_ui_azure": "Azure",
|
||||
"com_ui_back_to_chat": "Вернуться к чату",
|
||||
@@ -578,7 +551,6 @@
|
||||
"com_ui_command_placeholder": "Необязательно: введите команду для промта или будет использовано название",
|
||||
"com_ui_command_usage_placeholder": "Выберите промпт по команде или названию",
|
||||
"com_ui_complete_setup": "Завершить настройку",
|
||||
"com_ui_concise": "Кратко",
|
||||
"com_ui_confirm_action": "Подтвердить действие",
|
||||
"com_ui_confirm_admin_use_change": "Изменение этого параметра заблокирует доступ для администраторов, включая вас. Вы уверены, что хотите продолжить?",
|
||||
"com_ui_confirm_change": "Подтвердить изменения",
|
||||
@@ -634,7 +606,6 @@
|
||||
"com_ui_descending": "По убыванию",
|
||||
"com_ui_description": "Описание",
|
||||
"com_ui_description_placeholder": "Дополнительно: введите описание для промта",
|
||||
"com_ui_detailed": "Подробно",
|
||||
"com_ui_disabling": "Отключение...",
|
||||
"com_ui_download": "Скачать",
|
||||
"com_ui_download_artifact": "Скачать артифакт",
|
||||
@@ -700,7 +671,6 @@
|
||||
"com_ui_good_morning": "Доброе утро",
|
||||
"com_ui_happy_birthday": "Это мой первый день рождения!",
|
||||
"com_ui_hide_qr": "Скрыть QR код",
|
||||
"com_ui_high": "Высокое",
|
||||
"com_ui_host": "Хост",
|
||||
"com_ui_idea": "Идеи",
|
||||
"com_ui_image_created": "Изображение создано",
|
||||
@@ -724,17 +694,9 @@
|
||||
"com_ui_loading": "Загрузка...",
|
||||
"com_ui_locked": "Заблокировано",
|
||||
"com_ui_logo": "Логотип {{0}}",
|
||||
"com_ui_low": "Низкое",
|
||||
"com_ui_manage": "Управление",
|
||||
"com_ui_max_tags": "Максимально допустимое количество - {{0}}, используются последние значения.",
|
||||
"com_ui_mcp_servers": "MCP серверы",
|
||||
"com_ui_medium": "Средний",
|
||||
"com_ui_memories_allow_create": "Разрешить создание памяти",
|
||||
"com_ui_memories_allow_opt_out": "Разрешить пользователям отказаться от памяти",
|
||||
"com_ui_memories_allow_update": "Разрешить обновление памяти",
|
||||
"com_ui_memories_allow_use": "Разрешить использование памяти",
|
||||
"com_ui_memories_filter": "Отфильтровать память",
|
||||
"com_ui_memory": "Память",
|
||||
"com_ui_mention": "Упомянуть конечную точку, помощника или предустановку для быстрого переключения",
|
||||
"com_ui_min_tags": "Нельзя удалить больше значений, требуется минимум {{0}}.",
|
||||
"com_ui_misc": "Разное",
|
||||
@@ -777,8 +739,6 @@
|
||||
"com_ui_provider": "Провайдер",
|
||||
"com_ui_read_aloud": "Прочитать вслух",
|
||||
"com_ui_redirecting_to_provider": "Перенаправление на {{0}}, пожалуйста, подождите...",
|
||||
"com_ui_reference_saved_memories": "Ссылка на сохраненную память",
|
||||
"com_ui_reference_saved_memories_description": "Разрешить помощнику ссылаться на сохраненную память и использовать её при ответе",
|
||||
"com_ui_refresh_link": "Обновить ссылку",
|
||||
"com_ui_regenerate": "Повторная генерация",
|
||||
"com_ui_regenerate_backup": "Сгенерировать резервные коды заново",
|
||||
@@ -877,7 +837,6 @@
|
||||
"com_ui_upload_type": "Выберите тип загрузки",
|
||||
"com_ui_use_2fa_code": "Использовать код 2FA вместо этого",
|
||||
"com_ui_use_backup_code": "Использовать резервный код вместо этого",
|
||||
"com_ui_use_memory": "Использовать память",
|
||||
"com_ui_use_micrphone": "Использовать микрофон",
|
||||
"com_ui_used": "Использован",
|
||||
"com_ui_variables": "Переменные",
|
||||
@@ -886,7 +845,6 @@
|
||||
"com_ui_version_var": "Версия {{0}}",
|
||||
"com_ui_versions": "Версии",
|
||||
"com_ui_view_source": "Просмотреть исходный чат",
|
||||
"com_ui_web_search": "Веб-поиск",
|
||||
"com_ui_weekend_morning": "Хороших выходных",
|
||||
"com_ui_write": "Письмо",
|
||||
"com_ui_x_selected": "{{0}} выбрано",
|
||||
|
||||
@@ -198,7 +198,6 @@
|
||||
"com_endpoint_openai_max": "Max tokens att generera. Den totala längden på tokens för inmatning och svar är begränsad av modellen som används.",
|
||||
"com_endpoint_openai_pres": "Nummer mellan -2,0 och 2,0. Positiva värden minskar nya tokens baserat på om de förekommer i texten hittills, vilket ökar modellens sannolikhet att prata om nya ämnen.",
|
||||
"com_endpoint_openai_prompt_prefix_placeholder": "Ange anpassade instruktioner att inkludera i Systemmeddelande. Standard: inga",
|
||||
"com_endpoint_openai_reasoning_effort": "Endast resonerande modeller: begränsar ansträngningen för att resonera. Minskad ansträngning för resonemang kan resultera i snabbare svar och färre tokens som används för resonemang i ett svar. \"Minimal\" producerar mycket få resonemangstoken för snabbast tid-till-första-token, särskilt väl lämpad för kodning och för att följa instruktioner.",
|
||||
"com_endpoint_openai_temp": "Högre värden = mer slumpmässigt, medan lägre värden = mer fokuserat och bestämt. Vi rekommenderar att ändra detta eller Top P men inte båda.",
|
||||
"com_endpoint_openai_topp": "Ett alternativ till temperatur, kallat kärnprovtagning, där modellen beaktar resultaten av tokens med top_p-sannolikhetsmassa. Så 0,1 innebär att endast de tokens som utgör den översta 10% sannolikhetsmassan beaktas. Vi rekommenderar att ändra detta eller temperaturen men inte båda.",
|
||||
"com_endpoint_output": "Utdata",
|
||||
@@ -359,8 +358,6 @@
|
||||
"com_sources_title": "Källor",
|
||||
"com_ui_accept": "Jag accepterar",
|
||||
"com_ui_add": "Lägg till",
|
||||
"com_ui_agent_duplicate_error": "Det uppstod ett fel vid dupliceringen av agenten",
|
||||
"com_ui_agent_duplicated": "Agent duplicerad framgångsrikt",
|
||||
"com_ui_agents": "Agenter",
|
||||
"com_ui_agents_allow_create": "Tillåt att skapa agenter",
|
||||
"com_ui_agents_allow_use": "Tillåt användning av agenter",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -103,7 +103,6 @@
|
||||
"com_assistants_actions_disabled": "您需要先创建助手,然后才能添加操作。",
|
||||
"com_assistants_actions_info": "让您的助手通过 API 检索信息或执行操作",
|
||||
"com_assistants_add_actions": "添加操作",
|
||||
"com_assistants_add_mcp_server_tools": "添加 MCP 服务器工具",
|
||||
"com_assistants_add_tools": "添加工具",
|
||||
"com_assistants_allow_sites_you_trust": "只允许您信任的网站",
|
||||
"com_assistants_append_date": "添加当前日期和时间",
|
||||
@@ -520,7 +519,6 @@
|
||||
"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_thai": "ไทย",
|
||||
@@ -581,7 +579,6 @@
|
||||
"com_nav_tool_dialog": "助手工具",
|
||||
"com_nav_tool_dialog_agents": "智能体工具",
|
||||
"com_nav_tool_dialog_description": "必须保存助手才能保留工具选择。",
|
||||
"com_nav_tool_dialog_mcp_server_tools": "MCP 服务器工具",
|
||||
"com_nav_tool_remove": "移除",
|
||||
"com_nav_tool_search": "搜索工具",
|
||||
"com_nav_user": "默认用户",
|
||||
@@ -768,7 +765,6 @@
|
||||
"com_ui_complete_setup": "完成设置",
|
||||
"com_ui_concise": "简洁",
|
||||
"com_ui_configure_mcp_variables_for": "配置变量:{{0}}",
|
||||
"com_ui_confirm": "确认",
|
||||
"com_ui_confirm_action": "确认执行",
|
||||
"com_ui_confirm_admin_use_change": "更改此设置将阻止包括您的在内的所有管理员的权限。您确定要继续吗?",
|
||||
"com_ui_confirm_change": "确认更改",
|
||||
@@ -833,8 +829,6 @@
|
||||
"com_ui_delete_success": "已成功删除",
|
||||
"com_ui_delete_tool": "删除工具",
|
||||
"com_ui_delete_tool_confirm": "您确定要删除此工具吗?",
|
||||
"com_ui_delete_tool_error": "删除工具时发生错误:{{error}}",
|
||||
"com_ui_delete_tool_success": "工具删除成功",
|
||||
"com_ui_deleted": "已删除",
|
||||
"com_ui_deleting_file": "删除文件中...",
|
||||
"com_ui_descending": "降序",
|
||||
@@ -894,8 +888,6 @@
|
||||
"com_ui_field_max_length": "{{field}} 最多 {{length}} 个字符",
|
||||
"com_ui_field_required": "此字段为必填项",
|
||||
"com_ui_file_size": "文件大小",
|
||||
"com_ui_file_token_limit": "文件词元数限制",
|
||||
"com_ui_file_token_limit_desc": "为文件处理设定最大词元数限制,以控制成本和资源使用",
|
||||
"com_ui_files": "文件",
|
||||
"com_ui_filter_prompts": "筛选提示词",
|
||||
"com_ui_filter_prompts_name": "根据名称筛选提示词",
|
||||
@@ -956,7 +948,6 @@
|
||||
"com_ui_import_conversation_info": "从 JSON 文件导入对话",
|
||||
"com_ui_import_conversation_success": "对话导入成功",
|
||||
"com_ui_include_shadcnui": "包含 shadcn/ui 组件指令",
|
||||
"com_ui_initializing": "初始化中...",
|
||||
"com_ui_input": "输入",
|
||||
"com_ui_instructions": "指令",
|
||||
"com_ui_key": "键",
|
||||
@@ -976,8 +967,6 @@
|
||||
"com_ui_marketplace_allow_use": "允许使用市场",
|
||||
"com_ui_max_tags": "最多允许 {{0}} 个,用最新值。",
|
||||
"com_ui_mcp_authenticated_success": "MCP 服务器 “{{0}}” 认证成功",
|
||||
"com_ui_mcp_configure_server": "配置 {{0}}",
|
||||
"com_ui_mcp_configure_server_description": "配置自定义变量:{{0}}",
|
||||
"com_ui_mcp_enter_var": "输入值:{{0}}",
|
||||
"com_ui_mcp_init_failed": "初始化 MCP 服务器失败",
|
||||
"com_ui_mcp_initialize": "初始化",
|
||||
@@ -1206,10 +1195,8 @@
|
||||
"com_ui_travel": "旅行",
|
||||
"com_ui_trust_app": "我信任此应用",
|
||||
"com_ui_try_adjusting_search": "尝试调整您的搜索条件",
|
||||
"com_ui_ui_resources": "UI 资源",
|
||||
"com_ui_unarchive": "取消归档",
|
||||
"com_ui_unarchive_error": "取消归档对话失败",
|
||||
"com_ui_unavailable": "不可用",
|
||||
"com_ui_unknown": "未知",
|
||||
"com_ui_unset": "取消设置",
|
||||
"com_ui_untitled": "无标题",
|
||||
|
||||
235
package-lock.json
generated
235
package-lock.json
generated
@@ -59,12 +59,12 @@
|
||||
"@googleapis/youtube": "^20.0.0",
|
||||
"@keyv/redis": "^4.3.3",
|
||||
"@langchain/community": "^0.3.47",
|
||||
"@langchain/core": "^0.3.72",
|
||||
"@langchain/core": "^0.3.62",
|
||||
"@langchain/google-genai": "^0.2.13",
|
||||
"@langchain/google-vertexai": "^0.2.13",
|
||||
"@langchain/openai": "^0.5.18",
|
||||
"@langchain/textsplitters": "^0.1.0",
|
||||
"@librechat/agents": "^3.0.0-rc10",
|
||||
"@librechat/agents": "^2.4.76",
|
||||
"@librechat/api": "*",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||
@@ -2633,7 +2633,6 @@
|
||||
"@headlessui/react": "^2.1.2",
|
||||
"@librechat/client": "*",
|
||||
"@marsidev/react-turnstile": "^1.1.0",
|
||||
"@mcp-ui/client": "^5.7.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.3",
|
||||
@@ -21355,9 +21354,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/core": {
|
||||
"version": "0.3.72",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.72.tgz",
|
||||
"integrity": "sha512-WsGWVZYnlKffj2eEfDocPNiaTRoxyYiLSQdQ7oxZvxGZBqo/90vpjbC33UGK1uPNBM4kT+pkdaol/MnvKUh8TQ==",
|
||||
"version": "0.3.62",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.62.tgz",
|
||||
"integrity": "sha512-GqRTcoUPnozGRMUcA6QkP7LHL/OvanGdB51Jgb0w7IIPDI3wFugxMHZ4gphnGDtxsD1tQY5ykyEpYNxFK8kl1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cfworker/json-schema": "^4.0.2",
|
||||
@@ -21365,7 +21364,7 @@
|
||||
"camelcase": "6",
|
||||
"decamelize": "1.2.0",
|
||||
"js-tiktoken": "^1.0.12",
|
||||
"langsmith": "^0.3.46",
|
||||
"langsmith": "^0.3.33",
|
||||
"mustache": "^4.2.0",
|
||||
"p-queue": "^6.6.2",
|
||||
"p-retry": "4",
|
||||
@@ -21612,13 +21611,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/langgraph": {
|
||||
"version": "0.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.4.9.tgz",
|
||||
"integrity": "sha512-+rcdTGi4Ium4X/VtIX3Zw4RhxEkYWpwUyz806V6rffjHOAMamg6/WZDxpJbrP33RV/wJG1GH12Z29oX3Pqq3Aw==",
|
||||
"version": "0.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.3.12.tgz",
|
||||
"integrity": "sha512-4jKvfmxxgQyKnCvXdFbcKt6MdfaJoQ2WWqBR16o2E6D2RxqHvnLMMClZh4FSd6WYw39z5LGWvzRapFbRMqxu1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@langchain/langgraph-checkpoint": "^0.1.1",
|
||||
"@langchain/langgraph-sdk": "~0.1.0",
|
||||
"@langchain/langgraph-checkpoint": "~0.0.18",
|
||||
"@langchain/langgraph-sdk": "~0.0.102",
|
||||
"uuid": "^10.0.0",
|
||||
"zod": "^3.25.32"
|
||||
},
|
||||
@@ -21636,9 +21635,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/langgraph-checkpoint": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.1.1.tgz",
|
||||
"integrity": "sha512-h2bP0RUikQZu0Um1ZUPErQLXyhzroJqKRbRcxYRTAh49oNlsfeq4A3K4YEDRbGGuyPZI/Jiqwhks1wZwY73AZw==",
|
||||
"version": "0.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.18.tgz",
|
||||
"integrity": "sha512-IS7zJj36VgY+4pf8ZjsVuUWef7oTwt1y9ylvwu0aLuOn1d0fg05Om9DLm3v2GZ2Df6bhLV1kfWAM0IAl9O5rQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uuid": "^10.0.0"
|
||||
@@ -21647,7 +21646,7 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": ">=0.2.31 <0.4.0 || ^1.0.0-alpha"
|
||||
"@langchain/core": ">=0.2.31 <0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/langgraph-checkpoint/node_modules/uuid": {
|
||||
@@ -21664,9 +21663,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/langgraph-sdk": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.1.0.tgz",
|
||||
"integrity": "sha512-1EKwzwJpgpNqLcRuGG+kLvvhAaPiFWZ9shl/obhL8qDKtYdbR67WCYE+2jUObZ8vKQuCoul16ewJ78g5VrZlKA==",
|
||||
"version": "0.0.104",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.104.tgz",
|
||||
"integrity": "sha512-wUO6GMy65Y7DsWtjTJ3dA59enrZy2wN4o48AMYN7dF7u/PMXXYyBjBCKSzgVWqO6uWH2yNpyGDrcMwKuk5kQLA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15",
|
||||
@@ -21909,19 +21908,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@librechat/agents": {
|
||||
"version": "3.0.0-rc10",
|
||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.0.0-rc10.tgz",
|
||||
"integrity": "sha512-i1d+0jWwFjEkxne9+a/Y4ugTBg/RC604PK+OXz6fpfVnbJ4YxF2nd61BzflKbkU9rrnE0o8ogSx59Mc2vGhDjg==",
|
||||
"version": "2.4.76",
|
||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.76.tgz",
|
||||
"integrity": "sha512-DkWKpKcLgv9tA6bXJ8pSzHOA3iZRFQRt9oBjEEeW0onhEdPTmHVR3/dY5bxMKSP8rlA65M0yx1KaoLL8bhg06Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@langchain/anthropic": "^0.3.26",
|
||||
"@langchain/aws": "^0.1.12",
|
||||
"@langchain/community": "^0.3.47",
|
||||
"@langchain/core": "^0.3.72",
|
||||
"@langchain/core": "^0.3.62",
|
||||
"@langchain/deepseek": "^0.0.2",
|
||||
"@langchain/google-genai": "^0.2.13",
|
||||
"@langchain/google-vertexai": "^0.2.13",
|
||||
"@langchain/langgraph": "^0.4.9",
|
||||
"@langchain/langgraph": "^0.3.4",
|
||||
"@langchain/mistralai": "^0.2.1",
|
||||
"@langchain/ollama": "^0.2.3",
|
||||
"@langchain/openai": "0.5.18",
|
||||
@@ -22547,21 +22546,6 @@
|
||||
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mcp-ui/client": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@mcp-ui/client/-/client-5.7.0.tgz",
|
||||
"integrity": "sha512-+HbPw3VS46WUSWmyJ34ZVnygb81QByA3luR6y0JDbyDZxjYtHw1FcIN7v9WbbE8PrfI0WcuWCSiNOO6sOGbwpQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "*",
|
||||
"@quilted/threads": "^3.1.3",
|
||||
"@r2wc/react-to-web-component": "^2.0.4",
|
||||
"@remote-dom/core": "^1.8.0",
|
||||
"@remote-dom/react": "^1.2.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/eslint-formatter-sarif": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/eslint-formatter-sarif/-/eslint-formatter-sarif-3.1.0.tgz",
|
||||
@@ -23245,16 +23229,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@preact/signals-core": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.11.0.tgz",
|
||||
"integrity": "sha512-jglbibeWHuFRzEWVFY/TT7wB1PppJxmcSfUHcK+2J9vBRtiooMfw6tAPttojNYrrpdGViqAYCbPpmWYlMm+eMQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
@@ -23309,57 +23283,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"node_modules/@quilted/events": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@quilted/events/-/events-2.1.3.tgz",
|
||||
"integrity": "sha512-4fHaSLND8rmZ+tce9/4FNmG5UWTRpFtM54kOekf3tLON4ZLLnYzjjldELD35efd7+lT5+E3cdkacqc56d+kCrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@preact/signals-core": "^1.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@quilted/threads": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@quilted/threads/-/threads-3.3.1.tgz",
|
||||
"integrity": "sha512-0ASnjTH+hOu1Qwzi9NnsVcsbMhWVx8pEE8SXIHknqcc/1rXAU0QlKw9ARq0W43FAdzyVeuXeXtZN27ZC0iALKg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@quilted/events": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@preact/signals-core": "^1.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@preact/signals-core": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@r2wc/core": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@r2wc/core/-/core-1.2.0.tgz",
|
||||
"integrity": "sha512-vAfiuS5KywtV54SRzc4maEHcpdgeUyJzln+ATpNCOkO+ArIuOkTXd92b5YauVAd0A8B2rV/y9OeVW19vb73bUQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@r2wc/react-to-web-component": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@r2wc/react-to-web-component/-/react-to-web-component-2.0.4.tgz",
|
||||
"integrity": "sha512-g1dtTTEGETNUimYldTW+2hxY3mmJZjzPEca0vqCutUht2GHmpK9mT5r/urmEI7uSbOkn6HaymosgVy26lvU1JQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@r2wc/core": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/number": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
|
||||
@@ -26963,61 +26886,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@remote-dom/core": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@remote-dom/core/-/core-1.9.0.tgz",
|
||||
"integrity": "sha512-h8OO2NRns2paXO/q5hkfXrwlZKq7oKj9XedGosi7J8OP3+aW7N2Gv4MBBVVQGCfOiZPkOj5m3sQH7FdyUWl7PQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remote-dom/polyfill": "^1.4.4",
|
||||
"htm": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@preact/signals-core": "^1.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@preact/signals-core": {
|
||||
"optional": true
|
||||
},
|
||||
"preact": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@remote-dom/polyfill": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@remote-dom/polyfill/-/polyfill-1.4.5.tgz",
|
||||
"integrity": "sha512-V1qkKIl/wXyDO0I+tQDH06cBBNyyViZF3IYorkTTBf58dorqOP5Ta51vCCWeekPgdSOPuEKvHhvu6kAaKqVgww==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@remote-dom/react": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@remote-dom/react/-/react-1.2.2.tgz",
|
||||
"integrity": "sha512-PkvioODONTr1M0StGDYsR4Ssf5M0Rd4+IlWVvVoK3Zrw8nr7+5mJkgNofaj/z7i8Aep78L28PCW8/WduUt4unA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remote-dom/core": "^1.7.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"htm": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-alias": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.0.tgz",
|
||||
@@ -36741,12 +36609,6 @@
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/htm": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz",
|
||||
"integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
@@ -39297,9 +39159,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/langsmith": {
|
||||
"version": "0.3.67",
|
||||
"resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.67.tgz",
|
||||
"integrity": "sha512-l4y3RmJ9yWF5a29fLg3eWZQxn6Q6dxTOgLGgQHzPGZHF3NUynn+A+airYIe/Yt4rwjGbuVrABAPsXBkVu/Hi7g==",
|
||||
"version": "0.3.33",
|
||||
"resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.33.tgz",
|
||||
"integrity": "sha512-imNIaBL6+ElE5eMzNHYwFxo6W/6rHlqcaUjCYoIeGdCYWlARxE3CTGKul5DJnaUgGP2CTLFeNXyvRx5HWC/4KQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/uuid": "^10.0.0",
|
||||
@@ -39311,21 +39173,9 @@
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "*",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "*",
|
||||
"@opentelemetry/sdk-trace-base": "*",
|
||||
"openai": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentelemetry/exporter-trace-otlp-proto": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentelemetry/sdk-trace-base": {
|
||||
"optional": true
|
||||
},
|
||||
"openai": {
|
||||
"optional": true
|
||||
}
|
||||
@@ -45601,10 +45451,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -45673,16 +45522,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
"scheduler": "^0.23.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-flip-toolkit": {
|
||||
@@ -47604,10 +47452,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
@@ -51983,8 +51830,8 @@
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": "^0.3.72",
|
||||
"@librechat/agents": "^3.0.0-rc10",
|
||||
"@langchain/core": "^0.3.62",
|
||||
"@librechat/agents": "^2.4.76",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||
"axios": "^1.8.2",
|
||||
@@ -52079,7 +51926,7 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@librechat/client",
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.7",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.2",
|
||||
@@ -52485,7 +52332,7 @@
|
||||
},
|
||||
"packages/data-schemas": {
|
||||
"name": "@librechat/data-schemas",
|
||||
"version": "0.0.21",
|
||||
"version": "0.0.20",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
|
||||
@@ -73,8 +73,8 @@
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": "^0.3.72",
|
||||
"@librechat/agents": "^3.0.0-rc10",
|
||||
"@langchain/core": "^0.3.62",
|
||||
"@librechat/agents": "^2.4.76",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||
"axios": "^1.8.2",
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { PromptTemplate } from '@langchain/core/prompts';
|
||||
import { BaseMessage, getBufferString } from '@langchain/core/messages';
|
||||
import type { GraphEdge } from '@librechat/agents';
|
||||
|
||||
const DEFAULT_PROMPT_TEMPLATE = `Based on the following conversation and analysis from previous agents, please provide your insights:\n\n{convo}\n\nPlease add your specific expertise and perspective to this discussion.`;
|
||||
|
||||
/**
|
||||
* Helper function to create sequential chain edges with buffer string prompts
|
||||
*
|
||||
* @deprecated Agent Chain helper
|
||||
* @param agentIds - Array of agent IDs in order of execution
|
||||
* @param promptTemplate - Optional prompt template string; defaults to a predefined template if not provided
|
||||
* @returns Array of edges configured for sequential chain with buffer prompts
|
||||
*/
|
||||
export async function createSequentialChainEdges(
|
||||
agentIds: string[],
|
||||
promptTemplate = DEFAULT_PROMPT_TEMPLATE,
|
||||
): Promise<GraphEdge[]> {
|
||||
const edges: GraphEdge[] = [];
|
||||
|
||||
for (let i = 0; i < agentIds.length - 1; i++) {
|
||||
const fromAgent = agentIds[i];
|
||||
const toAgent = agentIds[i + 1];
|
||||
|
||||
edges.push({
|
||||
from: fromAgent,
|
||||
to: toAgent,
|
||||
edgeType: 'direct',
|
||||
// Use a prompt function to create the buffer string from all previous results
|
||||
prompt: async (messages: BaseMessage[], startIndex: number) => {
|
||||
/** Only the messages from this run (after startIndex) are passed in */
|
||||
const runMessages = messages.slice(startIndex);
|
||||
const bufferString = getBufferString(runMessages);
|
||||
const template = PromptTemplate.fromTemplate(promptTemplate);
|
||||
const result = await template.invoke({
|
||||
convo: bufferString,
|
||||
});
|
||||
return result.value;
|
||||
},
|
||||
/** Critical: exclude previous results so only the prompt is passed */
|
||||
excludeResults: true,
|
||||
description: `Sequential chain from ${fromAgent} to ${toAgent}`,
|
||||
});
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './chain';
|
||||
export * from './config';
|
||||
export * from './memory';
|
||||
export * from './migration';
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
} from '@librechat/agents';
|
||||
import type { TAttachment, MemoryArtifact } from 'librechat-data-provider';
|
||||
import type { ObjectId, MemoryMethods } from '@librechat/data-schemas';
|
||||
import type { BaseMessage, ToolMessage } from '@langchain/core/messages';
|
||||
import type { BaseMessage } from '@langchain/core/messages';
|
||||
import type { Response as ServerResponse } from 'express';
|
||||
import { Tokenizer } from '~/utils';
|
||||
|
||||
@@ -464,7 +464,7 @@ async function handleMemoryArtifact({
|
||||
data: ToolEndData;
|
||||
metadata?: ToolEndMetadata;
|
||||
}) {
|
||||
const output = data?.output as ToolMessage | undefined;
|
||||
const output = data?.output;
|
||||
if (!output) {
|
||||
return null;
|
||||
}
|
||||
@@ -507,7 +507,7 @@ export function createMemoryCallback({
|
||||
artifactPromises: Promise<Partial<TAttachment> | null>[];
|
||||
}): ToolEndCallback {
|
||||
return async (data: ToolEndData, metadata?: Record<string, unknown>) => {
|
||||
const output = data?.output as ToolMessage | undefined;
|
||||
const output = data?.output;
|
||||
const memoryArtifact = output?.artifact?.[Tools.memory] as MemoryArtifact;
|
||||
if (memoryArtifact == null) {
|
||||
return;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Run, Providers } from '@librechat/agents';
|
||||
import { providerEndpointMap, KnownEndpoints } from 'librechat-data-provider';
|
||||
import type {
|
||||
MultiAgentGraphConfig,
|
||||
OpenAIClientOptions,
|
||||
StandardGraphConfig,
|
||||
AgentInputs,
|
||||
EventHandler,
|
||||
GenericTool,
|
||||
RunConfig,
|
||||
GraphEvents,
|
||||
IState,
|
||||
} from '@librechat/agents';
|
||||
import type { Agent } from 'librechat-data-provider';
|
||||
import type * as t from '~/types';
|
||||
import { resolveHeaders } from '~/utils/env';
|
||||
|
||||
const customProviders = new Set([
|
||||
Providers.XAI,
|
||||
@@ -42,18 +40,13 @@ export function getReasoningKey(
|
||||
return reasoningKey;
|
||||
}
|
||||
|
||||
type RunAgent = Omit<Agent, 'tools'> & {
|
||||
tools?: GenericTool[];
|
||||
maxContextTokens?: number;
|
||||
toolContextMap?: Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new Run instance with custom handlers and configuration.
|
||||
*
|
||||
* @param options - The options for creating the Run instance.
|
||||
* @param options.agents - The agents for this run.
|
||||
* @param options.agent - The agent for this run.
|
||||
* @param options.signal - The signal for this run.
|
||||
* @param options.req - The server request.
|
||||
* @param options.runId - Optional run ID; otherwise, a new run ID will be generated.
|
||||
* @param options.customHandlers - Custom event handlers.
|
||||
* @param options.streaming - Whether to use streaming.
|
||||
@@ -62,108 +55,61 @@ type RunAgent = Omit<Agent, 'tools'> & {
|
||||
*/
|
||||
export async function createRun({
|
||||
runId,
|
||||
agent,
|
||||
signal,
|
||||
agents,
|
||||
requestBody,
|
||||
tokenCounter,
|
||||
customHandlers,
|
||||
indexTokenCountMap,
|
||||
streaming = true,
|
||||
streamUsage = true,
|
||||
}: {
|
||||
agents: RunAgent[];
|
||||
agent: Omit<Agent, 'tools'> & { tools?: GenericTool[] };
|
||||
signal: AbortSignal;
|
||||
runId?: string;
|
||||
streaming?: boolean;
|
||||
streamUsage?: boolean;
|
||||
requestBody?: t.RequestBody;
|
||||
} & Pick<RunConfig, 'tokenCounter' | 'customHandlers' | 'indexTokenCountMap'>): Promise<
|
||||
Run<IState>
|
||||
> {
|
||||
const agentInputs: AgentInputs[] = [];
|
||||
const buildAgentContext = (agent: RunAgent) => {
|
||||
const provider =
|
||||
(providerEndpointMap[
|
||||
agent.provider as keyof typeof providerEndpointMap
|
||||
] as unknown as Providers) ?? agent.provider;
|
||||
customHandlers?: Record<GraphEvents, EventHandler>;
|
||||
}): Promise<Run<IState>> {
|
||||
const provider =
|
||||
(providerEndpointMap[
|
||||
agent.provider as keyof typeof providerEndpointMap
|
||||
] as unknown as Providers) ?? agent.provider;
|
||||
|
||||
const llmConfig: t.RunLLMConfig = Object.assign(
|
||||
{
|
||||
provider,
|
||||
streaming,
|
||||
streamUsage,
|
||||
},
|
||||
agent.model_parameters,
|
||||
);
|
||||
|
||||
const systemMessage = Object.values(agent.toolContextMap ?? {})
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
const systemContent = [
|
||||
systemMessage,
|
||||
agent.instructions ?? '',
|
||||
agent.additional_instructions ?? '',
|
||||
]
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
/**
|
||||
* Resolve request-based headers for Custom Endpoints. Note: if this is added to
|
||||
* non-custom endpoints, needs consideration of varying provider header configs.
|
||||
* This is done at this step because the request body may contain dynamic values
|
||||
* that need to be resolved after agent initialization.
|
||||
*/
|
||||
if (llmConfig?.configuration?.defaultHeaders != null) {
|
||||
llmConfig.configuration.defaultHeaders = resolveHeaders({
|
||||
headers: llmConfig.configuration.defaultHeaders as Record<string, string>,
|
||||
body: requestBody,
|
||||
});
|
||||
}
|
||||
|
||||
/** Resolves issues with new OpenAI usage field */
|
||||
if (
|
||||
customProviders.has(agent.provider) ||
|
||||
(agent.provider === Providers.OPENAI && agent.endpoint !== agent.provider)
|
||||
) {
|
||||
llmConfig.streamUsage = false;
|
||||
llmConfig.usage = true;
|
||||
}
|
||||
|
||||
const reasoningKey = getReasoningKey(provider, llmConfig, agent.endpoint);
|
||||
const agentInput: AgentInputs = {
|
||||
const llmConfig: t.RunLLMConfig = Object.assign(
|
||||
{
|
||||
provider,
|
||||
reasoningKey,
|
||||
agentId: agent.id,
|
||||
tools: agent.tools,
|
||||
clientOptions: llmConfig,
|
||||
instructions: systemContent,
|
||||
maxContextTokens: agent.maxContextTokens,
|
||||
};
|
||||
agentInputs.push(agentInput);
|
||||
};
|
||||
streaming,
|
||||
streamUsage,
|
||||
},
|
||||
agent.model_parameters,
|
||||
);
|
||||
|
||||
for (const agent of agents) {
|
||||
buildAgentContext(agent);
|
||||
/** Resolves issues with new OpenAI usage field */
|
||||
if (
|
||||
customProviders.has(agent.provider) ||
|
||||
(agent.provider === Providers.OPENAI && agent.endpoint !== agent.provider)
|
||||
) {
|
||||
llmConfig.streamUsage = false;
|
||||
llmConfig.usage = true;
|
||||
}
|
||||
|
||||
const graphConfig: RunConfig['graphConfig'] = {
|
||||
const reasoningKey = getReasoningKey(provider, llmConfig, agent.endpoint);
|
||||
const graphConfig: StandardGraphConfig = {
|
||||
signal,
|
||||
agents: agentInputs,
|
||||
edges: agents[0].edges,
|
||||
llmConfig,
|
||||
reasoningKey,
|
||||
tools: agent.tools,
|
||||
instructions: agent.instructions,
|
||||
additional_instructions: agent.additional_instructions,
|
||||
// toolEnd: agent.end_after_tools,
|
||||
};
|
||||
|
||||
if (agentInputs.length > 1 || ((graphConfig as MultiAgentGraphConfig).edges?.length ?? 0) > 0) {
|
||||
(graphConfig as unknown as MultiAgentGraphConfig).type = 'multi-agent';
|
||||
} else {
|
||||
(graphConfig as StandardGraphConfig).type = 'standard';
|
||||
// TEMPORARY FOR TESTING
|
||||
if (agent.provider === Providers.ANTHROPIC || agent.provider === Providers.BEDROCK) {
|
||||
graphConfig.streamBuffer = 2000;
|
||||
}
|
||||
|
||||
return Run.create({
|
||||
runId,
|
||||
graphConfig,
|
||||
tokenCounter,
|
||||
customHandlers,
|
||||
indexTokenCountMap,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,17 +38,6 @@ export const agentSupportContactSchema = z
|
||||
})
|
||||
.optional();
|
||||
|
||||
/** Graph edge schema for agent handoffs */
|
||||
export const graphEdgeSchema = z.object({
|
||||
from: z.union([z.string(), z.array(z.string())]),
|
||||
to: z.union([z.string(), z.array(z.string())]),
|
||||
description: z.string().optional(),
|
||||
edgeType: z.enum(['handoff', 'direct']).optional(),
|
||||
prompt: z.union([z.string(), z.function()]).optional(),
|
||||
excludeResults: z.boolean().optional(),
|
||||
promptKey: z.string().optional(),
|
||||
});
|
||||
|
||||
/** Base agent schema with all common fields */
|
||||
export const agentBaseSchema = z.object({
|
||||
name: z.string().nullable().optional(),
|
||||
@@ -57,9 +46,7 @@ export const agentBaseSchema = z.object({
|
||||
avatar: agentAvatarSchema.nullable().optional(),
|
||||
model_parameters: z.record(z.unknown()).optional(),
|
||||
tools: z.array(z.string()).optional(),
|
||||
/** @deprecated Use edges instead */
|
||||
agent_ids: z.array(z.string()).optional(),
|
||||
edges: z.array(graphEdgeSchema).optional(),
|
||||
end_after_tools: z.boolean().optional(),
|
||||
hide_sequential_outputs: z.boolean().optional(),
|
||||
artifacts: z.string().optional(),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ErrorTypes, EModelEndpoint, mapModelToAzureConfig } from 'librechat-data-provider';
|
||||
import type {
|
||||
InitializeOpenAIOptionsParams,
|
||||
OpenAIOptionsResult,
|
||||
OpenAIConfigOptions,
|
||||
LLMConfigResult,
|
||||
UserKeyValues,
|
||||
} from '~/types';
|
||||
import { createHandleLLMNewToken } from '~/utils/generators';
|
||||
import { getAzureCredentials } from '~/utils/azure';
|
||||
import { isUserProvided } from '~/utils/common';
|
||||
import { resolveHeaders } from '~/utils/env';
|
||||
@@ -26,7 +27,7 @@ export const initializeOpenAI = async ({
|
||||
overrideEndpoint,
|
||||
getUserKeyValues,
|
||||
checkUserKeyExpiry,
|
||||
}: InitializeOpenAIOptionsParams): Promise<LLMConfigResult> => {
|
||||
}: InitializeOpenAIOptionsParams): Promise<OpenAIOptionsResult> => {
|
||||
const { PROXY, OPENAI_API_KEY, AZURE_API_KEY, OPENAI_REVERSE_PROXY, AZURE_OPENAI_BASEURL } =
|
||||
process.env;
|
||||
|
||||
@@ -159,8 +160,17 @@ export const initializeOpenAI = async ({
|
||||
}
|
||||
|
||||
if (streamRate) {
|
||||
options.llmConfig._lc_stream_delay = streamRate;
|
||||
options.llmConfig.callbacks = [
|
||||
{
|
||||
handleLLMNewToken: createHandleLLMNewToken(streamRate),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return options;
|
||||
const result: OpenAIOptionsResult = {
|
||||
...options,
|
||||
streamRate,
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
import fs from 'fs';
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
import type {
|
||||
AudioProcessingResult,
|
||||
ServerRequest,
|
||||
AudioFileInfo,
|
||||
STTService,
|
||||
FileObject,
|
||||
} from '~/types';
|
||||
import type { STTService, AudioFileInfo, FileObject, AudioProcessingResult } from '~/types';
|
||||
|
||||
/**
|
||||
* Processes audio files using Speech-to-Text (STT) service.
|
||||
* @returns A promise that resolves to an object containing text and bytes.
|
||||
* @param {Object} params - The parameters object.
|
||||
* @param {FileObject} params.file - The audio file object.
|
||||
* @param {STTService} params.sttService - The STT service instance.
|
||||
* @returns {Promise<AudioProcessingResult>} A promise that resolves to an object containing text and bytes.
|
||||
*/
|
||||
export async function processAudioFile({
|
||||
req,
|
||||
file,
|
||||
sttService,
|
||||
}: {
|
||||
req: ServerRequest;
|
||||
file: FileObject;
|
||||
sttService: STTService;
|
||||
}): Promise<AudioProcessingResult> {
|
||||
@@ -29,7 +24,7 @@ export async function processAudioFile({
|
||||
size: file.size,
|
||||
};
|
||||
|
||||
const [provider, sttSchema] = await sttService.getProviderSchema(req);
|
||||
const [provider, sttSchema] = await sttService.getProviderSchema();
|
||||
const text = await sttService.sttRequest(provider, sttSchema, { audioBuffer, audioFile });
|
||||
|
||||
return {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user