Compare commits

...

31 Commits

Author SHA1 Message Date
Marco Beretta
e1af9d21f0 Merge branch 'dev' into feat/prompt-enhancement 2025-07-10 12:11:22 +02:00
Danny Avila
4918899c8d 🖨️ fix: Use Azure Serverless API Version for Responses API (#8316) 2025-07-08 21:07:52 -04:00
Danny Avila
7e37211458 🗝️ refactor: loadServiceKey to Support Stringified JSON and Env Var Renaming (#8317)
* feat: Enhance loadServiceKey to support stringified JSON input

* chore: Update GOOGLE_SERVICE_KEY_FILE_PATH to GOOGLE_SERVICE_KEY_FILE for consistency
2025-07-08 21:07:33 -04:00
Theo N. Truong
e57fc83d40 🔧 fix: Import Path for Custom Configuration Loading (#8319) 2025-07-08 21:07:04 -04:00
Danny Avila
550610dba9 ⚖️ feat: Add Violation Scores (#8304)
- Introduced new violation scores for TTS, STT, Fork, Import, and File Upload actions in the .env.example file.
- Updated logViolation function to accept a score parameter, allowing for dynamic severity levels based on the action type.
- Modified limiters for Fork, Import, Message, STT, TTS, Tool Call, and File Upload to utilize the new violation scores when logging violations.
2025-07-07 17:08:40 -04:00
github-actions[bot]
916cd46221 🌍 i18n: Update translation.json with latest translations (#8288)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-07 17:08:15 -04:00
Dustin Healy
12b08183ff 🐛 fix: Memories Key Updates (#8302)
* Updated the PATCH /memories/:key endpoint to allow key changes while ensuring no duplicate keys exist.
* Improved error handling in MemoryCreateDialog and MemoryEditDialog for key validation and duplication scenarios.
* Added a new translation for memory key validation error in translation.json.
2025-07-07 16:38:55 -04:00
Danny Avila
f4d97e1672 📝 docs: Update README 2025-07-07 01:14:07 -04:00
Danny Avila
035fa081c1 🔧 refactor: Prevent Unnecessary Google Service Key Loading (#8287)
* 🔧 refactor: Improve Google Key Handling in `loadAsyncEndpoints`

- Enhanced logic to check if GOOGLE_KEY is provided, including user-provided checks.
- Updated service key loading mechanism to only attempt loading if GOOGLE_KEY is not provided.
- Added error logging for service key loading failures.

* 🔧 refactor: Enhance service key loading logic in `initializeClient`
2025-07-07 01:10:08 -04:00
Danny Avila
aecf8f19a6 🔧 fix: Initialize reasoningKey to 'reasoning_content' (#8286)
* chore: bump @librechat/agents to v2.4.56

* chore: bump @librechat/api version to 1.2.6

* fix: initialize reasoningKey to 'reasoning_content' in createRun function
2025-07-07 01:05:40 -04:00
Dustin Healy
35f548a94d 🔄 refactor: Google grounding field to web_search for Consistency (#8285)
- Updated the Google configuration and related schemas to replace 'grounding' with 'web_search' for consistency.
- Adjusted the logic in the getGoogleConfig function to reflect the new naming convention.
- Ensured all references in parameter settings and conversation schemas are updated accordingly.
2025-07-07 00:41:51 -04:00
Danny Avila
e60c0cf201 🔍 feat: Anthropic Web Search (#8281)
* chore: bump @librechat/agents to ^2.4.54 for anthropic web search support

* WIP: hardcoded web search tool usage

* feat: Implement web search functionality in Anthropic integration

- Updated parameters panel to include web search for anthropic models.
- Updated necessary schemas to accomodate toggle functionality

* chore: Set default web search option to false in anthropicSettings

* refactor: Rename webSearch to web_search for consistency across settings and schemas

* chore: bump @librechat/agents to v2.4.55

---------

Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
2025-07-06 21:43:09 -04:00
github-actions[bot]
5b392f9cb0 🌍 i18n: Update translation.json with latest translations (#8255)
* 🌍 i18n: Update translation.json with latest translations

* Update translation.json

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
2025-07-05 18:04:57 -04:00
Dustin Healy
e0f468da20 🔍 feat: Add SearXNG for Web Search and Enhance ApiKeyDialog (#8242)
* 🔍 feat: Add SearXNG Web Search support and enhance ApiKeyDialog

- Updated WebSearch component to include authentication data for web search functionality so it won't show badge after being revoked
- Refactored ApiKeyDialog to streamline provider, scraper, and reranker selection with new InputSection component
- Added support for SearXNG as a search provider and updated translation files accordingly
- Improved form handling in useAuthSearchTool to accommodate new API keys and URLs

* 📜 chore: remove unused i18next key

* 📦 chore: address comments (swap API key and URL fields in SearXNG config, change input fields to 'text' from 'password'

* 📦 chore: make URL fields go first in ApiKeyDialog

* chore: bump @librechat/agents to v2.4.52

* ci: update webSearch configuration to include searxng fields in AppService.spec.js

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2025-07-05 17:58:22 -04:00
Danny Avila
91a2df4759 🔧 refactor: Change Permissions Check from some to every for Stricter Access Validation (#8270)
* 🔧 refactor: Change Permissions Check from `some` to `every` for Stricter Access Validation

* 🧪 ci: Add comprehensive tests for access middleware functions

* fix: custom provider check logic in `getProviderConfig` function
2025-07-05 15:53:08 -04:00
Danny Avila
97a99985fa 🛡️ feat: Rate Limiting for Conversation Forking (#8269)
* chore: Improve error logging for fetching conversations, and use new TS packages for utils

* feat: Implement fork limiters for conversation forking requests

* chore: error message for conversation index deletion to clarify syncing behavior

* feat: Enhance error handling for forking with rate limit message
2025-07-05 15:02:32 -04:00
Danny Avila
3554625a06 refactor: Add Robust Timestamp handling for Conversation Imports (#8262) 2025-07-05 12:44:19 -04:00
Danny Avila
a37bf6719c 🧪 refactor: Add Validation for Agent Creation/Updates (#8261)
* refactor: Add validation schemas for agent creation and updates

* fix: Ensure author validation is applied in correct order for agent update handler

* ci: Add comprehensive unit tests for agent creation and update handlers with mass assignment protection

* fix: add missing  web_search tool in system tools configuration
2025-07-05 11:34:28 -04:00
Danny Avila
e513f50c08 ⚒️ refactor: Keep useAvailableToolsQuery Enabled for All Endpoints 2025-07-04 15:43:17 -04:00
Danny Avila
f5511e4a4e 🔁 refactor: Capabilities for Tools/File handling for Direct Endpoints (#8253)
* feat: add useAgentCapabilities hook to manage agent capabilities

* refactor: move  agents and endpoints configuration to AgentPanel context provider

* refactor: implement useGetAgentsConfig hook for consolidated agents and endpoints management

* refactor: enhance ToolsDropdown to utilize agent capabilities and streamline dropdown item rendering

* chore: reorder return values in useAgentCapabilities for improved clarity

* refactor: enhance agent capabilities handling in AttachFileMenu and update file handling logic to allow capabilities to be used for non-agents endpoints
2025-07-04 14:51:26 -04:00
Danny Avila
a288ad1d9c 🪄 feat: Artifacts Badge & Optimize Ephemeral Agent State (#8252)
* 🔧 fix: Update type annotations in useEventHandlers for better type safety

* 🔧 refactor: `useToolToggle` for improved localStorage synchronization and allow string/falsy values for setting to storage

*  feat: Implement Artifacts badge to BadgeRow with toggle options and UI components

- Added Artifacts component to manage artifacts state and options.
- Introduced ArtifactsSubMenu for additional settings related to artifacts.
- Integrated artifacts functionality into BadgeRow and ToolsDropdown components.
- Updated localStorage handling for artifacts state persistence.
- Enhanced localization for artifacts-related strings in translation files.
- Refactored Agent model to include artifacts in the ephemeral agent response.

* fix: set ephemeral agent state for conversation on finalization

* chore: remove beta settings dialog tab

* refactor: improve Ephemeral Agent statefulness

* fix: update setValue parameter to use 'value' instead of 'isChecked' in CheckboxButton

* refactor: update color classes for Artifact toggle and order of dropdown components

* chore: remove unused i18n localization
2025-07-04 13:25:04 -04:00
Sebastien Bruel
458580ec87 🥅 refactor: Express App default Error Handling with ErrorController (#8249) 2025-07-04 13:24:57 -04:00
github-actions[bot]
4285d5841c 🌍 i18n: Update translation.json with latest translations (#8235)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-04 11:48:54 -04:00
Sebastien Bruel
5ee55cda4f 📦 chore: bump @modelcontextprotocol/sdk to 1.13.3 and cleanup mcp/connection.ts (#8241) 2025-07-04 09:28:57 -04:00
Danny Avila
404d40cbef 📦 chore: override @langchain/openai to v0.5.16 2025-07-03 23:16:42 -04:00
Danny Avila
f4680b016c 📦 chore: bump @librechat/agents to v2.4.51 (#8234) 2025-07-03 22:35:13 -04:00
Ruben Talstra
077224b351 feat: Add support for Armenian, Latvian, and Uyghur languages (#8227) 2025-07-03 11:16:33 -04:00
Marco Beretta
3d261a969d Merge branch 'dev' into feat/prompt-enhancement 2025-06-23 14:48:46 +02:00
Marco Beretta
84f62eb70c feat: Refactor FilterPrompts and List components for improved search handling and sorting functionality 2025-06-02 23:32:09 +02:00
Marco Beretta
3e698338aa feat: Implement CreatePromptButton and enhance AutoSendPrompt with dialog settings 2025-06-02 18:56:52 +02:00
Marco Beretta
0e26df0390 feat: Add user prompt preferences and favorites functionality 2025-06-02 18:56:51 +02:00
148 changed files with 7309 additions and 1470 deletions

View File

@@ -349,6 +349,11 @@ REGISTRATION_VIOLATION_SCORE=1
CONCURRENT_VIOLATION_SCORE=1
MESSAGE_VIOLATION_SCORE=1
NON_BROWSER_VIOLATION_SCORE=20
TTS_VIOLATION_SCORE=0
STT_VIOLATION_SCORE=0
FORK_VIOLATION_SCORE=0
IMPORT_VIOLATION_SCORE=0
FILE_UPLOAD_VIOLATION_SCORE=0
LOGIN_MAX=7
LOGIN_WINDOW=5

View File

@@ -52,7 +52,7 @@
- 🖥️ **UI & Experience** inspired by ChatGPT with enhanced design and features
- 🤖 **AI Model Selection**:
- Anthropic (Claude), AWS Bedrock, OpenAI, Azure OpenAI, Google, Vertex AI, OpenAI Assistants API (incl. Azure)
- Anthropic (Claude), AWS Bedrock, OpenAI, Azure OpenAI, Google, Vertex AI, OpenAI Responses API (incl. Azure)
- [Custom Endpoints](https://www.librechat.ai/docs/quick_start/custom_endpoints): Use any OpenAI-compatible API with LibreChat, no proxy required
- Compatible with [Local & Remote AI Providers](https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints):
- Ollama, groq, Cohere, Mistral AI, Apple MLX, koboldcpp, together.ai,
@@ -66,10 +66,9 @@
- 🔦 **Agents & Tools Integration**:
- **[LibreChat Agents](https://www.librechat.ai/docs/features/agents)**:
- No-Code Custom Assistants: Build specialized, AI-driven helpers without coding
- Flexible & Extensible: Attach tools like DALL-E-3, file search, code execution, and more
- Compatible with Custom Endpoints, OpenAI, Azure, Anthropic, AWS Bedrock, and more
- Flexible & Extensible: Use MCP Servers, tools, file search, code execution, and more
- Compatible with Custom Endpoints, OpenAI, Azure, Anthropic, AWS Bedrock, Google, Vertex AI, Responses API, and more
- [Model Context Protocol (MCP) Support](https://modelcontextprotocol.io/clients#librechat) for Tools
- Use LibreChat Agents and OpenAI Assistants with Files, Code Interpreter, Tools, and API Actions
- 🔍 **Web Search**:
- Search the internet and retrieve relevant information to enhance your AI context

View File

@@ -9,7 +9,7 @@ const banViolation = require('./banViolation');
* @param {Object} res - Express response object.
* @param {string} type - The type of violation.
* @param {Object} errorMessage - The error message to log.
* @param {number} [score=1] - The severity of the violation. Defaults to 1
* @param {number | string} [score=1] - The severity of the violation. Defaults to 1
*/
const logViolation = async (req, res, type, errorMessage, score = 1) => {
const userId = req.user?.id ?? req.user?._id;

View File

@@ -90,7 +90,7 @@ const loadEphemeralAgent = async ({ req, agent_id, endpoint, model_parameters: _
}
const instructions = req.body.promptPrefix;
return {
const result = {
id: agent_id,
instructions,
provider: endpoint,
@@ -98,6 +98,11 @@ const loadEphemeralAgent = async ({ req, agent_id, endpoint, model_parameters: _
model,
tools,
};
if (ephemeralAgent?.artifacts != null && ephemeralAgent.artifacts) {
result.artifacts = ephemeralAgent.artifacts;
}
return result;
};
/**

View File

@@ -1,6 +1,6 @@
const { logger } = require('@librechat/data-schemas');
const { createTempChatExpirationDate } = require('@librechat/api');
const getCustomConfig = require('~/server/services/Config/loadCustomConfig');
const getCustomConfig = require('~/server/services/Config/getCustomConfig');
const { getMessages, deleteMessages } = require('./Message');
const { Conversation } = require('~/db/models');

View File

@@ -1,7 +1,7 @@
const { z } = require('zod');
const { logger } = require('@librechat/data-schemas');
const { createTempChatExpirationDate } = require('@librechat/api');
const getCustomConfig = require('~/server/services/Config/loadCustomConfig');
const getCustomConfig = require('~/server/services/Config/getCustomConfig');
const { Message } = require('~/db/models');
const idSchema = z.string().uuid();

View File

@@ -48,7 +48,7 @@
"@langchain/google-genai": "^0.2.13",
"@langchain/google-vertexai": "^0.2.13",
"@langchain/textsplitters": "^0.1.0",
"@librechat/agents": "^2.4.50",
"@librechat/agents": "^2.4.56",
"@librechat/api": "*",
"@librechat/data-schemas": "*",
"@node-saml/passport-saml": "^5.0.0",

View File

@@ -24,17 +24,23 @@ const handleValidationError = (err, res) => {
}
};
// eslint-disable-next-line no-unused-vars
module.exports = (err, req, res, next) => {
module.exports = (err, _req, res, _next) => {
try {
if (err.name === 'ValidationError') {
return (err = handleValidationError(err, res));
return handleValidationError(err, res);
}
if (err.code && err.code == 11000) {
return (err = handleDuplicateKeyError(err, res));
return handleDuplicateKeyError(err, res);
}
} catch (err) {
// Special handling for errors like SyntaxError
if (err.statusCode && err.body) {
return res.status(err.statusCode).send(err.body);
}
logger.error('ErrorController => error', err);
res.status(500).send('An unknown error occurred.');
return res.status(500).send('An unknown error occurred.');
} catch (err) {
logger.error('ErrorController => processing error', err);
return res.status(500).send('Processing error in ErrorController.');
}
};

View File

@@ -0,0 +1,241 @@
const errorController = require('./ErrorController');
const { logger } = require('~/config');
// Mock the logger
jest.mock('~/config', () => ({
logger: {
error: jest.fn(),
},
}));
describe('ErrorController', () => {
let mockReq, mockRes, mockNext;
beforeEach(() => {
mockReq = {};
mockRes = {
status: jest.fn().mockReturnThis(),
send: jest.fn(),
};
mockNext = jest.fn();
logger.error.mockClear();
});
describe('ValidationError handling', () => {
it('should handle ValidationError with single error', () => {
const validationError = {
name: 'ValidationError',
errors: {
email: { message: 'Email is required', path: 'email' },
},
};
errorController(validationError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.send).toHaveBeenCalledWith({
messages: '["Email is required"]',
fields: '["email"]',
});
expect(logger.error).toHaveBeenCalledWith('Validation error:', validationError.errors);
});
it('should handle ValidationError with multiple errors', () => {
const validationError = {
name: 'ValidationError',
errors: {
email: { message: 'Email is required', path: 'email' },
password: { message: 'Password is required', path: 'password' },
},
};
errorController(validationError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.send).toHaveBeenCalledWith({
messages: '"Email is required Password is required"',
fields: '["email","password"]',
});
expect(logger.error).toHaveBeenCalledWith('Validation error:', validationError.errors);
});
it('should handle ValidationError with empty errors object', () => {
const validationError = {
name: 'ValidationError',
errors: {},
};
errorController(validationError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.send).toHaveBeenCalledWith({
messages: '[]',
fields: '[]',
});
});
});
describe('Duplicate key error handling', () => {
it('should handle duplicate key error (code 11000)', () => {
const duplicateKeyError = {
code: 11000,
keyValue: { email: 'test@example.com' },
};
errorController(duplicateKeyError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(409);
expect(mockRes.send).toHaveBeenCalledWith({
messages: 'An document with that ["email"] already exists.',
fields: '["email"]',
});
expect(logger.error).toHaveBeenCalledWith('Duplicate key error:', duplicateKeyError.keyValue);
});
it('should handle duplicate key error with multiple fields', () => {
const duplicateKeyError = {
code: 11000,
keyValue: { email: 'test@example.com', username: 'testuser' },
};
errorController(duplicateKeyError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(409);
expect(mockRes.send).toHaveBeenCalledWith({
messages: 'An document with that ["email","username"] already exists.',
fields: '["email","username"]',
});
expect(logger.error).toHaveBeenCalledWith('Duplicate key error:', duplicateKeyError.keyValue);
});
it('should handle error with code 11000 as string', () => {
const duplicateKeyError = {
code: '11000',
keyValue: { email: 'test@example.com' },
};
errorController(duplicateKeyError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(409);
expect(mockRes.send).toHaveBeenCalledWith({
messages: 'An document with that ["email"] already exists.',
fields: '["email"]',
});
});
});
describe('SyntaxError handling', () => {
it('should handle errors with statusCode and body', () => {
const syntaxError = {
statusCode: 400,
body: 'Invalid JSON syntax',
};
errorController(syntaxError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.send).toHaveBeenCalledWith('Invalid JSON syntax');
});
it('should handle errors with different statusCode and body', () => {
const customError = {
statusCode: 422,
body: { error: 'Unprocessable entity' },
};
errorController(customError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(422);
expect(mockRes.send).toHaveBeenCalledWith({ error: 'Unprocessable entity' });
});
it('should handle error with statusCode but no body', () => {
const partialError = {
statusCode: 400,
};
errorController(partialError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.');
});
it('should handle error with body but no statusCode', () => {
const partialError = {
body: 'Some error message',
};
errorController(partialError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.');
});
});
describe('Unknown error handling', () => {
it('should handle unknown errors', () => {
const unknownError = new Error('Some unknown error');
errorController(unknownError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.');
expect(logger.error).toHaveBeenCalledWith('ErrorController => error', unknownError);
});
it('should handle errors with code other than 11000', () => {
const mongoError = {
code: 11100,
message: 'Some MongoDB error',
};
errorController(mongoError, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.');
expect(logger.error).toHaveBeenCalledWith('ErrorController => error', mongoError);
});
it('should handle null/undefined errors', () => {
errorController(null, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.send).toHaveBeenCalledWith('Processing error in ErrorController.');
expect(logger.error).toHaveBeenCalledWith(
'ErrorController => processing error',
expect.any(Error),
);
});
});
describe('Catch block handling', () => {
beforeEach(() => {
// Restore logger mock to normal behavior for these tests
logger.error.mockRestore();
logger.error = jest.fn();
});
it('should handle errors when logger.error throws', () => {
// Create fresh mocks for this test
const freshMockRes = {
status: jest.fn().mockReturnThis(),
send: jest.fn(),
};
// Mock logger to throw on the first call, succeed on the second
logger.error
.mockImplementationOnce(() => {
throw new Error('Logger error');
})
.mockImplementation(() => {});
const testError = new Error('Test error');
errorController(testError, mockReq, freshMockRes, mockNext);
expect(freshMockRes.status).toHaveBeenCalledWith(500);
expect(freshMockRes.send).toHaveBeenCalledWith('Processing error in ErrorController.');
expect(logger.error).toHaveBeenCalledTimes(2);
});
});
});

View File

@@ -1,6 +1,8 @@
const { z } = require('zod');
const fs = require('fs').promises;
const { nanoid } = require('nanoid');
const { logger } = require('@librechat/data-schemas');
const { agentCreateSchema, agentUpdateSchema } = require('@librechat/api');
const {
Tools,
Constants,
@@ -8,6 +10,7 @@ const {
SystemRoles,
EToolResources,
actionDelimiter,
removeNullishValues,
} = require('librechat-data-provider');
const {
getAgent,
@@ -30,6 +33,7 @@ const { deleteFileByFilter } = require('~/models/File');
const systemTools = {
[Tools.execute_code]: true,
[Tools.file_search]: true,
[Tools.web_search]: true,
};
/**
@@ -42,9 +46,13 @@ const systemTools = {
*/
const createAgentHandler = async (req, res) => {
try {
const { tools = [], provider, name, description, instructions, model, ...agentData } = req.body;
const validatedData = agentCreateSchema.parse(req.body);
const { tools = [], ...agentData } = removeNullishValues(validatedData);
const { id: userId } = req.user;
agentData.id = `agent_${nanoid()}`;
agentData.author = userId;
agentData.tools = [];
const availableTools = await getCachedTools({ includeGlobal: true });
@@ -58,19 +66,13 @@ const createAgentHandler = async (req, res) => {
}
}
Object.assign(agentData, {
author: userId,
name,
description,
instructions,
provider,
model,
});
agentData.id = `agent_${nanoid()}`;
const agent = await createAgent(agentData);
res.status(201).json(agent);
} catch (error) {
if (error instanceof z.ZodError) {
logger.error('[/Agents] Validation error', error.errors);
return res.status(400).json({ error: 'Invalid request data', details: error.errors });
}
logger.error('[/Agents] Error creating agent', error);
res.status(500).json({ error: error.message });
}
@@ -154,14 +156,16 @@ const getAgentHandler = async (req, res) => {
const updateAgentHandler = async (req, res) => {
try {
const id = req.params.id;
const { projectIds, removeProjectIds, ...updateData } = req.body;
const validatedData = agentUpdateSchema.parse(req.body);
const { projectIds, removeProjectIds, ...updateData } = removeNullishValues(validatedData);
const isAdmin = req.user.role === SystemRoles.ADMIN;
const existingAgent = await getAgent({ id });
const isAuthor = existingAgent.author.toString() === req.user.id;
if (!existingAgent) {
return res.status(404).json({ error: 'Agent not found' });
}
const isAuthor = existingAgent.author.toString() === req.user.id;
const hasEditPermission = existingAgent.isCollaborative || isAdmin || isAuthor;
if (!hasEditPermission) {
@@ -200,6 +204,11 @@ const updateAgentHandler = async (req, res) => {
return res.json(updatedAgent);
} catch (error) {
if (error instanceof z.ZodError) {
logger.error('[/Agents/:id] Validation error', error.errors);
return res.status(400).json({ error: 'Invalid request data', details: error.errors });
}
logger.error('[/Agents/:id] Error updating Agent', error);
if (error.statusCode === 409) {

View File

@@ -0,0 +1,659 @@
const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { agentSchema } = require('@librechat/data-schemas');
// Only mock the dependencies that are not database-related
jest.mock('~/server/services/Config', () => ({
getCachedTools: jest.fn().mockResolvedValue({
web_search: true,
execute_code: true,
file_search: true,
}),
}));
jest.mock('~/models/Project', () => ({
getProjectByName: jest.fn().mockResolvedValue(null),
}));
jest.mock('~/server/services/Files/strategies', () => ({
getStrategyFunctions: jest.fn(),
}));
jest.mock('~/server/services/Files/images/avatar', () => ({
resizeAvatar: jest.fn(),
}));
jest.mock('~/server/services/Files/S3/crud', () => ({
refreshS3Url: jest.fn(),
}));
jest.mock('~/server/services/Files/process', () => ({
filterFile: jest.fn(),
}));
jest.mock('~/models/Action', () => ({
updateAction: jest.fn(),
getActions: jest.fn().mockResolvedValue([]),
}));
jest.mock('~/models/File', () => ({
deleteFileByFilter: jest.fn(),
}));
const { createAgent: createAgentHandler, updateAgent: updateAgentHandler } = require('./v1');
/**
* @type {import('mongoose').Model<import('@librechat/data-schemas').IAgent>}
*/
let Agent;
describe('Agent Controllers - Mass Assignment Protection', () => {
let mongoServer;
let mockReq;
let mockRes;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const mongoUri = mongoServer.getUri();
await mongoose.connect(mongoUri);
Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema);
}, 20000);
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
});
beforeEach(async () => {
await Agent.deleteMany({});
// Reset all mocks
jest.clearAllMocks();
// Setup mock request and response objects
mockReq = {
user: {
id: new mongoose.Types.ObjectId().toString(),
role: 'USER',
},
body: {},
params: {},
app: {
locals: {
fileStrategy: 'local',
},
},
};
mockRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
};
});
describe('createAgentHandler', () => {
test('should create agent with allowed fields only', async () => {
const validData = {
name: 'Test Agent',
description: 'A test agent',
instructions: 'Be helpful',
provider: 'openai',
model: 'gpt-4',
tools: ['web_search'],
model_parameters: { temperature: 0.7 },
tool_resources: {
file_search: { file_ids: ['file1', 'file2'] },
},
};
mockReq.body = validData;
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
expect(mockRes.json).toHaveBeenCalled();
const createdAgent = mockRes.json.mock.calls[0][0];
expect(createdAgent.name).toBe('Test Agent');
expect(createdAgent.description).toBe('A test agent');
expect(createdAgent.provider).toBe('openai');
expect(createdAgent.model).toBe('gpt-4');
expect(createdAgent.author.toString()).toBe(mockReq.user.id);
expect(createdAgent.tools).toContain('web_search');
// Verify in database
const agentInDb = await Agent.findOne({ id: createdAgent.id });
expect(agentInDb).toBeDefined();
expect(agentInDb.name).toBe('Test Agent');
expect(agentInDb.author.toString()).toBe(mockReq.user.id);
});
test('should reject creation with unauthorized fields (mass assignment protection)', async () => {
const maliciousData = {
// Required fields
provider: 'openai',
model: 'gpt-4',
name: 'Malicious Agent',
// Unauthorized fields that should be stripped
author: new mongoose.Types.ObjectId().toString(), // Should not be able to set author
authorName: 'Hacker', // Should be stripped
isCollaborative: true, // Should be stripped on creation
versions: [], // Should be stripped
_id: new mongoose.Types.ObjectId(), // Should be stripped
id: 'custom_agent_id', // Should be overridden
createdAt: new Date('2020-01-01'), // Should be stripped
updatedAt: new Date('2020-01-01'), // Should be stripped
};
mockReq.body = maliciousData;
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
const createdAgent = mockRes.json.mock.calls[0][0];
// Verify unauthorized fields were not set
expect(createdAgent.author.toString()).toBe(mockReq.user.id); // Should be the request user, not the malicious value
expect(createdAgent.authorName).toBeUndefined();
expect(createdAgent.isCollaborative).toBeFalsy();
expect(createdAgent.versions).toHaveLength(1); // Should have exactly 1 version from creation
expect(createdAgent.id).not.toBe('custom_agent_id'); // Should have generated ID
expect(createdAgent.id).toMatch(/^agent_/); // Should have proper prefix
// Verify timestamps are recent (not the malicious dates)
const createdTime = new Date(createdAgent.createdAt).getTime();
const now = Date.now();
expect(now - createdTime).toBeLessThan(5000); // Created within last 5 seconds
// Verify in database
const agentInDb = await Agent.findOne({ id: createdAgent.id });
expect(agentInDb.author.toString()).toBe(mockReq.user.id);
expect(agentInDb.authorName).toBeUndefined();
});
test('should validate required fields', async () => {
const invalidData = {
name: 'Missing Required Fields',
// Missing provider and model
};
mockReq.body = invalidData;
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith(
expect.objectContaining({
error: 'Invalid request data',
details: expect.any(Array),
}),
);
// Verify nothing was created in database
const count = await Agent.countDocuments();
expect(count).toBe(0);
});
test('should handle tool_resources validation', async () => {
const dataWithInvalidToolResources = {
provider: 'openai',
model: 'gpt-4',
name: 'Agent with Tool Resources',
tool_resources: {
// Valid resources
file_search: {
file_ids: ['file1', 'file2'],
vector_store_ids: ['vs1'],
},
execute_code: {
file_ids: ['file3'],
},
// Invalid resource (should be stripped by schema)
invalid_resource: {
file_ids: ['file4'],
},
},
};
mockReq.body = dataWithInvalidToolResources;
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
const createdAgent = mockRes.json.mock.calls[0][0];
expect(createdAgent.tool_resources).toBeDefined();
expect(createdAgent.tool_resources.file_search).toBeDefined();
expect(createdAgent.tool_resources.execute_code).toBeDefined();
expect(createdAgent.tool_resources.invalid_resource).toBeUndefined(); // Should be stripped
// Verify in database
const agentInDb = await Agent.findOne({ id: createdAgent.id });
expect(agentInDb.tool_resources.invalid_resource).toBeUndefined();
});
test('should handle avatar validation', async () => {
const dataWithAvatar = {
provider: 'openai',
model: 'gpt-4',
name: 'Agent with Avatar',
avatar: {
filepath: 'https://example.com/avatar.png',
source: 's3',
},
};
mockReq.body = dataWithAvatar;
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
const createdAgent = mockRes.json.mock.calls[0][0];
expect(createdAgent.avatar).toEqual({
filepath: 'https://example.com/avatar.png',
source: 's3',
});
});
test('should handle invalid avatar format', async () => {
const dataWithInvalidAvatar = {
provider: 'openai',
model: 'gpt-4',
name: 'Agent with Invalid Avatar',
avatar: 'just-a-string', // Invalid format
};
mockReq.body = dataWithInvalidAvatar;
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith(
expect.objectContaining({
error: 'Invalid request data',
}),
);
});
});
describe('updateAgentHandler', () => {
let existingAgentId;
let existingAgentAuthorId;
beforeEach(async () => {
// Create an existing agent for update tests
existingAgentAuthorId = new mongoose.Types.ObjectId();
const agent = await Agent.create({
id: `agent_${uuidv4()}`,
name: 'Original Agent',
provider: 'openai',
model: 'gpt-3.5-turbo',
author: existingAgentAuthorId,
description: 'Original description',
isCollaborative: false,
versions: [
{
name: 'Original Agent',
provider: 'openai',
model: 'gpt-3.5-turbo',
description: 'Original description',
createdAt: new Date(),
updatedAt: new Date(),
},
],
});
existingAgentId = agent.id;
});
test('should update agent with allowed fields only', async () => {
mockReq.user.id = existingAgentAuthorId.toString(); // Set as author
mockReq.params.id = existingAgentId;
mockReq.body = {
name: 'Updated Agent',
description: 'Updated description',
model: 'gpt-4',
isCollaborative: true, // This IS allowed in updates
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.status).not.toHaveBeenCalledWith(400);
expect(mockRes.status).not.toHaveBeenCalledWith(403);
expect(mockRes.json).toHaveBeenCalled();
const updatedAgent = mockRes.json.mock.calls[0][0];
expect(updatedAgent.name).toBe('Updated Agent');
expect(updatedAgent.description).toBe('Updated description');
expect(updatedAgent.model).toBe('gpt-4');
expect(updatedAgent.isCollaborative).toBe(true);
expect(updatedAgent.author).toBe(existingAgentAuthorId.toString());
// Verify in database
const agentInDb = await Agent.findOne({ id: existingAgentId });
expect(agentInDb.name).toBe('Updated Agent');
expect(agentInDb.isCollaborative).toBe(true);
});
test('should reject update with unauthorized fields (mass assignment protection)', async () => {
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;
mockReq.body = {
name: 'Updated Name',
// Unauthorized fields that should be stripped
author: new mongoose.Types.ObjectId().toString(), // Should not be able to change author
authorName: 'Hacker', // Should be stripped
id: 'different_agent_id', // Should be stripped
_id: new mongoose.Types.ObjectId(), // Should be stripped
versions: [], // Should be stripped
createdAt: new Date('2020-01-01'), // Should be stripped
updatedAt: new Date('2020-01-01'), // Should be stripped
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalled();
const updatedAgent = mockRes.json.mock.calls[0][0];
// Verify unauthorized fields were not changed
expect(updatedAgent.author).toBe(existingAgentAuthorId.toString()); // Should not have changed
expect(updatedAgent.authorName).toBeUndefined();
expect(updatedAgent.id).toBe(existingAgentId); // Should not have changed
expect(updatedAgent.name).toBe('Updated Name'); // Only this should have changed
// Verify in database
const agentInDb = await Agent.findOne({ id: existingAgentId });
expect(agentInDb.author.toString()).toBe(existingAgentAuthorId.toString());
expect(agentInDb.id).toBe(existingAgentId);
});
test('should reject update from non-author when not collaborative', async () => {
const differentUserId = new mongoose.Types.ObjectId().toString();
mockReq.user.id = differentUserId; // Different user
mockReq.params.id = existingAgentId;
mockReq.body = {
name: 'Unauthorized Update',
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(403);
expect(mockRes.json).toHaveBeenCalledWith({
error: 'You do not have permission to modify this non-collaborative agent',
});
// Verify agent was not modified in database
const agentInDb = await Agent.findOne({ id: existingAgentId });
expect(agentInDb.name).toBe('Original Agent');
});
test('should allow update from non-author when collaborative', async () => {
// First make the agent collaborative
await Agent.updateOne({ id: existingAgentId }, { isCollaborative: true });
const differentUserId = new mongoose.Types.ObjectId().toString();
mockReq.user.id = differentUserId; // Different user
mockReq.params.id = existingAgentId;
mockReq.body = {
name: 'Collaborative Update',
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.status).not.toHaveBeenCalledWith(403);
expect(mockRes.json).toHaveBeenCalled();
const updatedAgent = mockRes.json.mock.calls[0][0];
expect(updatedAgent.name).toBe('Collaborative Update');
// Author field should be removed for non-author
expect(updatedAgent.author).toBeUndefined();
// Verify in database
const agentInDb = await Agent.findOne({ id: existingAgentId });
expect(agentInDb.name).toBe('Collaborative Update');
});
test('should allow admin to update any agent', async () => {
const adminUserId = new mongoose.Types.ObjectId().toString();
mockReq.user.id = adminUserId;
mockReq.user.role = 'ADMIN'; // Set as admin
mockReq.params.id = existingAgentId;
mockReq.body = {
name: 'Admin Update',
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.status).not.toHaveBeenCalledWith(403);
expect(mockRes.json).toHaveBeenCalled();
const updatedAgent = mockRes.json.mock.calls[0][0];
expect(updatedAgent.name).toBe('Admin Update');
});
test('should handle projectIds updates', async () => {
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;
const projectId1 = new mongoose.Types.ObjectId().toString();
const projectId2 = new mongoose.Types.ObjectId().toString();
mockReq.body = {
projectIds: [projectId1, projectId2],
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalled();
const updatedAgent = mockRes.json.mock.calls[0][0];
expect(updatedAgent).toBeDefined();
// Note: updateAgentProjects requires more setup, so we just verify the handler doesn't crash
});
test('should validate tool_resources in updates', async () => {
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;
mockReq.body = {
tool_resources: {
ocr: {
file_ids: ['ocr1', 'ocr2'],
},
execute_code: {
file_ids: ['img1'],
},
// Invalid tool resource
invalid_tool: {
file_ids: ['invalid'],
},
},
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalled();
const updatedAgent = mockRes.json.mock.calls[0][0];
expect(updatedAgent.tool_resources).toBeDefined();
expect(updatedAgent.tool_resources.ocr).toBeDefined();
expect(updatedAgent.tool_resources.execute_code).toBeDefined();
expect(updatedAgent.tool_resources.invalid_tool).toBeUndefined();
});
test('should return 404 for non-existent agent', async () => {
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = `agent_${uuidv4()}`; // Non-existent ID
mockReq.body = {
name: 'Update Non-existent',
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(404);
expect(mockRes.json).toHaveBeenCalledWith({ error: 'Agent not found' });
});
test('should handle validation errors properly', async () => {
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;
mockReq.body = {
model_parameters: 'invalid-not-an-object', // Should be an object
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith(
expect.objectContaining({
error: 'Invalid request data',
details: expect.any(Array),
}),
);
});
});
describe('Mass Assignment Attack Scenarios', () => {
test('should prevent setting system fields during creation', async () => {
const systemFields = {
provider: 'openai',
model: 'gpt-4',
name: 'System Fields Test',
// System fields that should never be settable by users
__v: 99,
_id: new mongoose.Types.ObjectId(),
versions: [
{
name: 'Fake Version',
provider: 'fake',
model: 'fake-model',
},
],
};
mockReq.body = systemFields;
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
const createdAgent = mockRes.json.mock.calls[0][0];
// Verify system fields were not affected
expect(createdAgent.__v).not.toBe(99);
expect(createdAgent.versions).toHaveLength(1); // Should only have the auto-created version
expect(createdAgent.versions[0].name).toBe('System Fields Test'); // From actual creation
expect(createdAgent.versions[0].provider).toBe('openai'); // From actual creation
// Verify in database
const agentInDb = await Agent.findOne({ id: createdAgent.id });
expect(agentInDb.__v).not.toBe(99);
});
test('should prevent privilege escalation through isCollaborative', async () => {
// Create a non-collaborative agent
const authorId = new mongoose.Types.ObjectId();
const agent = await Agent.create({
id: `agent_${uuidv4()}`,
name: 'Private Agent',
provider: 'openai',
model: 'gpt-4',
author: authorId,
isCollaborative: false,
versions: [
{
name: 'Private Agent',
provider: 'openai',
model: 'gpt-4',
createdAt: new Date(),
updatedAt: new Date(),
},
],
});
// Try to make it collaborative as a different user
const attackerId = new mongoose.Types.ObjectId().toString();
mockReq.user.id = attackerId;
mockReq.params.id = agent.id;
mockReq.body = {
isCollaborative: true, // Trying to escalate privileges
};
await updateAgentHandler(mockReq, mockRes);
// Should be rejected
expect(mockRes.status).toHaveBeenCalledWith(403);
// Verify in database that it's still not collaborative
const agentInDb = await Agent.findOne({ id: agent.id });
expect(agentInDb.isCollaborative).toBe(false);
});
test('should prevent author hijacking', async () => {
const originalAuthorId = new mongoose.Types.ObjectId();
const attackerId = new mongoose.Types.ObjectId();
// Admin creates an agent
mockReq.user.id = originalAuthorId.toString();
mockReq.user.role = 'ADMIN';
mockReq.body = {
provider: 'openai',
model: 'gpt-4',
name: 'Admin Agent',
author: attackerId.toString(), // Trying to set different author
};
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
const createdAgent = mockRes.json.mock.calls[0][0];
// Author should be the actual user, not the attempted value
expect(createdAgent.author.toString()).toBe(originalAuthorId.toString());
expect(createdAgent.author.toString()).not.toBe(attackerId.toString());
// Verify in database
const agentInDb = await Agent.findOne({ id: createdAgent.id });
expect(agentInDb.author.toString()).toBe(originalAuthorId.toString());
});
test('should strip unknown fields to prevent future vulnerabilities', async () => {
mockReq.body = {
provider: 'openai',
model: 'gpt-4',
name: 'Future Proof Test',
// Unknown fields that might be added in future
superAdminAccess: true,
bypassAllChecks: true,
internalFlag: 'secret',
futureFeature: 'exploit',
};
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
const createdAgent = mockRes.json.mock.calls[0][0];
// Verify unknown fields were stripped
expect(createdAgent.superAdminAccess).toBeUndefined();
expect(createdAgent.bypassAllChecks).toBeUndefined();
expect(createdAgent.internalFlag).toBeUndefined();
expect(createdAgent.futureFeature).toBeUndefined();
// Also check in database
const agentInDb = await Agent.findOne({ id: createdAgent.id }).lean();
expect(agentInDb.superAdminAccess).toBeUndefined();
expect(agentInDb.bypassAllChecks).toBeUndefined();
expect(agentInDb.internalFlag).toBeUndefined();
expect(agentInDb.futureFeature).toBeUndefined();
});
});
});

View File

@@ -55,7 +55,6 @@ const startServer = async () => {
/* Middleware */
app.use(noIndex);
app.use(errorController);
app.use(express.json({ limit: '3mb' }));
app.use(express.urlencoded({ extended: true, limit: '3mb' }));
app.use(mongoSanitize());
@@ -121,6 +120,9 @@ const startServer = async () => {
app.use('/api/tags', routes.tags);
app.use('/api/mcp', routes.mcp);
// Add the error controller one more time after all routes
app.use(errorController);
app.use((req, res) => {
res.set({
'Cache-Control': process.env.INDEX_CACHE_CONTROL || 'no-cache, no-store, must-revalidate',

View File

@@ -1,5 +1,4 @@
const fs = require('fs');
const path = require('path');
const request = require('supertest');
const { MongoMemoryServer } = require('mongodb-memory-server');
const mongoose = require('mongoose');
@@ -59,6 +58,30 @@ describe('Server Configuration', () => {
expect(response.headers['pragma']).toBe('no-cache');
expect(response.headers['expires']).toBe('0');
});
it('should return 500 for unknown errors via ErrorController', async () => {
// Testing the error handling here on top of unit tests to ensure the middleware is correctly integrated
// Mock MongoDB operations to fail
const originalFindOne = mongoose.models.User.findOne;
const mockError = new Error('MongoDB operation failed');
mongoose.models.User.findOne = jest.fn().mockImplementation(() => {
throw mockError;
});
try {
const response = await request(app).post('/api/auth/login').send({
email: 'test@example.com',
password: 'password123',
});
expect(response.status).toBe(500);
expect(response.text).toBe('An unknown error occurred.');
} finally {
// Restore original function
mongoose.models.User.findOne = originalFindOne;
}
});
});
// Polls the /health endpoint every 30ms for up to 10 seconds to wait for the server to start completely

View File

@@ -18,7 +18,6 @@ const message = 'Your account has been temporarily banned due to violations of o
* @function
* @param {Object} req - Express Request object.
* @param {Object} res - Express Response object.
* @param {String} errorMessage - Error message to be displayed in case of /api/ask or /api/edit request.
*
* @returns {Promise<Object>} - Returns a Promise which when resolved sends a response status of 403 with a specific message if request is not of api/ask or api/edit types. If it is, calls `denyRequest()` function.
*/
@@ -135,6 +134,7 @@ const checkBan = async (req, res, next = () => {}) => {
return await banResponse(req, res);
} catch (error) {
logger.error('Error in checkBan middleware:', error);
return next(error);
}
};

View File

@@ -0,0 +1,95 @@
const rateLimit = require('express-rate-limit');
const { isEnabled } = require('@librechat/api');
const { RedisStore } = require('rate-limit-redis');
const { logger } = require('@librechat/data-schemas');
const { ViolationTypes } = require('librechat-data-provider');
const ioredisClient = require('~/cache/ioredisClient');
const logViolation = require('~/cache/logViolation');
const getEnvironmentVariables = () => {
const FORK_IP_MAX = parseInt(process.env.FORK_IP_MAX) || 30;
const FORK_IP_WINDOW = parseInt(process.env.FORK_IP_WINDOW) || 1;
const FORK_USER_MAX = parseInt(process.env.FORK_USER_MAX) || 7;
const FORK_USER_WINDOW = parseInt(process.env.FORK_USER_WINDOW) || 1;
const FORK_VIOLATION_SCORE = process.env.FORK_VIOLATION_SCORE;
const forkIpWindowMs = FORK_IP_WINDOW * 60 * 1000;
const forkIpMax = FORK_IP_MAX;
const forkIpWindowInMinutes = forkIpWindowMs / 60000;
const forkUserWindowMs = FORK_USER_WINDOW * 60 * 1000;
const forkUserMax = FORK_USER_MAX;
const forkUserWindowInMinutes = forkUserWindowMs / 60000;
return {
forkIpWindowMs,
forkIpMax,
forkIpWindowInMinutes,
forkUserWindowMs,
forkUserMax,
forkUserWindowInMinutes,
forkViolationScore: FORK_VIOLATION_SCORE,
};
};
const createForkHandler = (ip = true) => {
const {
forkIpMax,
forkUserMax,
forkViolationScore,
forkIpWindowInMinutes,
forkUserWindowInMinutes,
} = getEnvironmentVariables();
return async (req, res) => {
const type = ViolationTypes.FILE_UPLOAD_LIMIT;
const errorMessage = {
type,
max: ip ? forkIpMax : forkUserMax,
limiter: ip ? 'ip' : 'user',
windowInMinutes: ip ? forkIpWindowInMinutes : forkUserWindowInMinutes,
};
await logViolation(req, res, type, errorMessage, forkViolationScore);
res.status(429).json({ message: 'Too many conversation fork requests. Try again later' });
};
};
const createForkLimiters = () => {
const { forkIpWindowMs, forkIpMax, forkUserWindowMs, forkUserMax } = getEnvironmentVariables();
const ipLimiterOptions = {
windowMs: forkIpWindowMs,
max: forkIpMax,
handler: createForkHandler(),
};
const userLimiterOptions = {
windowMs: forkUserWindowMs,
max: forkUserMax,
handler: createForkHandler(false),
keyGenerator: function (req) {
return req.user?.id;
},
};
if (isEnabled(process.env.USE_REDIS) && ioredisClient) {
logger.debug('Using Redis for fork rate limiters.');
const sendCommand = (...args) => ioredisClient.call(...args);
const ipStore = new RedisStore({
sendCommand,
prefix: 'fork_ip_limiter:',
});
const userStore = new RedisStore({
sendCommand,
prefix: 'fork_user_limiter:',
});
ipLimiterOptions.store = ipStore;
userLimiterOptions.store = userStore;
}
const forkIpLimiter = rateLimit(ipLimiterOptions);
const forkUserLimiter = rateLimit(userLimiterOptions);
return { forkIpLimiter, forkUserLimiter };
};
module.exports = { createForkLimiters };

View File

@@ -1,16 +1,17 @@
const rateLimit = require('express-rate-limit');
const { isEnabled } = require('@librechat/api');
const { RedisStore } = require('rate-limit-redis');
const { logger } = require('@librechat/data-schemas');
const { ViolationTypes } = require('librechat-data-provider');
const ioredisClient = require('~/cache/ioredisClient');
const logViolation = require('~/cache/logViolation');
const { isEnabled } = require('~/server/utils');
const { logger } = require('~/config');
const getEnvironmentVariables = () => {
const IMPORT_IP_MAX = parseInt(process.env.IMPORT_IP_MAX) || 100;
const IMPORT_IP_WINDOW = parseInt(process.env.IMPORT_IP_WINDOW) || 15;
const IMPORT_USER_MAX = parseInt(process.env.IMPORT_USER_MAX) || 50;
const IMPORT_USER_WINDOW = parseInt(process.env.IMPORT_USER_WINDOW) || 15;
const IMPORT_VIOLATION_SCORE = process.env.IMPORT_VIOLATION_SCORE;
const importIpWindowMs = IMPORT_IP_WINDOW * 60 * 1000;
const importIpMax = IMPORT_IP_MAX;
@@ -27,12 +28,18 @@ const getEnvironmentVariables = () => {
importUserWindowMs,
importUserMax,
importUserWindowInMinutes,
importViolationScore: IMPORT_VIOLATION_SCORE,
};
};
const createImportHandler = (ip = true) => {
const { importIpMax, importIpWindowInMinutes, importUserMax, importUserWindowInMinutes } =
getEnvironmentVariables();
const {
importIpMax,
importUserMax,
importViolationScore,
importIpWindowInMinutes,
importUserWindowInMinutes,
} = getEnvironmentVariables();
return async (req, res) => {
const type = ViolationTypes.FILE_UPLOAD_LIMIT;
@@ -43,7 +50,7 @@ const createImportHandler = (ip = true) => {
windowInMinutes: ip ? importIpWindowInMinutes : importUserWindowInMinutes,
};
await logViolation(req, res, type, errorMessage);
await logViolation(req, res, type, errorMessage, importViolationScore);
res.status(429).json({ message: 'Too many conversation import requests. Try again later' });
};
};

View File

@@ -4,6 +4,7 @@ const createSTTLimiters = require('./sttLimiters');
const loginLimiter = require('./loginLimiter');
const importLimiters = require('./importLimiters');
const uploadLimiters = require('./uploadLimiters');
const forkLimiters = require('./forkLimiters');
const registerLimiter = require('./registerLimiter');
const toolCallLimiter = require('./toolCallLimiter');
const messageLimiters = require('./messageLimiters');
@@ -14,6 +15,7 @@ module.exports = {
...uploadLimiters,
...importLimiters,
...messageLimiters,
...forkLimiters,
loginLimiter,
registerLimiter,
toolCallLimiter,

View File

@@ -11,6 +11,7 @@ const {
MESSAGE_IP_WINDOW = 1,
MESSAGE_USER_MAX = 40,
MESSAGE_USER_WINDOW = 1,
MESSAGE_VIOLATION_SCORE: score,
} = process.env;
const ipWindowMs = MESSAGE_IP_WINDOW * 60 * 1000;
@@ -39,7 +40,7 @@ const createHandler = (ip = true) => {
windowInMinutes: ip ? ipWindowInMinutes : userWindowInMinutes,
};
await logViolation(req, res, type, errorMessage);
await logViolation(req, res, type, errorMessage, score);
return await denyRequest(req, res, errorMessage);
};
};

View File

@@ -11,6 +11,7 @@ const getEnvironmentVariables = () => {
const STT_IP_WINDOW = parseInt(process.env.STT_IP_WINDOW) || 1;
const STT_USER_MAX = parseInt(process.env.STT_USER_MAX) || 50;
const STT_USER_WINDOW = parseInt(process.env.STT_USER_WINDOW) || 1;
const STT_VIOLATION_SCORE = process.env.STT_VIOLATION_SCORE;
const sttIpWindowMs = STT_IP_WINDOW * 60 * 1000;
const sttIpMax = STT_IP_MAX;
@@ -27,11 +28,12 @@ const getEnvironmentVariables = () => {
sttUserWindowMs,
sttUserMax,
sttUserWindowInMinutes,
sttViolationScore: STT_VIOLATION_SCORE,
};
};
const createSTTHandler = (ip = true) => {
const { sttIpMax, sttIpWindowInMinutes, sttUserMax, sttUserWindowInMinutes } =
const { sttIpMax, sttIpWindowInMinutes, sttUserMax, sttUserWindowInMinutes, sttViolationScore } =
getEnvironmentVariables();
return async (req, res) => {
@@ -43,7 +45,7 @@ const createSTTHandler = (ip = true) => {
windowInMinutes: ip ? sttIpWindowInMinutes : sttUserWindowInMinutes,
};
await logViolation(req, res, type, errorMessage);
await logViolation(req, res, type, errorMessage, sttViolationScore);
res.status(429).json({ message: 'Too many STT requests. Try again later' });
};
};

View File

@@ -6,6 +6,8 @@ const logViolation = require('~/cache/logViolation');
const { isEnabled } = require('~/server/utils');
const { logger } = require('~/config');
const { TOOL_CALL_VIOLATION_SCORE: score } = process.env;
const handler = async (req, res) => {
const type = ViolationTypes.TOOL_CALL_LIMIT;
const errorMessage = {
@@ -15,7 +17,7 @@ const handler = async (req, res) => {
windowInMinutes: 1,
};
await logViolation(req, res, type, errorMessage, 0);
await logViolation(req, res, type, errorMessage, score);
res.status(429).json({ message: 'Too many tool call requests. Try again later' });
};

View File

@@ -11,6 +11,7 @@ const getEnvironmentVariables = () => {
const TTS_IP_WINDOW = parseInt(process.env.TTS_IP_WINDOW) || 1;
const TTS_USER_MAX = parseInt(process.env.TTS_USER_MAX) || 50;
const TTS_USER_WINDOW = parseInt(process.env.TTS_USER_WINDOW) || 1;
const TTS_VIOLATION_SCORE = process.env.TTS_VIOLATION_SCORE;
const ttsIpWindowMs = TTS_IP_WINDOW * 60 * 1000;
const ttsIpMax = TTS_IP_MAX;
@@ -27,11 +28,12 @@ const getEnvironmentVariables = () => {
ttsUserWindowMs,
ttsUserMax,
ttsUserWindowInMinutes,
ttsViolationScore: TTS_VIOLATION_SCORE,
};
};
const createTTSHandler = (ip = true) => {
const { ttsIpMax, ttsIpWindowInMinutes, ttsUserMax, ttsUserWindowInMinutes } =
const { ttsIpMax, ttsIpWindowInMinutes, ttsUserMax, ttsUserWindowInMinutes, ttsViolationScore } =
getEnvironmentVariables();
return async (req, res) => {
@@ -43,7 +45,7 @@ const createTTSHandler = (ip = true) => {
windowInMinutes: ip ? ttsIpWindowInMinutes : ttsUserWindowInMinutes,
};
await logViolation(req, res, type, errorMessage);
await logViolation(req, res, type, errorMessage, ttsViolationScore);
res.status(429).json({ message: 'Too many TTS requests. Try again later' });
};
};

View File

@@ -11,6 +11,7 @@ const getEnvironmentVariables = () => {
const FILE_UPLOAD_IP_WINDOW = parseInt(process.env.FILE_UPLOAD_IP_WINDOW) || 15;
const FILE_UPLOAD_USER_MAX = parseInt(process.env.FILE_UPLOAD_USER_MAX) || 50;
const FILE_UPLOAD_USER_WINDOW = parseInt(process.env.FILE_UPLOAD_USER_WINDOW) || 15;
const FILE_UPLOAD_VIOLATION_SCORE = process.env.FILE_UPLOAD_VIOLATION_SCORE;
const fileUploadIpWindowMs = FILE_UPLOAD_IP_WINDOW * 60 * 1000;
const fileUploadIpMax = FILE_UPLOAD_IP_MAX;
@@ -27,6 +28,7 @@ const getEnvironmentVariables = () => {
fileUploadUserWindowMs,
fileUploadUserMax,
fileUploadUserWindowInMinutes,
fileUploadViolationScore: FILE_UPLOAD_VIOLATION_SCORE,
};
};
@@ -36,6 +38,7 @@ const createFileUploadHandler = (ip = true) => {
fileUploadIpWindowInMinutes,
fileUploadUserMax,
fileUploadUserWindowInMinutes,
fileUploadViolationScore,
} = getEnvironmentVariables();
return async (req, res) => {
@@ -47,7 +50,7 @@ const createFileUploadHandler = (ip = true) => {
windowInMinutes: ip ? fileUploadIpWindowInMinutes : fileUploadUserWindowInMinutes,
};
await logViolation(req, res, type, errorMessage);
await logViolation(req, res, type, errorMessage, fileUploadViolationScore);
res.status(429).json({ message: 'Too many file upload requests. Try again later' });
};
};

View File

@@ -1,16 +1,17 @@
const multer = require('multer');
const express = require('express');
const { sleep } = require('@librechat/agents');
const { isEnabled } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas');
const { CacheKeys, EModelEndpoint } = require('librechat-data-provider');
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');
const { createImportLimiters } = require('~/server/middleware');
const { deleteToolCalls } = require('~/models/ToolCall');
const { isEnabled, sleep } = require('~/server/utils');
const getLogStores = require('~/cache/getLogStores');
const { logger } = require('~/config');
const assistantClients = {
[EModelEndpoint.azureAssistants]: require('~/server/services/Endpoints/azureAssistants'),
@@ -43,6 +44,7 @@ router.get('/', async (req, res) => {
});
res.status(200).json(result);
} catch (error) {
logger.error('Error fetching conversations', error);
res.status(500).json({ error: 'Error fetching conversations' });
}
});
@@ -156,6 +158,7 @@ router.post('/update', async (req, res) => {
});
const { importIpLimiter, importUserLimiter } = createImportLimiters();
const { forkIpLimiter, forkUserLimiter } = createForkLimiters();
const upload = multer({ storage: storage, fileFilter: importFileFilter });
/**
@@ -189,7 +192,7 @@ router.post(
* @param {express.Response<TForkConvoResponse>} res - Express response object.
* @returns {Promise<void>} - The response after forking the conversation.
*/
router.post('/fork', async (req, res) => {
router.post('/fork', forkIpLimiter, forkUserLimiter, async (req, res) => {
try {
/** @type {TForkConvoRequest} */
const { conversationId, messageId, option, splitAtTarget, latestMessageId } = req.body;

View File

@@ -172,40 +172,68 @@ router.patch('/preferences', checkMemoryOptOut, async (req, res) => {
/**
* PATCH /memories/:key
* Updates the value of an existing memory entry for the authenticated user.
* Body: { value: string }
* Body: { key?: string, value: string }
* Returns 200 and { updated: true, memory: <updatedDoc> } when successful.
*/
router.patch('/:key', checkMemoryUpdate, async (req, res) => {
const { key } = req.params;
const { value } = req.body || {};
const { key: urlKey } = req.params;
const { key: bodyKey, value } = req.body || {};
if (typeof value !== 'string' || value.trim() === '') {
return res.status(400).json({ error: 'Value is required and must be a non-empty string.' });
}
// Use the key from the body if provided, otherwise use the key from the URL
const newKey = bodyKey || urlKey;
try {
const tokenCount = Tokenizer.getTokenCount(value, 'o200k_base');
const memories = await getAllUserMemories(req.user.id);
const existingMemory = memories.find((m) => m.key === key);
const existingMemory = memories.find((m) => m.key === urlKey);
if (!existingMemory) {
return res.status(404).json({ error: 'Memory not found.' });
}
const result = await setMemory({
userId: req.user.id,
key,
value,
tokenCount,
});
// If the key is changing, we need to handle it specially
if (newKey !== urlKey) {
const keyExists = memories.find((m) => m.key === newKey);
if (keyExists) {
return res.status(409).json({ error: 'Memory with this key already exists.' });
}
if (!result.ok) {
return res.status(500).json({ error: 'Failed to update memory.' });
const createResult = await createMemory({
userId: req.user.id,
key: newKey,
value,
tokenCount,
});
if (!createResult.ok) {
return res.status(500).json({ error: 'Failed to create new memory.' });
}
const deleteResult = await deleteMemory({ userId: req.user.id, key: urlKey });
if (!deleteResult.ok) {
return res.status(500).json({ error: 'Failed to delete old memory.' });
}
} else {
// Key is not changing, just update the value
const result = await setMemory({
userId: req.user.id,
key: newKey,
value,
tokenCount,
});
if (!result.ok) {
return res.status(500).json({ error: 'Failed to update memory.' });
}
}
const updatedMemories = await getAllUserMemories(req.user.id);
const updatedMemory = updatedMemories.find((m) => m.key === key);
const updatedMemory = updatedMemories.find((m) => m.key === newKey);
res.json({ updated: true, memory: updatedMemory });
} catch (error) {

View File

@@ -16,8 +16,10 @@ const {
// updatePromptLabels,
makePromptProduction,
} = require('~/models/Prompt');
const { requireJwtAuth } = require('~/server/middleware');
const { requireJwtAuth, generateCheckAccess } = require('~/server/middleware');
const { getUserById, updateUser } = require('~/models');
const { getRoleByName } = require('~/models/Role');
const { logger } = require('~/config');
const router = express.Router();
@@ -181,6 +183,28 @@ router.patch('/:promptId/tags/production', checkPromptCreate, async (req, res) =
}
});
/**
* Route to get user's prompt preferences (favorites and rankings)
* GET /preferences
*/
router.get('/preferences', async (req, res) => {
try {
const user = await getUserById(req.user.id);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
res.json({
favorites: user.promptFavorites || [],
rankings: user.promptRanking || [],
});
} catch (error) {
logger.error('Error getting user preferences', error);
res.status(500).json({ message: 'Error getting user preferences' });
}
});
router.get('/:promptId', async (req, res) => {
const { promptId } = req.params;
const author = req.user.id;
@@ -251,4 +275,79 @@ const deletePromptGroupController = async (req, res) => {
router.delete('/:promptId', checkPromptCreate, deletePromptController);
router.delete('/groups/:groupId', checkPromptCreate, deletePromptGroupController);
/**
* Route to toggle favorite status for a prompt group
* POST /favorites/:groupId
*/
router.post('/favorites/:groupId', async (req, res) => {
try {
const { groupId } = req.params;
const user = await getUserById(req.user.id);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
const favorites = user.promptFavorites || [];
const isFavorite = favorites.some((id) => id.toString() === groupId.toString());
let updatedFavorites;
if (isFavorite) {
updatedFavorites = favorites.filter((id) => id.toString() !== groupId.toString());
} else {
updatedFavorites = [...favorites, groupId];
}
await updateUser(req.user.id, { promptFavorites: updatedFavorites });
const response = {
promptGroupId: groupId,
isFavorite: !isFavorite,
};
res.json(response);
} catch (error) {
logger.error('Error toggling favorite status', error);
res.status(500).json({ message: 'Error updating favorite status' });
}
});
/**
* Route to update prompt group rankings
* PUT /rankings
*/
router.put('/rankings', async (req, res) => {
try {
const { rankings } = req.body;
if (!Array.isArray(rankings)) {
return res.status(400).json({ message: 'Rankings must be an array' });
}
const user = await getUserById(req.user.id);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
const promptRanking = rankings
.filter(({ promptGroupId, order }) => promptGroupId && !isNaN(parseInt(order, 10)))
.map(({ promptGroupId, order }) => ({
promptGroupId,
order: parseInt(order, 10),
}));
const updatedUser = await updateUser(req.user.id, { promptRanking });
res.json({
message: 'Rankings updated successfully',
rankings: updatedUser?.promptRanking || [],
});
} catch (error) {
logger.error('Error updating rankings', error);
res.status(500).json({ message: 'Error updating rankings' });
}
});
module.exports = router;

View File

@@ -152,12 +152,14 @@ describe('AppService', () => {
filteredTools: undefined,
includedTools: undefined,
webSearch: {
safeSearch: 1,
jinaApiKey: '${JINA_API_KEY}',
cohereApiKey: '${COHERE_API_KEY}',
serperApiKey: '${SERPER_API_KEY}',
searxngApiKey: '${SEARXNG_API_KEY}',
firecrawlApiKey: '${FIRECRAWL_API_KEY}',
firecrawlApiUrl: '${FIRECRAWL_API_URL}',
jinaApiKey: '${JINA_API_KEY}',
safeSearch: 1,
serperApiKey: '${SERPER_API_KEY}',
searxngInstanceUrl: '${SEARXNG_INSTANCE_URL}',
},
memory: undefined,
agents: {

View File

@@ -1,4 +1,10 @@
const { CacheKeys, EModelEndpoint, orderEndpointsConfig } = require('librechat-data-provider');
const {
CacheKeys,
EModelEndpoint,
isAgentsEndpoint,
orderEndpointsConfig,
defaultAgentCapabilities,
} = require('librechat-data-provider');
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
const loadConfigEndpoints = require('./loadConfigEndpoints');
const getLogStores = require('~/cache/getLogStores');
@@ -80,8 +86,12 @@ async function getEndpointsConfig(req) {
* @returns {Promise<boolean>}
*/
const checkCapability = async (req, capability) => {
const isAgents = isAgentsEndpoint(req.body?.original_endpoint || req.body?.endpoint);
const endpointsConfig = await getEndpointsConfig(req);
const capabilities = endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [];
const capabilities =
isAgents || endpointsConfig?.[EModelEndpoint.agents]?.capabilities != null
? (endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [])
: defaultAgentCapabilities;
return capabilities.includes(capability);
};

View File

@@ -1,7 +1,7 @@
const path = require('path');
const { loadServiceKey } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas');
const { loadServiceKey, isUserProvided } = require('@librechat/api');
const { EModelEndpoint } = require('librechat-data-provider');
const { isUserProvided } = require('~/server/utils');
const { config } = require('./EndpointService');
const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } = config;
@@ -11,28 +11,28 @@ const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, go
* @param {Express.Request} req - The request object
*/
async function loadAsyncEndpoints(req) {
let i = 0;
let serviceKey, googleUserProvides;
const serviceKeyPath =
process.env.GOOGLE_SERVICE_KEY_FILE_PATH ||
path.join(__dirname, '../../..', 'data', 'auth.json');
try {
serviceKey = await loadServiceKey(serviceKeyPath);
} catch {
if (i === 0) {
i++;
/** Check if GOOGLE_KEY is provided at all(including 'user_provided') */
const isGoogleKeyProvided = googleKey && googleKey.trim() !== '';
if (isGoogleKeyProvided) {
/** If GOOGLE_KEY is provided, check if it's user_provided */
googleUserProvides = isUserProvided(googleKey);
} else {
/** Only attempt to load service key if GOOGLE_KEY is not provided */
const serviceKeyPath =
process.env.GOOGLE_SERVICE_KEY_FILE || path.join(__dirname, '../../..', 'data', 'auth.json');
try {
serviceKey = await loadServiceKey(serviceKeyPath);
} catch (error) {
logger.error('Error loading service key', error);
serviceKey = null;
}
}
if (isUserProvided(googleKey)) {
googleUserProvides = true;
if (i <= 1) {
i++;
}
}
const google = serviceKey || googleKey ? { userProvide: googleUserProvides } : false;
const google = serviceKey || isGoogleKeyProvided ? { userProvide: googleUserProvides } : false;
const useAzure = req.app.locals[EModelEndpoint.azureOpenAI]?.plugins;
const gptPlugins =

View File

@@ -149,7 +149,9 @@ const initializeAgent = async ({
) {
throw new Error(`{ "type": "${ErrorTypes.GOOGLE_TOOL_CONFLICT}"}`);
} else if (
(agent.provider === Providers.OPENAI || agent.provider === Providers.AZURE) &&
(agent.provider === Providers.OPENAI ||
agent.provider === Providers.AZURE ||
agent.provider === Providers.ANTHROPIC) &&
options.tools?.length &&
structuredTools?.length
) {

View File

@@ -78,7 +78,17 @@ function getLLMConfig(apiKey, options = {}) {
requestOptions.anthropicApiUrl = options.reverseProxyUrl;
}
const tools = [];
if (mergedOptions.web_search) {
tools.push({
type: 'web_search_20250305',
name: 'web_search',
});
}
return {
tools,
/** @type {AnthropicClientOptions} */
llmConfig: removeNullishValues(requestOptions),
};

View File

@@ -17,16 +17,24 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
let serviceKey = {};
try {
const serviceKeyPath =
process.env.GOOGLE_SERVICE_KEY_FILE_PATH ||
path.join(__dirname, '../../../..', 'data', 'auth.json');
serviceKey = await loadServiceKey(serviceKeyPath);
if (!serviceKey) {
/** Check if GOOGLE_KEY is provided at all (including 'user_provided') */
const isGoogleKeyProvided =
(GOOGLE_KEY && GOOGLE_KEY.trim() !== '') || (isUserProvided && userKey != null);
if (!isGoogleKeyProvided) {
/** Only attempt to load service key if GOOGLE_KEY is not provided */
try {
const serviceKeyPath =
process.env.GOOGLE_SERVICE_KEY_FILE ||
path.join(__dirname, '../../../..', 'data', 'auth.json');
serviceKey = await loadServiceKey(serviceKeyPath);
if (!serviceKey) {
serviceKey = {};
}
} catch (_e) {
// Service key loading failed, but that's okay if not required
serviceKey = {};
}
} catch (_e) {
// Do nothing
}
const credentials = isUserProvided

View File

@@ -13,7 +13,7 @@ const { getCustomEndpointConfig } = require('~/server/services/Config');
*/
function isKnownCustomProvider(provider) {
return [Providers.XAI, Providers.OLLAMA, Providers.DEEPSEEK, Providers.OPENROUTER].includes(
provider || '',
provider?.toLowerCase() || '',
);
}
@@ -56,7 +56,7 @@ async function getProviderConfig(provider) {
overrideProvider = Providers.OPENAI;
}
if (isKnownCustomProvider(overrideProvider)) {
if (isKnownCustomProvider(overrideProvider || provider) && !customEndpointConfig) {
customEndpointConfig = await getCustomEndpointConfig(provider);
if (!customEndpointConfig) {
throw new Error(`Provider ${provider} not supported`);

View File

@@ -0,0 +1,280 @@
const { Constants } = require('librechat-data-provider');
const { ImportBatchBuilder } = require('./importBatchBuilder');
const { getImporter } = require('./importers');
// Mock the database methods
jest.mock('~/models/Conversation', () => ({
bulkSaveConvos: jest.fn(),
}));
jest.mock('~/models/Message', () => ({
bulkSaveMessages: jest.fn(),
}));
jest.mock('~/cache/getLogStores');
const getLogStores = require('~/cache/getLogStores');
const mockedCacheGet = jest.fn();
getLogStores.mockImplementation(() => ({
get: mockedCacheGet,
}));
describe('Import Timestamp Ordering', () => {
beforeEach(() => {
jest.clearAllMocks();
mockedCacheGet.mockResolvedValue(null);
});
describe('LibreChat Import - Timestamp Issues', () => {
test('should maintain proper timestamp order between parent and child messages', async () => {
// Create a LibreChat export with out-of-order timestamps
const jsonData = {
conversationId: 'test-convo-123',
title: 'Test Conversation',
messages: [
{
messageId: 'parent-1',
parentMessageId: Constants.NO_PARENT,
text: 'Parent Message',
sender: 'user',
isCreatedByUser: true,
createdAt: '2023-01-01T00:02:00Z', // Parent created AFTER child
},
{
messageId: 'child-1',
parentMessageId: 'parent-1',
text: 'Child Message',
sender: 'assistant',
isCreatedByUser: false,
createdAt: '2023-01-01T00:01:00Z', // Child created BEFORE parent
},
{
messageId: 'grandchild-1',
parentMessageId: 'child-1',
text: 'Grandchild Message',
sender: 'user',
isCreatedByUser: true,
createdAt: '2023-01-01T00:00:30Z', // Even earlier
},
],
};
const requestUserId = 'user-123';
const importBatchBuilder = new ImportBatchBuilder(requestUserId);
jest.spyOn(importBatchBuilder, 'saveMessage');
const importer = getImporter(jsonData);
await importer(jsonData, requestUserId, () => importBatchBuilder);
// Check the actual messages stored in the builder
const savedMessages = importBatchBuilder.messages;
const parent = savedMessages.find((msg) => msg.text === 'Parent Message');
const child = savedMessages.find((msg) => msg.text === 'Child Message');
const grandchild = savedMessages.find((msg) => msg.text === 'Grandchild Message');
// Verify all messages were found
expect(parent).toBeDefined();
expect(child).toBeDefined();
expect(grandchild).toBeDefined();
// FIXED behavior: timestamps ARE corrected
expect(new Date(child.createdAt).getTime()).toBeGreaterThan(
new Date(parent.createdAt).getTime(),
);
expect(new Date(grandchild.createdAt).getTime()).toBeGreaterThan(
new Date(child.createdAt).getTime(),
);
});
test('should handle complex multi-branch scenario with out-of-order timestamps', async () => {
const jsonData = {
conversationId: 'complex-test-123',
title: 'Complex Test',
messages: [
// Branch 1: Root -> A -> B with reversed timestamps
{
messageId: 'root-1',
parentMessageId: Constants.NO_PARENT,
text: 'Root 1',
sender: 'user',
isCreatedByUser: true,
createdAt: '2023-01-01T00:03:00Z',
},
{
messageId: 'a-1',
parentMessageId: 'root-1',
text: 'A1',
sender: 'assistant',
isCreatedByUser: false,
createdAt: '2023-01-01T00:02:00Z', // Before parent
},
{
messageId: 'b-1',
parentMessageId: 'a-1',
text: 'B1',
sender: 'user',
isCreatedByUser: true,
createdAt: '2023-01-01T00:01:00Z', // Before grandparent
},
// Branch 2: Root -> C -> D with mixed timestamps
{
messageId: 'root-2',
parentMessageId: Constants.NO_PARENT,
text: 'Root 2',
sender: 'user',
isCreatedByUser: true,
createdAt: '2023-01-01T00:00:30Z', // Earlier than branch 1
},
{
messageId: 'c-2',
parentMessageId: 'root-2',
text: 'C2',
sender: 'assistant',
isCreatedByUser: false,
createdAt: '2023-01-01T00:04:00Z', // Much later
},
{
messageId: 'd-2',
parentMessageId: 'c-2',
text: 'D2',
sender: 'user',
isCreatedByUser: true,
createdAt: '2023-01-01T00:02:30Z', // Between root and parent
},
],
};
const requestUserId = 'user-123';
const importBatchBuilder = new ImportBatchBuilder(requestUserId);
jest.spyOn(importBatchBuilder, 'saveMessage');
const importer = getImporter(jsonData);
await importer(jsonData, requestUserId, () => importBatchBuilder);
const savedMessages = importBatchBuilder.messages;
// Verify that timestamps are preserved as-is (not corrected)
const root1 = savedMessages.find((msg) => msg.text === 'Root 1');
const a1 = savedMessages.find((msg) => msg.text === 'A1');
const b1 = savedMessages.find((msg) => msg.text === 'B1');
const root2 = savedMessages.find((msg) => msg.text === 'Root 2');
const c2 = savedMessages.find((msg) => msg.text === 'C2');
const d2 = savedMessages.find((msg) => msg.text === 'D2');
// Branch 1: timestamps should now be in correct order
expect(new Date(a1.createdAt).getTime()).toBeGreaterThan(new Date(root1.createdAt).getTime());
expect(new Date(b1.createdAt).getTime()).toBeGreaterThan(new Date(a1.createdAt).getTime());
// Branch 2: all timestamps should be properly ordered
expect(new Date(c2.createdAt).getTime()).toBeGreaterThan(new Date(root2.createdAt).getTime());
expect(new Date(d2.createdAt).getTime()).toBeGreaterThan(new Date(c2.createdAt).getTime());
});
test('recursive format should NOW have timestamp protection', async () => {
// Create a recursive LibreChat export with out-of-order timestamps
const jsonData = {
conversationId: 'recursive-test-123',
title: 'Recursive Test',
recursive: true,
messages: [
{
messageId: 'parent-1',
parentMessageId: Constants.NO_PARENT,
text: 'Parent Message',
sender: 'User',
isCreatedByUser: true,
createdAt: '2023-01-01T00:02:00Z', // Parent created AFTER child
children: [
{
messageId: 'child-1',
parentMessageId: 'parent-1',
text: 'Child Message',
sender: 'Assistant',
isCreatedByUser: false,
createdAt: '2023-01-01T00:01:00Z', // Child created BEFORE parent
children: [
{
messageId: 'grandchild-1',
parentMessageId: 'child-1',
text: 'Grandchild Message',
sender: 'User',
isCreatedByUser: true,
createdAt: '2023-01-01T00:00:30Z', // Even earlier
children: [],
},
],
},
],
},
],
};
const requestUserId = 'user-123';
const importBatchBuilder = new ImportBatchBuilder(requestUserId);
const importer = getImporter(jsonData);
await importer(jsonData, requestUserId, () => importBatchBuilder);
const savedMessages = importBatchBuilder.messages;
// Messages should be saved
expect(savedMessages).toHaveLength(3);
// In recursive format, timestamps are NOT included in the saved messages
// The saveMessage method doesn't receive createdAt for recursive imports
const parent = savedMessages.find((msg) => msg.text === 'Parent Message');
const child = savedMessages.find((msg) => msg.text === 'Child Message');
const grandchild = savedMessages.find((msg) => msg.text === 'Grandchild Message');
expect(parent).toBeDefined();
expect(child).toBeDefined();
expect(grandchild).toBeDefined();
// Recursive imports NOW preserve and correct timestamps
expect(parent.createdAt).toBeDefined();
expect(child.createdAt).toBeDefined();
expect(grandchild.createdAt).toBeDefined();
// Timestamps should be corrected to maintain proper order
expect(new Date(child.createdAt).getTime()).toBeGreaterThan(
new Date(parent.createdAt).getTime(),
);
expect(new Date(grandchild.createdAt).getTime()).toBeGreaterThan(
new Date(child.createdAt).getTime(),
);
});
});
describe('Comparison with Fork Functionality', () => {
test('fork functionality correctly handles timestamp issues (for comparison)', async () => {
const { cloneMessagesWithTimestamps } = require('./fork');
const messagesToClone = [
{
messageId: 'parent',
parentMessageId: Constants.NO_PARENT,
text: 'Parent Message',
createdAt: '2023-01-01T00:02:00Z', // Parent created AFTER child
},
{
messageId: 'child',
parentMessageId: 'parent',
text: 'Child Message',
createdAt: '2023-01-01T00:01:00Z', // Child created BEFORE parent
},
];
const importBatchBuilder = new ImportBatchBuilder('user-123');
jest.spyOn(importBatchBuilder, 'saveMessage');
cloneMessagesWithTimestamps(messagesToClone, importBatchBuilder);
const savedMessages = importBatchBuilder.messages;
const parent = savedMessages.find((msg) => msg.text === 'Parent Message');
const child = savedMessages.find((msg) => msg.text === 'Child Message');
// Fork functionality DOES correct the timestamps
expect(new Date(child.createdAt).getTime()).toBeGreaterThan(
new Date(parent.createdAt).getTime(),
);
});
});
});

View File

@@ -1,6 +1,7 @@
const { v4: uuidv4 } = require('uuid');
const { EModelEndpoint, Constants, openAISettings, CacheKeys } = require('librechat-data-provider');
const { createImportBatchBuilder } = require('./importBatchBuilder');
const { cloneMessagesWithTimestamps } = require('./fork');
const getLogStores = require('~/cache/getLogStores');
const logger = require('~/config/winston');
@@ -107,67 +108,47 @@ async function importLibreChatConvo(
if (jsonData.recursive) {
/**
* Recursively traverse the messages tree and save each message to the database.
* Flatten the recursive message tree into a flat array
* @param {TMessage[]} messages
* @param {string} parentMessageId
* @param {TMessage[]} flatMessages
*/
const traverseMessages = async (messages, parentMessageId = null) => {
const flattenMessages = (
messages,
parentMessageId = Constants.NO_PARENT,
flatMessages = [],
) => {
for (const message of messages) {
if (!message.text && !message.content) {
continue;
}
let savedMessage;
if (message.sender?.toLowerCase() === 'user' || message.isCreatedByUser) {
savedMessage = await importBatchBuilder.saveMessage({
text: message.text,
content: message.content,
sender: 'user',
isCreatedByUser: true,
parentMessageId: parentMessageId,
});
} else {
savedMessage = await importBatchBuilder.saveMessage({
text: message.text,
content: message.content,
sender: message.sender,
isCreatedByUser: false,
model: options.model,
parentMessageId: parentMessageId,
});
}
const flatMessage = {
...message,
parentMessageId: parentMessageId,
children: undefined, // Remove children from flat structure
};
flatMessages.push(flatMessage);
if (!firstMessageDate && message.createdAt) {
firstMessageDate = new Date(message.createdAt);
}
if (message.children && message.children.length > 0) {
await traverseMessages(message.children, savedMessage.messageId);
flattenMessages(message.children, message.messageId, flatMessages);
}
}
return flatMessages;
};
await traverseMessages(messagesToImport);
const flatMessages = flattenMessages(messagesToImport);
cloneMessagesWithTimestamps(flatMessages, importBatchBuilder);
} else if (messagesToImport) {
const idMapping = new Map();
cloneMessagesWithTimestamps(messagesToImport, importBatchBuilder);
for (const message of messagesToImport) {
if (!firstMessageDate && message.createdAt) {
firstMessageDate = new Date(message.createdAt);
}
const newMessageId = uuidv4();
idMapping.set(message.messageId, newMessageId);
const clonedMessage = {
...message,
messageId: newMessageId,
parentMessageId:
message.parentMessageId && message.parentMessageId !== Constants.NO_PARENT
? idMapping.get(message.parentMessageId) || Constants.NO_PARENT
: Constants.NO_PARENT,
};
importBatchBuilder.saveMessage(clonedMessage);
}
} else {
throw new Error('Invalid LibreChat file format');

View File

@@ -175,36 +175,60 @@ describe('importLibreChatConvo', () => {
jest.spyOn(importBatchBuilder, 'saveMessage');
jest.spyOn(importBatchBuilder, 'saveBatch');
// When
const importer = getImporter(jsonData);
await importer(jsonData, requestUserId, () => importBatchBuilder);
// Create a map to track original message IDs to new UUIDs
const idToUUIDMap = new Map();
importBatchBuilder.saveMessage.mock.calls.forEach((call) => {
const message = call[0];
idToUUIDMap.set(message.originalMessageId, message.messageId);
// Get the imported messages
const messages = importBatchBuilder.messages;
expect(messages.length).toBeGreaterThan(0);
// Build maps for verification
const textToMessageMap = new Map();
const messageIdToMessage = new Map();
messages.forEach((msg) => {
if (msg.text) {
// For recursive imports, text might be very long, so just use the first 100 chars as key
const textKey = msg.text.substring(0, 100);
textToMessageMap.set(textKey, msg);
}
messageIdToMessage.set(msg.messageId, msg);
});
const checkChildren = (children, parentId) => {
children.forEach((child) => {
const childUUID = idToUUIDMap.get(child.messageId);
const expectedParentId = idToUUIDMap.get(parentId) ?? null;
const messageCall = importBatchBuilder.saveMessage.mock.calls.find(
(call) => call[0].messageId === childUUID,
);
const actualParentId = messageCall[0].parentMessageId;
expect(actualParentId).toBe(expectedParentId);
if (child.children && child.children.length > 0) {
checkChildren(child.children, child.messageId);
// Count expected messages from the tree
const countMessagesInTree = (nodes) => {
let count = 0;
nodes.forEach((node) => {
if (node.text || node.content) {
count++;
}
if (node.children && node.children.length > 0) {
count += countMessagesInTree(node.children);
}
});
return count;
};
// Start hierarchy validation from root messages
checkChildren(jsonData.messages, null);
const expectedMessageCount = countMessagesInTree(jsonData.messages);
expect(messages.length).toBe(expectedMessageCount);
// Verify all messages have valid parent relationships
messages.forEach((msg) => {
if (msg.parentMessageId !== Constants.NO_PARENT) {
const parent = messageIdToMessage.get(msg.parentMessageId);
expect(parent).toBeDefined();
// Verify timestamp ordering
if (msg.createdAt && parent.createdAt) {
expect(new Date(msg.createdAt).getTime()).toBeGreaterThanOrEqual(
new Date(parent.createdAt).getTime(),
);
}
}
});
// Verify at least one root message exists
const rootMessages = messages.filter((msg) => msg.parentMessageId === Constants.NO_PARENT);
expect(rootMessages.length).toBeGreaterThan(0);
expect(importBatchBuilder.saveBatch).toHaveBeenCalled();
});

View File

@@ -1,9 +1,9 @@
import React, { createContext, useContext, useState } from 'react';
import { Constants, EModelEndpoint } from 'librechat-data-provider';
import type { TPlugin, AgentToolType, Action, MCP } from 'librechat-data-provider';
import type { MCP, Action, TPlugin, AgentToolType } from 'librechat-data-provider';
import type { AgentPanelContextType } from '~/common';
import { useAvailableToolsQuery, useGetActionsQuery } from '~/data-provider';
import { useLocalize } from '~/hooks';
import { useLocalize, useGetAgentsConfig } from '~/hooks';
import { Panel } from '~/common';
const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined);
@@ -75,21 +75,25 @@ export function AgentPanelProvider({ children }: { children: React.ReactNode })
{} as Record<string, AgentToolType & { tools?: AgentToolType[] }>,
);
const value = {
action,
setAction,
const { agentsConfig, endpointsConfig } = useGetAgentsConfig();
const value: AgentPanelContextType = {
mcp,
setMcp,
mcps,
setMcps,
activePanel,
setActivePanel,
setCurrentAgentId,
agent_id,
groupedTools,
/** Query data for actions and tools */
actions,
tools,
action,
setMcp,
actions,
setMcps,
agent_id,
setAction,
activePanel,
groupedTools,
agentsConfig,
setActivePanel,
endpointsConfig,
setCurrentAgentId,
};
return <AgentPanelContext.Provider value={value}>{children}</AgentPanelContext.Provider>;

View File

@@ -1,14 +1,25 @@
import React, { createContext, useContext } from 'react';
import { Tools, LocalStorageKeys } from 'librechat-data-provider';
import { useMCPSelect, useToolToggle, useCodeApiKeyForm, useSearchApiKeyForm } from '~/hooks';
import React, { createContext, useContext, useEffect, useRef } from 'react';
import { useSetRecoilState } from 'recoil';
import { Tools, Constants, LocalStorageKeys, AgentCapabilities } from 'librechat-data-provider';
import type { TAgentsEndpoint } from 'librechat-data-provider';
import {
useSearchApiKeyForm,
useGetAgentsConfig,
useCodeApiKeyForm,
useToolToggle,
useMCPSelect,
} from '~/hooks';
import { useGetStartupConfig } from '~/data-provider';
import { ephemeralAgentByConvoId } from '~/store';
interface BadgeRowContextType {
conversationId?: string | null;
agentsConfig?: TAgentsEndpoint | null;
mcpSelect: ReturnType<typeof useMCPSelect>;
webSearch: ReturnType<typeof useToolToggle>;
codeInterpreter: ReturnType<typeof useToolToggle>;
artifacts: ReturnType<typeof useToolToggle>;
fileSearch: ReturnType<typeof useToolToggle>;
codeInterpreter: ReturnType<typeof useToolToggle>;
codeApiKeyForm: ReturnType<typeof useCodeApiKeyForm>;
searchApiKeyForm: ReturnType<typeof useSearchApiKeyForm>;
startupConfig: ReturnType<typeof useGetStartupConfig>['data'];
@@ -26,10 +37,88 @@ export function useBadgeRowContext() {
interface BadgeRowProviderProps {
children: React.ReactNode;
isSubmitting?: boolean;
conversationId?: string | null;
}
export default function BadgeRowProvider({ children, conversationId }: BadgeRowProviderProps) {
export default function BadgeRowProvider({
children,
isSubmitting,
conversationId,
}: BadgeRowProviderProps) {
const hasInitializedRef = useRef(false);
const lastKeyRef = useRef<string>('');
const { agentsConfig } = useGetAgentsConfig();
const key = conversationId ?? Constants.NEW_CONVO;
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(key));
/** Initialize ephemeralAgent from localStorage on mount and when conversation changes */
useEffect(() => {
if (isSubmitting) {
return;
}
// Check if this is a new conversation or the first load
if (!hasInitializedRef.current || lastKeyRef.current !== key) {
hasInitializedRef.current = true;
lastKeyRef.current = key;
// Load all localStorage values
const codeToggleKey = `${LocalStorageKeys.LAST_CODE_TOGGLE_}${key}`;
const webSearchToggleKey = `${LocalStorageKeys.LAST_WEB_SEARCH_TOGGLE_}${key}`;
const fileSearchToggleKey = `${LocalStorageKeys.LAST_FILE_SEARCH_TOGGLE_}${key}`;
const artifactsToggleKey = `${LocalStorageKeys.LAST_ARTIFACTS_TOGGLE_}${key}`;
const codeToggleValue = localStorage.getItem(codeToggleKey);
const webSearchToggleValue = localStorage.getItem(webSearchToggleKey);
const fileSearchToggleValue = localStorage.getItem(fileSearchToggleKey);
const artifactsToggleValue = localStorage.getItem(artifactsToggleKey);
const initialValues: Record<string, any> = {};
if (codeToggleValue !== null) {
try {
initialValues[Tools.execute_code] = JSON.parse(codeToggleValue);
} catch (e) {
console.error('Failed to parse code toggle value:', e);
}
}
if (webSearchToggleValue !== null) {
try {
initialValues[Tools.web_search] = JSON.parse(webSearchToggleValue);
} catch (e) {
console.error('Failed to parse web search toggle value:', e);
}
}
if (fileSearchToggleValue !== null) {
try {
initialValues[Tools.file_search] = JSON.parse(fileSearchToggleValue);
} catch (e) {
console.error('Failed to parse file search toggle value:', e);
}
}
if (artifactsToggleValue !== null) {
try {
initialValues[AgentCapabilities.artifacts] = JSON.parse(artifactsToggleValue);
} catch (e) {
console.error('Failed to parse artifacts toggle value:', e);
}
}
// Always set values for all tools (use defaults if not in localStorage)
// If ephemeralAgent is null, create a new object with just our tool values
setEphemeralAgent((prev) => ({
...(prev || {}),
[Tools.execute_code]: initialValues[Tools.execute_code] ?? false,
[Tools.web_search]: initialValues[Tools.web_search] ?? false,
[Tools.file_search]: initialValues[Tools.file_search] ?? false,
[AgentCapabilities.artifacts]: initialValues[AgentCapabilities.artifacts] ?? false,
}));
}
}, [key, isSubmitting, setEphemeralAgent]);
/** Startup config */
const { data: startupConfig } = useGetStartupConfig();
@@ -74,10 +163,20 @@ export default function BadgeRowProvider({ children, conversationId }: BadgeRowP
isAuthenticated: true,
});
/** Artifacts hook - using a custom key since it's not a Tool but a capability */
const artifacts = useToolToggle({
conversationId,
toolKey: AgentCapabilities.artifacts,
localStorageKey: LocalStorageKeys.LAST_ARTIFACTS_TOGGLE_,
isAuthenticated: true,
});
const value: BadgeRowContextType = {
mcpSelect,
webSearch,
artifacts,
fileSearch,
agentsConfig,
startupConfig,
conversationId,
codeApiKeyForm,

View File

@@ -206,9 +206,7 @@ export type AgentPanelProps = {
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
setMcp: React.Dispatch<React.SetStateAction<t.MCP | undefined>>;
setAction: React.Dispatch<React.SetStateAction<t.Action | undefined>>;
endpointsConfig?: t.TEndpointsConfig;
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
agentsConfig?: t.TAgentsEndpoint | null;
};
export type AgentPanelContextType = {
@@ -225,6 +223,8 @@ export type AgentPanelContextType = {
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
groupedTools?: Record<string, t.AgentToolType & { tools?: t.AgentToolType[] }>;
agent_id?: string;
agentsConfig?: t.TAgentsEndpoint | null;
endpointsConfig?: t.TEndpointsConfig | null;
};
export type AgentModelPanelProps = {

View File

@@ -0,0 +1,152 @@
import React, { memo, useState, useCallback, useMemo } from 'react';
import * as Ariakit from '@ariakit/react';
import { ArtifactModes } from 'librechat-data-provider';
import { WandSparkles, ChevronDown } from 'lucide-react';
import CheckboxButton from '~/components/ui/CheckboxButton';
import { useBadgeRowContext } from '~/Providers';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
interface ArtifactsToggleState {
enabled: boolean;
mode: string;
}
function Artifacts() {
const localize = useLocalize();
const { artifacts } = useBadgeRowContext();
const { toggleState, debouncedChange, isPinned } = artifacts;
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const currentState = useMemo<ArtifactsToggleState>(() => {
if (typeof toggleState === 'string' && toggleState) {
return { enabled: true, mode: toggleState };
}
return { enabled: false, mode: '' };
}, [toggleState]);
const isEnabled = currentState.enabled;
const isShadcnEnabled = currentState.mode === ArtifactModes.SHADCNUI;
const isCustomEnabled = currentState.mode === ArtifactModes.CUSTOM;
const handleToggle = useCallback(() => {
if (isEnabled) {
debouncedChange({ value: '' });
} else {
debouncedChange({ value: ArtifactModes.DEFAULT });
}
}, [isEnabled, debouncedChange]);
const handleShadcnToggle = useCallback(() => {
if (isShadcnEnabled) {
debouncedChange({ value: ArtifactModes.DEFAULT });
} else {
debouncedChange({ value: ArtifactModes.SHADCNUI });
}
}, [isShadcnEnabled, debouncedChange]);
const handleCustomToggle = useCallback(() => {
if (isCustomEnabled) {
debouncedChange({ value: ArtifactModes.DEFAULT });
} else {
debouncedChange({ value: ArtifactModes.CUSTOM });
}
}, [isCustomEnabled, debouncedChange]);
if (!isEnabled && !isPinned) {
return null;
}
return (
<div className="flex">
<CheckboxButton
className={cn('max-w-fit', isEnabled && 'rounded-r-none border-r-0')}
checked={isEnabled}
setValue={handleToggle}
label={localize('com_ui_artifacts')}
isCheckedClassName="border-amber-600/40 bg-amber-500/10 hover:bg-amber-700/10"
icon={<WandSparkles className="icon-md" />}
/>
{isEnabled && (
<Ariakit.MenuProvider open={isPopoverOpen} setOpen={setIsPopoverOpen}>
<Ariakit.MenuButton
className={cn(
'w-7 rounded-l-none rounded-r-full border-b border-l-0 border-r border-t border-border-light md:w-6',
'border-amber-600/40 bg-amber-500/10 hover:bg-amber-700/10',
'transition-colors',
)}
onClick={(e) => e.stopPropagation()}
>
<ChevronDown className="ml-1 h-4 w-4 text-text-secondary md:ml-0" />
</Ariakit.MenuButton>
<Ariakit.Menu
gutter={8}
className={cn(
'animate-popover z-50 flex max-h-[300px]',
'flex-col overflow-auto overscroll-contain rounded-xl',
'bg-surface-secondary px-1.5 py-1 text-text-primary shadow-lg',
'border border-border-light',
'min-w-[250px] outline-none',
)}
portal
>
<div className="px-2 py-1.5">
<div className="mb-2 text-xs font-medium text-text-secondary">
{localize('com_ui_artifacts_options')}
</div>
{/* Include shadcn/ui Option */}
<Ariakit.MenuItem
hideOnClick={false}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleShadcnToggle();
}}
disabled={isCustomEnabled}
className={cn(
'mb-1 flex items-center justify-between rounded-lg px-2 py-2',
'cursor-pointer outline-none transition-colors',
'hover:bg-black/[0.075] dark:hover:bg-white/10',
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
isCustomEnabled && 'cursor-not-allowed opacity-50',
)}
>
<div className="flex items-center gap-2">
<Ariakit.MenuItemCheck checked={isShadcnEnabled} />
<span className="text-sm">{localize('com_ui_include_shadcnui' as any)}</span>
</div>
</Ariakit.MenuItem>
{/* Custom Prompt Mode Option */}
<Ariakit.MenuItem
hideOnClick={false}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleCustomToggle();
}}
className={cn(
'flex items-center justify-between rounded-lg px-2 py-2',
'cursor-pointer outline-none transition-colors',
'hover:bg-black/[0.075] dark:hover:bg-white/10',
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
)}
>
<div className="flex items-center gap-2">
<Ariakit.MenuItemCheck checked={isCustomEnabled} />
<span className="text-sm">{localize('com_ui_custom_prompt_mode' as any)}</span>
</div>
</Ariakit.MenuItem>
</div>
</Ariakit.Menu>
</Ariakit.MenuProvider>
)}
</div>
);
}
export default memo(Artifacts);

View File

@@ -0,0 +1,147 @@
import React from 'react';
import * as Ariakit from '@ariakit/react';
import { ChevronRight, WandSparkles } from 'lucide-react';
import { ArtifactModes } from 'librechat-data-provider';
import { PinIcon } from '~/components/svg';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
interface ArtifactsSubMenuProps {
isArtifactsPinned: boolean;
setIsArtifactsPinned: (value: boolean) => void;
artifactsMode: string;
handleArtifactsToggle: () => void;
handleShadcnToggle: () => void;
handleCustomToggle: () => void;
}
const ArtifactsSubMenu = ({
isArtifactsPinned,
setIsArtifactsPinned,
artifactsMode,
handleArtifactsToggle,
handleShadcnToggle,
handleCustomToggle,
...props
}: ArtifactsSubMenuProps) => {
const localize = useLocalize();
const menuStore = Ariakit.useMenuStore({
focusLoop: true,
showTimeout: 100,
placement: 'right',
});
const isEnabled = artifactsMode !== '' && artifactsMode !== undefined;
const isShadcnEnabled = artifactsMode === ArtifactModes.SHADCNUI;
const isCustomEnabled = artifactsMode === ArtifactModes.CUSTOM;
return (
<Ariakit.MenuProvider store={menuStore}>
<Ariakit.MenuItem
{...props}
hideOnClick={false}
render={
<Ariakit.MenuButton
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
handleArtifactsToggle();
}}
onMouseEnter={() => {
if (isEnabled) {
menuStore.show();
}
}}
className="flex w-full cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-surface-hover"
/>
}
>
<div className="flex items-center gap-2">
<WandSparkles className="icon-md" />
<span>{localize('com_ui_artifacts')}</span>
{isEnabled && <ChevronRight className="ml-auto h-3 w-3" />}
</div>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsArtifactsPinned(!isArtifactsPinned);
}}
className={cn(
'rounded p-1 transition-all duration-200',
'hover:bg-surface-tertiary hover:shadow-sm',
!isArtifactsPinned && 'text-text-secondary hover:text-text-primary',
)}
aria-label={isArtifactsPinned ? 'Unpin' : 'Pin'}
>
<div className="h-4 w-4">
<PinIcon unpin={isArtifactsPinned} />
</div>
</button>
</Ariakit.MenuItem>
{isEnabled && (
<Ariakit.Menu
portal={true}
unmountOnHide={true}
className={cn(
'animate-popover-left z-50 ml-3 flex min-w-[250px] flex-col rounded-xl',
'border border-border-light bg-surface-secondary px-1.5 py-1 shadow-lg',
)}
>
<div className="px-2 py-1.5">
<div className="mb-2 text-xs font-medium text-text-secondary">
{localize('com_ui_artifacts_options')}
</div>
{/* Include shadcn/ui Option */}
<Ariakit.MenuItem
hideOnClick={false}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleShadcnToggle();
}}
disabled={isCustomEnabled}
className={cn(
'mb-1 flex items-center justify-between rounded-lg px-2 py-2',
'cursor-pointer text-text-primary outline-none transition-colors',
'hover:bg-black/[0.075] dark:hover:bg-white/10',
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
isCustomEnabled && 'cursor-not-allowed opacity-50',
)}
>
<div className="flex items-center gap-2">
<Ariakit.MenuItemCheck checked={isShadcnEnabled} />
<span className="text-sm">{localize('com_ui_include_shadcnui' as any)}</span>
</div>
</Ariakit.MenuItem>
{/* Custom Prompt Mode Option */}
<Ariakit.MenuItem
hideOnClick={false}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleCustomToggle();
}}
className={cn(
'flex items-center justify-between rounded-lg px-2 py-2',
'cursor-pointer text-text-primary outline-none transition-colors',
'hover:bg-black/[0.075] dark:hover:bg-white/10',
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
)}
>
<div className="flex items-center gap-2">
<Ariakit.MenuItemCheck checked={isCustomEnabled} />
<span className="text-sm">{localize('com_ui_custom_prompt_mode' as any)}</span>
</div>
</Ariakit.MenuItem>
</div>
</Ariakit.Menu>
)}
</Ariakit.MenuProvider>
);
};
export default React.memo(ArtifactsSubMenu);

View File

@@ -18,6 +18,7 @@ import { useChatBadges } from '~/hooks';
import { Badge } from '~/components/ui';
import ToolDialogs from './ToolDialogs';
import FileSearch from './FileSearch';
import Artifacts from './Artifacts';
import MCPSelect from './MCPSelect';
import WebSearch from './WebSearch';
import store from '~/store';
@@ -27,6 +28,7 @@ interface BadgeRowProps {
onChange: (badges: Pick<BadgeItem, 'id'>[]) => void;
onToggle?: (badgeId: string, currentActive: boolean) => void;
conversationId?: string | null;
isSubmitting?: boolean;
isInChat: boolean;
}
@@ -140,6 +142,7 @@ const dragReducer = (state: DragState, action: DragAction): DragState => {
function BadgeRow({
showEphemeralBadges,
conversationId,
isSubmitting,
onChange,
onToggle,
isInChat,
@@ -317,7 +320,7 @@ function BadgeRow({
}, [dragState.draggedBadge, handleMouseMove, handleMouseUp]);
return (
<BadgeRowProvider conversationId={conversationId}>
<BadgeRowProvider conversationId={conversationId} isSubmitting={isSubmitting}>
<div ref={containerRef} className="relative flex flex-wrap items-center gap-2">
{showEphemeralBadges === true && <ToolsDropdown />}
{tempBadges.map((badge, index) => (
@@ -364,6 +367,7 @@ function BadgeRow({
<WebSearch />
<CodeInterpreter />
<FileSearch />
<Artifacts />
<MCPSelect />
</>
)}

View File

@@ -305,6 +305,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
</div>
<BadgeRow
showEphemeralBadges={!isAgentsEndpoint(endpoint) && !isAssistantsEndpoint(endpoint)}
isSubmitting={isSubmitting || isSubmittingAdded}
conversationId={conversationId}
onChange={setBadges}
isInChat={

View File

@@ -2,11 +2,10 @@ import { useSetRecoilState } from 'recoil';
import * as Ariakit from '@ariakit/react';
import React, { useRef, useState, useMemo } from 'react';
import { FileSearch, ImageUpIcon, TerminalSquareIcon, FileType2Icon } from 'lucide-react';
import { EToolResources, EModelEndpoint, defaultAgentCapabilities } from 'librechat-data-provider';
import type { EndpointFileConfig } from 'librechat-data-provider';
import { useLocalize, useGetAgentsConfig, useFileHandling, useAgentCapabilities } from '~/hooks';
import { FileUpload, TooltipAnchor, DropdownPopup, AttachmentIcon } from '~/components';
import { EToolResources, EModelEndpoint } from 'librechat-data-provider';
import { useGetEndpointsQuery } from '~/data-provider';
import { useLocalize, useFileHandling } from '~/hooks';
import { ephemeralAgentByConvoId } from '~/store';
import { cn } from '~/utils';
@@ -23,20 +22,17 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
const [isPopoverActive, setIsPopoverActive] = useState(false);
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(conversationId));
const [toolResource, setToolResource] = useState<EToolResources | undefined>();
const { data: endpointsConfig } = useGetEndpointsQuery();
const { handleFileChange } = useFileHandling({
overrideEndpoint: EModelEndpoint.agents,
overrideEndpointFileConfig: endpointFileConfig,
});
const { agentsConfig } = useGetAgentsConfig();
/** TODO: Ephemeral Agent Capabilities
* Allow defining agent capabilities on a per-endpoint basis
* Use definition for agents endpoint for ephemeral agents
* */
const capabilities = useMemo(
() => endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [],
[endpointsConfig],
);
const capabilities = useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
const handleUploadClick = (isImage?: boolean) => {
if (!inputRef.current) {
@@ -60,7 +56,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
},
];
if (capabilities.includes(EToolResources.ocr)) {
if (capabilities.ocrEnabled) {
items.push({
label: localize('com_ui_upload_ocr_text'),
onClick: () => {
@@ -71,7 +67,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
});
}
if (capabilities.includes(EToolResources.file_search)) {
if (capabilities.fileSearchEnabled) {
items.push({
label: localize('com_ui_upload_file_search'),
onClick: () => {
@@ -83,7 +79,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
});
}
if (capabilities.includes(EToolResources.execute_code)) {
if (capabilities.codeEnabled) {
items.push({
label: localize('com_ui_upload_code_files'),
onClick: () => {

View File

@@ -2,11 +2,18 @@ import React, { useState, useMemo, useCallback } from 'react';
import * as Ariakit from '@ariakit/react';
import { Globe, Settings, Settings2, TerminalSquareIcon } from 'lucide-react';
import type { MenuItemProps } from '~/common';
import { Permissions, PermissionTypes, AuthType } from 'librechat-data-provider';
import {
AuthType,
Permissions,
ArtifactModes,
PermissionTypes,
defaultAgentCapabilities,
} from 'librechat-data-provider';
import { TooltipAnchor, DropdownPopup } from '~/components';
import { useLocalize, useHasAccess, useAgentCapabilities } from '~/hooks';
import ArtifactsSubMenu from '~/components/Chat/Input/ArtifactsSubMenu';
import MCPSubMenu from '~/components/Chat/Input/MCPSubMenu';
import { PinIcon, VectorIcon } from '~/components/svg';
import { useLocalize, useHasAccess } from '~/hooks';
import { useBadgeRowContext } from '~/Providers';
import { cn } from '~/utils';
@@ -21,12 +28,17 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
const {
webSearch,
mcpSelect,
artifacts,
fileSearch,
agentsConfig,
startupConfig,
codeApiKeyForm,
codeInterpreter,
searchApiKeyForm,
} = useBadgeRowContext();
const { codeEnabled, webSearchEnabled, artifactsEnabled, fileSearchEnabled } =
useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
const { setIsDialogOpen: setIsCodeDialogOpen, menuTriggerRef: codeMenuTriggerRef } =
codeApiKeyForm;
const { setIsDialogOpen: setIsSearchDialogOpen, menuTriggerRef: searchMenuTriggerRef } =
@@ -42,6 +54,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
authData: codeAuthData,
} = codeInterpreter;
const { isPinned: isFileSearchPinned, setIsPinned: setIsFileSearchPinned } = fileSearch;
const { isPinned: isArtifactsPinned, setIsPinned: setIsArtifactsPinned } = artifacts;
const {
mcpValues,
mcpServerNames,
@@ -72,19 +85,46 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
const handleWebSearchToggle = useCallback(() => {
const newValue = !webSearch.toggleState;
webSearch.debouncedChange({ isChecked: newValue });
webSearch.debouncedChange({ value: newValue });
}, [webSearch]);
const handleCodeInterpreterToggle = useCallback(() => {
const newValue = !codeInterpreter.toggleState;
codeInterpreter.debouncedChange({ isChecked: newValue });
codeInterpreter.debouncedChange({ value: newValue });
}, [codeInterpreter]);
const handleFileSearchToggle = useCallback(() => {
const newValue = !fileSearch.toggleState;
fileSearch.debouncedChange({ isChecked: newValue });
fileSearch.debouncedChange({ value: newValue });
}, [fileSearch]);
const handleArtifactsToggle = useCallback(() => {
const currentState = artifacts.toggleState;
if (!currentState || currentState === '') {
artifacts.debouncedChange({ value: ArtifactModes.DEFAULT });
} else {
artifacts.debouncedChange({ value: '' });
}
}, [artifacts]);
const handleShadcnToggle = useCallback(() => {
const currentState = artifacts.toggleState;
if (currentState === ArtifactModes.SHADCNUI) {
artifacts.debouncedChange({ value: ArtifactModes.DEFAULT });
} else {
artifacts.debouncedChange({ value: ArtifactModes.SHADCNUI });
}
}, [artifacts]);
const handleCustomToggle = useCallback(() => {
const currentState = artifacts.toggleState;
if (currentState === ArtifactModes.CUSTOM) {
artifacts.debouncedChange({ value: ArtifactModes.DEFAULT });
} else {
artifacts.debouncedChange({ value: ArtifactModes.CUSTOM });
}
}, [artifacts]);
const handleMCPToggle = useCallback(
(serverName: string) => {
const currentValues = mcpSelect.mcpValues ?? [];
@@ -98,9 +138,10 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
const mcpPlaceholder = startupConfig?.interface?.mcpServers?.placeholder;
const dropdownItems = useMemo(() => {
const items: MenuItemProps[] = [];
items.push({
const dropdownItems: MenuItemProps[] = [];
if (fileSearchEnabled) {
dropdownItems.push({
onClick: handleFileSearchToggle,
hideOnClick: false,
render: (props) => (
@@ -129,159 +170,149 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
</div>
),
});
}
if (canUseWebSearch) {
items.push({
onClick: handleWebSearchToggle,
hideOnClick: false,
render: (props) => (
<div {...props}>
<div className="flex items-center gap-2">
<Globe className="icon-md" />
<span>{localize('com_ui_web_search')}</span>
</div>
<div className="flex items-center gap-1">
{showWebSearchSettings && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsSearchDialogOpen(true);
}}
className={cn(
'rounded p-1 transition-all duration-200',
'hover:bg-surface-secondary hover:shadow-sm',
'text-text-secondary hover:text-text-primary',
)}
aria-label="Configure web search"
ref={searchMenuTriggerRef}
>
<div className="h-4 w-4">
<Settings className="h-4 w-4" />
</div>
</button>
)}
if (canUseWebSearch && webSearchEnabled) {
dropdownItems.push({
onClick: handleWebSearchToggle,
hideOnClick: false,
render: (props) => (
<div {...props}>
<div className="flex items-center gap-2">
<Globe className="icon-md" />
<span>{localize('com_ui_web_search')}</span>
</div>
<div className="flex items-center gap-1">
{showWebSearchSettings && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsSearchPinned(!isSearchPinned);
setIsSearchDialogOpen(true);
}}
className={cn(
'rounded p-1 transition-all duration-200',
'hover:bg-surface-secondary hover:shadow-sm',
!isSearchPinned && 'text-text-secondary hover:text-text-primary',
'text-text-secondary hover:text-text-primary',
)}
aria-label={isSearchPinned ? 'Unpin' : 'Pin'}
aria-label="Configure web search"
ref={searchMenuTriggerRef}
>
<div className="h-4 w-4">
<PinIcon unpin={isSearchPinned} />
<Settings className="h-4 w-4" />
</div>
</button>
</div>
</div>
),
});
}
if (canRunCode) {
items.push({
onClick: handleCodeInterpreterToggle,
hideOnClick: false,
render: (props) => (
<div {...props}>
<div className="flex items-center gap-2">
<TerminalSquareIcon className="icon-md" />
<span>{localize('com_assistants_code_interpreter')}</span>
</div>
<div className="flex items-center gap-1">
{showCodeSettings && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsCodeDialogOpen(true);
}}
ref={codeMenuTriggerRef}
className={cn(
'rounded p-1 transition-all duration-200',
'hover:bg-surface-secondary hover:shadow-sm',
'text-text-secondary hover:text-text-primary',
)}
aria-label="Configure code interpreter"
>
<div className="h-4 w-4">
<Settings className="h-4 w-4" />
</div>
</button>
)}
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsSearchPinned(!isSearchPinned);
}}
className={cn(
'rounded p-1 transition-all duration-200',
'hover:bg-surface-secondary hover:shadow-sm',
!isSearchPinned && 'text-text-secondary hover:text-text-primary',
)}
aria-label={isSearchPinned ? 'Unpin' : 'Pin'}
>
<div className="h-4 w-4">
<PinIcon unpin={isSearchPinned} />
</div>
</button>
</div>
</div>
),
});
}
if (canRunCode && codeEnabled) {
dropdownItems.push({
onClick: handleCodeInterpreterToggle,
hideOnClick: false,
render: (props) => (
<div {...props}>
<div className="flex items-center gap-2">
<TerminalSquareIcon className="icon-md" />
<span>{localize('com_assistants_code_interpreter')}</span>
</div>
<div className="flex items-center gap-1">
{showCodeSettings && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsCodePinned(!isCodePinned);
setIsCodeDialogOpen(true);
}}
ref={codeMenuTriggerRef}
className={cn(
'rounded p-1 transition-all duration-200',
'hover:bg-surface-secondary hover:shadow-sm',
!isCodePinned && 'text-text-primary hover:text-text-primary',
'text-text-secondary hover:text-text-primary',
)}
aria-label={isCodePinned ? 'Unpin' : 'Pin'}
aria-label="Configure code interpreter"
>
<div className="h-4 w-4">
<PinIcon unpin={isCodePinned} />
<Settings className="h-4 w-4" />
</div>
</button>
</div>
)}
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsCodePinned(!isCodePinned);
}}
className={cn(
'rounded p-1 transition-all duration-200',
'hover:bg-surface-secondary hover:shadow-sm',
!isCodePinned && 'text-text-primary hover:text-text-primary',
)}
aria-label={isCodePinned ? 'Unpin' : 'Pin'}
>
<div className="h-4 w-4">
<PinIcon unpin={isCodePinned} />
</div>
</button>
</div>
),
});
}
</div>
),
});
}
if (mcpServerNames && mcpServerNames.length > 0) {
items.push({
hideOnClick: false,
render: (props) => (
<MCPSubMenu
{...props}
mcpValues={mcpValues}
isMCPPinned={isMCPPinned}
placeholder={mcpPlaceholder}
mcpServerNames={mcpServerNames}
setIsMCPPinned={setIsMCPPinned}
handleMCPToggle={handleMCPToggle}
/>
),
});
}
if (artifactsEnabled) {
dropdownItems.push({
hideOnClick: false,
render: (props) => (
<ArtifactsSubMenu
{...props}
isArtifactsPinned={isArtifactsPinned}
setIsArtifactsPinned={setIsArtifactsPinned}
artifactsMode={artifacts.toggleState as string}
handleArtifactsToggle={handleArtifactsToggle}
handleShadcnToggle={handleShadcnToggle}
handleCustomToggle={handleCustomToggle}
/>
),
});
}
return items;
}, [
localize,
mcpValues,
canRunCode,
isMCPPinned,
isCodePinned,
mcpPlaceholder,
mcpServerNames,
isSearchPinned,
setIsMCPPinned,
canUseWebSearch,
setIsCodePinned,
handleMCPToggle,
showCodeSettings,
setIsSearchPinned,
isFileSearchPinned,
codeMenuTriggerRef,
setIsCodeDialogOpen,
searchMenuTriggerRef,
showWebSearchSettings,
setIsFileSearchPinned,
handleWebSearchToggle,
setIsSearchDialogOpen,
handleFileSearchToggle,
handleCodeInterpreterToggle,
]);
if (mcpServerNames && mcpServerNames.length > 0) {
dropdownItems.push({
hideOnClick: false,
render: (props) => (
<MCPSubMenu
{...props}
mcpValues={mcpValues}
isMCPPinned={isMCPPinned}
placeholder={mcpPlaceholder}
mcpServerNames={mcpServerNames}
setIsMCPPinned={setIsMCPPinned}
handleMCPToggle={handleMCPToggle}
/>
),
});
}
const menuTrigger = (
<TooltipAnchor

View File

@@ -8,7 +8,7 @@ import { useBadgeRowContext } from '~/Providers';
function WebSearch() {
const localize = useLocalize();
const { webSearch: webSearchData, searchApiKeyForm } = useBadgeRowContext();
const { toggleState: webSearch, debouncedChange, isPinned } = webSearchData;
const { toggleState: webSearch, debouncedChange, isPinned, authData } = webSearchData;
const { badgeTriggerRef } = searchApiKeyForm;
const canUseWebSearch = useHasAccess({
@@ -21,7 +21,7 @@ function WebSearch() {
}
return (
(webSearch || isPinned) && (
(isPinned || (webSearch && authData?.authenticated)) && (
<CheckboxButton
ref={badgeTriggerRef}
className="max-w-fit"

View File

@@ -233,9 +233,17 @@ export default function Fork({
status: 'info',
});
},
onError: () => {
onError: (error) => {
/** Rate limit error (429 status code) */
const isRateLimitError =
(error as any)?.response?.status === 429 ||
(error as any)?.status === 429 ||
(error as any)?.statusCode === 429;
showToast({
message: localize('com_ui_fork_error'),
message: isRateLimitError
? localize('com_ui_fork_error_rate_limit')
: localize('com_ui_fork_error'),
status: 'error',
});
},

View File

@@ -17,7 +17,6 @@ import {
General,
Chat,
Speech,
Beta,
Commands,
Data,
Account,
@@ -233,9 +232,6 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
<Tabs.Content value={SettingsTabValues.CHAT}>
<Chat />
</Tabs.Content>
<Tabs.Content value={SettingsTabValues.BETA}>
<Beta />
</Tabs.Content>
<Tabs.Content value={SettingsTabValues.COMMANDS}>
<Commands />
</Tabs.Content>

View File

@@ -1,18 +0,0 @@
import { memo } from 'react';
import CodeArtifacts from './CodeArtifacts';
import ChatBadges from './ChatBadges';
function Beta() {
return (
<div className="flex flex-col gap-3 p-1 text-sm text-text-primary">
<div className="pb-3">
<CodeArtifacts />
</div>
{/* <div className="pb-3">
<ChatBadges />
</div> */}
</div>
);
}
export default memo(Beta);

View File

@@ -1,22 +0,0 @@
import { useSetRecoilState } from 'recoil';
import { Button } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';
export default function ChatBadges() {
const setIsEditing = useSetRecoilState<boolean>(store.isEditingBadges);
const localize = useLocalize();
const handleEditChatBadges = () => {
setIsEditing(true);
};
return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_edit_chat_badges')}</div>
<Button variant="outline" onClick={handleEditChatBadges}>
{localize('com_ui_edit')}
</Button>
</div>
);
}

View File

@@ -1,95 +0,0 @@
import { useRecoilState } from 'recoil';
import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';
export default function CodeArtifacts() {
const [codeArtifacts, setCodeArtifacts] = useRecoilState<boolean>(store.codeArtifacts);
const [includeShadcnui, setIncludeShadcnui] = useRecoilState<boolean>(store.includeShadcnui);
const [customPromptMode, setCustomPromptMode] = useRecoilState<boolean>(store.customPromptMode);
const localize = useLocalize();
const handleCodeArtifactsChange = (value: boolean) => {
setCodeArtifacts(value);
if (!value) {
setIncludeShadcnui(false);
setCustomPromptMode(false);
}
};
const handleIncludeShadcnuiChange = (value: boolean) => {
setIncludeShadcnui(value);
};
const handleCustomPromptModeChange = (value: boolean) => {
setCustomPromptMode(value);
if (value) {
setIncludeShadcnui(false);
}
};
return (
<div className="space-y-4">
<h3 className="text-lg font-medium">{localize('com_ui_artifacts')}</h3>
<div className="space-y-2">
<SwitchItem
id="codeArtifacts"
label={localize('com_ui_artifacts_toggle')}
checked={codeArtifacts}
onCheckedChange={handleCodeArtifactsChange}
hoverCardText="com_nav_info_code_artifacts"
/>
<SwitchItem
id="includeShadcnui"
label={localize('com_ui_include_shadcnui')}
checked={includeShadcnui}
onCheckedChange={handleIncludeShadcnuiChange}
hoverCardText="com_nav_info_include_shadcnui"
disabled={!codeArtifacts || customPromptMode}
/>
<SwitchItem
id="customPromptMode"
label={localize('com_ui_custom_prompt_mode')}
checked={customPromptMode}
onCheckedChange={handleCustomPromptModeChange}
hoverCardText="com_nav_info_custom_prompt_mode"
disabled={!codeArtifacts}
/>
</div>
</div>
);
}
function SwitchItem({
id,
label,
checked,
onCheckedChange,
hoverCardText,
disabled = false,
}: {
id: string;
label: string;
checked: boolean;
onCheckedChange: (value: boolean) => void;
hoverCardText: string;
disabled?: boolean;
}) {
return (
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className={disabled ? 'text-gray-400' : ''}>{label}</div>
<HoverCardSettings side="bottom" text={hoverCardText} />
</div>
<Switch
id={id}
checked={checked}
onCheckedChange={onCheckedChange}
className="ml-4"
data-testid={id}
disabled={disabled}
/>
</div>
);
}

View File

@@ -86,6 +86,7 @@ export const LangSelector = ({
{ value: 'fr-FR', label: localize('com_nav_lang_french') },
{ value: 'he-HE', label: localize('com_nav_lang_hebrew') },
{ value: 'hu-HU', label: localize('com_nav_lang_hungarian') },
{ value: 'hy-AM', label: localize('com_nav_lang_armenian') },
{ value: 'it-IT', label: localize('com_nav_lang_italian') },
{ value: 'pl-PL', label: localize('com_nav_lang_polish') },
{ value: 'pt-BR', label: localize('com_nav_lang_brazilian_portuguese') },
@@ -96,9 +97,11 @@ export const LangSelector = ({
{ value: 'cs-CZ', label: localize('com_nav_lang_czech') },
{ value: 'sv-SE', label: localize('com_nav_lang_swedish') },
{ value: 'ko-KR', label: localize('com_nav_lang_korean') },
{ value: 'lv-LV', label: localize('com_nav_lang_latvian') },
{ value: 'vi-VN', label: localize('com_nav_lang_vietnamese') },
{ value: 'th-TH', label: localize('com_nav_lang_thai') },
{ value: 'tr-TR', label: localize('com_nav_lang_turkish') },
{ value: 'ug', label: localize('com_nav_lang_uyghur') },
{ value: 'nl-NL', label: localize('com_nav_lang_dutch') },
{ value: 'id-ID', label: localize('com_nav_lang_indonesia') },
{ value: 'fi-FI', label: localize('com_nav_lang_finnish') },

View File

@@ -1,7 +1,6 @@
export { default as General } from './General/General';
export { default as Chat } from './Chat/Chat';
export { default as Data } from './Data/Data';
export { default as Beta } from './Beta/Beta';
export { default as Commands } from './Commands/Commands';
export { RevokeKeysButton } from './Data/RevokeKeysButton';
export { default as Account } from './Account/Account';

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { Plus } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import { useLocalize, useHasAccess } from '~/hooks';
import { Button } from '~/components/ui';
const CreatePromptButton: React.FC<{ isChatRoute: boolean }> = ({ isChatRoute }) => {
const navigate = useNavigate();
const localize = useLocalize();
const hasCreateAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.CREATE,
});
return (
<>
{hasCreateAccess && (
<div className="flex w-full justify-end">
<Button
variant="outline"
className={`w-full bg-transparent ${isChatRoute ? '' : 'mx-2'}`}
onClick={() => navigate('/d/prompts/new')}
>
<Plus className="size-4" aria-hidden />
{localize('com_ui_create_prompt')}
</Button>
</div>
)}
</>
);
};
export default CreatePromptButton;

View File

@@ -1,5 +1,14 @@
import { Cog } from 'lucide-react';
import { useRecoilState } from 'recoil';
import { Switch } from '~/components/ui';
import {
Label,
Switch,
Button,
OGDialog,
OGDialogTrigger,
OGDialogContent,
OGDialogTitle,
} from '~/components';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
import store from '~/store';
@@ -22,20 +31,30 @@ export default function AutoSendPrompt({
};
return (
<div
className={cn(
'flex select-none items-center justify-end gap-2 text-right text-sm',
className,
)}
>
<div> {localize('com_nav_auto_send_prompts')} </div>
<Switch
aria-label="toggle-auto-send-prompts"
id="autoSendPrompts"
checked={autoSendPrompts}
onCheckedChange={handleCheckedChange}
data-testid="autoSendPrompts"
/>
</div>
<>
<OGDialog>
<OGDialogTrigger className="flex items-center justify-center">
<Button size="sm" variant="outline" className="bg-transparent hover:bg-surface-hover">
<Cog className="h-4 w-4 text-text-primary" />
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-96">
<OGDialogTitle className="text-lg font-semibold">
{localize('com_ui_prompts_settings')}
</OGDialogTitle>
<div className={cn('flex justify-between text-text-secondary', className)}>
<Label>{localize('com_nav_auto_send_prompts')}</Label>
<Switch
aria-label="toggle-auto-send-prompts"
id="autoSendPrompts"
checked={autoSendPrompts}
onCheckedChange={handleCheckedChange}
data-testid="autoSendPrompts"
/>
</div>
</OGDialogContent>
</OGDialog>
</>
);
}

View File

@@ -10,6 +10,7 @@ import {
} from '~/components/ui';
import { useLocalize, useSubmitMessage, useCustomLink, useAuthContext } from '~/hooks';
import VariableDialog from '~/components/Prompts/Groups/VariableDialog';
import FavoriteButton from '~/components/Prompts/Groups/FavoriteButton';
import PreviewPrompt from '~/components/Prompts/PreviewPrompt';
import ListCard from '~/components/Prompts/Groups/ListCard';
import { detectVariables } from '~/utils';
@@ -64,6 +65,7 @@ function ChatGroupItem({
{groupIsGlobal === true && (
<EarthIcon className="icon-md text-green-400" aria-label="Global prompt group" />
)}
<FavoriteButton groupId={group._id ?? ''} size="16" />
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<button

View File

@@ -2,12 +2,12 @@ import { memo, useState, useRef, useMemo, useCallback, KeyboardEvent } from 'rea
import { EarthIcon, Pen } from 'lucide-react';
import { useNavigate, useParams } from 'react-router-dom';
import { SystemRoles, type TPromptGroup } from 'librechat-data-provider';
import { Input, Label, Button, OGDialog, OGDialogTrigger, TrashIcon } from '~/components';
import { useDeletePromptGroup, useUpdatePromptGroup } from '~/data-provider';
import { Input, Label, Button, OGDialog, OGDialogTrigger } from '~/components/ui';
import FavoriteButton from '~/components/Prompts/Groups/FavoriteButton';
import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon';
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
import { useLocalize, useAuthContext } from '~/hooks';
import { TrashIcon } from '~/components/svg';
import { cn } from '~/utils/';
interface DashGroupItemProps {
@@ -49,7 +49,6 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
const { isLoading } = updateGroup;
const handleSaveRename = useCallback(() => {
console.log(group._id ?? '', { name: nameInputValue });
updateGroup.mutate({ id: group._id ?? '', payload: { name: nameInputValue } });
}, [group._id, nameInputValue, updateGroup]);
@@ -99,6 +98,7 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
aria-label={localize('com_ui_global_group')}
/>
)}
<FavoriteButton groupId={group._id ?? ''} size="16" />
{(isOwner || user?.role === SystemRoles.ADMIN) && (
<>
<OGDialog>

View File

@@ -0,0 +1,74 @@
import React, { memo, useCallback } from 'react';
import { useTogglePromptFavorite, useGetUserPromptPreferences } from '~/data-provider';
import { Button, StarIcon } from '~/components';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
interface FavoriteButtonProps {
groupId: string;
size?: string | number;
onToggle?: (isFavorite: boolean) => void;
}
function FavoriteButton({ groupId, size = '1em', onToggle }: FavoriteButtonProps) {
const localize = useLocalize();
const { data: preferences } = useGetUserPromptPreferences();
const toggleFavorite = useTogglePromptFavorite();
const isFavorite = preferences?.favorites?.includes(groupId) ?? false;
const handleToggle = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
toggleFavorite.mutate(
{ groupId },
{
onSuccess: () => {
onToggle?.(!isFavorite);
},
},
);
},
[groupId, isFavorite, onToggle, toggleFavorite],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
handleToggle(e as unknown as React.MouseEvent);
}
},
[handleToggle],
);
return (
<Button
variant="ghost"
onClick={handleToggle}
onKeyDown={handleKeyDown}
disabled={toggleFavorite.isLoading}
aria-label={
isFavorite ? localize('com_ui_remove_from_favorites') : localize('com_ui_add_to_favorites')
}
title={
isFavorite ? localize('com_ui_remove_from_favorites') : localize('com_ui_add_to_favorites')
}
className="h-8 w-8 p-0 hover:bg-surface-hover"
>
<StarIcon
size={size}
filled={isFavorite}
className={cn(
'transition-colors duration-200',
isFavorite
? 'text-yellow-500 hover:text-yellow-600'
: 'text-gray-400 hover:text-yellow-500',
)}
/>
</Button>
);
}
export default memo(FavoriteButton);

View File

@@ -1,5 +1,5 @@
import { ListFilter, User, Share2 } from 'lucide-react';
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { SystemCategories } from 'librechat-data-provider';
import { usePromptGroupsNav, useLocalize, useCategories } from '~/hooks';
@@ -19,7 +19,6 @@ export default function FilterPrompts({
const setCategory = useSetRecoilState(store.promptsCategory);
const categoryFilter = useRecoilValue(store.promptsCategory);
const { categories } = useCategories('h-4 w-4');
const [isSearching, setIsSearching] = useState(false);
const filterOptions = useMemo(() => {
const baseOptions: Option[] = [
@@ -41,7 +40,7 @@ export default function FilterPrompts({
{ divider: true, value: null },
];
const categoryOptions = categories
const categoryOptions = categories?.length
? [...categories]
: [
{
@@ -55,22 +54,18 @@ export default function FilterPrompts({
const onSelect = useCallback(
(value: string) => {
if (value === SystemCategories.ALL) {
setCategory('');
} else {
setCategory(value);
}
setCategory(value === SystemCategories.ALL ? '' : value);
},
[setCategory],
);
useEffect(() => {
setIsSearching(true);
const timeout = setTimeout(() => {
setIsSearching(false);
}, 500);
return () => clearTimeout(timeout);
}, [displayName]);
const handleSearchChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setDisplayName(e.target.value);
setName(e.target.value);
},
[setName],
);
return (
<div className={cn('flex w-full gap-2 text-text-primary', className)}>
@@ -78,7 +73,7 @@ export default function FilterPrompts({
value={categoryFilter || SystemCategories.ALL}
onChange={onSelect}
options={filterOptions}
className="bg-transparent"
className="rounded-lg bg-transparent"
icon={<ListFilter className="h-4 w-4" />}
label="Filter: "
ariaLabel={localize('com_ui_filter_prompts')}
@@ -86,11 +81,7 @@ export default function FilterPrompts({
/>
<AnimatedSearchInput
value={displayName}
onChange={(e) => {
setDisplayName(e.target.value);
setName(e.target.value);
}}
isSearching={isSearching}
onChange={handleSearchChange}
placeholder={localize('com_ui_filter_prompts_name')}
/>
</div>

View File

@@ -1,5 +1,3 @@
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import PanelNavigation from '~/components/Prompts/Groups/PanelNavigation';
import ManagePrompts from '~/components/Prompts/ManagePrompts';
import { useMediaQuery, usePromptGroupsNav } from '~/hooks';
@@ -18,14 +16,14 @@ export default function GroupSidePanel({
groupsQuery,
promptGroups,
hasPreviousPage,
isChatRoute,
}: {
children?: React.ReactNode;
isDetailView?: boolean;
className?: string;
isChatRoute: boolean;
} & ReturnType<typeof usePromptGroupsNav>) {
const location = useLocation();
const isSmallerScreen = useMediaQuery('(max-width: 1024px)');
const isChatRoute = useMemo(() => location.pathname?.startsWith('/c/'), [location.pathname]);
return (
<div

View File

@@ -1,81 +1,107 @@
import { Plus } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import type { TPromptGroup, TStartupConfig } from 'librechat-data-provider';
import {
RankablePromptList,
SortedPromptList,
RankingProvider,
} from '~/components/Prompts/Groups/RankingComponent';
import DashGroupItem from '~/components/Prompts/Groups/DashGroupItem';
import ChatGroupItem from '~/components/Prompts/Groups/ChatGroupItem';
import { useGetStartupConfig } from '~/data-provider';
import { useLocalize, useHasAccess } from '~/hooks';
import { Button, Skeleton } from '~/components/ui';
import { Skeleton } from '~/components/ui';
import { useLocalize } from '~/hooks';
import { useMemo } from 'react';
interface ListProps {
groups?: TPromptGroup[];
isChatRoute: boolean;
isLoading: boolean;
enableRanking?: boolean;
}
export default function List({
groups = [],
isChatRoute,
isLoading,
}: {
groups?: TPromptGroup[];
isChatRoute: boolean;
isLoading: boolean;
}) {
const navigate = useNavigate();
enableRanking = true,
}: ListProps) {
const localize = useLocalize();
const { data: startupConfig = {} as Partial<TStartupConfig> } = useGetStartupConfig();
const { instanceProjectId } = startupConfig;
const hasCreateAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.CREATE,
});
const renderGroupItem = useMemo(
() => (group: TPromptGroup) => {
const Component = isChatRoute ? ChatGroupItem : DashGroupItem;
return <Component key={group._id} group={group} instanceProjectId={instanceProjectId} />;
},
[isChatRoute, instanceProjectId],
);
const emptyMessage = localize('com_ui_nothing_found');
if (isLoading) {
return (
<RankingProvider>
<div className="flex h-full flex-col">
<div className="flex-grow overflow-y-auto">
<div className="overflow-y-auto overflow-x-hidden">
{isChatRoute ? (
<Skeleton className="my-2 flex h-[84px] w-full rounded-2xl border-0 px-3 pb-4 pt-3" />
) : (
Array.from({ length: 10 }).map((_, index) => (
<Skeleton
key={index}
className="w-100 mx-2 my-2 flex h-14 rounded-lg border-0 p-4"
/>
))
)}
</div>
</div>
</div>
</RankingProvider>
);
}
if (groups.length === 0) {
return (
<RankingProvider>
<div className="flex h-full flex-col">
<div className="flex-grow overflow-y-auto">
<div className="overflow-y-auto overflow-x-hidden">
{isChatRoute ? (
<div className="my-2 flex h-[84px] w-full items-center justify-center rounded-2xl border border-border-light bg-transparent px-3 pb-4 pt-3 text-text-primary">
{emptyMessage}
</div>
) : (
<div className="my-12 flex w-full items-center justify-center text-lg font-semibold text-text-primary">
{emptyMessage}
</div>
)}
</div>
</div>
</div>
</RankingProvider>
);
}
const shouldUseRanking = !isChatRoute && enableRanking;
const renderContent = () => {
if (isChatRoute) {
return <SortedPromptList groups={groups} renderItem={renderGroupItem} />;
}
if (shouldUseRanking) {
return <RankablePromptList groups={groups} renderItem={renderGroupItem} />;
}
return groups.map((group) => renderGroupItem(group));
};
return (
<div className="flex h-full flex-col">
{hasCreateAccess && (
<div className="flex w-full justify-end">
<Button
variant="outline"
className={`w-full bg-transparent ${isChatRoute ? '' : 'mx-2'}`}
onClick={() => navigate('/d/prompts/new')}
>
<Plus className="size-4" aria-hidden />
{localize('com_ui_create_prompt')}
</Button>
</div>
)}
<div className="flex-grow overflow-y-auto">
<div className="overflow-y-auto overflow-x-hidden">
{isLoading && isChatRoute && (
<Skeleton className="my-2 flex h-[84px] w-full rounded-2xl border-0 px-3 pb-4 pt-3" />
)}
{isLoading &&
!isChatRoute &&
Array.from({ length: 10 }).map((_, index: number) => (
<Skeleton key={index} className="w-100 mx-2 my-2 flex h-14 rounded-lg border-0 p-4" />
))}
{!isLoading && groups.length === 0 && isChatRoute && (
<div className="my-2 flex h-[84px] w-full items-center justify-center rounded-2xl border border-border-light bg-transparent px-3 pb-4 pt-3 text-text-primary">
{localize('com_ui_nothing_found')}
</div>
)}
{!isLoading && groups.length === 0 && !isChatRoute && (
<div className="my-12 flex w-full items-center justify-center text-lg font-semibold text-text-primary">
{localize('com_ui_nothing_found')}
</div>
)}
{groups.map((group) => {
if (isChatRoute) {
return (
<ChatGroupItem
key={group._id}
group={group}
instanceProjectId={instanceProjectId}
/>
);
}
return (
<DashGroupItem key={group._id} group={group} instanceProjectId={instanceProjectId} />
);
})}
<RankingProvider>
<div className="flex h-full flex-col">
<div className="flex-grow overflow-y-auto">
<div className="overflow-y-auto overflow-x-hidden">{renderContent()}</div>
</div>
</div>
</div>
</RankingProvider>
);
}

View File

@@ -1,6 +1,8 @@
import { memo } from 'react';
import { Button, ThemeSelector } from '~/components/ui';
import AutoSendPrompt from '~/components/Prompts/Groups/AutoSendPrompt';
import { Button } from '~/components';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
function PanelNavigation({
prevPage,
@@ -19,14 +21,16 @@ function PanelNavigation({
}) {
const localize = useLocalize();
return (
<>
<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}>
<div className={cn('my-1 flex justify-between', !isChatRoute && 'mx-2')}>
<AutoSendPrompt className="text-xs dark:text-white" />
<div className="mb-2 flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => prevPage()}
disabled={!hasPreviousPage}
className="bg-transparent hover:bg-surface-hover"
>
{localize('com_ui_prev')}
</Button>
<Button
@@ -34,6 +38,7 @@ function PanelNavigation({
size="sm"
onClick={() => nextPage()}
disabled={!hasNextPage || isFetching}
className="bg-transparent hover:bg-surface-hover"
>
{localize('com_ui_next')}
</Button>

View File

@@ -0,0 +1,294 @@
import React, { useCallback, useEffect, useState, useRef, ReactNode } from 'react';
import { GripVertical } from 'lucide-react';
import { useDrag, useDrop, useDragLayer } from 'react-dnd';
import type { TPromptGroup } from 'librechat-data-provider';
import { useUpdatePromptRankings, useGetUserPromptPreferences } from '~/data-provider';
import CategoryIcon from './CategoryIcon';
import { Label } from '~/components';
import { cn } from '~/utils';
const ITEM_TYPE = 'PROMPT_GROUP';
interface DraggablePromptItemProps {
group: TPromptGroup;
index: number;
moveItem: (dragIndex: number, hoverIndex: number) => void;
isDragging: boolean;
children: ReactNode;
}
interface DragItem {
index: number;
id: string;
type: string;
group: TPromptGroup;
}
const sortGroups = (groups: TPromptGroup[], rankings: any[], favorites: string[]) => {
const rankingMap = new Map(rankings.map((ranking) => [ranking.promptGroupId, ranking.order]));
return [...groups].sort((a, b) => {
const aId = a._id ?? '';
const bId = b._id ?? '';
const aIsFavorite = favorites.includes(aId);
const bIsFavorite = favorites.includes(bId);
if (aIsFavorite && !bIsFavorite) return -1;
if (!aIsFavorite && bIsFavorite) return 1;
const aRank = rankingMap.get(aId);
const bRank = rankingMap.get(bId);
if (aRank !== undefined && bRank !== undefined) return aRank - bRank;
if (aRank !== undefined) return -1;
if (bRank !== undefined) return 1;
return a.name.localeCompare(b.name);
});
};
function DraggablePromptItem({
group,
index,
moveItem,
isDragging: isAnyDragging,
children,
}: DraggablePromptItemProps) {
const ref = useRef<HTMLDivElement>(null);
const [{ isDragging }, drag, preview] = useDrag({
type: ITEM_TYPE,
item: { type: ITEM_TYPE, index, id: group._id, group },
collect: (monitor) => ({ isDragging: monitor.isDragging() }),
});
const [{ isOver }, drop] = useDrop<DragItem, void, { isOver: boolean }>({
accept: ITEM_TYPE,
hover: (item, monitor) => {
if (!ref.current || item.index === index) return;
const hoverBoundingRect = ref.current.getBoundingClientRect();
const hoverMiddleY = hoverBoundingRect.height / 2;
const clientOffset = monitor.getClientOffset();
if (!clientOffset) return;
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
if (item.index < index && hoverClientY < hoverMiddleY * 0.8) return;
if (item.index > index && hoverClientY > hoverMiddleY * 1.2) return;
moveItem(item.index, index);
item.index = index;
},
collect: (monitor) => ({ isOver: monitor.isOver({ shallow: true }) }),
});
drag(drop(ref));
useEffect(() => {
preview(new Image(), { captureDraggingState: false });
}, [preview]);
return (
<div
ref={ref}
className={cn(
'group relative transition-all duration-300 ease-in-out',
isDragging && 'opacity-0',
isAnyDragging && !isDragging && 'transition-transform',
isOver && !isDragging && 'scale-[1.02]',
)}
style={{ cursor: isDragging ? 'grabbing' : 'grab' }}
>
<div
className={cn(
'absolute left-2 top-1/2 z-10 -translate-y-1/2 opacity-0 group-hover:opacity-100',
isDragging && 'opacity-100',
)}
>
<GripVertical className="h-4 w-4 text-gray-400" />
</div>
<div className="pl-8">{children}</div>
</div>
);
}
function CustomDragLayer() {
const { itemType, item, currentOffset, isDragging } = useDragLayer((monitor) => ({
itemType: monitor.getItemType(),
item: monitor.getItem() as DragItem,
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
}));
if (!isDragging || !currentOffset || itemType !== ITEM_TYPE || !item?.group) return null;
return (
<div
style={{
position: 'fixed',
pointerEvents: 'none',
zIndex: 100,
left: 0,
top: 0,
width: '100%',
height: '100%',
}}
>
<div
style={{
transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`,
}}
>
<div className="mx-2 my-2 flex h-[60px] w-[430px] min-w-[300px] cursor-pointer rounded-lg border border-border-light bg-surface-primary p-3 opacity-90 shadow-lg">
<div className="flex items-center gap-2 truncate pr-2">
<CategoryIcon
category={item.group.category ?? ''}
className="icon-lg"
aria-hidden="true"
/>
<Label className="text-md cursor-pointer truncate font-semibold text-text-primary">
{item.group.name}
</Label>
</div>
</div>
</div>
</div>
);
}
function SortedPromptList({
groups,
renderItem,
}: {
groups: TPromptGroup[];
renderItem: (group: TPromptGroup) => ReactNode;
}) {
const { data: preferences } = useGetUserPromptPreferences();
const [sortedGroups, setSortedGroups] = useState<TPromptGroup[]>([]);
useEffect(() => {
if (!groups?.length) {
setSortedGroups([]);
return;
}
const rankings = preferences?.rankings || [];
const favorites = preferences?.favorites || [];
setSortedGroups(sortGroups(groups, rankings, favorites));
}, [groups, preferences]);
return (
<div className="space-y-2">
{sortedGroups.map((group) => (
<div key={group._id} className="transition-all duration-300 ease-in-out">
{renderItem(group)}
</div>
))}
</div>
);
}
interface RankablePromptListProps {
groups: TPromptGroup[];
renderItem: (group: TPromptGroup) => ReactNode;
onRankingChange?: (rankings: string[]) => void;
}
function RankablePromptList({ groups, renderItem, onRankingChange }: RankablePromptListProps) {
const { data: preferences } = useGetUserPromptPreferences();
const updateRankings = useUpdatePromptRankings();
const [sortedGroups, setSortedGroups] = useState<TPromptGroup[]>([]);
const [isDragging, setIsDragging] = useState(false);
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
if (!groups?.length) {
setSortedGroups([]);
return;
}
const rankings = preferences?.rankings || [];
const favorites = preferences?.favorites || [];
setSortedGroups(sortGroups(groups, rankings, favorites));
}, [groups, preferences]);
const moveItem = useCallback(
(dragIndex: number, hoverIndex: number) => {
if (dragIndex === hoverIndex) return;
setSortedGroups((prevGroups) => {
const newGroups = [...prevGroups];
const [draggedItem] = newGroups.splice(dragIndex, 1);
newGroups.splice(hoverIndex, 0, draggedItem);
return newGroups;
});
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current);
saveTimeoutRef.current = setTimeout(() => {
setSortedGroups((currentGroups) => {
const newRankings = currentGroups
.map((group, index) => (group._id ? { promptGroupId: group._id, order: index } : null))
.filter(
(ranking): ranking is { promptGroupId: string; order: number } => ranking !== null,
);
if (newRankings.length > 0) {
updateRankings
.mutateAsync({ rankings: newRankings })
.then(() => onRankingChange?.(newRankings.map((r) => r.promptGroupId)))
.catch(console.error);
}
return currentGroups;
});
}, 500);
},
[updateRankings, onRankingChange],
);
useEffect(() => {
const handleDragStart = () => setIsDragging(true);
const handleDragEnd = () => setIsDragging(false);
document.addEventListener('dragstart', handleDragStart);
document.addEventListener('dragend', handleDragEnd);
return () => {
document.removeEventListener('dragstart', handleDragStart);
document.removeEventListener('dragend', handleDragEnd);
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current);
};
}, []);
return (
<div className={cn('space-y-2 transition-all duration-300', isDragging && 'space-y-3')}>
{sortedGroups.map((group, index) => (
<div
key={group._id || index}
className="transition-all duration-300 ease-in-out"
style={{ transform: `translateY(${isDragging ? '2px' : '0'})` }}
>
<DraggablePromptItem
group={group}
index={index}
moveItem={moveItem}
isDragging={isDragging}
>
{renderItem(group)}
</DraggablePromptItem>
</div>
))}
</div>
);
}
function RankingProvider({ children }: { children: ReactNode }) {
return (
<div>
<CustomDragLayer />
{children}
</div>
);
}
export { RankablePromptList, SortedPromptList, RankingProvider };

View File

@@ -1,16 +1,23 @@
import React, { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import PromptSidePanel from '~/components/Prompts/Groups/GroupSidePanel';
import AutoSendPrompt from '~/components/Prompts/Groups/AutoSendPrompt';
import FilterPrompts from '~/components/Prompts/Groups/FilterPrompts';
import ManagePrompts from '~/components/Prompts/ManagePrompts';
import CreatePrompt from '~/components/Prompts/CreatePrompt';
import { usePromptGroupsNav } from '~/hooks';
export default function PromptsAccordion() {
const location = useLocation();
const groupsNav = usePromptGroupsNav();
const isChatRoute = useMemo(() => location.pathname?.startsWith('/c/'), [location.pathname]);
return (
<div className="flex h-full w-full flex-col">
<PromptSidePanel className="mt-2 space-y-2 lg:w-full xl:w-full" {...groupsNav}>
<div className="mt-2 flex h-full w-full flex-col">
<PromptSidePanel isChatRoute={isChatRoute} className="lg:w-full xl:w-full" {...groupsNav}>
<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 className="flex w-full flex-row items-center justify-between gap-2">
<ManagePrompts className="select-none" />
<CreatePrompt isChatRoute={isChatRoute} />
</div>
</PromptSidePanel>
</div>

View File

@@ -2,20 +2,20 @@ import { useMemo } from 'react';
import { ChevronLeft } from 'lucide-react';
import { AgentCapabilities } from 'librechat-data-provider';
import { useFormContext, Controller } from 'react-hook-form';
import type { AgentForm, AgentPanelProps } from '~/common';
import type { AgentForm } from '~/common';
import { useAgentPanelContext } from '~/Providers';
import MaxAgentSteps from './MaxAgentSteps';
import AgentChain from './AgentChain';
import { useLocalize } from '~/hooks';
import AgentChain from './AgentChain';
import { Panel } from '~/common';
export default function AdvancedPanel({
agentsConfig,
setActivePanel,
}: Pick<AgentPanelProps, 'setActivePanel' | 'agentsConfig'>) {
export default function AdvancedPanel() {
const localize = useLocalize();
const methods = useFormContext<AgentForm>();
const { control, watch } = methods;
const currentAgentId = watch('id');
const { agentsConfig, setActivePanel } = useAgentPanelContext();
const chainEnabled = useMemo(
() => agentsConfig?.capabilities.includes(AgentCapabilities.chain) ?? false,
[agentsConfig],

View File

@@ -1,9 +1,10 @@
import React, { useState, useMemo, useCallback } from 'react';
import { EModelEndpoint } from 'librechat-data-provider';
import { Controller, useWatch, useFormContext } from 'react-hook-form';
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common';
import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils';
import { useToastContext, useFileMapContext, useAgentPanelContext } from '~/Providers';
import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities';
import Action from '~/components/SidePanel/Builder/Action';
import { ToolSelectDialog } from '~/components/Tools';
import { icons } from '~/hooks/Endpoint/Icons';
@@ -26,17 +27,20 @@ const inputClass = cn(
removeFocusOutlines,
);
export default function AgentConfig({
agentsConfig,
createMutation,
endpointsConfig,
}: Pick<AgentPanelProps, 'agentsConfig' | 'createMutation' | 'endpointsConfig'>) {
export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'createMutation'>) {
const localize = useLocalize();
const fileMap = useFileMapContext();
const { showToast } = useToastContext();
const methods = useFormContext<AgentForm>();
const [showToolDialog, setShowToolDialog] = useState(false);
const { actions, setAction, groupedTools: allTools, setActivePanel } = useAgentPanelContext();
const {
actions,
setAction,
agentsConfig,
setActivePanel,
endpointsConfig,
groupedTools: allTools,
} = useAgentPanelContext();
const { control } = methods;
const provider = useWatch({ control, name: 'provider' });
@@ -45,34 +49,15 @@ export default function AgentConfig({
const tools = useWatch({ control, name: 'tools' });
const agent_id = useWatch({ control, name: 'id' });
const toolsEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.tools) ?? false,
[agentsConfig],
);
const actionsEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.actions) ?? false,
[agentsConfig],
);
const artifactsEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.artifacts) ?? false,
[agentsConfig],
);
const ocrEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.ocr) ?? false,
[agentsConfig],
);
const fileSearchEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.file_search) ?? false,
[agentsConfig],
);
const webSearchEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.web_search) ?? false,
[agentsConfig],
);
const codeEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.execute_code) ?? false,
[agentsConfig],
);
const {
ocrEnabled,
codeEnabled,
toolsEnabled,
actionsEnabled,
artifactsEnabled,
webSearchEnabled,
fileSearchEnabled,
} = useAgentCapabilities(agentsConfig?.capabilities);
const context_files = useMemo(() => {
if (typeof agent === 'string') {

View File

@@ -7,8 +7,6 @@ import {
Constants,
SystemRoles,
EModelEndpoint,
TAgentsEndpoint,
TEndpointsConfig,
isAssistantsEndpoint,
} from 'librechat-data-provider';
import type { AgentForm, StringOption } from '~/common';
@@ -30,19 +28,15 @@ import { Button } from '~/components';
import ModelPanel from './ModelPanel';
import { Panel } from '~/common';
export default function AgentPanel({
agentsConfig,
endpointsConfig,
}: {
agentsConfig: TAgentsEndpoint | null;
endpointsConfig: TEndpointsConfig;
}) {
export default function AgentPanel() {
const localize = useLocalize();
const { user } = useAuthContext();
const { showToast } = useToastContext();
const {
activePanel,
agentsConfig,
setActivePanel,
endpointsConfig,
setCurrentAgentId,
agent_id: current_agent_id,
} = useAgentPanelContext();
@@ -323,14 +317,10 @@ export default function AgentPanel({
<ModelPanel models={models} providers={providers} setActivePanel={setActivePanel} />
)}
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && (
<AgentConfig
createMutation={create}
agentsConfig={agentsConfig}
endpointsConfig={endpointsConfig}
/>
<AgentConfig createMutation={create} />
)}
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.advanced && (
<AdvancedPanel setActivePanel={setActivePanel} agentsConfig={agentsConfig} />
<AdvancedPanel />
)}
{canEditAgent && !agentQuery.isInitialLoading && (
<AgentFooter

View File

@@ -1,8 +1,5 @@
import { useEffect, useMemo } from 'react';
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
import type { TConfig, TEndpointsConfig, TAgentsEndpoint } from 'librechat-data-provider';
import { useEffect } from 'react';
import { AgentPanelProvider, useAgentPanelContext } from '~/Providers/AgentPanelContext';
import { useGetEndpointsQuery } from '~/data-provider';
import VersionPanel from './Version/VersionPanel';
import { useChatContext } from '~/Providers';
import ActionsPanel from './ActionsPanel';
@@ -22,21 +19,6 @@ function AgentPanelSwitchWithContext() {
const { conversation } = useChatContext();
const { activePanel, setCurrentAgentId } = useAgentPanelContext();
// TODO: Implement MCP endpoint
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
const agentsConfig = useMemo<TAgentsEndpoint | null>(() => {
const config = endpointsConfig?.[EModelEndpoint.agents] ?? null;
if (!config) return null;
return {
...(config as TConfig),
capabilities: Array.isArray(config.capabilities)
? config.capabilities.map((cap) => cap as unknown as AgentCapabilities)
: ([] as AgentCapabilities[]),
} as TAgentsEndpoint;
}, [endpointsConfig]);
useEffect(() => {
const agent_id = conversation?.agent_id ?? '';
if (agent_id) {
@@ -57,5 +39,5 @@ function AgentPanelSwitchWithContext() {
if (activePanel === Panel.mcp) {
return <MCPPanel />;
}
return <AgentPanel agentsConfig={agentsConfig} endpointsConfig={endpointsConfig} />;
return <AgentPanel />;
}

View File

@@ -60,7 +60,7 @@ export default function Artifacts() {
/>
<SwitchItem
id="includeShadcnui"
label={localize('com_ui_include_shadcnui_agent')}
label={localize('com_ui_include_shadcnui')}
checked={isShadcnEnabled}
onCheckedChange={handleShadcnuiChange}
hoverCardText={localize('com_nav_info_include_shadcnui')}

View File

@@ -1,13 +1,16 @@
import { useState } from 'react';
import { ChevronDown } from 'lucide-react';
import * as Menu from '@ariakit/react/menu';
import { AuthType, SearchCategories, RerankerTypes } from 'librechat-data-provider';
import type { UseFormRegister, UseFormHandleSubmit } from 'react-hook-form';
import {
AuthType,
SearchCategories,
RerankerTypes,
SearchProviders,
ScraperTypes,
} from 'librechat-data-provider';
import type { SearchApiKeyFormData } from '~/hooks/Plugins/useAuthSearchTool';
import type { MenuItemProps } from '~/common';
import { Input, Button, OGDialog, Label } from '~/components/ui';
import type { UseFormRegister, UseFormHandleSubmit } from 'react-hook-form';
import InputSection, { type DropdownOption } from './InputSection';
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
import DropdownPopup from '~/components/ui/DropdownPopup';
import { Button, OGDialog } from '~/components/ui';
import { useGetStartupConfig } from '~/data-provider';
import { useLocalize } from '~/hooks';
@@ -36,151 +39,119 @@ export default function ApiKeyDialog({
}) {
const localize = useLocalize();
const { data: config } = useGetStartupConfig();
const [selectedReranker, setSelectedReranker] = useState<
RerankerTypes.JINA | RerankerTypes.COHERE
>(
config?.webSearch?.rerankerType === RerankerTypes.COHERE
? RerankerTypes.COHERE
: RerankerTypes.JINA,
const [selectedProvider, setSelectedProvider] = useState(
config?.webSearch?.searchProvider || SearchProviders.SERPER,
);
const [selectedReranker, setSelectedReranker] = useState(
config?.webSearch?.rerankerType || RerankerTypes.JINA,
);
const [selectedScraper, setSelectedScraper] = useState(ScraperTypes.FIRECRAWL);
const [providerDropdownOpen, setProviderDropdownOpen] = useState(false);
const [scraperDropdownOpen, setScraperDropdownOpen] = useState(false);
const [rerankerDropdownOpen, setRerankerDropdownOpen] = useState(false);
const providerItems: MenuItemProps[] = [
const providerOptions: DropdownOption[] = [
{
key: SearchProviders.SERPER,
label: localize('com_ui_web_search_provider_serper'),
onClick: () => {},
inputs: {
serperApiKey: {
placeholder: localize('com_ui_enter_api_key'),
type: 'password' as const,
link: {
url: 'https://serper.dev/api-keys',
text: localize('com_ui_web_search_provider_serper_key'),
},
},
},
},
{
key: SearchProviders.SEARXNG,
label: localize('com_ui_web_search_provider_searxng'),
inputs: {
searxngInstanceUrl: {
placeholder: localize('com_ui_web_search_searxng_instance_url'),
type: 'text' as const,
},
searxngApiKey: {
placeholder: localize('com_ui_web_search_searxng_api_key'),
type: 'password' as const,
},
},
},
];
const scraperItems: MenuItemProps[] = [
{
label: localize('com_ui_web_search_scraper_firecrawl'),
onClick: () => {},
},
];
const rerankerItems: MenuItemProps[] = [
const rerankerOptions: DropdownOption[] = [
{
key: RerankerTypes.JINA,
label: localize('com_ui_web_search_reranker_jina'),
onClick: () => setSelectedReranker(RerankerTypes.JINA),
inputs: {
jinaApiKey: {
placeholder: localize('com_ui_web_search_jina_key'),
type: 'password' as const,
link: {
url: 'https://jina.ai/api-dashboard/',
text: localize('com_ui_web_search_reranker_jina_key'),
},
},
},
},
{
key: RerankerTypes.COHERE,
label: localize('com_ui_web_search_reranker_cohere'),
onClick: () => setSelectedReranker(RerankerTypes.COHERE),
inputs: {
cohereApiKey: {
placeholder: localize('com_ui_web_search_cohere_key'),
type: 'password' as const,
link: {
url: 'https://dashboard.cohere.com/welcome/login',
text: localize('com_ui_web_search_reranker_cohere_key'),
},
},
},
},
];
const showProviderDropdown = !config?.webSearch?.searchProvider;
const showScraperDropdown = !config?.webSearch?.scraperType;
const showRerankerDropdown = !config?.webSearch?.rerankerType;
const scraperOptions: DropdownOption[] = [
{
key: ScraperTypes.FIRECRAWL,
label: localize('com_ui_web_search_scraper_firecrawl'),
inputs: {
firecrawlApiUrl: {
placeholder: localize('com_ui_web_search_firecrawl_url'),
type: 'text' as const,
},
firecrawlApiKey: {
placeholder: localize('com_ui_enter_api_key'),
type: 'password' as const,
link: {
url: 'https://docs.firecrawl.dev/introduction#api-key',
text: localize('com_ui_web_search_scraper_firecrawl_key'),
},
},
},
},
];
const [dropdownOpen, setDropdownOpen] = useState({
provider: false,
reranker: false,
scraper: false,
});
// Determine which categories are SYSTEM_DEFINED
const providerAuthType = authTypes.find(([cat]) => cat === SearchCategories.PROVIDERS)?.[1];
const scraperAuthType = authTypes.find(([cat]) => cat === SearchCategories.SCRAPERS)?.[1];
const rerankerAuthType = authTypes.find(([cat]) => cat === SearchCategories.RERANKERS)?.[1];
function renderRerankerInput() {
if (config?.webSearch?.rerankerType === RerankerTypes.JINA) {
return (
<>
<Input
type="password"
placeholder={localize('com_ui_web_search_jina_key')}
autoComplete="one-time-code"
readOnly={true}
onFocus={(e) => (e.target.readOnly = false)}
{...register('jinaApiKey')}
/>
<div className="mt-1 text-xs text-text-secondary">
<a
href="https://jina.ai/api-dashboard/"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
{localize('com_ui_web_search_reranker_jina_key')}
</a>
</div>
</>
);
}
if (config?.webSearch?.rerankerType === RerankerTypes.COHERE) {
return (
<>
<Input
type="password"
placeholder={localize('com_ui_web_search_cohere_key')}
autoComplete="one-time-code"
readOnly={true}
onFocus={(e) => (e.target.readOnly = false)}
{...register('cohereApiKey')}
/>
<div className="mt-1 text-xs text-text-secondary">
<a
href="https://dashboard.cohere.com/welcome/login"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
{localize('com_ui_web_search_reranker_cohere_key')}
</a>
</div>
</>
);
}
if (!config?.webSearch?.rerankerType && selectedReranker === RerankerTypes.JINA) {
return (
<>
<Input
type="password"
placeholder={localize('com_ui_web_search_jina_key')}
autoComplete="one-time-code"
readOnly={true}
onFocus={(e) => (e.target.readOnly = false)}
{...register('jinaApiKey')}
/>
<div className="mt-1 text-xs text-text-secondary">
<a
href="https://jina.ai/api-dashboard/"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
{localize('com_ui_web_search_reranker_jina_key')}
</a>
</div>
</>
);
}
if (!config?.webSearch?.rerankerType && selectedReranker === RerankerTypes.COHERE) {
return (
<>
<Input
type="password"
placeholder={localize('com_ui_web_search_cohere_key')}
autoComplete="one-time-code"
readOnly={true}
onFocus={(e) => (e.target.readOnly = false)}
{...register('cohereApiKey')}
/>
<div className="mt-1 text-xs text-text-secondary">
<a
href="https://dashboard.cohere.com/welcome/login"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
{localize('com_ui_web_search_reranker_cohere_key')}
</a>
</div>
</>
);
}
return null;
}
const handleProviderChange = (key: string) => {
setSelectedProvider(key as SearchProviders);
};
const handleRerankerChange = (key: string) => {
setSelectedReranker(key as RerankerTypes);
};
const handleScraperChange = (key: string) => {
setSelectedScraper(key as ScraperTypes);
};
return (
<OGDialog
@@ -195,153 +166,56 @@ export default function ApiKeyDialog({
main={
<>
<div className="mb-4 text-center font-medium">{localize('com_ui_web_search')}</div>
<div className="mb-4 text-center text-sm">
{localize('com_ui_web_search_api_subtitle')}
</div>
<form onSubmit={handleSubmit(onSubmit)}>
{/* Search Provider Section */}
{/* Provider Section */}
{providerAuthType !== AuthType.SYSTEM_DEFINED && (
<div className="mb-6">
<div className="mb-2 flex items-center justify-between">
<Label className="text-md w-fit font-medium">
{localize('com_ui_web_search_provider')}
</Label>
{showProviderDropdown ? (
<DropdownPopup
menuId="search-provider-dropdown"
items={providerItems}
isOpen={providerDropdownOpen}
setIsOpen={setProviderDropdownOpen}
trigger={
<Menu.MenuButton
onClick={() => setProviderDropdownOpen(!providerDropdownOpen)}
className="flex items-center rounded-md border border-border-light px-3 py-1 text-sm text-text-secondary"
>
{localize('com_ui_web_search_provider_serper')}
<ChevronDown className="ml-1 h-4 w-4" />
</Menu.MenuButton>
}
/>
) : (
<div className="text-sm text-text-secondary">
{localize('com_ui_web_search_provider_serper')}
</div>
)}
</div>
<Input
type="password"
placeholder={`${localize('com_ui_enter_api_key')}`}
autoComplete="one-time-code"
readOnly={true}
onFocus={(e) => (e.target.readOnly = false)}
{...register('serperApiKey', { required: true })}
/>
<div className="mt-1 text-xs text-text-secondary">
<a
href="https://serper.dev/api-key"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
{localize('com_ui_web_search_provider_serper_key')}
</a>
</div>
</div>
<InputSection
title={localize('com_ui_web_search_provider')}
selectedKey={selectedProvider}
onSelectionChange={handleProviderChange}
dropdownOptions={providerOptions}
showDropdown={!config?.webSearch?.searchProvider}
register={register}
dropdownOpen={dropdownOpen.provider}
setDropdownOpen={(open) =>
setDropdownOpen((prev) => ({ ...prev, provider: open }))
}
dropdownKey="provider"
/>
)}
{/* Scraper Section */}
{scraperAuthType !== AuthType.SYSTEM_DEFINED && (
<div className="mb-6">
<div className="mb-2 flex items-center justify-between">
<Label className="text-md w-fit font-medium">
{localize('com_ui_web_search_scraper')}
</Label>
{showScraperDropdown ? (
<DropdownPopup
menuId="scraper-dropdown"
items={scraperItems}
isOpen={scraperDropdownOpen}
setIsOpen={setScraperDropdownOpen}
trigger={
<Menu.MenuButton
onClick={() => setScraperDropdownOpen(!scraperDropdownOpen)}
className="flex items-center rounded-md border border-border-light px-3 py-1 text-sm text-text-secondary"
>
{localize('com_ui_web_search_scraper_firecrawl')}
<ChevronDown className="ml-1 h-4 w-4" />
</Menu.MenuButton>
}
/>
) : (
<div className="text-sm text-text-secondary">
{localize('com_ui_web_search_scraper_firecrawl')}
</div>
)}
</div>
<Input
type="password"
placeholder={`${localize('com_ui_enter_api_key')}`}
autoComplete="one-time-code"
readOnly={true}
onFocus={(e) => (e.target.readOnly = false)}
className="mb-2"
{...register('firecrawlApiKey')}
/>
<Input
type="text"
placeholder={localize('com_ui_web_search_firecrawl_url')}
className="mb-1"
{...register('firecrawlApiUrl')}
/>
<div className="mt-1 text-xs text-text-secondary">
<a
href="https://docs.firecrawl.dev/introduction#api-key"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
{localize('com_ui_web_search_scraper_firecrawl_key')}
</a>
</div>
</div>
<InputSection
title={localize('com_ui_web_search_scraper')}
selectedKey={selectedScraper}
onSelectionChange={handleScraperChange}
dropdownOptions={scraperOptions}
showDropdown={!config?.webSearch?.scraperType}
register={register}
dropdownOpen={dropdownOpen.scraper}
setDropdownOpen={(open) =>
setDropdownOpen((prev) => ({ ...prev, scraper: open }))
}
dropdownKey="scraper"
/>
)}
{/* Reranker Section */}
{rerankerAuthType !== AuthType.SYSTEM_DEFINED && (
<div className="mb-6">
<div className="mb-2 flex items-center justify-between">
<Label className="text-md w-fit font-medium">
{localize('com_ui_web_search_reranker')}
</Label>
{showRerankerDropdown && (
<DropdownPopup
menuId="reranker-dropdown"
isOpen={rerankerDropdownOpen}
setIsOpen={setRerankerDropdownOpen}
items={rerankerItems}
trigger={
<Menu.MenuButton
onClick={() => setRerankerDropdownOpen(!rerankerDropdownOpen)}
className="flex items-center rounded-md border border-border-light px-3 py-1 text-sm text-text-secondary"
>
{selectedReranker === RerankerTypes.JINA
? localize('com_ui_web_search_reranker_jina')
: localize('com_ui_web_search_reranker_cohere')}
<ChevronDown className="ml-1 h-4 w-4" />
</Menu.MenuButton>
}
/>
)}
{!showRerankerDropdown && (
<div className="text-sm text-text-secondary">
{config?.webSearch?.rerankerType === RerankerTypes.COHERE
? localize('com_ui_web_search_reranker_cohere')
: localize('com_ui_web_search_reranker_jina')}
</div>
)}
</div>
{renderRerankerInput()}
</div>
<InputSection
title={localize('com_ui_web_search_reranker')}
selectedKey={selectedReranker}
onSelectionChange={handleRerankerChange}
dropdownOptions={rerankerOptions}
showDropdown={!config?.webSearch?.rerankerType}
register={register}
dropdownOpen={dropdownOpen.reranker}
setDropdownOpen={(open) =>
setDropdownOpen((prev) => ({ ...prev, reranker: open }))
}
dropdownKey="reranker"
/>
)}
</form>
</>
@@ -353,10 +227,7 @@ export default function ApiKeyDialog({
}}
buttons={
isToolAuthenticated && (
<Button
onClick={onRevoke}
className="bg-destructive text-white transition-all duration-200 hover:bg-destructive/80"
>
<Button onClick={onRevoke} className="bg-red-500 text-white hover:bg-red-600">
{localize('com_ui_revoke')}
</Button>
)

View File

@@ -0,0 +1,144 @@
import { useState } from 'react';
import { ChevronDown, Eye, EyeOff } from 'lucide-react';
import * as Menu from '@ariakit/react/menu';
import type { UseFormRegister } from 'react-hook-form';
import type { SearchApiKeyFormData } from '~/hooks/Plugins/useAuthSearchTool';
import type { MenuItemProps } from '~/common';
import { Input, Label } from '~/components/ui';
import DropdownPopup from '~/components/ui/DropdownPopup';
import { useLocalize } from '~/hooks';
interface InputConfig {
placeholder: string;
type?: 'text' | 'password';
link?: {
url: string;
text: string;
};
}
interface DropdownOption {
key: string;
label: string;
inputs?: Record<string, InputConfig>;
}
interface InputSectionProps {
title: string;
selectedKey: string;
onSelectionChange: (key: string) => void;
dropdownOptions: DropdownOption[];
showDropdown: boolean;
register: UseFormRegister<SearchApiKeyFormData>;
dropdownOpen: boolean;
setDropdownOpen: (open: boolean) => void;
dropdownKey: string;
}
export default function InputSection({
title,
selectedKey,
onSelectionChange,
dropdownOptions,
showDropdown,
register,
dropdownOpen,
setDropdownOpen,
dropdownKey,
}: InputSectionProps) {
const localize = useLocalize();
const [passwordVisibility, setPasswordVisibility] = useState<Record<string, boolean>>({});
const selectedOption = dropdownOptions.find((opt) => opt.key === selectedKey);
const dropdownItems: MenuItemProps[] = dropdownOptions.map((option) => ({
label: option.label,
onClick: () => onSelectionChange(option.key),
}));
const togglePasswordVisibility = (fieldName: string) => {
setPasswordVisibility((prev) => ({
...prev,
[fieldName]: !prev[fieldName],
}));
};
return (
<div className="mb-6">
<div className="mb-2 flex items-center justify-between">
<Label className="text-md w-fit font-medium">{title}</Label>
{showDropdown ? (
<DropdownPopup
menuId={`${dropdownKey}-dropdown`}
items={dropdownItems}
isOpen={dropdownOpen}
setIsOpen={setDropdownOpen}
trigger={
<Menu.MenuButton
onClick={() => setDropdownOpen(!dropdownOpen)}
className="flex items-center rounded-md border border-border-light px-3 py-1 text-sm text-text-secondary"
>
{selectedOption?.label}
<ChevronDown className="ml-1 h-4 w-4" />
</Menu.MenuButton>
}
/>
) : (
<div className="text-sm text-text-secondary">{selectedOption?.label}</div>
)}
</div>
{selectedOption?.inputs &&
Object.entries(selectedOption.inputs).map(([name, config], index) => (
<div key={name}>
<div className="relative">
<Input
type={'text'} // so password autofill doesn't show
placeholder={config.placeholder}
autoComplete={config.type === 'password' ? 'one-time-code' : 'off'}
readOnly={config.type === 'password'}
onFocus={
config.type === 'password' ? (e) => (e.target.readOnly = false) : undefined
}
className={`${index > 0 ? 'mb-2' : 'mb-2'} ${
config.type === 'password' ? 'pr-10' : ''
}`}
{...register(name as keyof SearchApiKeyFormData)}
/>
{config.type === 'password' && (
<button
type="button"
onClick={() => togglePasswordVisibility(name)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary transition-colors hover:text-text-primary"
aria-label={
passwordVisibility[name]
? localize('com_ui_hide_password')
: localize('com_ui_show_password')
}
>
<div className="relative h-4 w-4">
{passwordVisibility[name] ? (
<EyeOff className="absolute inset-0 h-4 w-4 duration-200 animate-in fade-in" />
) : (
<Eye className="absolute inset-0 h-4 w-4 duration-200 animate-in fade-in" />
)}
</div>
</button>
)}
</div>
{config.link && (
<div className="mt-1 text-xs text-text-secondary">
<a
href={config.link.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
{config.link.text}
</a>
</div>
)}
</div>
))}
</div>
);
}
export type { InputConfig, DropdownOption };

View File

@@ -52,6 +52,10 @@ export default function MemoryCreateDialog({
if (axiosError.response?.status === 409 || errorMessage.includes('already exists')) {
errorMessage = localize('com_ui_memory_key_exists');
}
// Check for key validation error (lowercase and underscores only)
else if (errorMessage.includes('lowercase letters and underscores')) {
errorMessage = localize('com_ui_memory_key_validation');
}
}
} else if (error.message) {
errorMessage = error.message;

View File

@@ -44,9 +44,29 @@ export default function MemoryEditDialog({
status: 'success',
});
},
onError: () => {
onError: (error: Error) => {
let errorMessage = localize('com_ui_error');
if (error && typeof error === 'object' && 'response' in error) {
const axiosError = error as any;
if (axiosError.response?.data?.error) {
errorMessage = axiosError.response.data.error;
// Check for duplicate key error
if (axiosError.response?.status === 409 || errorMessage.includes('already exists')) {
errorMessage = localize('com_ui_memory_key_exists');
}
// Check for key validation error (lowercase and underscores only)
else if (errorMessage.includes('lowercase letters and underscores')) {
errorMessage = localize('com_ui_memory_key_validation');
}
}
} else if (error.message) {
errorMessage = error.message;
}
showToast({
message: localize('com_ui_error'),
message: errorMessage,
status: 'error',
});
},

View File

@@ -0,0 +1,37 @@
import React from 'react';
interface StarIconProps {
className?: string;
size?: string | number;
filled?: boolean;
}
export default function StarIcon({ className = '', size = '1em', filled = false }: StarIconProps) {
return filled ? (
<svg
xmlns="http://www.w3.org/2000/svg"
height={size}
width={size}
viewBox="0 0 24 24"
fill="currentColor"
className={className}
>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
height={size}
width={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
</svg>
);
}

View File

@@ -60,6 +60,7 @@ export { default as CircleHelpIcon } from './CircleHelpIcon';
export { default as BedrockIcon } from './BedrockIcon';
export { default as ThumbUpIcon } from './ThumbUpIcon';
export { default as ThumbDownIcon } from './ThumbDownIcon';
export { default as StarIcon } from './StarIcon';
export { default as XAIcon } from './XAIcon';
export { default as PersonalizationIcon } from './PersonalizationIcon';
export { default as MCPIcon } from './MCPIcon';

View File

@@ -12,7 +12,10 @@ const CheckboxButton = React.forwardRef<
checked?: boolean;
defaultChecked?: boolean;
isCheckedClassName?: string;
setValue?: (values: { e?: React.ChangeEvent<HTMLInputElement>; isChecked: boolean }) => void;
setValue?: (values: {
e?: React.ChangeEvent<HTMLInputElement>;
value: boolean | string;
}) => void;
}
>(({ icon, label, setValue, className, checked, defaultChecked, isCheckedClassName }, ref) => {
const checkbox = useCheckboxStore();
@@ -22,7 +25,7 @@ const CheckboxButton = React.forwardRef<
if (typeof isChecked !== 'boolean') {
return;
}
setValue?.({ e, isChecked: !isChecked });
setValue?.({ e, value: !isChecked });
};
// Sync with controlled checked prop

View File

@@ -1,5 +1,5 @@
import { useRecoilValue } from 'recoil';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { dataService, QueryKeys } from 'librechat-data-provider';
import type { UseMutationResult } from '@tanstack/react-query';
import type t from 'librechat-data-provider';
@@ -327,3 +327,131 @@ export const useMakePromptProduction = (options?: t.MakePromptProductionOptions)
},
});
};
/* Prompt Favorites and Rankings */
export const useTogglePromptFavorite = (
options?: t.UpdatePromptGroupOptions,
): UseMutationResult<t.TPromptFavoriteResponse, unknown, { groupId: string }, unknown> => {
const { onMutate, onError, onSuccess } = options || {};
const queryClient = useQueryClient();
return useMutation({
mutationFn: (variables: { groupId: string }) =>
dataService.togglePromptFavorite(variables.groupId),
onMutate: async (variables: { groupId: string }) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({ queryKey: [QueryKeys.promptGroups] });
await queryClient.cancelQueries({ queryKey: [QueryKeys.userPromptPreferences] });
// Snapshot the previous values
const previousPreferences = queryClient.getQueryData<t.TGetUserPromptPreferencesResponse>([
QueryKeys.userPromptPreferences,
]);
// Optimistically update the favorites
if (previousPreferences) {
const isFavorite = previousPreferences.favorites.includes(variables.groupId);
const newFavorites = isFavorite
? previousPreferences.favorites.filter((id) => id !== variables.groupId)
: [...previousPreferences.favorites, variables.groupId];
queryClient.setQueryData<t.TGetUserPromptPreferencesResponse>(
[QueryKeys.userPromptPreferences],
{
...previousPreferences,
favorites: newFavorites,
},
);
}
if (onMutate) {
return onMutate(variables);
}
return { previousPreferences };
},
onError: (err, variables, context) => {
// Revert optimistic update on error
if (context?.previousPreferences) {
queryClient.setQueryData([QueryKeys.userPromptPreferences], context.previousPreferences);
}
if (onError) {
onError(err, variables, context);
}
},
onSuccess: (response, variables, context) => {
// Invalidate and refetch related queries
queryClient.invalidateQueries({ queryKey: [QueryKeys.userPromptPreferences] });
queryClient.invalidateQueries({ queryKey: [QueryKeys.promptGroups] });
if (onSuccess) {
onSuccess(response, variables, context);
}
},
});
};
export const useUpdatePromptRankings = (
options?: t.UpdatePromptGroupOptions,
): UseMutationResult<t.TPromptRankingResponse, unknown, t.TPromptRankingRequest, unknown> => {
const { onMutate, onError, onSuccess } = options || {};
const queryClient = useQueryClient();
return useMutation({
mutationFn: (variables: t.TPromptRankingRequest) => dataService.updatePromptRankings(variables),
onMutate: async (variables: t.TPromptRankingRequest) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({ queryKey: [QueryKeys.userPromptPreferences] });
// Snapshot the previous values
const previousPreferences = queryClient.getQueryData<t.TGetUserPromptPreferencesResponse>([
QueryKeys.userPromptPreferences,
]);
// Optimistically update the rankings
if (previousPreferences) {
queryClient.setQueryData<t.TGetUserPromptPreferencesResponse>(
[QueryKeys.userPromptPreferences],
{
...previousPreferences,
rankings: variables.rankings,
},
);
}
if (onMutate) {
return onMutate(variables);
}
return { previousPreferences };
},
onError: (err, variables, context) => {
// Revert optimistic update on error
if (context?.previousPreferences) {
queryClient.setQueryData([QueryKeys.userPromptPreferences], context.previousPreferences);
}
if (onError) {
onError(err, variables, context);
}
},
onSuccess: (response, variables, context) => {
// Don't automatically invalidate queries to prevent infinite loops
// The optimistic update in onMutate handles the UI update
// Manual invalidation can be done by components when needed
if (onSuccess) {
onSuccess(response, variables, context);
}
},
});
};
export const useGetUserPromptPreferences = () => {
return useQuery({
queryKey: [QueryKeys.userPromptPreferences],
queryFn: () => dataService.getUserPromptPreferences(),
staleTime: 1000 * 60 * 5, // 5 minutes
refetchOnWindowFocus: false, // Prevent refetch on window focus
refetchOnMount: false, // Prevent refetch on component mount
});
};

View File

@@ -2,15 +2,16 @@ import {
QueryKeys,
dataService,
EModelEndpoint,
isAgentsEndpoint,
defaultOrderQuery,
defaultAssistantsVersion,
} from 'librechat-data-provider';
import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import type {
InfiniteData,
UseInfiniteQueryOptions,
QueryObserverResult,
UseQueryOptions,
InfiniteData,
} from '@tanstack/react-query';
import type t from 'librechat-data-provider';
import type {
@@ -203,7 +204,7 @@ export const useAvailableToolsQuery = <TData = t.TPlugin[]>(
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
const enabled = isAgentsEndpoint(endpoint) ? true : !!endpointsConfig?.[endpoint] && keyProvided;
const version: string | number | undefined =
endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
return useQuery<t.TPlugin[], unknown, TData>(

View File

@@ -1,2 +1,4 @@
export { default as useAgentsMap } from './useAgentsMap';
export { default as useSelectAgent } from './useSelectAgent';
export { default as useAgentCapabilities } from './useAgentCapabilities';
export { default as useGetAgentsConfig } from './useGetAgentsConfig';

View File

@@ -0,0 +1,61 @@
import { useMemo } from 'react';
import { AgentCapabilities } from 'librechat-data-provider';
interface AgentCapabilitiesResult {
toolsEnabled: boolean;
actionsEnabled: boolean;
artifactsEnabled: boolean;
ocrEnabled: boolean;
fileSearchEnabled: boolean;
webSearchEnabled: boolean;
codeEnabled: boolean;
}
export default function useAgentCapabilities(
capabilities: AgentCapabilities[] | undefined,
): AgentCapabilitiesResult {
const toolsEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.tools) ?? false,
[capabilities],
);
const actionsEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.actions) ?? false,
[capabilities],
);
const artifactsEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.artifacts) ?? false,
[capabilities],
);
const ocrEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.ocr) ?? false,
[capabilities],
);
const fileSearchEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.file_search) ?? false,
[capabilities],
);
const webSearchEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.web_search) ?? false,
[capabilities],
);
const codeEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.execute_code) ?? false,
[capabilities],
);
return {
ocrEnabled,
codeEnabled,
toolsEnabled,
actionsEnabled,
artifactsEnabled,
webSearchEnabled,
fileSearchEnabled,
};
}

View File

@@ -0,0 +1,35 @@
import { useMemo } from 'react';
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
import type { TAgentsEndpoint, TEndpointsConfig, TConfig } from 'librechat-data-provider';
import { useGetEndpointsQuery } from '~/data-provider';
interface UseGetAgentsConfigOptions {
endpointsConfig?: TEndpointsConfig;
}
export default function useGetAgentsConfig(options?: UseGetAgentsConfigOptions): {
agentsConfig?: TAgentsEndpoint | null;
endpointsConfig?: TEndpointsConfig | null;
} {
const { endpointsConfig: providedConfig } = options || {};
const { data: queriedConfig } = useGetEndpointsQuery({
enabled: !providedConfig,
});
const endpointsConfig = providedConfig || queriedConfig;
const agentsConfig = useMemo<TAgentsEndpoint | null>(() => {
const config = endpointsConfig?.[EModelEndpoint.agents] ?? null;
if (!config) return null;
return {
...(config as TConfig),
capabilities: Array.isArray(config.capabilities)
? config.capabilities.map((cap) => cap as unknown as AgentCapabilities)
: ([] as AgentCapabilities[]),
} as TAgentsEndpoint;
}, [endpointsConfig]);
return { agentsConfig, endpointsConfig };
}

View File

@@ -25,7 +25,6 @@ import type { TAskFunction, ExtendedFile } from '~/common';
import useSetFilesToDelete from '~/hooks/Files/useSetFilesToDelete';
import useGetSender from '~/hooks/Conversations/useGetSender';
import store, { useGetEphemeralAgent } from '~/store';
import { getArtifactsMode } from '~/utils/artifacts';
import { getEndpointField, logger } from '~/utils';
import useUserKey from '~/hooks/Input/useUserKey';
import { useNavigate } from 'react-router-dom';
@@ -68,9 +67,6 @@ export default function useChatFunctions({
const setFilesToDelete = useSetFilesToDelete();
const getEphemeralAgent = useGetEphemeralAgent();
const isTemporary = useRecoilValue(store.isTemporary);
const codeArtifacts = useRecoilValue(store.codeArtifacts);
const includeShadcnui = useRecoilValue(store.includeShadcnui);
const customPromptMode = useRecoilValue(store.customPromptMode);
const { getExpiry } = useUserKey(immutableConversation?.endpoint ?? '');
const setShowStopButton = useSetRecoilState(store.showStopButtonByIndex(index));
const resetLatestMultiMessage = useResetRecoilState(store.latestMessageFamily(index + 1));
@@ -187,10 +183,6 @@ export default function useChatFunctions({
endpointType,
overrideConvoId,
overrideUserMessageId,
artifacts:
endpoint !== EModelEndpoint.agents
? getArtifactsMode({ codeArtifacts, includeShadcnui, customPromptMode })
: undefined,
},
convo,
) as TEndpointOption;

View File

@@ -25,10 +25,10 @@ import useUpdateFiles from './useUpdateFiles';
type UseFileHandling = {
fileSetter?: FileSetter;
fileFilter?: (file: File) => boolean;
additionalMetadata?: Record<string, string | undefined>;
overrideEndpoint?: EModelEndpoint;
fileFilter?: (file: File) => boolean;
overrideEndpointFileConfig?: EndpointFileConfig;
additionalMetadata?: Record<string, string | undefined>;
};
const useFileHandling = (params?: UseFileHandling) => {
@@ -151,6 +151,10 @@ const useFileHandling = (params?: UseFileHandling) => {
const formData = new FormData();
formData.append('endpoint', endpoint);
formData.append(
'original_endpoint',
conversation?.endpointType || conversation?.endpoint || '',
);
formData.append('file', extendedFile.file as File, encodeURIComponent(filename));
formData.append('file_id', extendedFile.file_id);

View File

@@ -4,7 +4,14 @@ import { AuthType, Tools, QueryKeys } from 'librechat-data-provider';
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
export type SearchApiKeyFormData = {
// Selected options
selectedProvider: string;
selectedReranker: string;
selectedScraper: string;
// API keys and URLs
serperApiKey: string;
searxngInstanceUrl: string;
searxngApiKey: string;
firecrawlApiKey: string;
firecrawlApiUrl: string;
jinaApiKey: string;
@@ -42,6 +49,8 @@ const useAuthSearchTool = (options?: { isEntityTool: boolean }) => {
(data: SearchApiKeyFormData) => {
const auth = Object.entries({
serperApiKey: data.serperApiKey,
searxngInstanceUrl: data.searxngInstanceUrl,
searxngApiKey: data.searxngApiKey,
firecrawlApiKey: data.firecrawlApiKey,
firecrawlApiUrl: data.firecrawlApiUrl,
jinaApiKey: data.jinaApiKey,

View File

@@ -19,12 +19,11 @@ export default function useSearchApiKeyForm({
const onSubmitHandler = useCallback(
(data: SearchApiKeyFormData) => {
reset();
installTool(data);
setIsDialogOpen(false);
onSubmit?.();
},
[onSubmit, reset, installTool],
[onSubmit, installTool],
);
const handleRevokeApiKey = useCallback(() => {

View File

@@ -1,6 +1,6 @@
import { useRef, useEffect, useCallback, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useCallback, useMemo, useEffect } from 'react';
import debounce from 'lodash/debounce';
import { useRecoilState } from 'recoil';
import { Constants, LocalStorageKeys } from 'librechat-data-provider';
import type { VerifyToolAuthResponse } from 'librechat-data-provider';
import type { UseQueryOptions } from '@tanstack/react-query';
@@ -19,9 +19,11 @@ const storageCondition = (value: unknown, rawCurrentValue?: string | null) => {
console.error(e);
}
}
return value !== undefined && value !== null && value !== '' && value !== false;
return value !== undefined && value !== null;
};
type ToolValue = boolean | string;
interface UseToolToggleOptions {
conversationId?: string | null;
toolKey: string;
@@ -60,36 +62,52 @@ export function useToolToggle({
[externalIsAuthenticated, authConfig, authQuery.data?.authenticated],
);
const isToolEnabled = useMemo(() => {
return ephemeralAgent?.[toolKey] ?? false;
}, [ephemeralAgent, toolKey]);
/** Track previous value to prevent infinite loops */
const prevIsToolEnabled = useRef(isToolEnabled);
const [toggleState, setToggleState] = useLocalStorage<boolean>(
// Keep localStorage in sync
const [, setLocalStorageValue] = useLocalStorage<ToolValue>(
`${localStorageKey}${key}`,
isToolEnabled,
false,
undefined,
storageCondition,
);
// The actual current value comes from ephemeralAgent
const toolValue = useMemo(() => {
return ephemeralAgent?.[toolKey] ?? false;
}, [ephemeralAgent, toolKey]);
const isToolEnabled = useMemo(() => {
// For backward compatibility, treat truthy string values as enabled
if (typeof toolValue === 'string') {
return toolValue.length > 0;
}
return toolValue === true;
}, [toolValue]);
// Sync to localStorage when ephemeralAgent changes
useEffect(() => {
const value = ephemeralAgent?.[toolKey];
if (value !== undefined) {
setLocalStorageValue(value);
}
}, [ephemeralAgent, toolKey, setLocalStorageValue]);
const [isPinned, setIsPinned] = useLocalStorage<boolean>(`${localStorageKey}pinned`, false);
const handleChange = useCallback(
({ e, isChecked }: { e?: React.ChangeEvent<HTMLInputElement>; isChecked: boolean }) => {
({ e, value }: { e?: React.ChangeEvent<HTMLInputElement>; value: ToolValue }) => {
if (isAuthenticated !== undefined && !isAuthenticated && setIsDialogOpen) {
setIsDialogOpen(true);
e?.preventDefault?.();
return;
}
setToggleState(isChecked);
// Update ephemeralAgent (localStorage will sync automatically via effect)
setEphemeralAgent((prev) => ({
...prev,
[toolKey]: isChecked,
...(prev || {}),
[toolKey]: value,
}));
},
[setToggleState, setIsDialogOpen, isAuthenticated, setEphemeralAgent, toolKey],
[setIsDialogOpen, isAuthenticated, setEphemeralAgent, toolKey],
);
const debouncedChange = useMemo(
@@ -97,18 +115,12 @@ export function useToolToggle({
[handleChange],
);
useEffect(() => {
if (prevIsToolEnabled.current !== isToolEnabled) {
setToggleState(isToolEnabled);
}
prevIsToolEnabled.current = isToolEnabled;
}, [isToolEnabled, setToggleState]);
return {
toggleState,
toggleState: toolValue, // Return the actual value from ephemeralAgent
handleChange,
isToolEnabled,
setToggleState,
toolValue,
setToggleState: (value: ToolValue) => handleChange({ value }), // Adapter for direct setting
ephemeralAgent,
debouncedChange,
setEphemeralAgent,

View File

@@ -68,7 +68,7 @@ const createErrorMessage = ({
errorMetadata?: Partial<TMessage>;
submission: EventSubmission;
error?: Error | unknown;
}) => {
}): TMessage => {
const currentMessages = getMessages();
const latestMessage = currentMessages?.[currentMessages.length - 1];
let errorMessage: TMessage;
@@ -123,7 +123,7 @@ const createErrorMessage = ({
error: true,
};
}
return tMessageSchema.parse(errorMessage);
return tMessageSchema.parse(errorMessage) as TMessage;
};
export const getConvoTitle = ({
@@ -374,9 +374,6 @@ export default function useEventHandlers({
});
let update = {} as TConversation;
if (conversationId) {
applyAgentTemplate(conversationId, submission.conversation.conversationId);
}
if (setConversation && !isAddedRequest) {
setConversation((prevState) => {
const parentId = isRegenerate ? userMessage.overrideParentMessageId : parentMessageId;
@@ -411,6 +408,14 @@ export default function useEventHandlers({
});
}
if (conversationId) {
applyAgentTemplate(
conversationId,
submission.conversation.conversationId,
submission.ephemeralAgent,
);
}
if (resetLatestMessage) {
resetLatestMessage();
}
@@ -513,6 +518,15 @@ export default function useEventHandlers({
}
return update;
});
if (conversation.conversationId && submission.ephemeralAgent) {
applyAgentTemplate(
conversation.conversationId,
submissionConvo.conversationId,
submission.ephemeralAgent,
);
}
if (location.pathname === '/c/new') {
navigate(`/c/${conversation.conversationId}`, { replace: true });
}
@@ -521,18 +535,19 @@ export default function useEventHandlers({
setIsSubmitting(false);
},
[
setShowStopButton,
setCompleted,
getMessages,
announcePolite,
navigate,
genTitle,
setConversation,
isAddedRequest,
setIsSubmitting,
getMessages,
setMessages,
queryClient,
setCompleted,
isAddedRequest,
announcePolite,
setConversation,
setIsSubmitting,
setShowStopButton,
location.pathname,
navigate,
applyAgentTemplate,
],
);
@@ -550,7 +565,7 @@ export default function useEventHandlers({
queryClient.setQueryData<TMessage[]>([QueryKeys.messages, convoId], finalMessages);
};
const parseErrorResponse = (data: TResData | Partial<TMessage>) => {
const parseErrorResponse = (data: TResData | Partial<TMessage>): TMessage => {
const metadata = data['responseMessage'] ?? data;
const errorMessage: Partial<TMessage> = {
...initialResponse,
@@ -563,7 +578,7 @@ export default function useEventHandlers({
errorMessage.messageId = v4();
}
return tMessageSchema.parse(errorMessage);
return tMessageSchema.parse(errorMessage) as TMessage;
};
if (!data) {
@@ -613,7 +628,7 @@ export default function useEventHandlers({
...data,
error: true,
parentMessageId: userMessage.messageId,
});
}) as TMessage;
setErrorMessages(receivedConvoId, errorResponse);
if (receivedConvoId && paramId === Constants.NEW_CONVO && newConversation) {

View File

@@ -1,4 +1,6 @@
{
"chat_direction_left_to_right": "شيء ما يحتاج أن يوضع هنا. كان فارغاً",
"chat_direction_right_to_left": "شيء ما يحتاج أن يوضع هنا. كان فارغاً",
"com_a11y_ai_composing": "الذكاء الاصطناعي ما زال يكتب",
"com_a11y_end": "انتهى الذكاء الاصطناعي من الرد",
"com_a11y_start": "بدأ الذكاء الاصطناعي بالرد",
@@ -9,6 +11,9 @@
"com_agents_create_error": "حدث خطأ أثناء إنشاء الوكيل الخاص بك",
"com_agents_description_placeholder": "اختياري: اشرح عميلك هنا",
"com_agents_enable_file_search": "تمكين البحث عن الملفات",
"com_agents_file_context": "سياق الملف (قارئ الحروف البصري)",
"com_agents_file_context_disabled": "يحب أولاً إنشاء الوكيل قبل رفع الملف لمحلل سياق الملف",
"com_agents_file_context_info": "الملفات المرفوعة كـ \"سياق\" تتم معالجتها باستخدام قارئ الحروف البصري (OCR) لاستخراج النص، والذي يُضاف بعد ذلك إلى التعليمات الموجِهة للوكيل. مثالية للوثائق والصور التي تحتوي على نص أو ملفات PDF حيث تحتاج إلى المحتوى النصي الكامل للملف.",
"com_agents_file_search_disabled": "يجب إنشاء الوكيل قبل تحميل الملفات للبحث في الملفات.",
"com_agents_file_search_info": "عند التمكين، سيتم إعلام الوكيل بأسماء الملفات المدرجة أدناه بالضبط، مما يتيح له استرجاع السياق ذي الصلة من هذه الملفات.",
"com_agents_instructions_placeholder": "التعليمات النظامية التي يستخدمها الوكيل",
@@ -567,6 +572,8 @@
"com_ui_fork_success": "تم تفريع المحادثة بنجاح",
"com_ui_fork_visible": "الرسائل المرئية فقط",
"com_ui_go_to_conversation": "انتقل إلى المحادثة",
"com_ui_good_afternoon": "طاب يومك",
"com_ui_good_morning": "صباح الخير",
"com_ui_happy_birthday": "إنه عيد ميلادي الأول!",
"com_ui_host": "مُضيف",
"com_ui_image_gen": "توليد الصور",
@@ -622,10 +629,17 @@
"com_ui_prompts_allow_use": "السماح باستخدام الأوامر",
"com_ui_provider": "مزود",
"com_ui_read_aloud": "قراءة بصوت عالٍ",
"com_ui_reference_saved_memories_description": "السماح للمساعد لاستخدام والرجوع لذكرياتك المخزنة عند الإجابة",
"com_ui_regenerate": "إعادة توليد",
"com_ui_regenerating": "جار إعادة التوليد...",
"com_ui_region": "المنطقة",
"com_ui_rename": "إعادة تسمية",
"com_ui_rename_conversation": "إعادة تسمية المحادثة",
"com_ui_rename_failed": "فشل في إعادة تسمية المحادثة",
"com_ui_rename_prompt": "إعادة تسمية الأمر",
"com_ui_requires_auth": "يتطلب مصادقة",
"com_ui_reset_var": "إعادة تعيين {{0}}",
"com_ui_reset_zoom": "إعادة تعيين التقريب",
"com_ui_result": "النتيجة",
"com_ui_revoke": "إلغاء",
"com_ui_revoke_info": "إلغاء جميع بيانات الاعتماد المقدمة من المستخدم.",
@@ -634,13 +648,20 @@
"com_ui_revoke_keys": "إلغاء المفاتيح",
"com_ui_revoke_keys_confirm": "هل أنت متأكد من أنك تريد إلغاء جميع المفاتيح؟",
"com_ui_role_select": "الدور",
"com_ui_roleplay": "القيام بالدور",
"com_ui_run_code": "تنفيذ الشفرة",
"com_ui_run_code_error": "حدث خطأ أثناء تشغيل الكود",
"com_ui_run_code_error": "حدث خطأ أثناء تشغيل الشفرة",
"com_ui_save": "حفظ",
"com_ui_save_submit": "حفظ وإرسال",
"com_ui_saved": "تم الحفظ!",
"com_ui_saving": "جار الحفظ...",
"com_ui_schema": "المخطط",
"com_ui_scope": "مجال",
"com_ui_search": "بحث",
"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_provider": "اختر مزودًا",
@@ -654,20 +675,34 @@
"com_ui_share_create_message": "سيظل اسمك وأي رسائل تضيفها بعد المشاركة خاصة.",
"com_ui_share_delete_error": "حدث خطأ أثناء حذف الرابط المشترك.",
"com_ui_share_error": "حدث خطأ أثناء مشاركة رابط الدردشة",
"com_ui_share_form_description": "شيء ما يحتاج أن يوضع هنا. كان فارغاً",
"com_ui_share_link_to_chat": "شارك الرابط في الدردشة",
"com_ui_share_to_all_users": "مشاركة مع جميع المستخدمين",
"com_ui_share_update_message": "سيظل اسمك والتعليمات المخصصة وأي رسائل تضيفها بعد المشاركة خاصة.",
"com_ui_share_var": "مشاركة {{0}}",
"com_ui_shared_link_bulk_delete_success": "تم حذف الرابط المشترك بنجاح",
"com_ui_shared_link_delete_success": "تم حذف الرابط المشترك بنجاح",
"com_ui_shared_link_not_found": "الرابط المشترك غير موجود",
"com_ui_shared_prompts": "المطالبات المشتركة",
"com_ui_shop": "تسووق",
"com_ui_show": "عرض",
"com_ui_show_all": "عرض الكل",
"com_ui_show_image_details": "إظهار تفاصيل الصورة",
"com_ui_show_qr": "إظهار رمز الـ QR",
"com_ui_sign_in_to_domain": "تسجيل الدخول الى {{0}}",
"com_ui_simple": "بسيط",
"com_ui_size": "الحجم",
"com_ui_special_var_current_date": "التاريخ الآن",
"com_ui_special_var_current_datetime": "التاريخ والوقت الآن",
"com_ui_special_var_current_user": "المستخدم الحالي",
"com_ui_special_variables": "المتغيرات الخاصة:",
"com_ui_special_variables_more_info": "يمكنك اختيار متغيّر خاص من القائمة المنسدلة أدناه: {{current_date}}` (today's date and day of week), `{{current_datetime}}` (local date and time), `{{utc_iso_datetime}}` (UTC ISO datetime), و `{{current_user}}` (your account name).",
"com_ui_speech_while_submitting": "لا يمكن إرسال الكلام أثناء إنشاء الرد",
"com_ui_stop": "توقف",
"com_ui_storage": "التخزين",
"com_ui_submit": "إرسال",
"com_ui_teach_or_explain": "علِّم",
"com_ui_temporary": "دردشة مؤقتة",
"com_ui_terms_and_conditions": "شروط الخدمة",
"com_ui_terms_of_service": "شروط الخدمة",
"com_ui_tools": "أدوات المساعدين",

View File

@@ -322,7 +322,6 @@
"com_nav_delete_cache_storage": "Esborra la memòria cau de TTS",
"com_nav_delete_data_info": "Totes les teves dades s'eliminaran.",
"com_nav_delete_warning": "AVÍS: Això eliminarà permanentment el teu compte.",
"com_nav_edit_chat_badges": "Edita les insígnies del xat",
"com_nav_enable_cache_tts": "Habilita la memòria cau TTS",
"com_nav_enable_cloud_browser_voice": "Utilitza veus al núvol",
"com_nav_enabled": "Habilitat",
@@ -688,7 +687,6 @@
"com_ui_import_conversation_info": "Importa converses des d'un fitxer JSON",
"com_ui_import_conversation_success": "Converses importades amb èxit",
"com_ui_include_shadcnui": "Inclou instruccions de components shadcn/ui",
"com_ui_include_shadcnui_agent": "Inclou instruccions shadcn/ui",
"com_ui_input": "Entrada",
"com_ui_instructions": "Instruccions",
"com_ui_late_night": "Bona matinada",

View File

@@ -21,6 +21,7 @@
"com_agents_name_placeholder": "Valgfrit: Navnet på agenten",
"com_agents_no_access": "Du har ikke adgang til at redigere denne agent.",
"com_agents_not_available": "Agent ikke tilgængelig",
"com_agents_search_info": "Når det er aktiveret, kan din agent søge på nettet efter opdaterede oplysninger. Kræver en gyldig API-nøgle.",
"com_agents_search_name": "Søg agenter efter navn",
"com_agents_update_error": "Der opstod en fejl ved opdateringen af din agent.",
"com_assistants_action_attempt": "Assistenten vil tale med {{0}}",
@@ -38,6 +39,7 @@
"com_assistants_code_interpreter": "Kodefortolker",
"com_assistants_code_interpreter_files": "Filerne nedenfor er kun til Kodefortolker:",
"com_assistants_code_interpreter_info": "Kodefortolkeren gør det muligt for assistenten at skrive og køre kode. Dette værktøj kan behandle filer med forskellige data og formateringer og generere filer såsom grafer.",
"com_assistants_completed_action": "Talte med {{0}}",
"com_assistants_completed_function": "Kørte {{0}}",
"com_assistants_conversation_starters": "Samtalestartere",
"com_assistants_conversation_starters_placeholder": "Indtast en samtalestarter",
@@ -48,6 +50,7 @@
"com_assistants_description_placeholder": "Valgfrit: Beskriv din assistent her",
"com_assistants_domain_info": "Assistenten sendte disse oplysninger til {{0}}",
"com_assistants_file_search": "Filsøgning",
"com_assistants_file_search_info": "Filsøgning giver assistenten mulighed for at hente viden fra filer, som du eller dine brugere uploader. Når en fil er uploadet, beslutter assistenten automatisk, hvornår der skal hentes indhold baseret på brugeranmodninger. Vedhæftning af vektorlagre til filsøgning er endnu ikke understøttet. Du kan vedhæfte dem fra Provider Playground eller vedhæfte filer til beskeder til filsøgning på trådbasis.",
"com_assistants_function_use": "Assistent brugt {{0}}",
"com_assistants_image_vision": "Billedvision",
"com_assistants_instructions_placeholder": "De systeminstruktioner, som assistenten bruger",
@@ -59,6 +62,7 @@
"com_assistants_non_retrieval_model": "Filsøgning er ikke aktiveret på denne model. Vælg venligst en anden model.",
"com_assistants_retrieval": "Hentning",
"com_assistants_running_action": "Afvikler handling",
"com_assistants_running_var": "Kører {{0}}",
"com_assistants_search_name": "Søg assistenter efter navn",
"com_assistants_update_actions_error": "Der opstod en fejl ved oprettelse eller opdatering af handlingen.",
"com_assistants_update_actions_success": "Vellykket oprettet eller opdateret Handling",
@@ -120,6 +124,7 @@
"com_auth_reset_password_if_email_exists": "Hvis der findes en konto med denne e-mail, er der sendt en e-mail med instruktioner til nulstilling af adgangskode. Sørg for at tjekke din spam-mappe.",
"com_auth_reset_password_link_sent": "E-mail sendt",
"com_auth_reset_password_success": "Nulstilling af adgangskode genemført",
"com_auth_saml_login": "Fortsæt med SAML",
"com_auth_sign_in": "Log ind",
"com_auth_sign_up": "Tilmeld dig",
"com_auth_submit_registration": "Send registrering",
@@ -131,6 +136,8 @@
"com_auth_username_min_length": "Brugernavn skal være mindst 2 tegn",
"com_auth_verify_your_identity": "Bekræft din identitet",
"com_auth_welcome_back": "Velkommen tilbage",
"com_citation_more_details": "Flere detaljer om {{label}}",
"com_citation_source": "Kilde",
"com_click_to_download": "(klik her for at downloade)",
"com_download_expired": "(download udløbet)",
"com_download_expires": "(klik her for at downloade - udløber {{0}})",
@@ -142,6 +149,10 @@
"com_endpoint_anthropic_maxoutputtokens": "Maksimalt antal tokens, der kan genereres i svaret. Angiv en lavere værdi for kortere svar og en højere værdi for længere svar. Bemærk: Modeller kan stoppe, før de når dette maksimum.",
"com_endpoint_anthropic_prompt_cache": "Prompt caching gør det muligt at genbruge store kontekster eller instruktioner på tværs af API-kald, hvilket reducerer omkostninger og ventetid.",
"com_endpoint_anthropic_temp": "Spænder fra 0 til 1. Brug temp tættere på 0 til analytiske/ multiple choice-opgaver og tættere på 1 til kreative og generative opgaver. Vi anbefaler at ændre dette eller Top P, men ikke begge dele.",
"com_endpoint_anthropic_thinking": "Aktiverer intern ræsonnering for understøttede Claude-modeller (3.7 Sonnet). Bemærk: kræver, at \"Thinking Budget\" er indstillet og lavere end \"Max Output Tokens\"",
"com_endpoint_anthropic_thinking_budget": "Bestemmer det maksimale antal tokens, som Claude må bruge til sin interne ræsonnementsproces. Større budgetter kan forbedre kvaliteten af svarene ved at muliggøre en mere grundig analyse af komplekse problemer, selv om Claude måske ikke bruger hele det tildelte budget, især ved intervaller over 32K. Denne indstilling skal være lavere end \"Max Output Tokens\".",
"com_endpoint_anthropic_topk": "Top-k ændrer, hvordan modellen udvælger symboler til output. En top-k på 1 betyder, at det valgte token er det mest sandsynlige blandt alle tokens i modellens ordforråd (også kaldet grådig afkodning), mens en top-k på 3 betyder, at det næste token vælges blandt de 3 mest sandsynlige tokens (ved hjælp af temperatur).",
"com_endpoint_anthropic_topp": "Top-p ændrer, hvordan modellen udvælger tokens til output. Tokens vælges fra de mest K (se topK-parameter) sandsynlige til de mindst sandsynlige, indtil summen af deres sandsynligheder er lig med top-p-værdien.",
"com_endpoint_assistant": "Assistent",
"com_endpoint_assistant_model": "Assistentmodel",
"com_endpoint_assistant_placeholder": "Vælg en assistent fra sidepanelet til højre",
@@ -154,6 +165,7 @@
"com_endpoint_config_google_gemini_api": "(Gemini API)",
"com_endpoint_config_google_service_key": "Google Service Konto -nøgle",
"com_endpoint_config_key": "Angiv API-nøgle",
"com_endpoint_config_key_encryption": "Din nøgle vil blive krypteret og slettet den",
"com_endpoint_config_key_for": "Indstil API-nøgle til",
"com_endpoint_config_key_google_need_to": "Du er nødt til at",
"com_endpoint_config_key_google_service_account": "Opret en servicekonto",
@@ -186,6 +198,8 @@
"com_endpoint_google_custom_name_placeholder": "Indstil et brugerdefineret navn til Google",
"com_endpoint_google_maxoutputtokens": "Maksimalt antal tokens, der kan genereres i svaret. Angiv en lavere værdi for kortere svar og en højere værdi for længere svar. Bemærk: Modeller kan stoppe, før de når dette maksimum.",
"com_endpoint_google_temp": "Højere værdier = mere tilfældige, mens lavere værdier = mere fokuserede og deterministiske. Vi anbefaler at ændre dette eller Top P, men ikke begge dele.",
"com_endpoint_google_topk": "Top-k ændrer, hvordan modellen udvælger symboler til output. En top-k på 1 betyder, at det valgte token er det mest sandsynlige blandt alle tokens i modellens ordforråd (også kaldet grådig afkodning), mens en top-k på 3 betyder, at det næste token vælges blandt de 3 mest sandsynlige tokens (ved hjælp af temperatur).",
"com_endpoint_google_topp": "Top-p ændrer, hvordan modellen udvælger tokens til output. Tokens vælges fra de mest K (se topK-parameter) sandsynlige til de mindst sandsynlige, indtil summen af deres sandsynligheder er lig med top-p-værdien.",
"com_endpoint_instructions_assistants": "Overskriv instruktioner",
"com_endpoint_instructions_assistants_placeholder": "Overstyrer instruktionerne fra assistenten. Det er nyttigt, hvis man vil ændre adfærden for hver enkelt kørsel.",
"com_endpoint_max_output_tokens": "Maks. output-tokens",
@@ -196,6 +210,18 @@
"com_endpoint_no_presets": "Ingen forudindstillinger endnu, brug indstillingsknappen til at oprette en",
"com_endpoint_open_menu": "Åbn menu",
"com_endpoint_openai_custom_name_placeholder": "Indstil et brugerdefineret navn til AI'en",
"com_endpoint_openai_detail": "Opløsningen for Vision-anmodninger. \"Lav\" er billigere og hurtigere, \"Høj\" er mere detaljeret og dyrere, og \"Auto\" vælger automatisk mellem de to baseret på billedopløsningen.",
"com_endpoint_openai_freq": "Tal mellem -2,0 og 2,0. Positive værdier straffer nye tokens baseret på deres eksisterende frekvens i teksten indtil videre, hvilket mindsker modellens sandsynlighed for at gentage den samme linje ordret.",
"com_endpoint_openai_max": "Det maksimale antal tokens, der skal genereres. Den samlede længde af input-tokens og genererede tokens er begrænset af modellens kontekstlængde.",
"com_endpoint_openai_max_tokens": "Valgfrit 'max_tokens'-felt, der repræsenterer det maksimale antal tokens, der kan genereres i chatudfyldningen. Den samlede længde af input-tokens og genererede tokens er begrænset af modellernes kontekstlængde. Du kan opleve fejl, hvis dette antal overskrider det maksimale antal kontekst-tokens.",
"com_endpoint_openai_pres": "Tal mellem -2,0 og 2,0. Positive værdier straffer nye tokens baseret på, om de optræder i teksten indtil videre, hvilket øger modellens sandsynlighed for at tale om nye emner.",
"com_endpoint_openai_prompt_prefix_placeholder": "Indstil brugerdefinerede instruktioner, der skal inkluderes i systembeskeden. Standard: ingen",
"com_endpoint_openai_reasoning_effort": "Kun o1- og o3-modeller: Begrænser indsatsen for ræsonnement for ræsonnerende modeller. At reducere ræsonneringsindsatsen kan resultere i hurtigere svar og færre tokens brugt på ræsonnering i et svar.",
"com_endpoint_openai_resend": "Send alle tidligere vedhæftede billeder igen. Bemærk: Dette kan øge tokenomkostningerne betydeligt, og du kan opleve fejl med mange vedhæftede billeder.",
"com_endpoint_openai_resend_files": "Send alle tidligere vedhæftede filer igen. Bemærk: Dette vil øge tokenomkostningerne, og du kan opleve fejl med mange vedhæftede filer.",
"com_endpoint_openai_stop": "Op til 4 sekvenser, hvor API'en stopper med at generere yderligere tokens.",
"com_endpoint_openai_temp": "Højere værdier = mere tilfældige, mens lavere værdier = mere fokuserede og deterministiske. Vi anbefaler at ændre dette eller Top P, men ikke begge dele.",
"com_endpoint_openai_topp": "Et alternativ til sampling med temperatur, kaldet nucleus sampling, hvor modellen tager højde for resultaterne af tokens med top_p sandsynlighedsmasse. Så 0,1 betyder, at kun de tokens, der har den største sandsynlighedsmasse på 10 %, tages i betragtning. Vi anbefaler at ændre dette eller temperaturen, men ikke begge dele.",
"com_endpoint_output": "Produktion",
"com_endpoint_plug_image_detail": "Billeddetaljer",
"com_endpoint_plug_resend_files": "Send filer igen",
@@ -249,7 +275,10 @@
"com_error_files_upload": "Der opstod en fejl under upload af filen.",
"com_error_files_upload_canceled": "Anmodningen om filoverførsel blev annulleret. Bemærk: Filuploaden kan stadig være under behandling og skal slettes manuelt.",
"com_error_files_validation": "Der opstod en fejl under validering af filen.",
"com_error_input_length": "Antallet af tokener i den seneste meddelelse er for langt og overskrider tokengrænsen, eller dine tokengrænseparametre er forkert konfigureret, hvilket påvirker kontekstvinduet negativt. Mere information: {{0}}. Forkort venligst din besked, juster den maksimale kontekststørrelse fra samtaleparametrene, eller forgren samtalen for at fortsætte.",
"com_error_invalid_agent_provider": "Den \"{{0}}\"-udbyder er ikke tilgængelig for brug med agenter. Gå til din agents indstillinger, og vælg en aktuelt tilgængelig udbyder.",
"com_error_invalid_user_key": "Ugyldig nøgle angivet. Angiv venligst en gyldig nøgle, og prøv igen.",
"com_error_moderation": "Det ser ud til, at det indsendte indhold er blevet markeret af vores moderationssystem for ikke at være i overensstemmelse med vores retningslinjer for fællesskabet. Vi kan ikke gå videre med dette specifikke emne. Hvis du har andre spørgsmål eller emner, du gerne vil udforske, kan du redigere din besked eller oprette en ny samtale.",
"com_error_no_base_url": "Ingen base-URL fundet. Angiv venligst en og prøv igen.",
"com_error_no_user_key": "Ingen nøgle fundet. Angiv venligst en nøgle, og prøv igen.",
"com_files_filter": "Filtrer filer ...",
@@ -275,6 +304,27 @@
"com_nav_auto_transcribe_audio": "Automatisk transskribering af lyd",
"com_nav_automatic_playback": "Autoplay Seneste besked",
"com_nav_balance": "Balance",
"com_nav_balance_auto_refill_disabled": "Automatisk genopfyldning er deaktiveret.",
"com_nav_balance_auto_refill_error": "Fejl ved indlæsning af indstillinger for automatisk genopfyldning.",
"com_nav_balance_auto_refill_settings": "Indstillinger for automatisk genopfyldning",
"com_nav_balance_day": "dag",
"com_nav_balance_days": "dage",
"com_nav_balance_every": "Hver",
"com_nav_balance_hour": "time",
"com_nav_balance_hours": "timer",
"com_nav_balance_interval": "Interval:",
"com_nav_balance_last_refill": "Sidste genopfyldning:",
"com_nav_balance_minute": "minut",
"com_nav_balance_minutes": "minutter",
"com_nav_balance_month": "måned",
"com_nav_balance_months": "måneder",
"com_nav_balance_next_refill": "Næste genopfyldning:",
"com_nav_balance_next_refill_info": "Den næste genopfyldning sker kun automatisk, når begge betingelser er opfyldt: Det angivne tidsinterval er gået siden sidste genopfyldning, og hvis du sender en besked, vil din saldo falde til under nul.",
"com_nav_balance_refill_amount": "Genopfyldningsmængde:",
"com_nav_balance_second": "anden",
"com_nav_balance_seconds": "sekunder",
"com_nav_balance_week": "uge",
"com_nav_balance_weeks": "uger",
"com_nav_browser": "Browser",
"com_nav_center_chat_input": "Center Chat Input på velkomstskærmen",
"com_nav_change_picture": "Skift billede",
@@ -298,7 +348,6 @@
"com_nav_delete_cache_storage": "Slet TTS-cache-lagring",
"com_nav_delete_data_info": "Alle dine data vil blive slettet.",
"com_nav_delete_warning": "ADVARSEL: Dette vil slette din konto permanent.",
"com_nav_edit_chat_badges": "Rediger chat-badges",
"com_nav_enable_cache_tts": "Aktivér cache-TTS",
"com_nav_enable_cloud_browser_voice": "Brug cloud-baserede stemmer",
"com_nav_enabled": "Aktiveret",
@@ -325,18 +374,31 @@
"com_nav_info_code_artifacts": "Aktiverer visning af eksperimentelle kodeartefakter ved siden af chatten",
"com_nav_info_code_artifacts_agent": "Aktiverer brugen af kodeartefakter for denne agent. Som standard tilføjes yderligere instruktioner specifikke for brugen af artefakter, medmindre \"Brugerdefineret prompttilstand\" er aktiveret.",
"com_nav_info_custom_prompt_mode": "Når den er aktiveret, vil standardsystemprompten for artefakter ikke blive inkluderet. Alle instruktioner til generering af artefakter skal angives manuelt i denne tilstand.",
"com_nav_info_enter_to_send": "Når den er aktiveret, sender du din besked ved at trykke på `ENTER`. Når den er deaktiveret, vil et tryk på Enter tilføje en ny linje, og du skal trykke på `CTRL + ENTER` / `⌘ + ENTER` for at sende din besked.",
"com_nav_info_fork_change_default": "`Kun synlige beskeder` inkluderer kun den direkte sti til den valgte besked. `Include related branches` tilføjer grene langs stien. `Include all to/from here` inkluderer alle forbundne beskeder og grene.",
"com_nav_info_fork_split_target_setting": "Når den er aktiveret, vil forking begynde fra målbeskeden til den seneste besked i samtalen i henhold til den valgte adfærd.",
"com_nav_info_include_shadcnui": "Når det er aktiveret, vil der blive inkluderet instruktioner til brug af shadcn/ui-komponenter. shadcn/ui er en samling af genanvendelige komponenter, der er bygget med Radix UI og Tailwind CSS. Bemærk: Det er lange instruktioner, og du bør kun aktivere dem, hvis det er vigtigt for dig at informere LLM om de korrekte importer og komponenter. For mere information om disse komponenter, besøg: https://ui.shadcn.com/",
"com_nav_info_latex_parsing": "Når det er aktiveret, vil LaTeX-kode i meddelelser blive gengivet som matematiske ligninger. Hvis du deaktiverer dette, kan det forbedre ydeevnen, hvis du ikke har brug for LaTeX-rendering.",
"com_nav_info_save_badges_state": "Når den er aktiveret, gemmes chatbadgenes tilstand. Det betyder, at hvis du opretter en ny chat, vil badges forblive i samme tilstand som i den forrige chat. Hvis du deaktiverer denne mulighed, vil badges blive nulstillet til deres standardtilstand, hver gang du opretter en ny chat.",
"com_nav_info_save_draft": "Når det er aktiveret, gemmes den tekst og de vedhæftede filer, du indtaster i chatformularen, automatisk lokalt som kladder. Disse kladder vil være tilgængelige, selv om du genindlæser siden eller skifter til en anden samtale. Kladder gemmes lokalt på din enhed og slettes, når beskeden er sendt.",
"com_nav_info_show_thinking": "Når den er aktiveret, vil chatten som standard vise de tænkende dropdowns åbne, så du kan se AI'ens ræsonnementer i realtid. Når den er deaktiveret, forbliver de tænkende dropdowns lukket som standard for at give en renere og mere strømlinet grænseflade.",
"com_nav_info_user_name_display": "Når den er aktiveret, vises afsenderens brugernavn over hver besked, du sender. Når det er deaktiveret, vil du kun se \"Du\" over dine beskeder.",
"com_nav_lang_arabic": "Arabisk",
"com_nav_lang_auto": "Automatisk detektion",
"com_nav_lang_brazilian_portuguese": "Portugisisk Brasiliansk",
"com_nav_lang_catalan": "Catalansk",
"com_nav_lang_chinese": "Kinesisk",
"com_nav_lang_czech": "Tjekkisk",
"com_nav_lang_danish": "Dansk",
"com_nav_lang_dutch": "Hollandsk",
"com_nav_lang_english": "Engelsk",
"com_nav_lang_estonian": "Estisk",
"com_nav_lang_finnish": "Finsk",
"com_nav_lang_french": "Fransk ",
"com_nav_lang_georgian": "Georgisk",
"com_nav_lang_german": "Tysk",
"com_nav_lang_hebrew": "Hebraisk",
"com_nav_lang_hungarian": "Ungarsk",
"com_nav_lang_indonesia": "Indonesien",
"com_nav_lang_italian": "Italiensk",
"com_nav_lang_japanese": "Japansk",
@@ -350,6 +412,7 @@
"com_nav_lang_thai": "Thai",
"com_nav_lang_traditional_chinese": "Kinesisk",
"com_nav_lang_turkish": "Tyrkisk",
"com_nav_lang_vietnamese": "Vietnamesisk",
"com_nav_language": "Sprog",
"com_nav_latex_parsing": "Parsing af LaTeX i beskeder (kan påvirke ydeevnen)",
"com_nav_log_out": "Log ud",
@@ -374,6 +437,7 @@
"com_nav_search_placeholder": "Søg efter beskeder",
"com_nav_send_message": "Send besked",
"com_nav_setting_account": "Konto",
"com_nav_setting_balance": "Balance",
"com_nav_setting_beta": "Beta-funktioner",
"com_nav_setting_chat": "Chat",
"com_nav_setting_data": "Datakontrol",
@@ -411,6 +475,12 @@
"com_sidepanel_hide_panel": "Skjul panel",
"com_sidepanel_manage_files": "Administrer filer",
"com_sidepanel_parameters": "Parametre",
"com_sources_image_alt": "Søgeresultatbillede",
"com_sources_more_sources": "+{{count}} kilder",
"com_sources_tab_all": "Alle",
"com_sources_tab_images": "Billeder",
"com_sources_tab_news": "Nyheder",
"com_sources_title": "Kilder",
"com_ui_2fa_account_security": "To-faktor-autentificering tilføjer et ekstra lag af sikkerhed til din konto",
"com_ui_2fa_disable": "Deaktiver 2FA",
"com_ui_2fa_disable_error": "Der opstod en fejl ved deaktivering af to-faktor-autentificering",
@@ -445,6 +515,20 @@
"com_ui_agent_recursion_limit_info": "Begrænser, hvor mange trin agenten kan tage i en kørsel, før den giver et endeligt svar. Standard er 25 trin. Et trin er enten en AI API-anmodning eller en værktøjsbrugsrunde. For eksempel tager en grundlæggende værktøjsinteraktion 3 trin: indledende anmodning, værktøjsbrug og opfølgende anmodning.",
"com_ui_agent_shared_to_all": "Der skal stå noget her. Det var tomt.",
"com_ui_agent_var": "{{0}} agent",
"com_ui_agent_version": "Version",
"com_ui_agent_version_active": "Aktiv version",
"com_ui_agent_version_duplicate": "Duplikatversion fundet. Dette vil skabe en version, der er identisk med Version {{versionIndex}}.",
"com_ui_agent_version_empty": "Ingen tilgængelige versioner",
"com_ui_agent_version_error": "Fejl ved hentning af versioner",
"com_ui_agent_version_history": "Versionshistorik",
"com_ui_agent_version_no_agent": "Ingen agent valgt. Vælg venligst en agent for at se versionshistorikken.",
"com_ui_agent_version_no_date": "Dato ikke tilgængelig",
"com_ui_agent_version_restore": "Gendan",
"com_ui_agent_version_restore_confirm": "Er du sikker på, at du vil gendanne denne version?",
"com_ui_agent_version_restore_error": "Kunne ikke gendanne version",
"com_ui_agent_version_restore_success": "Version gendannet med succes",
"com_ui_agent_version_title": "Version {{versionNumber}}",
"com_ui_agent_version_unknown_date": "Ukendt dato",
"com_ui_agents": "Agenter",
"com_ui_agents_allow_create": "Tillad oprettelse af agenter",
"com_ui_agents_allow_share_global": "Tillad deling af agenter til alle brugere",
@@ -528,6 +612,7 @@
"com_ui_confirm_change": "Bekræft ændring",
"com_ui_context": "Kontekst",
"com_ui_continue": "Fortsæt",
"com_ui_controls": "Kontrolelementer",
"com_ui_convo_delete_error": "Kunne ikke slette samtalen",
"com_ui_copied": "Kopieret!",
"com_ui_copied_to_clipboard": "Kopieret til udklipsholder",
@@ -617,8 +702,23 @@
"com_ui_fork_default": "Brug standard forgreningsmulighed",
"com_ui_fork_error": "Der opstod en fejl under forgreningen af samtalen",
"com_ui_fork_from_message": "Vælg en forgreningsmulighed",
"com_ui_fork_info_1": "Brug denne indstilling til at forkaste meddelelser med den ønskede adfærd.",
"com_ui_fork_info_2": "\"Forgrening\" betyder, at man opretter en ny samtale, der starter/slutter med specifikke beskeder i den aktuelle samtale og opretter en kopi i henhold til de valgte indstillinger.",
"com_ui_fork_info_3": "\"Målbeskeden\" henviser enten til den besked, som denne popup blev åbnet fra, eller, hvis du markerer \"{{0}}\", den seneste besked i samtalen.",
"com_ui_fork_info_branches": "Denne indstilling forgrener de synlige beskeder sammen med relaterede grene",
"com_ui_fork_info_button_label": "Se oplysninger om forgrening af samtaler",
"com_ui_fork_info_remember": "Marker dette for at huske de indstillinger, du vælger, til fremtidig brug, hvilket gør det hurtigere at forgrene samtaler som ønsket.",
"com_ui_fork_info_start": "Hvis det er markeret, vil forgrening begynde fra denne besked til den seneste besked i samtalen i henhold til den adfærd, der er valgt ovenfor.",
"com_ui_fork_info_target": "Denne indstilling forgrener alle beskeder, der fører op til målbeskeden, inklusive dens naboer",
"com_ui_fork_info_visible": "Denne indstilling deler kun de synlige meddelelser; med andre ord den direkte vej til målmeddelelsen uden nogen forgrening.",
"com_ui_fork_more_details_about": "Se yderligere oplysninger og detaljer om \"{{0}}\" forgreningsmulighed",
"com_ui_fork_more_info_options": "Se detaljeret forklaring af alle forgreningslindstillinger og deres adfærd",
"com_ui_fork_processing": "Forgrener samtale...",
"com_ui_fork_remember": "Husk ",
"com_ui_fork_remember_checked": "Dit valg vil blive husket efter brug. Du kan til enhver tid ændre det i indstillingerne.",
"com_ui_fork_split_target": "Start forgrening her",
"com_ui_fork_split_target_setting": "Start forgrening fra målbesked som standard",
"com_ui_fork_success": "Samtalen er forgrenet",
"com_ui_fork_visible": "Kun synlige beskeder",
"com_ui_generate_backup": "Generer backup-koder",
"com_ui_generate_qrcode": "Generer QR-kode",
@@ -643,7 +743,6 @@
"com_ui_import_conversation_info": "Importer samtaler fra en JSON-fil",
"com_ui_import_conversation_success": "Samtaler importeret med succes",
"com_ui_include_shadcnui": "Inkluder instruktioner til shadcn/ui-komponenter",
"com_ui_include_shadcnui_agent": "Inkluder instruktioner til shadcn/ui",
"com_ui_input": "Input",
"com_ui_instructions": "Instruktioner",
"com_ui_late_night": "Glædelig sen aften",
@@ -818,6 +917,25 @@
"com_ui_version_var": "Version {{0}}",
"com_ui_versions": "Versioner",
"com_ui_view_source": "Se kilde-chat",
"com_ui_web_search": "Websøgning",
"com_ui_web_search_cohere_key": "Indtast Cohere API-nøgle",
"com_ui_web_search_firecrawl_url": "Firecrawl API URL (valgfri)",
"com_ui_web_search_jina_key": "Indtast Jina API-nøgle",
"com_ui_web_search_processing": "Behandler resultater",
"com_ui_web_search_provider": "Søgeudbyder",
"com_ui_web_search_provider_serper": "Serper API",
"com_ui_web_search_provider_serper_key": "Få din Serper API-nøgle",
"com_ui_web_search_reading": "Læser resultater",
"com_ui_web_search_reranker": "Genanker",
"com_ui_web_search_reranker_cohere": "Cohere",
"com_ui_web_search_reranker_cohere_key": "Få din Cohere API-nøgle",
"com_ui_web_search_reranker_jina": "Jina AI",
"com_ui_web_search_reranker_jina_key": "Få din Jina API-nøgle",
"com_ui_web_search_scraper": "Skraber",
"com_ui_web_search_scraper_firecrawl": "Firecrawl API",
"com_ui_web_search_scraper_firecrawl_key": "Få din Firecrawl API-nøgle",
"com_ui_web_searching": "Søger på nettet",
"com_ui_web_searching_again": "Søger på nettet igen",
"com_ui_weekend_morning": "God weekend",
"com_ui_write": "Skriver",
"com_ui_x_selected": "{{0}} udvalgt",

View File

@@ -17,16 +17,23 @@
"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_instructions_placeholder": "Die Systemanweisungen, die der Agent verwendet",
"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_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_not_available": "Agent nicht verfügbar",
"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_name": "Agenten nach Namen suchen",
"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 müsst einen Assistenten erstellen, bevor du Aktionen hinzufügen kannst.",
"com_assistants_actions_disabled": "Du musst einen Assistenten erstellen, bevor du Aktionen hinzufügen kannst.",
"com_assistants_actions_info": "Lasse deinen Assistenten Informationen abrufen oder Aktionen über APIs ausführen",
"com_assistants_add_actions": "Aktionen hinzufügen",
"com_assistants_add_tools": "Werkzeuge hinzufügen",
@@ -124,6 +131,7 @@
"com_auth_reset_password_if_email_exists": "Wenn ein Konto mit dieser E-Mail-Adresse existiert, wurde eine E-Mail mit Anweisungen zum Zurücksetzen des Passworts gesendet. Bitte überprüfe auch deinen Spam-Ordner.",
"com_auth_reset_password_link_sent": "E-Mail gesendet",
"com_auth_reset_password_success": "Passwort erfolgreich zurückgesetzt",
"com_auth_saml_login": "Weiter mit SAML",
"com_auth_sign_in": "Anmelden",
"com_auth_sign_up": "Registrieren",
"com_auth_submit_registration": "Registrierung absenden",
@@ -135,8 +143,8 @@
"com_auth_username_min_length": "Benutzername muss mindestens 2 Zeichen lang sein",
"com_auth_verify_your_identity": "Bestätige deine Identität",
"com_auth_welcome_back": "Willkommen zurück",
"com_citation_more_details": "Mehr Details über {{label}}",
"com_citation_source": "Quelle",
"com_citation_more_details": "Mehr Details über {{label}}\n",
"com_citation_source": "Quelle\n",
"com_click_to_download": "(hier klicken zum Herunterladen)",
"com_download_expired": "Download abgelaufen",
"com_download_expires": "(hier klicken zum Herunterladen - läuft ab am {{0}})",
@@ -197,8 +205,11 @@
"com_endpoint_google_custom_name_placeholder": "Lege einen benutzerdefinierten Namen für Google fest",
"com_endpoint_google_maxoutputtokens": "Maximale Anzahl von Token, die in der Antwort generiert werden können. Gib einen niedrigeren Wert für kürzere Antworten und einen höheren Wert für längere Antworten an. Hinweis: Modelle können möglicherweise vor Erreichen dieses Maximums stoppen.",
"com_endpoint_google_temp": "Höhere Werte = zufälliger, während niedrigere Werte = fokussierter und deterministischer. Wir empfehlen, entweder dies oder Top P zu ändern, aber nicht beides.",
"com_endpoint_google_thinking": "Aktiviert oder deaktiviert die Argumentation. Diese Einstellung wird nur von bestimmten Modellen (Serie 2.5) unterstützt. Bei älteren Modellen hat diese Einstellung möglicherweise keine Wirkung.",
"com_endpoint_google_thinking_budget": "Gibt die Anzahl der Tokens an, die das Modell \"zum Nachdenken\" verwendet. Die tatsächliche Anzahl kann je nach Eingabeaufforderung diesen Wert über- oder unterschreiten.\n\nDiese Einstellung wird nur von bestimmten Modellen (2.5-Serie) unterstützt. Gemini 2.5 Pro unterstützt 12832.768 Token. Gemini 2.5 Flash unterstützt 024.576 Token. Gemini 2.5 Flash Lite unterstützt 51224.576 Token.\n\nLeer lassen oder auf „-1“ setzen, damit das Modell automatisch entscheidet, wann und wie viel nachgedacht werden soll. Standardmäßig denkt Gemini 2.5 Flash Lite nicht.",
"com_endpoint_google_topk": "Top-k ändert, wie das Modell Token für die Antwort auswählt. Ein Top-k von 1 bedeutet, dass das ausgewählte Token das wahrscheinlichste unter allen Token im Vokabular des Modells ist (auch Greedy-Decoding genannt), während ein Top-k von 3 bedeutet, dass das nächste Token aus den 3 wahrscheinlichsten Token ausgewählt wird (unter Verwendung der Temperatur).",
"com_endpoint_google_topp": "Top-p ändert, wie das Modell Token für die Antwort auswählt. Token werden von den wahrscheinlichsten K (siehe topK-Parameter) bis zu den am wenigsten wahrscheinlichen ausgewählt, bis die Summe ihrer Wahrscheinlichkeiten dem Top-p-Wert entspricht.",
"com_endpoint_google_use_search_grounding": "Nutze den Abgleich mit der Google-Suche, um Antworten auf Basis von Echtzeit-Informationen aus dem Web zu fundieren. Dies ermöglicht den Modellen, auf aktuelle Informationen zuzugreifen und genauere, aktuellere Antworten zu geben.",
"com_endpoint_instructions_assistants": "Anweisungen überschreiben",
"com_endpoint_instructions_assistants_placeholder": "Überschreibt die Anweisungen des Assistenten. Dies ist nützlich, um das Verhalten auf Basis einzelner Ausführungen zu modifizieren.",
"com_endpoint_max_output_tokens": "Max. Antwort-Token",
@@ -206,7 +217,7 @@
"com_endpoint_message_new": "Nachricht an {{0}}",
"com_endpoint_message_not_appendable": "Bearbeite deine Nachricht oder generiere neu.",
"com_endpoint_my_preset": "Meine Voreinstellung",
"com_endpoint_no_presets": "Noch keine Voreinstellungen, verwende die KI-Einstellungsschaltfläche, um eine zu erstellen",
"com_endpoint_no_presets": "Noch keine Voreinstellungen, verwende die KI-Einstellungsschaltfläche, um eine Voreinstellung zu erstellen",
"com_endpoint_open_menu": "Menü öffnen",
"com_endpoint_openai_custom_name_placeholder": "Lege einen benutzerdefinierten Namen für die KI fest.",
"com_endpoint_openai_detail": "Die Auflösung für Bilderkennungs-Anfragen. \"Niedrig\" ist günstiger und schneller, \"Hoch\" ist detaillierter und teurer, und \"Auto\" wählt automatisch zwischen den beiden basierend auf der Bildauflösung.",
@@ -215,12 +226,15 @@
"com_endpoint_openai_max_tokens": "Optionales 'max_tokens'-Feld, das die maximale Anzahl von Token darstellt, die in der Chat-Vervollständigung generiert werden können. Die Gesamtlänge der Eingabe-Token und der generierten Token ist durch die Kontextlänge des Modells begrenzt. Du kannst Fehler erleben, wenn diese Zahl die maximalen Kontext-Token überschreitet.",
"com_endpoint_openai_pres": "Zahl zwischen -2,0 und 2,0. Positive Werte bestrafen neue Token basierend darauf, ob sie im bisherigen Text vorkommen, wodurch die Wahrscheinlichkeit des Modells erhöht wird, über neue Themen zu sprechen.",
"com_endpoint_openai_prompt_prefix_placeholder": "Lege benutzerdefinierte Anweisungen fest, die in die Systemnachricht an die KI aufgenommen werden sollen. Standard: keine",
"com_endpoint_openai_reasoning_effort": "Nur für o1-Modelle: Begrenzt den Aufwand des Nachdenkens bei Schlussfolgerungsmodellen. Die Reduzierung des Nachdenkeaufwands kann zu schnelleren Antworten und weniger Token führen, die für das Überlegen vor einer Antwort verwendet werden.",
"com_endpoint_openai_reasoning_effort": "Nur o1- und o3-Modelle: Beschränkt den Schlussfolgerungsaufwand für Schlussfolgerungsmodelle. Ein reduzierter Schlussfolgerungsaufwand kann zu schnelleren Antworten und weniger Tokens führen, die für die Schlussfolgerung in einer Antwort verwendet werden.",
"com_endpoint_openai_reasoning_summary": "Nur für Responses-API: Eine Zusammenfassung des Denkprozesses des Modells. Dies kann nützlich sein, um den Denkprozess zu debuggen und zu verstehen. Wähle zwischen \"keine\", \"automatisch\", \"kurz\" oder \"detailliert\".",
"com_endpoint_openai_resend": "Alle im Chat zuvor angehängten Bilder mit jeder neuen Nachricht erneut senden. Hinweis: Dies kann die Kosten der Anfrage aufgrund höherer Token-Anzahl erheblich erhöhen und du kannst bei vielen Bildanhängen Fehler erleben.",
"com_endpoint_openai_resend_files": "Alle im Chat zuvor angehängten Dateien mit jeder neuen Nachricht erneut senden. Hinweis: Dies wird die Kosten der Anfrage aufgrund höherer Token-Anzahl erheblich erhöhen und du kannst bei vielen Anhängen Fehler erleben.",
"com_endpoint_openai_stop": "Bis zu 4 Sequenzen, bei denen die API keine weiteren Token generiert.",
"com_endpoint_openai_temp": "Entspricht der Kreativität der KI. Höhere Werte = zufälliger und kreativer, während niedrigere Werte = unkreativer und deterministischer. Wir empfehlen, entweder dies oder Top P zu ändern, aber nicht beides. Temperaturen über 1 sind nicht empfehlenswert.",
"com_endpoint_openai_topp": "Eine Alternative zum Sampling mit Temperatur, genannt Nucleus-Sampling, bei dem das Modell die Ergebnisse der Token mit Top-p-Wahrscheinlichkeitsmasse berücksichtigt. So bedeutet 0,1, dass nur die Token betrachtet werden, die die Top 10% der Wahrscheinlichkeitsmasse ausmachen. Wir empfehlen, entweder dies oder die Temperatur zu ändern, aber nicht beides.",
"com_endpoint_openai_use_responses_api": "Nutze die Responses-API anstelle von Chat Completions. Diese API enthält erweiterte Funktionen von OpenAI. Erforderlich für o1-pro, o3-pro und um Zusammenfassungen des Denkprozesses zu aktivieren.",
"com_endpoint_openai_use_web_search": "Aktiviere die Websuche über die integrierten Suchfunktionen von OpenAI. Dies ermöglicht es dem Modell, das Web nach aktuellen Informationen zu durchsuchen und präzisere, aktuellere Antworten zu geben.",
"com_endpoint_output": "Antwort",
"com_endpoint_plug_image_detail": "Bild-Detail",
"com_endpoint_plug_resend_files": "Anhänge erneut senden",
@@ -251,6 +265,7 @@
"com_endpoint_prompt_prefix_assistants_placeholder": "Lege zusätzliche Anweisungen oder Kontext zusätzlich zu den Hauptanweisungen des Assistenten fest. Wird ignoriert, wenn leer.",
"com_endpoint_prompt_prefix_placeholder": "Lege benutzerdefinierte Anweisungen oder Kontext fest. Wird ignoriert, wenn leer.",
"com_endpoint_reasoning_effort": "Denkaufwand",
"com_endpoint_reasoning_summary": "Zusammenfassung des Denkprozesses",
"com_endpoint_save_as_preset": "Voreinstellung speichern",
"com_endpoint_search": "Endpunkt nach Namen suchen",
"com_endpoint_search_endpoint_models": "{{0}} KI-Modelle durchsuchen...",
@@ -266,6 +281,8 @@
"com_endpoint_top_k": "Top K",
"com_endpoint_top_p": "Top P",
"com_endpoint_use_active_assistant": "Aktiven Assistenten verwenden",
"com_endpoint_use_responses_api": "Responses-API nutzen",
"com_endpoint_use_search_grounding": "Fundierung mit Google-Websuche",
"com_error_expired_user_key": "Der angegebene API-Key für {{0}} ist am {{1}} abgelaufen. Bitte gebe einen neuen API-Key ein und versuche es erneut.",
"com_error_files_dupe": "Doppelte Datei erkannt.",
"com_error_files_empty": "Leere Dateien sind nicht zulässig",
@@ -274,7 +291,9 @@
"com_error_files_upload": "Beim Hochladen der Datei ist ein Fehler aufgetreten",
"com_error_files_upload_canceled": "Die Datei-Upload-Anfrage wurde abgebrochen. Hinweis: Der Upload-Vorgang könnte noch im Hintergrund laufen und die Datei muss möglicherweise manuell gelöscht werden.",
"com_error_files_validation": "Bei der Validierung der Datei ist ein Fehler aufgetreten.",
"com_error_input_length": "Die Token-Anzahl der letzten Nachricht ist zu hoch und überschreitet das Token-Limit ({{0}}). Bitte kürze deine Nachricht, passe die maximale Kontextgröße in den Gesprächsparametern an oder erstelle eine Abzweigung des Gesprächs, um fortzufahren.",
"com_error_google_tool_conflict": "Die integrierten Google-Tools können nicht zusammen mit externen Tools verwendet werden. Bitte deaktiviere entweder die integrierten oder die externen Tools.",
"com_error_heic_conversion": "Das HEIC-Bild konnte nicht in JPEG konvertiert werden. Bitte versuchen Sie, das Bild manuell zu konvertieren oder verwenden Sie ein anderes Format.",
"com_error_input_length": "Die Anzahl der Tokens der letzten Nachricht ist zu lang und überschreitet das Token-Limit. Oder Ihre Token-Limit-Parameter sind falsch konfiguriert, was sich negativ auf das Kontextfenster auswirkt. Weitere Informationen: {{0}}. Bitte kürzen Sie Ihre Nachricht, passen Sie die maximale Kontextgröße in den Konversationsparametern an oder teilen Sie die Konversation auf, um fortzufahren.",
"com_error_invalid_agent_provider": "Der Anbieter \"{{0}}\" steht für die Verwendung mit Agents nicht zur Verfügung. Bitte gehe zu den Einstellungen deines Agents und wähle einen aktuell verfügbaren Anbieter aus.",
"com_error_invalid_user_key": "Ungültiger API-Key angegeben. Bitte gebe einen gültigen API-Key ein und versuche es erneut.",
"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.",
@@ -285,6 +304,7 @@
"com_files_number_selected": "{{0}} von {{1}} Datei(en) ausgewählt",
"com_generated_files": "Generierte Dateien:",
"com_hide_examples": "Beispiele ausblenden",
"com_info_heic_converting": "HEIC-Bild wird in JPEG konventiert...",
"com_nav_2fa": "Zwei-Faktor-Authentifizierung (2FA)",
"com_nav_account_settings": "Kontoeinstellungen",
"com_nav_always_make_prod": "Neue Versionen direkt produktiv nehmen",
@@ -302,6 +322,27 @@
"com_nav_auto_transcribe_audio": "Audio automatisch transkribieren",
"com_nav_automatic_playback": "Automatische Wiedergabe der neuesten Nachricht",
"com_nav_balance": "Guthaben",
"com_nav_balance_auto_refill_disabled": "Automatisches Auffüllen ist deaktiviert",
"com_nav_balance_auto_refill_error": "Die Einstellungen ",
"com_nav_balance_auto_refill_settings": "Einstellungen zum automatischen Auffüllen",
"com_nav_balance_day": "Tag",
"com_nav_balance_days": "Tage",
"com_nav_balance_every": "Jeden",
"com_nav_balance_hour": "Stunde",
"com_nav_balance_hours": "Stunden",
"com_nav_balance_interval": "Intervall",
"com_nav_balance_last_refill": "Letztes Auffüllen:",
"com_nav_balance_minute": "Minute",
"com_nav_balance_minutes": "Minuten",
"com_nav_balance_month": "Monat",
"com_nav_balance_months": "Monate",
"com_nav_balance_next_refill": "Nächstes Auffüllen:",
"com_nav_balance_next_refill_info": "Die nächste Aufladung erfolgt nur dann automatisch, wenn beide Bedingungen erfüllt sind: Das angegebene Zeitintervall ist seit der letzten Aufladung verstrichen, und das Senden einer Aufforderung würde dazu führen, dass das Guthaben unter Null sinkt.",
"com_nav_balance_refill_amount": "Nachfüllmenge:",
"com_nav_balance_second": "Sekunde",
"com_nav_balance_seconds": "Sekunden",
"com_nav_balance_week": "Woche",
"com_nav_balance_weeks": "Wochen",
"com_nav_browser": "Browser",
"com_nav_center_chat_input": "Chat-Eingabe im Willkommensbildschirm zentrieren",
"com_nav_change_picture": "Bild ändern",
@@ -325,7 +366,6 @@
"com_nav_delete_cache_storage": "TTS-Cache-Speicher löschen",
"com_nav_delete_data_info": "Alle deine Daten werden gelöscht.",
"com_nav_delete_warning": "WARNUNG: Dies wird dein Konto dauerhaft löschen.",
"com_nav_edit_chat_badges": "Chat Badges editieren",
"com_nav_enable_cache_tts": "TTS-Caching aktivieren",
"com_nav_enable_cloud_browser_voice": "Cloud-basierte Stimmen verwenden",
"com_nav_enabled": "Aktiviert",
@@ -349,6 +389,7 @@
"com_nav_font_size_xs": "Sehr klein",
"com_nav_help_faq": "Hilfe & FAQ",
"com_nav_hide_panel": "Rechte Seitenleiste verstecken",
"com_nav_info_balance": "Der Saldo zeigt an, wie viele Token-Credits Sie noch verwenden können. Token-Credits werden in Geldwert umgerechnet (z. B. 1000 Credits = 0,001 USD).",
"com_nav_info_code_artifacts": "Aktiviert die Anzeige experimenteller Code-Artefakte neben dem Chat",
"com_nav_info_code_artifacts_agent": "Aktiviert die Verwendung von Code-Artefakten für diesen Agenten. Standardmäßig werden zusätzliche, spezielle Anweisungen für die Nutzung von Artefakten hinzugefügt, es sei denn, der \"Benutzerdefinierte Prompt-Modus\" ist aktiviert.",
"com_nav_info_custom_prompt_mode": "Wenn aktiviert, wird die Standard-Systemaufforderung für Artefakte nicht eingeschlossen. Alle Anweisungen zur Erzeugung von Artefakten müssen in diesem Modus manuell bereitgestellt werden.",
@@ -396,6 +437,8 @@
"com_nav_log_out": "Abmelden",
"com_nav_long_audio_warning": "Längere Texte benötigen mehr Zeit zur Verarbeitung.",
"com_nav_maximize_chat_space": "Chat-Bereich maximieren",
"com_nav_mcp_vars_update_error": "Fehler beim Aktualisieren der benutzerdefinierten MCP-Benutzervariablen: {{0}}",
"com_nav_mcp_vars_updated": "Die MCP-Benutzervariablen wurden erfolgreich aktualisiert.",
"com_nav_modular_chat": "Ermöglicht das Wechseln der Endpunkte mitten im Gespräch",
"com_nav_my_files": "Meine Dateien",
"com_nav_not_supported": "Nicht unterstützt",
@@ -415,10 +458,13 @@
"com_nav_search_placeholder": "Nachrichten durchsuchen",
"com_nav_send_message": "Nachricht senden",
"com_nav_setting_account": "Konto",
"com_nav_setting_balance": "Saldo",
"com_nav_setting_beta": "Beta-Funktionen",
"com_nav_setting_chat": "Chat",
"com_nav_setting_data": "Datensteuerung",
"com_nav_setting_general": "Allgemein",
"com_nav_setting_mcp": "MCP Einstellungen",
"com_nav_setting_personalization": "Personalisierung",
"com_nav_setting_speech": "Sprache",
"com_nav_settings": "Einstellungen",
"com_nav_shared_links": "Geteilte Links",
@@ -445,19 +491,22 @@
"com_show_agent_settings": "Agenteneinstellungen anzeigen",
"com_show_completion_settings": "Vervollständigungseinstellungen anzeigen",
"com_show_examples": "Beispiele anzeigen",
"com_sidepanel_agent_builder": "Agenten Ersteller",
"com_sidepanel_agent_builder": "Agenten erstellen",
"com_sidepanel_assistant_builder": "Assistenten-Ersteller",
"com_sidepanel_attach_files": "Dateien anhängen",
"com_sidepanel_conversation_tags": "Lesezeichen",
"com_sidepanel_hide_panel": "Seitenleiste ausblenden",
"com_sidepanel_manage_files": "Dateien verwalten",
"com_sidepanel_mcp_enter_value": "Gib den Wert für {{0}} ein",
"com_sidepanel_mcp_no_servers_with_vars": "Keine MCP-Server mit konfigurierbaren Variablen.",
"com_sidepanel_mcp_variables_for": "MCP Variablen für {{0}}",
"com_sidepanel_parameters": "KI-Einstellungen",
"com_sources_image_alt": "Suchergebnis Bild",
"com_sources_more_sources": "+{{count}} Quellen",
"com_sources_image_alt": "Suchergebnis Bild\n",
"com_sources_more_sources": "+{{count}} Quellen\n",
"com_sources_tab_all": "Alles",
"com_sources_tab_images": "Bilder",
"com_sources_tab_news": "Nachrichten",
"com_sources_title": "Quellen",
"com_sources_title": "Quellen\n",
"com_ui_2fa_account_security": "Die Zwei-Faktor-Authentifizierung bietet Ihrem Konto eine zusätzliche Sicherheitsebene.",
"com_ui_2fa_disable": "2FA deaktivieren",
"com_ui_2fa_disable_error": "Beim Deaktivieren der Zwei-Faktor-Authentifizierung ist ein Fehler aufgetreten.",
@@ -471,9 +520,11 @@
"com_ui_accept": "Ich akzeptiere",
"com_ui_action_button": "Aktionstaste",
"com_ui_add": "Hinzufügen",
"com_ui_add_mcp": "MCP hinzufügen",
"com_ui_add_mcp_server": "MCP Server hinzufügen",
"com_ui_add_model_preset": "Ein KI-Modell oder eine Voreinstellung für eine zusätzliche Antwort hinzufügen",
"com_ui_add_multi_conversation": "Mehrere Chats hinzufügen",
"com_ui_adding_details": "Hinzufügen von Details",
"com_ui_adding_details": "Hinzufügen von Details\n",
"com_ui_admin": "Admin",
"com_ui_admin_access_warning": "Das Deaktivieren des Admin-Zugriffs auf diese Funktion kann zu unerwarteten Problemen in der Benutzeroberfläche führen, die ein Neuladen erfordern. Nach dem Speichern kann dies nur über die Schnittstelleneinstellung in der librechat.yaml-Konfiguration rückgängig gemacht werden, was sich auf alle Rollen auswirkt.",
"com_ui_admin_settings": "Admin-Einstellungen",
@@ -484,7 +535,7 @@
"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.",
"com_ui_agent_delete_error": "Beim Löschen des Assistenten ist ein Fehler aufgetreten",
"com_ui_agent_deleted": "Assistent erfolgreich gelöscht",
"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_editing_allowed": "Andere Nutzende können diesen Agenten bereits bearbeiten",
@@ -493,18 +544,21 @@
"com_ui_agent_shared_to_all": "Hier muss etwas eingegeben werden. War leer.",
"com_ui_agent_var": "{{0}} Agent",
"com_ui_agent_version": "Version",
"com_ui_agent_version_active": "Aktive Version",
"com_ui_agent_version_active": "Aktive Version\n",
"com_ui_agent_version_duplicate": "Doppelte Version entdeckt. Dies würde eine Version erzeugen, die identisch mit der Version {{versionIndex}} ist.",
"com_ui_agent_version_empty": "Keine Versionen verfügbar",
"com_ui_agent_version_empty": "Keine Versionen verfügbar\n",
"com_ui_agent_version_error": "Fehler beim Abrufen der Versionen",
"com_ui_agent_version_history": "Versionsgeschichte",
"com_ui_agent_version_history": "Versionsgeschichte\n",
"com_ui_agent_version_no_agent": "Kein Agent ausgewählt. Bitte wähle einen Agenten aus, um den Versionsverlauf anzuzeigen.",
"com_ui_agent_version_no_date": "Datum nicht verfügbar",
"com_ui_agent_version_restore": "Wiederherstellen",
"com_ui_agent_version_no_date": "Datum nicht verfügbar\n",
"com_ui_agent_version_restore": "Wiederherstellen\n",
"com_ui_agent_version_restore_confirm": "Bist du sicher, dass du diese Version wiederherstellen willst?",
"com_ui_agent_version_unknown_date": "Unbekanntes Datum",
"com_ui_agent_version_restore_error": "Die Version konnte nicht wiederhergestellt werden",
"com_ui_agent_version_restore_success": "Die Version wurde erfolgreich hergestellt",
"com_ui_agent_version_title": "Version {{versionNumber}}",
"com_ui_agent_version_unknown_date": "Unbekanntes Datum\n",
"com_ui_agents": "Agenten",
"com_ui_agents_allow_create": "Erlaube Agents zu erstellen",
"com_ui_agents_allow_create": "Erlaube Agenten zu erstellen",
"com_ui_agents_allow_share_global": "Erlaube das Teilen von Agenten mit allen Nutzern",
"com_ui_agents_allow_use": "Verwendung von Agenten erlauben",
"com_ui_all": "alle",
@@ -536,8 +590,11 @@
"com_ui_auth_url": "Autorisierungs-URL",
"com_ui_authentication": "Authentifizierung",
"com_ui_authentication_type": "Authentifizierungstyp",
"com_ui_auto": "Auto",
"com_ui_available_tools": "Verfügbare Tools",
"com_ui_avatar": "Avatar",
"com_ui_azure": "Azure",
"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_codes": "Backup-Codes",
@@ -567,7 +624,8 @@
"com_ui_bulk_delete_error": "Geteilte Links konnten nicht gelöscht werden.",
"com_ui_callback_url": "Callback-URL",
"com_ui_cancel": "Abbrechen",
"com_ui_category": "Kategorie",
"com_ui_cancelled": "Abgebrochen",
"com_ui_category": "Kategorie\n",
"com_ui_chat": "Chat",
"com_ui_chat_history": "Chatverlauf",
"com_ui_clear": "Löschen",
@@ -576,11 +634,13 @@
"com_ui_client_secret": "Client Secret",
"com_ui_close": "Schließen",
"com_ui_close_menu": "Menü schließen",
"com_ui_close_window": "Fenster schliessen",
"com_ui_code": "Code",
"com_ui_collapse_chat": "Chat einklappen",
"com_ui_command_placeholder": "Optional: Gib einen Promptbefehl ein oder den Namen.",
"com_ui_command_usage_placeholder": "Wähle einen Prompt nach Befehl oder Name aus",
"com_ui_complete_setup": "Einrichtung abschließen",
"com_ui_configure_mcp_variables_for": "Konfiguriere Variablen für {{0}}",
"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",
@@ -595,8 +655,10 @@
"com_ui_copy_to_clipboard": "In die Zwischenablage kopieren",
"com_ui_create": "Erstellen",
"com_ui_create_link": "Link erstellen",
"com_ui_create_memory": "Gedächtnis erstellen",
"com_ui_create_prompt": "Prompt erstellen",
"com_ui_creating_image": "Bild wird erstellt. Kann einen Moment dauern",
"com_ui_current": "Aktuell",
"com_ui_currently_production": "Aktuell im Produktivbetrieb",
"com_ui_custom": "Benutzerdefiniert",
"com_ui_custom_header_name": "Benutzerdefinierter Headername",
@@ -629,13 +691,20 @@
"com_ui_delete_confirm": "Dies wird löschen:",
"com_ui_delete_confirm_prompt_version_var": "Dies wird die ausgewählte Version für \"{{0}}\" löschen. Wenn keine anderen Versionen existieren, wird der Prompt gelöscht.",
"com_ui_delete_conversation": "Chat löschen?",
"com_ui_delete_mcp": "MCP löschen",
"com_ui_delete_mcp_confirm": "Bist du sicher, dass du diesen MCP-Server löschen möchtest?",
"com_ui_delete_mcp_error": "Fehler beim Löschen des MCP-Servers",
"com_ui_delete_mcp_success": "MCP-Server erfolgreich gelöscht",
"com_ui_delete_memory": "Gedächtnis löschen",
"com_ui_delete_prompt": "Prompt löschen?",
"com_ui_delete_shared_link": "Geteilten Link löschen?",
"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_deleted": "Gelöscht",
"com_ui_descending": "Absteigend",
"com_ui_description": "Beschreibung",
"com_ui_description_placeholder": "Optional: Gib eine Beschreibung für den Prompt ein",
"com_ui_deselect_all": "Alle abwählen",
"com_ui_disabling": "Deaktiviere …",
"com_ui_download": "Herunterladen",
"com_ui_download_artifact": "Artefakt herunterladen",
@@ -650,24 +719,45 @@
"com_ui_duplication_processing": "Konversation wird dupliziert...",
"com_ui_duplication_success": "Unterhaltung erfolgreich dupliziert",
"com_ui_edit": "Bearbeiten",
"com_ui_edit_editing_image": "Bild bearbeiten",
"com_ui_edit_editing_image": "Bild bearbeiten\n",
"com_ui_edit_mcp_server": "MCP-Server bearbeiten",
"com_ui_edit_memory": "Gedächtnis bearbeiten",
"com_ui_empty_category": "-",
"com_ui_endpoint": "Endpunkt",
"com_ui_endpoint_menu": "LLM-Endpunkt-Menü",
"com_ui_enter": "Eingabe",
"com_ui_enter_api_key": "API-Schlüssel eingeben",
"com_ui_enter_key": "Schlüssel eingeben",
"com_ui_enter_openapi_schema": "Gib hier dein OpenAPI-Schema ein",
"com_ui_enter_value": "Wert eingeben",
"com_ui_error": "Fehler",
"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_examples": "Beispiele",
"com_ui_expand_chat": "Chat erweitern",
"com_ui_export_convo_modal": "Konversation exportieren",
"com_ui_feedback_more": "Mehr ...",
"com_ui_feedback_more_information": "Zusätzliches Feedback",
"com_ui_feedback_negative": "Muss verbessert werden",
"com_ui_feedback_placeholder": "Geben Sie hier bitte weiteres Feedback an",
"com_ui_feedback_positive": "Prima, sehr gut",
"com_ui_feedback_tag_attention_to_detail": "Liebe zum Detail",
"com_ui_feedback_tag_bad_style": "Clear and Well-Written",
"com_ui_feedback_tag_clear_well_written": "Klar und gut geschrieben",
"com_ui_feedback_tag_creative_solution": "Kreative Lösung",
"com_ui_feedback_tag_inaccurate": "Ungenaue oder falsche Antwort",
"com_ui_feedback_tag_missing_image": "Bild erwartet",
"com_ui_feedback_tag_not_helpful": "Es fehlten nützliche Informationen",
"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_required": "Dieses Feld ist erforderlich",
"com_ui_files": "Dateien",
"com_ui_file_size": "Dateigröße",
"com_ui_files": "Dateien\n",
"com_ui_filter_prompts": "Prompts filtern",
"com_ui_filter_prompts_name": "Prompts nach Namen filtern",
"com_ui_final_touch": "Letzter Schliff",
"com_ui_final_touch": "Letzter Schliff\n",
"com_ui_finance": "Finanzen",
"com_ui_fork": "Abzweigen",
"com_ui_fork_all_target": "Alle bis/von hier einbeziehen",
@@ -697,7 +787,8 @@
"com_ui_generate_backup": "Backup-Codes generieren",
"com_ui_generate_qrcode": "QR-Code generieren",
"com_ui_generating": "Generiere …",
"com_ui_getting_started": "Erste Schritte",
"com_ui_generation_settings": "Einstellungen für die Generierung",
"com_ui_getting_started": "Erste Schritte\n",
"com_ui_global_group": "Leer etwas fehlt noch",
"com_ui_go_back": "Zurück",
"com_ui_go_to_conversation": "Zur Konversation gehen",
@@ -705,11 +796,14 @@
"com_ui_good_evening": "Guten Abend",
"com_ui_good_morning": "Guten Morgen",
"com_ui_happy_birthday": "Es ist mein 1. Geburtstag!",
"com_ui_hide_image_details": "Details zum Bild ausblenden",
"com_ui_hide_qr": "QR-Code ausblenden",
"com_ui_host": "Host",
"com_ui_icon": "Icon",
"com_ui_idea": "Ideen",
"com_ui_image_created": "Bild erstellt",
"com_ui_image_edited": "Bild bearbeitet",
"com_ui_image_created": "Bild erstellt\n",
"com_ui_image_details": "Details zum Bild",
"com_ui_image_edited": "Bild bearbeitet\n",
"com_ui_image_gen": "Bildgenerierung",
"com_ui_import": "Importieren",
"com_ui_import_conversation_error": "Beim Importieren Ihrer Konversationen ist ein Fehler aufgetreten",
@@ -717,9 +811,9 @@
"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_include_shadcnui_agent": "shadcn/ui-Anweisungen einfügen",
"com_ui_input": "Eingabe",
"com_ui_instructions": "Anweisungen",
"com_ui_key": "Schlüssel",
"com_ui_late_night": "Schöne späte Nacht",
"com_ui_latest_footer": "Alle KIs für alle.",
"com_ui_latest_production_version": "Neueste Produktiv-Version",
@@ -732,7 +826,25 @@
"com_ui_logo": "{{0}} Logo",
"com_ui_manage": "Verwalten",
"com_ui_max_tags": "Die maximale Anzahl ist {{0}}, es werden die neuesten Werte verwendet.",
"com_ui_mcp_dialog_desc": "Bitte geben Sie unten die erforderlichen Informationen ein.",
"com_ui_mcp_enter_var": "Geben Sie einen Wert für {{0}} ein",
"com_ui_mcp_server_not_found": "Server nicht gefunden",
"com_ui_mcp_servers": "MCP Server",
"com_ui_mcp_url": "MCP-Server-URL",
"com_ui_memories": "Gedächtnisse",
"com_ui_memories_allow_create": "Erinnerungen erstellen erlauben",
"com_ui_memories_allow_opt_out": "Benutzern erlauben, die Erinnerungsnutzung zu deaktivieren",
"com_ui_memories_allow_read": "Erinnerungen lesen erlauben",
"com_ui_memories_allow_update": "Erinnerungen aktualisieren erlauben",
"com_ui_memories_allow_use": "Erinnerungen nutzen erlauben",
"com_ui_memories_filter": "Erinnerungen filtern...",
"com_ui_memory": "Erinnerung",
"com_ui_memory_created": "Erinnerung erfolgreich erstellt",
"com_ui_memory_deleted": "Erinnerung gelöscht",
"com_ui_memory_deleted_items": "Gelöschte Erinnerungen",
"com_ui_memory_key_exists": "Eine Erinnerung mit diesem Schlüssel existiert bereits. Bitte verwende einen anderen Schlüssel.",
"com_ui_memory_updated": "Erinnerung aktualisiert",
"com_ui_memory_updated_items": "Aktualisierte Erinnerungen",
"com_ui_mention": "Erwähne einen Endpunkt, Assistenten oder eine Voreinstellung, um schnell dorthin zu wechseln",
"com_ui_min_tags": "Es können nicht mehr Werte entfernt werden, mindestens {{0}} sind erforderlich.",
"com_ui_misc": "Verschiedenes",
@@ -751,17 +863,30 @@
"com_ui_no_category": "Keine Kategorie",
"com_ui_no_changes": "Keine Änderungen zum Aktualisieren",
"com_ui_no_data": "Leer etwas fehlt noch",
"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_terms_content": "Keine Inhalte der Allgemeinen Geschäftsbedingungen zum Anzeigen",
"com_ui_no_valid_items": "Leer - Text fehlt noch",
"com_ui_none": "Keine",
"com_ui_not_used": "Nicht verwendet",
"com_ui_nothing_found": "Nichts gefunden",
"com_ui_oauth": "OAuth",
"com_ui_oauth_connected_to": "Verbunden mit",
"com_ui_oauth_error_callback_failed": "Authentifizierungs Callback fehlgeschlagen. Bitte versuchen Sie es erneut.",
"com_ui_oauth_error_generic": "Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"com_ui_oauth_error_invalid_state": "Ungültiger Statusparameter. Bitte versuchen Sie es erneut.",
"com_ui_oauth_error_missing_code": "Autorisierungscode fehlt. Bitte versuchen Sie es erneut.",
"com_ui_oauth_error_missing_state": "Statusparameter fehlt. Bitte versuchen Sie es erneut.",
"com_ui_oauth_error_title": "Authentifizierung fehlgeschlagen",
"com_ui_oauth_success_description": "Ihre Authentifizierung war erfolgreich. Dieses Fenster schliesst sich in",
"com_ui_oauth_success_title": "Authentifizierung erfolgreich",
"com_ui_of": "von",
"com_ui_off": "Aus",
"com_ui_on": "An",
"com_ui_openai": "OpenAI",
"com_ui_optional": "(Optional)",
"com_ui_page": "Seite",
"com_ui_preferences_updated": "Einstellungen erfolgreich aktualisiert",
"com_ui_prev": "Zurück",
"com_ui_preview": "Vorschau",
"com_ui_privacy_policy": "Datenschutzerklärung",
@@ -779,8 +904,11 @@
"com_ui_prompts_allow_share_global": "Teilen von Prompts mit allen Benutzern erlauben",
"com_ui_prompts_allow_use": "Verwendung von Prompts erlauben",
"com_ui_provider": "Anbieter",
"com_ui_quality": "Qualität",
"com_ui_read_aloud": "Vorlesen",
"com_ui_redirecting_to_provider": "Weiterleitung zu {{0}}, einen Moment bitte...",
"com_ui_reference_saved_memories": "Gespeicherte Erinnerungen referenzieren",
"com_ui_reference_saved_memories_description": "Erlaube dem Assistenten, deine gespeicherten Erinnerungen beim Antworten zu referenzieren und zu nutzen.",
"com_ui_refresh_link": "Link aktualisieren",
"com_ui_regenerate": "Neu generieren",
"com_ui_regenerate_backup": "Backup-Codes neu generieren",
@@ -792,6 +920,7 @@
"com_ui_rename_prompt": "Prompt umbenennen",
"com_ui_requires_auth": "Authentifizierung erforderlich",
"com_ui_reset_var": "{{0}} zurücksetzen",
"com_ui_reset_zoom": "Zoom zurücksetzen",
"com_ui_result": "Ergebnis",
"com_ui_revoke": "Widerrufen",
"com_ui_revoke_info": "Benutzer-API-Keys widerrufen",
@@ -807,11 +936,14 @@
"com_ui_save_badge_changes": "Änderungen an Badges 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_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_provider": "Wähle einen Anbieter",
@@ -837,6 +969,7 @@
"com_ui_shop": "Einkaufen",
"com_ui_show": "Anzeigen",
"com_ui_show_all": "Alle anzeigen",
"com_ui_show_image_details": "Details zum Bild anzeigen",
"com_ui_show_qr": "QR-Code anzeigen",
"com_ui_sign_in_to_domain": "Anmelden bei {{0}}",
"com_ui_simple": "Einfach",
@@ -858,15 +991,23 @@
"com_ui_terms_of_service": "Nutzungsbedingungen",
"com_ui_thinking": "Nachdenken...",
"com_ui_thoughts": "Gedanken",
"com_ui_token": "Token",
"com_ui_token_exchange_method": "Token-Austauschmethode",
"com_ui_token_url": "Token-URL",
"com_ui_tokens": "Tokens",
"com_ui_tool_collection_prefix": "Eine Tools Sammlung von",
"com_ui_tool_info": "Tool Information",
"com_ui_tool_more_info": "Mehr Information über dieses Tool",
"com_ui_tools": "Werkzeuge",
"com_ui_travel": "Reisen",
"com_ui_trust_app": "Ich vertraue dieser Anwendung",
"com_ui_unarchive": "Aus Archiv holen",
"com_ui_unarchive_error": "Konversation konnte nicht aus dem Archiv geholt werden",
"com_ui_unknown": "Unbekannt",
"com_ui_untitled": "Unbenannt",
"com_ui_update": "Aktualisieren",
"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_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.",
@@ -881,34 +1022,37 @@
"com_ui_upload_ocr_text": "Hochladen als Text",
"com_ui_upload_success": "Datei erfolgreich hochgeladen",
"com_ui_upload_type": "Upload-Typ auswählen",
"com_ui_usage": "Nutzung",
"com_ui_use_2fa_code": "Stattdessen 2FA-Code verwenden",
"com_ui_use_backup_code": "Stattdessen Backup-Code verwenden",
"com_ui_use_memory": "Erinnerung nutzen",
"com_ui_use_micrphone": "Mikrofon verwenden",
"com_ui_use_prompt": "Prompt verwenden",
"com_ui_used": "Verwendet",
"com_ui_value": "Wert",
"com_ui_variables": "Variablen",
"com_ui_variables_info": "Verwende doppelte geschweifte Klammern in Ihrem Text, um Variablen zu erstellen, z.B. {{Beispielvariable}}, die du später beim Verwenden des Prompts ausfüllen kannst.",
"com_ui_verify": "Überprüfen",
"com_ui_version_var": "Version {{0}}",
"com_ui_versions": "Versionen",
"com_ui_view_memory": "Erinnerung anzeigen",
"com_ui_view_source": "Quell-Chat anzeigen",
"com_ui_web_search": "Web-Suche",
"com_ui_web_search_api_subtitle": "Suche im Internet nach aktuellen Informationen",
"com_ui_web_search_cohere_key": "Cohere API-Schlüssel eingeben",
"com_ui_web_search_firecrawl_url": "Firecrawl API URL (optional)",
"com_ui_web_search": "Web-Suche\n",
"com_ui_web_search_cohere_key": "Cohere API-Key eingeben",
"com_ui_web_search_firecrawl_url": "Firecrawl API URL (optional)\n",
"com_ui_web_search_jina_key": "Den Jina API Schlüssel eingeben",
"com_ui_web_search_processing": "Ergebnisse verarbeiten",
"com_ui_web_search_provider": "Anbieter der Suche",
"com_ui_web_search_provider_serper": "Serper API",
"com_ui_web_search_provider_serper": "Serper API\n",
"com_ui_web_search_provider_serper_key": "Einen Serper API Schlüssel holen",
"com_ui_web_search_reading": "Lesen der Suchergebnisse",
"com_ui_web_search_reranker": "Reranker",
"com_ui_web_search_reranker_cohere": "Cohere",
"com_ui_web_search_reranker_cohere_key": "Einen Cohere API-Schlüssel holen",
"com_ui_web_search_reranker_jina": "Jina AI",
"com_ui_web_search_reranker_jina": "Jina AI\n",
"com_ui_web_search_reranker_jina_key": "Einen Jina API Schlüssel holen",
"com_ui_web_search_scraper": "Scraper",
"com_ui_web_search_scraper_firecrawl": "Firecrawl API",
"com_ui_web_search_scraper_firecrawl": "Firecrawl API\n",
"com_ui_web_search_scraper_firecrawl_key": "Einen Firecrawl API Schlüssel holen",
"com_ui_web_searching": "Internetsuche läuft",
"com_ui_web_searching_again": "Sucht erneut im Internet",

View File

@@ -160,6 +160,7 @@
"com_endpoint_anthropic_thinking_budget": "Determines the max number of tokens Claude is allowed use for its internal reasoning process. Larger budgets can improve response quality by enabling more thorough analysis for complex problems, although Claude may not use the entire budget allocated, especially at ranges above 32K. This setting must be lower than \"Max Output Tokens.\"",
"com_endpoint_anthropic_topk": "Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).",
"com_endpoint_anthropic_topp": "Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.",
"com_endpoint_anthropic_use_web_search": "Enable web search functionality using Anthropic's built-in search capabilities. This allows the model to search the web for up-to-date information and provide more accurate, current responses.",
"com_endpoint_assistant": "Assistant",
"com_endpoint_assistant_model": "Assistant Model",
"com_endpoint_assistant_placeholder": "Please select an Assistant from the right-hand Side Panel",
@@ -367,7 +368,6 @@
"com_nav_delete_cache_storage": "Delete TTS cache storage",
"com_nav_delete_data_info": "All your data will be deleted.",
"com_nav_delete_warning": "WARNING: This will permanently delete your account.",
"com_nav_edit_chat_badges": "Edit Chat Badges",
"com_nav_enable_cache_tts": "Enable cache TTS",
"com_nav_enable_cloud_browser_voice": "Use cloud-based voices",
"com_nav_enabled": "Enabled",
@@ -405,6 +405,7 @@
"com_nav_info_show_thinking": "When enabled, the chat will display the thinking dropdowns open by default, allowing you to view the AI's reasoning in real-time. When disabled, the thinking dropdowns will remain closed by default for a cleaner and more streamlined interface",
"com_nav_info_user_name_display": "When enabled, the username of the sender will be shown above each message you send. When disabled, you will only see \"You\" above your messages.",
"com_nav_lang_arabic": "العربية",
"com_nav_lang_armenian": "Հայերեն",
"com_nav_lang_auto": "Auto detect",
"com_nav_lang_brazilian_portuguese": "Português Brasileiro",
"com_nav_lang_catalan": "Català",
@@ -424,6 +425,7 @@
"com_nav_lang_italian": "Italiano",
"com_nav_lang_japanese": "日本語",
"com_nav_lang_korean": "한국어",
"com_nav_lang_latvian": "Latviski",
"com_nav_lang_persian": "فارسی",
"com_nav_lang_polish": "Polski",
"com_nav_lang_portuguese": "Português",
@@ -433,6 +435,7 @@
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_uyghur": "Uyƣur tili",
"com_nav_lang_vietnamese": "Tiếng Việt",
"com_nav_language": "Language",
"com_nav_latex_parsing": "Parsing LaTeX in messages (may affect performance)",
@@ -573,6 +576,7 @@
"com_ui_archive_error": "Failed to archive conversation",
"com_ui_artifact_click": "Click to open",
"com_ui_artifacts": "Artifacts",
"com_ui_artifacts_options": "Artifacts Options",
"com_ui_artifacts_toggle": "Toggle Artifacts UI",
"com_ui_artifacts_toggle_agent": "Enable Artifacts",
"com_ui_ascending": "Asc",
@@ -770,6 +774,7 @@
"com_ui_fork_change_default": "Default fork option",
"com_ui_fork_default": "Use default fork option",
"com_ui_fork_error": "There was an error forking the conversation",
"com_ui_fork_error_rate_limit": "Too many fork requests. Please try again later",
"com_ui_fork_from_message": "Select a fork option",
"com_ui_fork_info_1": "Use this setting to fork messages with the desired behavior.",
"com_ui_fork_info_2": "\"Forking\" refers to creating a new conversation that start/end from specific messages in the current conversation, creating a copy according to the options selected.",
@@ -802,6 +807,7 @@
"com_ui_good_morning": "Good morning",
"com_ui_happy_birthday": "It's my 1st birthday!",
"com_ui_hide_image_details": "Hide Image Details",
"com_ui_hide_password": "Hide password",
"com_ui_hide_qr": "Hide QR Code",
"com_ui_high": "High",
"com_ui_host": "Host",
@@ -817,7 +823,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_include_shadcnui_agent": "Include shadcn/ui instructions",
"com_ui_input": "Input",
"com_ui_instructions": "Instructions",
"com_ui_key": "Key",
@@ -852,6 +857,7 @@
"com_ui_memory_deleted": "Memory deleted",
"com_ui_memory_deleted_items": "Deleted Memories",
"com_ui_memory_key_exists": "A memory with this key already exists. Please use a different key.",
"com_ui_memory_key_validation": "Memory key must only contain lowercase letters and underscores.",
"com_ui_memory_updated": "Updated saved memory",
"com_ui_memory_updated_items": "Updated Memories",
"com_ui_mention": "Mention an endpoint, assistant, or preset to quickly switch to it",
@@ -912,6 +918,7 @@
"com_ui_prompts_allow_create": "Allow creating Prompts",
"com_ui_prompts_allow_share_global": "Allow sharing Prompts to all users",
"com_ui_prompts_allow_use": "Allow using Prompts",
"com_ui_prompts_settings": "Prompt Settings",
"com_ui_provider": "Provider",
"com_ui_quality": "Quality",
"com_ui_read_aloud": "Read aloud",
@@ -979,6 +986,7 @@
"com_ui_show": "Show",
"com_ui_show_all": "Show All",
"com_ui_show_image_details": "Show Image Details",
"com_ui_show_password": "Show password",
"com_ui_show_qr": "Show QR Code",
"com_ui_sign_in_to_domain": "Sign-in to {{0}}",
"com_ui_simple": "Simple",
@@ -1047,12 +1055,12 @@
"com_ui_view_memory": "View Memory",
"com_ui_view_source": "View source chat",
"com_ui_web_search": "Web Search",
"com_ui_web_search_api_subtitle": "Search the web for up-to-date information",
"com_ui_web_search_cohere_key": "Enter Cohere API Key",
"com_ui_web_search_firecrawl_url": "Firecrawl API URL (optional)",
"com_ui_web_search_jina_key": "Enter Jina API Key",
"com_ui_web_search_processing": "Processing results",
"com_ui_web_search_provider": "Search Provider",
"com_ui_web_search_provider_searxng": "SearXNG",
"com_ui_web_search_provider_serper": "Serper API",
"com_ui_web_search_provider_serper_key": "Get your Serper API key",
"com_ui_web_search_reading": "Reading results",
@@ -1064,6 +1072,8 @@
"com_ui_web_search_scraper": "Scraper",
"com_ui_web_search_scraper_firecrawl": "Firecrawl API",
"com_ui_web_search_scraper_firecrawl_key": "Get your Firecrawl API key",
"com_ui_web_search_searxng_api_key": "Enter SearXNG API Key (optional)",
"com_ui_web_search_searxng_instance_url": "SearXNG Instance URL",
"com_ui_web_searching": "Searching the web",
"com_ui_web_searching_again": "Searching the web again",
"com_ui_weekend_morning": "Happy weekend",
@@ -1071,5 +1081,9 @@
"com_ui_x_selected": "{{0}} selected",
"com_ui_yes": "Yes",
"com_ui_zoom": "Zoom",
"com_user_message": "You"
}
"com_user_message": "You",
"com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint.",
"com_ui_add_to_favorites": "Add to favorites",
"com_ui_remove_from_favorites": "Remove from favorites"
}

View File

@@ -310,7 +310,6 @@
"com_nav_delete_cache_storage": "Eliminar almacenamiento caché de TTS",
"com_nav_delete_data_info": "Se eliminarán todos sus datos",
"com_nav_delete_warning": "ADVERTENCIA: Esta acción eliminará su cuenta de forma permanente.",
"com_nav_edit_chat_badges": "Editar Insignias de Chat",
"com_nav_enable_cache_tts": "Habilitar caché de texto a voz",
"com_nav_enable_cloud_browser_voice": "Usar voces basadas en la nube",
"com_nav_enabled": "Habilitado",

View File

@@ -21,6 +21,7 @@
"com_agents_name_placeholder": "Valikuline: Agendi nimi",
"com_agents_no_access": "Sul pole õigust seda agenti muuta.",
"com_agents_not_available": "Agent pole saadaval",
"com_agents_search_info": "Kui see on lubatud, saab sinu agent otsida veebist ajakohast teavet. Vajalik on kehtiv API võti.",
"com_agents_search_name": "Otsi agente nime järgi",
"com_agents_update_error": "Agendi uuendamisel tekkis viga.",
"com_assistants_action_attempt": "Assistent soovib suhelda: {{0}}",
@@ -61,6 +62,7 @@
"com_assistants_non_retrieval_model": "Selles mudelis pole failiotsing lubatud. Valige mõni muu mudel.",
"com_assistants_retrieval": "Otsing",
"com_assistants_running_action": "Käivitatakse tegevust",
"com_assistants_running_var": "Töötab {{0}}",
"com_assistants_search_name": "Otsi assistente nime järgi",
"com_assistants_update_actions_error": "Tegevuse loomisel või uuendamisel tekkis viga.",
"com_assistants_update_actions_success": "Tegevuse loomine või uuendamine õnnestus",
@@ -122,6 +124,7 @@
"com_auth_reset_password_if_email_exists": "Kui selle e-postiga konto on olemas, on saadetud parooli lähtestamise juhistega e-kiri. Palun kontrolli oma rämpsposti kausta.",
"com_auth_reset_password_link_sent": "E-kiri saadetud",
"com_auth_reset_password_success": "Parooli lähtestamine õnnestus",
"com_auth_saml_login": "Jätka SAML-iga",
"com_auth_sign_in": "Logi sisse",
"com_auth_sign_up": "Registreeru",
"com_auth_submit_registration": "Saada registreerimine",
@@ -133,6 +136,8 @@
"com_auth_username_min_length": "Kasutajanimi peab olema vähemalt 2 tähemärki",
"com_auth_verify_your_identity": "Kontrolli",
"com_auth_welcome_back": "Teretulemast tagasi",
"com_citation_more_details": "Rohkem üksikasju {{label}} kohta",
"com_citation_source": "Allikas",
"com_click_to_download": "(vajuta siia, et alla laadida)",
"com_download_expired": "(allalaadimine aegunud)",
"com_download_expires": "(vajuta siia, et alla laadida - aegub {{0}})",
@@ -246,7 +251,7 @@
"com_endpoint_prompt_prefix_assistants": "Täiendavad juhised",
"com_endpoint_prompt_prefix_assistants_placeholder": "Määra täiendavad juhised või kontekst lisaks assistendi peamistele juhistele. Kui see on tühi, ignoreeritakse seda.",
"com_endpoint_prompt_prefix_placeholder": "Määra kohandatud juhised või kontekst. Kui see on tühi, ignoreeritakse seda.",
"com_endpoint_reasoning_effort": "Põhjendustegevus",
"com_endpoint_reasoning_effort": "Arutluse tase",
"com_endpoint_save_as_preset": "Salvesta eelseadistusena",
"com_endpoint_search": "Otsi otspunkti nime järgi",
"com_endpoint_search_endpoint_models": "Otsi {{0}} mudeleid...",
@@ -299,6 +304,27 @@
"com_nav_auto_transcribe_audio": "Transkribeeri heli automaatselt",
"com_nav_automatic_playback": "Esita viimane sõnum automaatselt",
"com_nav_balance": "Saldo",
"com_nav_balance_auto_refill_disabled": "Automaatne saldo täitmine on välja lülitatud.",
"com_nav_balance_auto_refill_error": "Viga automaatse saldo täitmise seadete laadimisel.",
"com_nav_balance_auto_refill_settings": "Automaatse saldo täitmise seaded",
"com_nav_balance_day": "päev",
"com_nav_balance_days": "päeva",
"com_nav_balance_every": "Iga",
"com_nav_balance_hour": "tund",
"com_nav_balance_hours": "tundi",
"com_nav_balance_interval": "Intervall:",
"com_nav_balance_last_refill": "Viimane täiendus:",
"com_nav_balance_minute": "minut",
"com_nav_balance_minutes": "minutit",
"com_nav_balance_month": "kuu",
"com_nav_balance_months": "kuud",
"com_nav_balance_next_refill": "Järgmine täiendus:",
"com_nav_balance_next_refill_info": "Järgmine täiendamine toimub automaatselt ainult siis, kui mõlemad tingimused on täidetud: määratud ajavahemik on möödunud viimasest täiendamisest ja päringu saatmine põhjustaks sinu saldo langemise alla nulli.",
"com_nav_balance_refill_amount": "Täitmise kogus:",
"com_nav_balance_second": "sekund",
"com_nav_balance_seconds": "sekundit",
"com_nav_balance_week": "nädal",
"com_nav_balance_weeks": "nädalat",
"com_nav_browser": "Brauser",
"com_nav_center_chat_input": "Kuva ",
"com_nav_change_picture": "Muuda pilti",
@@ -322,7 +348,6 @@
"com_nav_delete_cache_storage": "Kustuta TTS vahemälu",
"com_nav_delete_data_info": "Kõik sinu andmed kustutatakse.",
"com_nav_delete_warning": "HOIATUS: See kustutab sinu konto jäädavalt.",
"com_nav_edit_chat_badges": "Muuda vestluse märgiseid",
"com_nav_enable_cache_tts": "Luba TTS vahemälu",
"com_nav_enable_cloud_browser_voice": "Kasuta pilvepõhiseid hääli",
"com_nav_enabled": "Lubatud",
@@ -346,6 +371,7 @@
"com_nav_font_size_xs": "Eriti väike",
"com_nav_help_faq": "Abi ja KKK",
"com_nav_hide_panel": "Peida kõige parempoolsem külgpaneel",
"com_nav_info_balance": "Saldo näitab, kui palju märgikrediiti on sul kasutamiseks alles. Märgikrediidid tõlgitakse rahalisse väärtusesse (nt 1000 krediiti = 0,001 USD)",
"com_nav_info_code_artifacts": "Võimaldab katsetuslike koodiartefaktide kuvamist vestluse kõrval",
"com_nav_info_code_artifacts_agent": "Võimaldab selle agendi jaoks koodiartefaktide kasutamist. Vaikimisi lisatakse artefaktide kasutamisele spetsiifilised täiendavad juhised, välja arvatud juhul, kui on lubatud \"Kohandatud viibarežiim\".",
"com_nav_info_custom_prompt_mode": "Kui see on lubatud, siis vaikimisi artefaktide süsteemiviipa ei lisata. Kõik artefakte genereerivad juhised tuleb selles režiimis käsitsi esitada.",
@@ -361,7 +387,10 @@
"com_nav_lang_arabic": "العربية",
"com_nav_lang_auto": "Tuvasta automaatselt",
"com_nav_lang_brazilian_portuguese": "Português Brasileiro",
"com_nav_lang_catalan": "Català",
"com_nav_lang_chinese": "中文",
"com_nav_lang_czech": "Čeština",
"com_nav_lang_danish": "Taani",
"com_nav_lang_dutch": "Nederlands",
"com_nav_lang_english": "English",
"com_nav_lang_estonian": "Eesti keel",
@@ -409,6 +438,7 @@
"com_nav_search_placeholder": "Otsi sõnumeid",
"com_nav_send_message": "Saada sõnum",
"com_nav_setting_account": "Konto",
"com_nav_setting_balance": "Saldo",
"com_nav_setting_beta": "Beeta funktsioonid",
"com_nav_setting_chat": "Vestlus",
"com_nav_setting_data": "Andmekontroll",
@@ -446,6 +476,12 @@
"com_sidepanel_hide_panel": "Peida paneel",
"com_sidepanel_manage_files": "Halda faile",
"com_sidepanel_parameters": "Parameetrid",
"com_sources_image_alt": "Otsingutulemuse pilt",
"com_sources_more_sources": "+{{count}} allikat",
"com_sources_tab_all": "Kõik",
"com_sources_tab_images": "Pildid",
"com_sources_tab_news": "Uudised",
"com_sources_title": "Allikad",
"com_ui_2fa_account_security": "Kaheastmeline autentimine lisab teie kontole täiendava turvalisuse kihi",
"com_ui_2fa_disable": "Lülita 2FA välja",
"com_ui_2fa_disable_error": "Tekkis viga kaheastmelise autentimise väljalülitamisel",
@@ -457,9 +493,11 @@
"com_ui_2fa_setup": "Seadista 2FA",
"com_ui_2fa_verified": "Kaheastmeline autentimine õnnestus",
"com_ui_accept": "Nõustun",
"com_ui_action_button": "Tegevuse nupp",
"com_ui_add": "Lisa",
"com_ui_add_model_preset": "Lisa mudel või eelseadistus täiendava vastuse jaoks",
"com_ui_add_multi_conversation": "Lisa mitmevestlus",
"com_ui_adding_details": "Detailide lisamine",
"com_ui_admin": "Administraator",
"com_ui_admin_access_warning": "Administraatori juurdepääsu keelamine sellele funktsioonile võib põhjustada ootamatuid kasutajaliidese probleeme, mis nõuavad värskendamist. Kui see on salvestatud, on ainus viis taastada liideseseade kaudu librechat.yaml konfiguratsioonis, mis mõjutab kõiki rolle.",
"com_ui_admin_settings": "Administraatori seaded",
@@ -478,6 +516,20 @@
"com_ui_agent_recursion_limit_info": "Piirab, mitu sammu agent saab teha ühe käivituse jooksul enne lõpliku vastuse andmist. Vaikimisi lubatakse 25 sammu. Samm on kas AI API päring või tööriista kasutamise etapp. Näiteks, lihtne tööriista kasutamine võtab 3 sammu: esialgne päring, tööriista kasutamine ja järelpäring.",
"com_ui_agent_shared_to_all": "Seda agenti on jagatud kõigi kasutajatega",
"com_ui_agent_var": "{{0}} agent",
"com_ui_agent_version": "Versioon",
"com_ui_agent_version_active": "Aktiivne versioon",
"com_ui_agent_version_duplicate": "Tuvastati duplikaatversioon. See looks versiooni, mis on identne versiooniga {{versionIndex}}.",
"com_ui_agent_version_empty": "Versioone pole saadaval",
"com_ui_agent_version_error": "Viga versioonide laadimisel",
"com_ui_agent_version_history": "Versioonide ajalugu",
"com_ui_agent_version_no_agent": "Agenti pole valitud. Palun vali agent, et näha versiooni ajalugu.",
"com_ui_agent_version_no_date": "Kuupäev pole saadaval",
"com_ui_agent_version_restore": "Taasta",
"com_ui_agent_version_restore_confirm": "Oled sa kindel, et sa soovid selle versiooni taastada?",
"com_ui_agent_version_restore_error": "Versiooni taastamine ebaõnnestus",
"com_ui_agent_version_restore_success": "Versiooni taastamine õnnestus",
"com_ui_agent_version_title": "Versioon {{versionNumber}}",
"com_ui_agent_version_unknown_date": "Tundmatu kuupäev",
"com_ui_agents": "Agendid",
"com_ui_agents_allow_create": "Luba agentide loomine",
"com_ui_agents_allow_share_global": "Luba agentide jagamine kõigile kasutajatele",
@@ -542,6 +594,7 @@
"com_ui_bulk_delete_error": "Jagatud linkide kustutamine ebaõnnestus",
"com_ui_callback_url": "Tagasikutsumise URL",
"com_ui_cancel": "Tühista",
"com_ui_cancelled": "Tühistatud",
"com_ui_category": "Kategooria",
"com_ui_chat": "Vestlus",
"com_ui_chat_history": "Vestluse ajalugu",
@@ -571,6 +624,7 @@
"com_ui_create": "Loo",
"com_ui_create_link": "Loo link",
"com_ui_create_prompt": "Loo sisend",
"com_ui_creating_image": "Pildi loomine. Võib võtta hetke",
"com_ui_currently_production": "Praegu tootmises",
"com_ui_custom": "Kohandatud",
"com_ui_custom_header_name": "Kohandatud päise nimi",
@@ -624,6 +678,7 @@
"com_ui_duplication_processing": "Vestlust dubleeritakse...",
"com_ui_duplication_success": "Vestluse dubleerimine õnnestus",
"com_ui_edit": "Muuda",
"com_ui_edit_editing_image": "Pildi muutmine",
"com_ui_empty_category": "-",
"com_ui_endpoint": "Otspunkt",
"com_ui_endpoint_menu": "LLM otspunkti menüü",
@@ -636,9 +691,27 @@
"com_ui_examples": "Näited",
"com_ui_expand_chat": "Laiendage Vestlus",
"com_ui_export_convo_modal": "Ekspordi vestluse modaal",
"com_ui_feedback_more": "Rohkem...",
"com_ui_feedback_more_information": "Anna täiendavat tagasisidet",
"com_ui_feedback_negative": "Vajab täiustamist",
"com_ui_feedback_placeholder": "Palun esitage siia täiendav tagasiside",
"com_ui_feedback_positive": "Mulle meeldib see",
"com_ui_feedback_tag_accurate_reliable": "Täpne ja usaldusväärne",
"com_ui_feedback_tag_attention_to_detail": "Tähelepanu detailidele",
"com_ui_feedback_tag_bad_style": "Kehv stiil või toon",
"com_ui_feedback_tag_clear_well_written": "Selge ja hästi kirjutatud",
"com_ui_feedback_tag_creative_solution": "Loominguline lahendus",
"com_ui_feedback_tag_inaccurate": "Ebatäpne või vale vastus",
"com_ui_feedback_tag_missing_image": "Oodati pilti",
"com_ui_feedback_tag_not_helpful": "Puudus kasulik teave",
"com_ui_feedback_tag_not_matched": "Ei vastanud minu päringule",
"com_ui_feedback_tag_unjustified_refusal": "Keeldutud ilma põhjuseta",
"com_ui_field_required": "See väli on kohustuslik",
"com_ui_file_size": "Faili suurus",
"com_ui_files": "Failid",
"com_ui_filter_prompts": "Filtreeri sisendid",
"com_ui_filter_prompts_name": "Filtreeri sisendeid nime järgi",
"com_ui_final_touch": "Viimane puudutus",
"com_ui_finance": "Raha",
"com_ui_fork": "Hargne",
"com_ui_fork_all_target": "Kaasa kõik siia/siit",
@@ -668,6 +741,7 @@
"com_ui_generate_backup": "Loo varukoodid",
"com_ui_generate_qrcode": "Loo QR-kood",
"com_ui_generating": "Loomine...",
"com_ui_getting_started": "Genereerimise seaded",
"com_ui_global_group": "Ülene grupp",
"com_ui_go_back": "Mine tagasi",
"com_ui_go_to_conversation": "Mine vestlusesse",
@@ -675,9 +749,13 @@
"com_ui_good_evening": "Tere õhtust",
"com_ui_good_morning": "Tere hommikust",
"com_ui_happy_birthday": "Mul on 1. sünnipäev!",
"com_ui_hide_image_details": "Peida pildi detailid",
"com_ui_hide_qr": "Peida QR-kood",
"com_ui_host": "Host",
"com_ui_idea": "Ideed",
"com_ui_image_created": "Pilt loodud",
"com_ui_image_details": "Pildi üksikasjad",
"com_ui_image_edited": "Pilt muudetud",
"com_ui_image_gen": "Pildi genereerimine",
"com_ui_import": "Impordi",
"com_ui_import_conversation_error": "Vestluste importimisel tekkis viga",
@@ -685,7 +763,6 @@
"com_ui_import_conversation_info": "Impordi vestlused JSON-failist",
"com_ui_import_conversation_success": "Vestluste importimine õnnestus",
"com_ui_include_shadcnui": "Kaasa shadcn/ui komponentide juhised",
"com_ui_include_shadcnui_agent": "Kaasa shadcn/ui juhised",
"com_ui_input": "Sisend",
"com_ui_instructions": "Juhised",
"com_ui_late_night": "Head hilisõhtut",
@@ -747,6 +824,7 @@
"com_ui_prompts_allow_share_global": "Luba sisendite jagamine kõigile kasutajatele",
"com_ui_prompts_allow_use": "Luba sisendite kasutamine",
"com_ui_provider": "Teenusepakkuja",
"com_ui_quality": "Kvaliteet",
"com_ui_read_aloud": "Loe valjusti",
"com_ui_redirecting_to_provider": "Ümbersuunamine pakkujale {{0}}, palun oodake...",
"com_ui_refresh_link": "Värskenda linki",
@@ -805,6 +883,7 @@
"com_ui_shop": "Ostlemine",
"com_ui_show": "Kuva",
"com_ui_show_all": "Näita kõiki",
"com_ui_show_image_details": "Näita pildi üksikasju",
"com_ui_show_qr": "Näita QR-koodi",
"com_ui_sign_in_to_domain": "Logi sisse {{0}}",
"com_ui_simple": "Lihtne",
@@ -860,6 +939,25 @@
"com_ui_version_var": "Versioon {{0}}",
"com_ui_versions": "Versioonid",
"com_ui_view_source": "Vaata algset vestlust",
"com_ui_web_search": "Veebiotsing",
"com_ui_web_search_cohere_key": "Sisesta Cohere API võti",
"com_ui_web_search_firecrawl_url": "Firecrawl API URL (valikuline)",
"com_ui_web_search_jina_key": "Sisesta Jina API võti",
"com_ui_web_search_processing": "Tulemuste töötlemine",
"com_ui_web_search_provider": "Otsingupakkuja",
"com_ui_web_search_provider_serper": "Serper API",
"com_ui_web_search_provider_serper_key": "Hangi oma Serperi API võti",
"com_ui_web_search_reading": "Tulemuste lugemine",
"com_ui_web_search_reranker": "Järjestaja",
"com_ui_web_search_reranker_cohere": "Cohere",
"com_ui_web_search_reranker_cohere_key": "Hangi oma Cohere API võti",
"com_ui_web_search_reranker_jina": "Jina AI",
"com_ui_web_search_reranker_jina_key": "Hangi oma Jina API võti",
"com_ui_web_search_scraper": "Scraper",
"com_ui_web_search_scraper_firecrawl": "Firecrawl API",
"com_ui_web_search_scraper_firecrawl_key": "Hangi oma Firecrawli API võti",
"com_ui_web_searching": "Veebist otsimine",
"com_ui_web_searching_again": "Otsin uuesti veebist",
"com_ui_weekend_morning": "Head nädalavahetust",
"com_ui_write": "Kirjutamine",
"com_ui_x_selected": "{{0}} valitud",

View File

@@ -322,7 +322,6 @@
"com_nav_delete_cache_storage": "حافظه کش TTS را حذف کنید",
"com_nav_delete_data_info": "تمام اطلاعات شما حذف خواهد شد.",
"com_nav_delete_warning": "هشدار: با این کار حساب شما برای همیشه حذف می شود.",
"com_nav_edit_chat_badges": "مدالهای چت را ویرایش کنید",
"com_nav_enable_cache_tts": "کش TTS را فعال کنید",
"com_nav_enable_cloud_browser_voice": "از صداهای مبتنی بر ابر استفاده کنید",
"com_nav_enabled": "فعال شد",
@@ -679,7 +678,6 @@
"com_ui_import_conversation_info": "مکالمات را از یک فایل JSON وارد کنید",
"com_ui_import_conversation_success": "مکالمات با موفقیت وارد شد",
"com_ui_include_shadcnui": "شامل دستورالعمل های اجزای shadcn/ui",
"com_ui_include_shadcnui_agent": "دستورالعمل shadcn/ui را درج کنید",
"com_ui_input": "ورودی",
"com_ui_instructions": "دستورالعمل ها",
"com_ui_late_night": "آخر شب مبارک",

View File

@@ -1,4 +1,6 @@
{
"chat_direction_left_to_right": "Zone de saisie orientée de gauche à droite",
"chat_direction_right_to_left": "Zone de saisie orientée de droite à gauche",
"com_a11y_ai_composing": "L'IA est en train de composer",
"com_a11y_end": "L'IA a terminé sa réponse",
"com_a11y_start": "L'IA a commencé sa réponse",
@@ -15,21 +17,30 @@
"com_agents_file_search_disabled": "L'agent doit être créé avant de pouvoir télécharger des fichiers pour la Recherche de Fichiers.",
"com_agents_file_search_info": "Lorsque cette option est activée, l'agent sera informé des noms exacts des fichiers listés ci-dessous, lui permettant d'extraire le contexte pertinent de ces fichiers.",
"com_agents_instructions_placeholder": "Les instructions système que l'agent utilise",
"com_agents_mcp_description_placeholder": "Décrivez ce qu'il fait en quelques mots",
"com_agents_mcp_icon_size": "Taille minimale de 128 x 128 pixels",
"com_agents_mcp_info": "Ajoutez des serveurs MCP à vos agents pour lui permettre d'accomplir des tâches et d'interagir avec des services externes",
"com_agents_mcp_name_placeholder": "Outil personnalisé",
"com_agents_mcp_trust_subtext": "Connecteurs personnalisés non vérifiés par LibreChat",
"com_agents_mcps_disabled": "Vous devez créer un agent avant d'ajouter des MCP.",
"com_agents_missing_provider_model": "Veuillez sélectionner un fournisseur et un modèle avant de créer un agent.",
"com_agents_name_placeholder": "Facultatif : Le nom de l'agent",
"com_agents_no_access": "Vous n'avez pas l'autorisation de modifier cet agent.",
"com_agents_no_agent_id_error": "Aucun identifiant (ID) d'agent trouvé. Assurez-vous que l'agent existe.",
"com_agents_not_available": "Agent non disponible",
"com_agents_search_info": "Lorsque cette option est activée, votre agent est autorisé à rechercher des informations récentes sur le web. Nécessite une clé API valide.",
"com_agents_search_name": "Rechercher des agents par nom",
"com_agents_update_error": "Une erreur s'est produite lors de la mise à jour de votre agent",
"com_assistants_action_attempt": "L'assistant souhaite s'entretenir avec {{0}}",
"com_assistants_action_attempt": "L'assistant souhaite échanger avec {{0}}",
"com_assistants_actions": "Actions",
"com_assistants_actions_disabled": "Vous devez créer un assistant avant d'ajouter des actions.",
"com_assistants_actions_info": "Permettez à votre Assistant de récupérer des informations ou d'effectuer des actions via des API",
"com_assistants_add_actions": "Ajouter des actions",
"com_assistants_add_tools": "Ajouter des outils",
"com_assistants_allow_sites_you_trust": "N'autorisez que les sites en lesquels vous avez confiance.",
"com_assistants_allow_sites_you_trust": "Autoriser seulement les sites de confiance.",
"com_assistants_append_date": "Ajouter la date et l'heure actuelles",
"com_assistants_append_date_tooltip": "Lorsque activé, la date et l'heure actuelles du client seront ajoutées aux instructions du système de l'assistant.",
"com_assistants_attempt_info": "Assistant souhaite envoyer les éléments suivants :",
"com_assistants_available_actions": "Actions disponibles",
"com_assistants_capabilities": "Capacités des assistants",
"com_assistants_code_interpreter": "Interpréteur de code",
@@ -58,6 +69,7 @@
"com_assistants_non_retrieval_model": "La recherche de fichiers n'est pas activée pour ce modèle. Veuillez sélectionner un autre modèle.",
"com_assistants_retrieval": "Récupération",
"com_assistants_running_action": "Action en cours",
"com_assistants_running_var": "{{0}} en cours d'exécution",
"com_assistants_search_name": "Rechercher des assistants par nom",
"com_assistants_update_actions_error": "Une erreur s'est produite lors de la création ou de la mise à jour de l'action.",
"com_assistants_update_actions_success": "Action créée ou mise à jour avec succès",
@@ -87,7 +99,7 @@
"com_auth_email_verification_redirecting": "Redirection dans {{0}} secondes...",
"com_auth_email_verification_resend_prompt": "Vous n'avez pas reçu de courriel ?",
"com_auth_email_verification_success": "Courriel vérifié avec succès",
"com_auth_email_verifying_ellipsis": "Vérification...",
"com_auth_email_verifying_ellipsis": "Vérification en cours...",
"com_auth_error_create": "Il y a eu une erreur lors de la tentative d'enregistrement de votre compte. Veuillez réessayer.",
"com_auth_error_invalid_reset_token": "Ce jeton de réinitialisation de mot de passe n'est plus valide.",
"com_auth_error_login": "Impossible de se connecter avec les informations fournies. Veuillez vérifier vos identifiants et réessayer.",
@@ -119,17 +131,20 @@
"com_auth_reset_password_if_email_exists": "Si un compte avec ce courriel existe, un courriel avec des instructions de réinitialisation de mot de passe a été envoyé. Assurez-vous de vérifier votre dossier de courrier indésirable.",
"com_auth_reset_password_link_sent": "Courriel envoyé",
"com_auth_reset_password_success": "Réinitialisation du mot de passe réussie",
"com_auth_saml_login": "Continuer avec SAML",
"com_auth_sign_in": "Se connecter",
"com_auth_sign_up": "S'inscrire",
"com_auth_submit_registration": "Soumettre l'inscription",
"com_auth_to_reset_your_password": "pour réinitialiser votre mot de passe.",
"com_auth_to_try_again": "pour réessayer.",
"com_auth_two_factor": "Consultez votre application préférée de mot de passe à usage unique pour obtenir un code.",
"com_auth_two_factor": "Consultez votre application de mot de passe à usage unique pour obtenir un code.",
"com_auth_username": "Nom d'utilisateur",
"com_auth_username_max_length": "Le nom d'utilisateur doit être inférieur à 20 caractères",
"com_auth_username_min_length": "Le nom d'utilisateur doit comporter au moins 3 caractères",
"com_auth_verify_your_identity": "Vérifiez votre identité",
"com_auth_welcome_back": "Content de te revoir",
"com_citation_more_details": "Plus d'informations sur {{label}}",
"com_citation_source": "Source",
"com_click_to_download": "(cliquez ici pour télécharger)",
"com_download_expired": "Téléchargement expiré",
"com_download_expires": "(cliquez ici pour télécharger - expire le {{0}})",
@@ -179,6 +194,7 @@
"com_endpoint_default_blank": "par défaut : vide",
"com_endpoint_default_empty": "par défaut : vide",
"com_endpoint_default_with_num": "par défaut : {{0}}",
"com_endpoint_deprecated": "Obsolète",
"com_endpoint_deprecated_info": "Ce point de terminaison est obsolète et pourrait être supprimé dans les versions futures, veuillez utiliser le point de terminaison de l'agent à la place.",
"com_endpoint_deprecated_info_a11y": "Le point de terminaison du plugin est obsolète et pourrait être supprimé dans les versions futures, veuillez utiliser le point de terminaison de l'agent à la place.",
"com_endpoint_examples": " Exemples",
@@ -189,8 +205,11 @@
"com_endpoint_google_custom_name_placeholder": "Définir un nom personnalisé pour Google",
"com_endpoint_google_maxoutputtokens": "Nombre maximum de jetons qui peuvent être générés dans la réponse. Spécifiez une valeur plus faible pour des réponses plus courtes et une valeur plus élevée pour des réponses plus longues.",
"com_endpoint_google_temp": "Des valeurs plus élevées = plus aléatoires, tandis que des valeurs plus faibles = plus concentrées et déterministes. Nous vous recommandons de modifier ceci ou Top P mais pas les deux.",
"com_endpoint_google_thinking": "Active / désactive le raisonnement. Ce réglage n'est pris en charge que par certains modèles (de la série des 2.5). Pour les modèles plus anciens, ce réglage peut n'avoir aucun effet ou l'empêcher de fonctionner.",
"com_endpoint_google_thinking_budget": "Indique au modèle sur le nombre de jetons à utiliser pour la réflexion. Selon le message, le nombre de jetons effectivement consommés peut être supérieur ou inférieur à la valeur spécifiée.\n\nCe réglage n'est pris en charge que par certains modèles (de la série des 2.5). Gemini 2.5 Pro accepte une valeur comprise entre 128 et 32 768 jetons. Gemini 2.5 Flash accepte entre 0 et 24 576 jetons. Gemini 2.5 Flash Lite accepte entre 512 et 24 576 jetons.\n\nLaissez vide ou avec une valeur de \"-1\" pour laisser le model décider seul s'il doit réfléchir et à quel point il doit réfléchir. Par défaut, Gemini 2.5 Flash Lite ne réfléchit pas.",
"com_endpoint_google_topk": "Top-k change la façon dont le modèle sélectionne les jetons pour la sortie. Un top-k de 1 signifie que le jeton sélectionné est le plus probable parmi tous les jetons du vocabulaire du modèle (également appelé décodage glouton), tandis qu'un top-k de 3 signifie que le jeton suivant est sélectionné parmi les 3 jetons les plus probables (en utilisant la température).",
"com_endpoint_google_topp": "Top-p change la façon dont le modèle sélectionne les jetons pour la sortie. Les jetons sont sélectionnés du plus K (voir le paramètre topK) probable au moins jusqu'à ce que la somme de leurs probabilités égale la valeur top-p.",
"com_endpoint_google_use_search_grounding": "Utilise la fonctionnalité d'ancrage aux recherches de google pour ajouter des résultats de recherches web en temps réels aux réponses. Cela permet aux modèles d'accéder aux informations récentes et de fournir une réponse plus précise et à jour.",
"com_endpoint_instructions_assistants": "Instructions de remplacement",
"com_endpoint_instructions_assistants_placeholder": "Remplace les instructions de l'assistant. Cela est utile pour modifier le comportement au cas par cas.",
"com_endpoint_max_output_tokens": "Nombre maximum de jetons en sortie",
@@ -207,12 +226,14 @@
"com_endpoint_openai_max_tokens": "Champ `max_tokens` optionnel, représentant le nombre maximum de jetons pouvant être générés dans la complétion de conversation. La longueur totale des jetons d'entrée et des jetons générés est limitée par la longueur du contexte du modèle. Vous pouvez rencontrer des erreurs si ce nombre dépasse le maximum de jetons de contexte.",
"com_endpoint_openai_pres": "Nombre compris entre -2,0 et 2,0. Les valeurs positives pénalisent les nouveaux jetons en fonction du fait qu'ils apparaissent ou non dans le texte jusqu'à présent, augmentant ainsi la probabilité que le modèle parle de nouveaux sujets.",
"com_endpoint_openai_prompt_prefix_placeholder": "Définir des instructions personnalisées à inclure dans le message système. Par défaut : aucun",
"com_endpoint_openai_reasoning_effort": "Modèles o1 seulement : limite l'effort de raisonnement pour les modèles de raisonnement. La réduction de l'effort de raisonnement peut se traduire par des réponses plus rapides et moins de jetons utilisés pour le raisonnement dans une réponse",
"com_endpoint_openai_reasoning_effort": "Uniquement pour les modèles o1 et o3 : limite l'effort de raisonnement pour les modèles de raisonnement. La réduction de l'effort de raisonnement peut se traduire par des réponses plus rapides et moins de jetons utilisés pour le raisonnement dans une réponse",
"com_endpoint_openai_reasoning_summary": "API de Réponses uniquement : une synthèse du raisonnement est réalisé par le modèle. Cela peut être utile pour le débogage et pour comprendre les étapes de raisonnement du model. Les réglages possibles sont aucun, automatique, concis ou détaillé.",
"com_endpoint_openai_resend": "Renvoyer toutes les images précédemment jointes. Remarque : cela peut augmenter considérablement le coût en jetons et vous pouvez rencontrer des erreurs avec de nombreuses images en pièces jointes.",
"com_endpoint_openai_resend_files": "Renvoyer tous les fichiers précédemment joints. Remarque : cela augmentera le coût en jetons et vous pourriez rencontrer des erreurs avec de nombreuses pièces jointes.",
"com_endpoint_openai_stop": "Jusqu'à 4 séquences où l'API cessera de générer d'autres jetons.",
"com_endpoint_openai_temp": "Des valeurs plus élevées = plus aléatoires, tandis que des valeurs plus faibles = plus concentrées et déterministes. Nous vous recommandons de modifier ceci ou Top P mais pas les deux.",
"com_endpoint_openai_topp": "Une alternative à l'échantillonnage avec température, appelée échantillonnage du noyau, où le modèle considère les résultats des jetons avec une masse de probabilité top_p. Ainsi, 0,1 signifie que seuls les jetons représentant les 10 % de masse de probabilité les plus élevés sont pris en compte. Nous vous recommandons de modifier ceci ou la température mais pas les deux.",
"com_endpoint_openai_use_responses_api": "Utilise l'API de Réponses plutôt que la completion de messages, ce qui offre plus de fonctionnalités auprès d'OpenAI. Obligatoire pour o1-pro, o3-pro et pour activer la synthèse du raisonnement",
"com_endpoint_output": "Sortie",
"com_endpoint_plug_image_detail": "Détail de l'image",
"com_endpoint_plug_resend_files": "Renvoyer les fichiers",
@@ -243,10 +264,11 @@
"com_endpoint_prompt_prefix_assistants_placeholder": "Définir des instructions ou un contexte supplémentaire en plus des instructions principales de l'Assistant. Ignoré si vide.",
"com_endpoint_prompt_prefix_placeholder": "Définir des instructions ou un contexte personnalisé. Ignoré si vide.",
"com_endpoint_reasoning_effort": "Effort de raisonnement",
"com_endpoint_reasoning_summary": "Synthèse du raisonnement",
"com_endpoint_save_as_preset": "Enregistrer comme préréglage",
"com_endpoint_search": "Rechercher un endpoint par nom",
"com_endpoint_search_endpoint_models": "Recherche {{0}} modèles...",
"com_endpoint_search_models": "Recherche de modèles...",
"com_endpoint_search_endpoint_models": "Rechercher les modèles {{0}}...",
"com_endpoint_search_models": "Rechercher les modèles...",
"com_endpoint_search_var": "Recherche {{0}}...",
"com_endpoint_set_custom_name": "Définir un nom personnalisé, au cas où vous trouveriez ce préréglage",
"com_endpoint_skip_hover": "Activer le saut de l'étape de complétion, qui examine la réponse finale et les étapes générées",
@@ -258,6 +280,8 @@
"com_endpoint_top_k": "Top K",
"com_endpoint_top_p": "Top P",
"com_endpoint_use_active_assistant": "Utiliser l'assistant actif",
"com_endpoint_use_responses_api": "Utilise l'API de Réponses",
"com_endpoint_use_search_grounding": "Ancrage avec les recherches Google",
"com_error_expired_user_key": "La clé fournie pour {{0}} a expiré à {{1}}. Veuillez fournir une clé et réessayer.",
"com_error_files_dupe": "Fichier en double détecté.",
"com_error_files_empty": "Les fichiers vides ne sont pas autorisés",
@@ -266,6 +290,8 @@
"com_error_files_upload": "Une erreur s'est produite lors du téléversement du fichier",
"com_error_files_upload_canceled": "La demande de téléversement du fichier a été annulée. Remarque : le téléversement peut être toujours en cours de traitement et devra être supprimé manuellement.",
"com_error_files_validation": "Une erreur s'est produite lors de la validation du fichier.",
"com_error_google_tool_conflict": "L'utilisation combinée des outils intégrés de Google et d'outils externes n'est pas prise en charge. Veuillez désactiver soit les outils intégrés soit les outils externes.",
"com_error_heic_conversion": "La conversion de l'image HEIC en JPEG a échoué. Essayez de convertir l'image manuellement ou utilisez un autre format.",
"com_error_input_length": "Le nombre de jetons du dernier message est trop élevé et dépasse la limite autorisée ({{0}}). Veuillez raccourcir votre message, ajuster la taille maximale du contexte dans les paramètres de conversation, ou créer une nouvelle conversation pour continuer.",
"com_error_invalid_agent_provider": "Le \"fournisseur {{0}} \" n'est pas disponible pour les agents. Veuillez vous rendre dans les paramètres de votre agent et sélectionner un fournisseur actuellement disponible.",
"com_error_invalid_user_key": "Clé fournie non valide. Veuillez fournir une clé valide et réessayer.",
@@ -278,6 +304,7 @@
"com_files_table": "quelquechose doit être renseigné ici. c'était vide",
"com_generated_files": "Fichiers générés :",
"com_hide_examples": "Masquer les exemples",
"com_info_heic_converting": "Convertir les images HEIC en JPEG...",
"com_nav_2fa": "Authentification à deux facteurs (2FA)",
"com_nav_account_settings": "Paramètres du compte",
"com_nav_always_make_prod": "Rendre toujours les nouvelles versions en production",
@@ -295,7 +322,29 @@
"com_nav_auto_transcribe_audio": "Transcription audio automatique",
"com_nav_automatic_playback": "Lecture automatique du dernier message (externe seulement)",
"com_nav_balance": "Équilibre",
"com_nav_balance_auto_refill_disabled": "Le rechargement automatique est désactivé.",
"com_nav_balance_auto_refill_error": "Erreur lors du chargement des réglages de rechargement automatique.",
"com_nav_balance_auto_refill_settings": "Réglages de rechargement automatique.",
"com_nav_balance_day": "jour",
"com_nav_balance_days": "jours",
"com_nav_balance_every": "Chaque",
"com_nav_balance_hour": "heure",
"com_nav_balance_hours": "heures",
"com_nav_balance_interval": "Délai :",
"com_nav_balance_last_refill": "Dernier rechargement :",
"com_nav_balance_minute": "minute",
"com_nav_balance_minutes": "minutes",
"com_nav_balance_month": "mois",
"com_nav_balance_months": "mois",
"com_nav_balance_next_refill": "Prochain rechargement :",
"com_nav_balance_next_refill_info": "Le prochain rechargement s'effectura automatiquement lorsque les deux conditions seront remplies : le délai spécifié depuis le dernier rechargement est écoulé et l'envoi d'un message ferait tombé le solde en dessous de zéro.",
"com_nav_balance_refill_amount": "Montant rechargé :",
"com_nav_balance_second": "seconde",
"com_nav_balance_seconds": "secondes",
"com_nav_balance_week": "semaine",
"com_nav_balance_weeks": "semaines",
"com_nav_browser": "Navigateur",
"com_nav_center_chat_input": "Centrer la zone de saisie avec l'écran d'accueil",
"com_nav_change_picture": "Changer de photo",
"com_nav_chat_commands": "Commandes de chat",
"com_nav_chat_commands_info": "Ces commandes sont activées en tapant des caractères spécifiques au début de votre message. Chaque commande est déclenchée par son préfixe désigné. Vous pouvez les désactiver si vous utilisez fréquemment ces caractères pour commencer vos messages.",
@@ -317,7 +366,6 @@
"com_nav_delete_cache_storage": "Supprimer le stockage du cache TTS",
"com_nav_delete_data_info": "Toutes vos données seront supprimées.",
"com_nav_delete_warning": "ATTENTION : Cela supprimera définitivement votre compte.",
"com_nav_edit_chat_badges": "Modifier les badges de chat",
"com_nav_enable_cache_tts": "Activer le cache TTS",
"com_nav_enable_cloud_browser_voice": "Utiliser les voix cloud",
"com_nav_enabled": "Activé",
@@ -341,6 +389,7 @@
"com_nav_font_size_xs": "Très petit",
"com_nav_help_faq": "Aide & FAQ",
"com_nav_hide_panel": "Masquer le panneau latéral le plus à droite",
"com_nav_info_balance": "Le solde affiche le nombre de crédits de jetons il vous reste à utiliser. Les crédits de jetons représentent une valeur monétaire (par exemple, 1 000 crédits = 0,001 $)",
"com_nav_info_code_artifacts": "Active l'affichage des artéfacts de code expérimentaux à côté du chat",
"com_nav_info_code_artifacts_agent": "Active l'utilisation d'artefacts de code pour cet agent. Par défaut, des instructions supplémentaires spécifiques à l'utilisation des artefacts sont ajoutées, à moins que le \"Mode d'invite personnalisé\" ne soit activé.",
"com_nav_info_custom_prompt_mode": "Lorsqu'activé, le prompt système par défaut pour les artéfacts ne sera pas inclus. Toutes les instructions de génération d'artéfacts doivent être fournies manuellement dans ce mode.",
@@ -356,7 +405,10 @@
"com_nav_lang_arabic": "العربية",
"com_nav_lang_auto": "Détection automatique",
"com_nav_lang_brazilian_portuguese": "Português Brasileiro",
"com_nav_lang_catalan": "Catalan",
"com_nav_lang_chinese": "中文",
"com_nav_lang_czech": "Tchèque",
"com_nav_lang_danish": "Danois",
"com_nav_lang_dutch": "Nederlands",
"com_nav_lang_english": "English",
"com_nav_lang_estonian": "Eesti keel",
@@ -365,10 +417,12 @@
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_hungarian": "Hongrois",
"com_nav_lang_indonesia": "Indonesia",
"com_nav_lang_italian": "Italiano",
"com_nav_lang_japanese": "日本語",
"com_nav_lang_korean": "한국어",
"com_nav_lang_persian": "Persan",
"com_nav_lang_polish": "Polski",
"com_nav_lang_portuguese": "Português",
"com_nav_lang_russian": "Русский",
@@ -383,6 +437,8 @@
"com_nav_log_out": "Se déconnecter",
"com_nav_long_audio_warning": "Les textes plus longs prendront plus de temps à traiter.",
"com_nav_maximize_chat_space": "Maximiser l'espace de discussion",
"com_nav_mcp_vars_update_error": "Erreur lors de l'actualisation des variables de l'utilisateur MCP personnalisé : {{0}}",
"com_nav_mcp_vars_updated": "Actualisation réussie des variables de l'utilisateur MCP personnalisé.",
"com_nav_modular_chat": "Activer le changement de points de terminaison en cours de conversation",
"com_nav_my_files": "Mes fichiers",
"com_nav_not_supported": "Non pris en charge",
@@ -398,14 +454,17 @@
"com_nav_profile_picture": "Photo de profil",
"com_nav_save_badges_state": "Sauvegarder l'état des badges",
"com_nav_save_drafts": "Enregistrer les brouillons localement",
"com_nav_scroll_button": "Défilement jusqu'à la touche de fin",
"com_nav_scroll_button": "Bouton de défilement jusqu'à la fin",
"com_nav_search_placeholder": "Rechercher des messages",
"com_nav_send_message": "Envoyer un message",
"com_nav_setting_account": "Compte",
"com_nav_setting_balance": "Solde",
"com_nav_setting_beta": "Fonctionnalités bêta",
"com_nav_setting_chat": "Chat",
"com_nav_setting_data": "Contrôles des données",
"com_nav_setting_general": "Général",
"com_nav_setting_mcp": "Réglages MCP",
"com_nav_setting_personalization": "Personnalisation",
"com_nav_setting_speech": "Parole",
"com_nav_settings": "Paramètres",
"com_nav_shared_links": "Liens partagés",
@@ -438,26 +497,39 @@
"com_sidepanel_conversation_tags": "Signets",
"com_sidepanel_hide_panel": "Masquer le panneau",
"com_sidepanel_manage_files": "Gérer les fichiers",
"com_sidepanel_mcp_enter_value": "Saisissez la valeur de {{0}}",
"com_sidepanel_mcp_no_servers_with_vars": "Aucun serveur MCP dont les variables sont configurables.",
"com_sidepanel_mcp_variables_for": "Variables MCP de {{0}}",
"com_sidepanel_parameters": "Paramètres",
"com_sources_image_alt": "Image de résultat de recherche",
"com_sources_more_sources": "+{{count}} sources",
"com_sources_tab_all": "Tous",
"com_sources_tab_images": "Images",
"com_sources_tab_news": "Actualités",
"com_sources_title": "Sources",
"com_ui_2fa_account_security": "L'authentification à deux facteurs ajoute un niveau de sécurité supplémentaire à votre compte",
"com_ui_2fa_disable": "Désactiver l'authentification à deux facteurs (2FA)",
"com_ui_2fa_disable_error": "Une erreur s'est produite lors de la désactivation de l'authentification à deux facteurs.",
"com_ui_2fa_disable_error": "Une erreur s'est produite lors de la désactivation de l'authentification à deux facteurs",
"com_ui_2fa_disabled": "L'authentification à deux facteurs (2FA) a été désactivé",
"com_ui_2fa_enable": "Activer l'authentification à deux facteurs (2FA)",
"com_ui_2fa_enabled": "L'authentification à deux facteurs (2FA) a été activée",
"com_ui_2fa_generate_error": "Une erreur s'est produite lors de la génération des paramètres d'authentification à deux facteurs.",
"com_ui_2fa_generate_error": "Une erreur s'est produite lors de la génération des paramètres d'authentification à deux facteurs",
"com_ui_2fa_invalid": "Code d'authentification à deux facteurs invalide",
"com_ui_2fa_setup": "Configuration de l'authentification à deux facteurs (2FA)",
"com_ui_2fa_verified": "Vérification réussie de l'authentification à deux facteurs",
"com_ui_2fa_verified": "Vérification de l'authentification à deux facteurs réussie",
"com_ui_accept": "J'accepte",
"com_ui_action_button": "Bouton d'action",
"com_ui_add": "Ajouter",
"com_ui_add_mcp": "Ajouter MCP",
"com_ui_add_mcp_server": "Ajouter un server MCP",
"com_ui_add_model_preset": "Ajouter un modèle ou un préréglage pour une réponse supplémentaire",
"com_ui_add_multi_conversation": "Ajouter plusieurs conversations",
"com_ui_adding_details": "Ajouter des détails",
"com_ui_admin": "Administrateur",
"com_ui_admin_access_warning": "La désactivation de l'accès administrateur à cette fonctionnalité peut entraîner des problèmes d'interface imprévus nécessitant une actualisation. Une fois sauvegardé, le seul moyen de rétablir l'accès est via le paramètre d'interface dans la configuration librechat.yaml, ce qui affecte tous les rôles.",
"com_ui_admin_settings": "Paramètres administratifs",
"com_ui_advanced": "Avancé",
"com_ui_advanced_settings": "Paramètres avancés",
"com_ui_advanced_settings": "Réglages avancés",
"com_ui_agent": "Agent",
"com_ui_agent_chain": "Chaîne d'agents (mélange d'agents)",
"com_ui_agent_chain_info": "Active la création des séquences d'agents. Chaque agent peut accéder aux résultats des agents précédents de la chaîne. Basé sur l'architecture \"mélange d'agents\" où les agents utilisent les résultats précédents comme information auxiliaire.",
@@ -468,18 +540,39 @@
"com_ui_agent_duplicated": "Agent dupliqué avec succès",
"com_ui_agent_editing_allowed": "D'autres utilisateurs peuvent déjà modifier cet agent",
"com_ui_agent_recursion_limit": "Nombre maximal d'étapes de l'agent",
"com_ui_agent_shared_to_all": "il faut faire quelque chose ici. c'était vide",
"com_ui_agent_recursion_limit_info": "Limite le nombre d'étapes que l'agent peut exécuter avant de donner son résultat final. Par défaut, la limite est de 25 étapes. Une étape est soit une requête API soit une utilisation d'un outil. Par exemple, une utilisation simple d'un outil demande 3 étapes : requête initiale, utilisation de l'outil et envoi de la réponse.",
"com_ui_agent_shared_to_all": "Partagé les agents à tous",
"com_ui_agent_var": "agent {{0}}",
"com_ui_agent_version": "Version",
"com_ui_agent_version_active": "Version active",
"com_ui_agent_version_duplicate": "Duplicata de version détecté. Cela créerait une version identique à la version {{versionIndex}}",
"com_ui_agent_version_empty": "Aucune version disponible",
"com_ui_agent_version_error": "Erreur lors de la collecte des versions",
"com_ui_agent_version_history": "Historique des versions",
"com_ui_agent_version_no_agent": "Aucun agent sélectionné. Veuillez sélectionner un agent pour voir l'historique des versions.",
"com_ui_agent_version_no_date": "Aucune date disponible",
"com_ui_agent_version_restore": "Restaurer",
"com_ui_agent_version_restore_confirm": "Êtes-vous sûr de vouloir restaurer cette version ?",
"com_ui_agent_version_restore_error": "Restauration de version échouée",
"com_ui_agent_version_restore_success": "Restauration de version réussie",
"com_ui_agent_version_title": "Version {{versionNumber}}",
"com_ui_agent_version_unknown_date": "Date inconnue",
"com_ui_agents": "Agents",
"com_ui_agents_allow_create": "Autoriser la création d'Agents",
"com_ui_agents_allow_share_global": "Autoriser le partage des Agents avec tous les utilisateurs",
"com_ui_agents_allow_use": "Autoriser l'utilisation des Agents",
"com_ui_all": "tout",
"com_ui_all_proper": "Tout",
"com_ui_analyzing": "Analyse en cours",
"com_ui_analyzing_finished": "Analyse terminée",
"com_ui_api_key": "Clé API",
"com_ui_archive": "Archiver",
"com_ui_archive_delete_error": "Suppression de la conversation archivée échouée",
"com_ui_archive_error": "échec de l'archivage de la conversation",
"com_ui_artifact_click": "Cliquer pour ouvrir",
"com_ui_artifacts": "Artefacts",
"com_ui_artifacts_toggle": "Afficher/Masquer l'interface des artefacts",
"com_ui_artifacts_toggle_agent": "Activer Artifacts",
"com_ui_ascending": "Croissant",
"com_ui_assistant": "Assistant",
"com_ui_assistant_delete_error": "Une erreur s'est produite lors de la suppression de l'assistant.",
@@ -490,12 +583,26 @@
"com_ui_attach_error_openai": "Impossible de joindre les fichiers de l'Assistant à d'autres points d'accès",
"com_ui_attach_error_size": "Limite de taille de fichier dépassée pour le point de terminaison :",
"com_ui_attach_error_type": "Type de fichier non pris en charge pour ce point d'accès :",
"com_ui_attach_remove": "Supprimer le fichier",
"com_ui_attach_warn_endpoint": "Les fichiers non compatibles avec l'outil peuvent être ignorés",
"com_ui_attachment": "Pièce jointe",
"com_ui_auth_type": "Type d'auth",
"com_ui_auth_url": "Adresse URL d'authentification",
"com_ui_authentication": "Authentification",
"com_ui_authentication_type": "Type d'authentification",
"com_ui_auto": "Automatique",
"com_ui_available_tools": "Outils disponibles",
"com_ui_avatar": "Avatar",
"com_ui_azure": "Azure",
"com_ui_back": "Retour",
"com_ui_back_to_chat": "Retour à la discussion",
"com_ui_back_to_prompts": "Retour aux Prompts",
"com_ui_backup_codes": "Codes de sauvegarde",
"com_ui_backup_codes_regenerate_error": "Une erreur est survenue lors du renouvellement des codes de sauvegarde",
"com_ui_backup_codes_regenerated": "Codes de sauvegarde renouvelé avec succès",
"com_ui_basic": "Simple",
"com_ui_basic_auth_header": "En-tête d'autorisation simple",
"com_ui_bearer": "Porteur",
"com_ui_bookmark_delete_confirm": "Êtes-vous sûr de vouloir supprimer ce signet?",
"com_ui_bookmarks": "Signets",
"com_ui_bookmarks_add": "Ajouter des signets",
@@ -515,23 +622,33 @@
"com_ui_bookmarks_update_error": "Une erreur est survenue lors de la mise à jour du signet",
"com_ui_bookmarks_update_success": "Signet mis à jour avec succès",
"com_ui_bulk_delete_error": "Échec de la suppression des liens partagés",
"com_ui_callback_url": "Adresse URL de callback",
"com_ui_cancel": "Annuler",
"com_ui_cancelled": "Annulé",
"com_ui_category": "Catégorie",
"com_ui_chat": "Discussion",
"com_ui_chat_history": "Historique des discussions",
"com_ui_clear": "Effacer",
"com_ui_clear_all": "Tout effacer",
"com_ui_client_id": "Identifiant (ID) du client",
"com_ui_client_secret": "Secret du client",
"com_ui_close": "Fermer",
"com_ui_close_menu": "Fermer le menu",
"com_ui_close_window": "Fermer la fenêtre",
"com_ui_code": "Code",
"com_ui_collapse_chat": "Réduire la discussion",
"com_ui_command_placeholder": "Facultatif : Saisissez une commande pour l'invite ou le nom sera utilisé",
"com_ui_command_usage_placeholder": "Sélectionnez un prompt par commande ou par nom",
"com_ui_complete_setup": "Configuration complete",
"com_ui_concise": "Concis",
"com_ui_configure_mcp_variables_for": "Configurer les variables pour {{0}}",
"com_ui_confirm_action": "Confirmer l'action",
"com_ui_confirm_admin_use_change": "La modification de ce paramètre bloquera l'accès aux administrateurs, y compris vous-même. Êtes-vous sûr de vouloir continuer ?",
"com_ui_confirm_change": "Confirmer le changement",
"com_ui_confirm_change": "Confirmer la modification",
"com_ui_context": "Contexte",
"com_ui_continue": "Continuer",
"com_ui_controls": "Contrôles",
"com_ui_convo_delete_error": "Suppression de conversation échouée",
"com_ui_copied": "Copié !",
"com_ui_copied_to_clipboard": "Copié dans le presse-papier",
"com_ui_copy_code": "Copier le code",
@@ -539,8 +656,13 @@
"com_ui_copy_to_clipboard": "Copier dans le presse-papier",
"com_ui_create": "Créer",
"com_ui_create_link": "Créer un lien",
"com_ui_create_memory": "Créer un Souvenir",
"com_ui_create_prompt": "Créer un prompt",
"com_ui_creating_image": "Création de l'image en cours. Cela peut prendre un moment",
"com_ui_current": "Actuel",
"com_ui_currently_production": "En cours de production",
"com_ui_custom": "Personnalisé",
"com_ui_custom_header_name": "Nom d'en-tête personnalisé",
"com_ui_custom_prompt_mode": "Mode de prompt personnalisé",
"com_ui_dashboard": "Tableau de bord",
"com_ui_date": "Date",
@@ -561,6 +683,7 @@
"com_ui_date_today": "Aujourd'hui",
"com_ui_date_yesterday": "Hier",
"com_ui_decline": "Je n'accepte pas",
"com_ui_default_post_request": "Par défaut (requête POST)",
"com_ui_delete": "Supprimer",
"com_ui_delete_action": "Supprimer l'action",
"com_ui_delete_action_confirm": "Êtes-vous sûr de vouloir supprimer cette action ?",
@@ -569,15 +692,28 @@
"com_ui_delete_confirm": "Cela supprimera",
"com_ui_delete_confirm_prompt_version_var": "Cela supprimera la version sélectionnée pour \"{{0}}.\" S'il n'existe aucune autre version, le prompt sera supprimé.",
"com_ui_delete_conversation": "Supprimer la discussion?",
"com_ui_delete_mcp": "Supprimer MCP",
"com_ui_delete_mcp_confirm": "Êtes-vous sûr de vouloir supprimer ce serveur MCP ?",
"com_ui_delete_mcp_error": "Suppression de serveur MCP échouée",
"com_ui_delete_mcp_success": "Suppression de serveur MCP réussie",
"com_ui_delete_memory": "Supprimer les Souvenirs",
"com_ui_delete_prompt": "Supprimer le Prompt?",
"com_ui_delete_shared_link": "Supprimer le lien partagé ?",
"com_ui_delete_tool": "Supprimer l'outil",
"com_ui_delete_tool_confirm": "Êtes-vous sûr de vouloir supprimer cet outil ?",
"com_ui_deleted": "Supprimé",
"com_ui_descending": "Décroissant",
"com_ui_description": "Description",
"com_ui_description_placeholder": "Optionnel : Entrez une description à afficher dans le prompt",
"com_ui_deselect_all": "Tout désélectionner ",
"com_ui_detailed": "Détaillée",
"com_ui_disabling": "Désactivation en cours...",
"com_ui_download": "Télécharger",
"com_ui_download_artifact": "Télécharger Artifact",
"com_ui_download_backup": "Télécharger les codes de sauvegarde",
"com_ui_download_backup_tooltip": "Avant de continuer, téléchargez vos codes de sauvegarde. Vous en aurez besoin pour récupérer un accès si vous perdez votre appareil authenticator",
"com_ui_download_error": "Erreur lors du téléchargement du fichier. Le fichier a peut-être été supprimé.",
"com_ui_drag_drop": "Glisser et déposer",
"com_ui_dropdown_variables": "Variables déroulantes :",
"com_ui_dropdown_variables_info": "Créez des menus déroulants personnalisés pour vos prompts : `{{nom_variable:option1|option2|option3}}`",
"com_ui_duplicate": "Dupliquer",
@@ -585,18 +721,49 @@
"com_ui_duplication_processing": "Duplication de la conversation en cours...",
"com_ui_duplication_success": "Conversation dupliquée avec succès",
"com_ui_edit": "Modifier",
"com_ui_edit_editing_image": "Edition de l'image",
"com_ui_edit_mcp_server": "Editer le serveur MCP",
"com_ui_edit_memory": "Editer les Souvenirs",
"com_ui_empty_category": "-",
"com_ui_endpoint": "Point de terminaison",
"com_ui_endpoint_menu": "Menu des points de terminaison LLM",
"com_ui_enter": "Entrer",
"com_ui_enter_api_key": "Saisir la clé API",
"com_ui_enter_key": "Entrez la clé",
"com_ui_enter_openapi_schema": "Saisissez votre schéma OpenAPI ici",
"com_ui_enter_value": "Entrez la valeur",
"com_ui_error": "Erreur",
"com_ui_error_connection": "Erreur de connexion au serveur, essayez de rafraîchir la page.",
"com_ui_error_save_admin_settings": "Une erreur est survenue lors de la sauvegarde de vos paramètres administratifs.",
"com_ui_error_updating_preferences": "Une erreur est survenue lors de l'édition des préférences",
"com_ui_examples": "Exemples",
"com_ui_expand_chat": "Etendre le message",
"com_ui_export_convo_modal": "Exporter la conversation",
"com_ui_feedback_more": "Plus...",
"com_ui_feedback_more_information": "Envoyer des retours supplémentaires",
"com_ui_feedback_negative": "Amélioration nécessaires",
"com_ui_feedback_placeholder": "Veuillez indiquer vos retours supplémentaires ici",
"com_ui_feedback_positive": "J'adore",
"com_ui_feedback_tag_accurate_reliable": "Précis et fiable",
"com_ui_feedback_tag_attention_to_detail": "Attention aux détails",
"com_ui_feedback_tag_bad_style": "Style ou ton inadapté",
"com_ui_feedback_tag_clear_well_written": "Clair et bien écrit",
"com_ui_feedback_tag_creative_solution": "Solution créative",
"com_ui_feedback_tag_inaccurate": "Réponse imprécise ou incorrecte",
"com_ui_feedback_tag_many": "Nombreux",
"com_ui_feedback_tag_missing_image": "Image manquante",
"com_ui_feedback_tag_not_helpful": "Manquait d'informations utiles",
"com_ui_feedback_tag_not_matched": "Ne répond pas à ma demande",
"com_ui_feedback_tag_one": "Un seul",
"com_ui_feedback_tag_other": "Autre",
"com_ui_feedback_tag_unjustified_refusal": "A refusé sans raison",
"com_ui_field_required": "Ce champ est obligatoire",
"com_ui_file_size": "Taille du fichier",
"com_ui_files": "Fichiers",
"com_ui_filter_prompts": "Filtrer les messages",
"com_ui_filter_prompts_name": "Filtrer les prompts par nom",
"com_ui_final_touch": "Touche finale",
"com_ui_finance": "Finance",
"com_ui_fork": "Bifurquer",
"com_ui_fork_all_target": "Inclure tout à partir d'ici",
"com_ui_fork_branches": "Inclure les branches associées",
@@ -608,10 +775,13 @@
"com_ui_fork_info_2": "\"Forker\" fait référence à la création d'une nouvelle conversation qui commence/se termine à partir de messages spécifiques dans la conversation actuelle, en créant une copie selon les options sélectionnées.",
"com_ui_fork_info_3": "Le terme \"message cible\" fait référence soit au message à partir duquel cette fenêtre contextuelle a été ouverte, soit, si vous cochez \"{{0}}\", au dernier message de la conversation.",
"com_ui_fork_info_branches": "Cette option divise les messages visibles, ainsi que les branches associées ; en d'autres termes, le chemin direct vers le message cible, y compris les branches le long du chemin.",
"com_ui_fork_info_button_label": "Voir les informations sur les fourches de conversations",
"com_ui_fork_info_remember": "Cochez cette case pour mémoriser les options que vous sélectionnez pour une utilisation future, ce qui vous permettra de bifurquer plus rapidement les conversations selon vos préférences.",
"com_ui_fork_info_start": "Si cette option est cochée, le fork commencera à partir de ce message jusqu'au dernier message de la conversation, selon le comportement sélectionné ci-dessus.",
"com_ui_fork_info_target": "Cette option divise tous les messages menant au message cible, y compris ses voisins ; en d'autres termes, toutes les branches de messages, qu'elles soient visibles ou non et quel que soit leur chemin, sont incluses.",
"com_ui_fork_info_visible": "Cette option permet de diviser uniquement les messages visibles ; en d'autres termes, le chemin direct vers le message cible, sans aucune branche.",
"com_ui_fork_more_details_about": "Voir plus d'informations sur l'option de fouche \"{{0}}",
"com_ui_fork_more_info_options": "Voir des explications détaillées sur toutes les options de fourches et leurs effets",
"com_ui_fork_processing": "Bifurquer la conversation...",
"com_ui_fork_remember": "Se souvenir",
"com_ui_fork_remember_checked": "Votre sélection sera mémorisée après utilisation. Vous pouvez la modifier à tout moment dans les paramètres.",
@@ -619,12 +789,29 @@
"com_ui_fork_split_target_setting": "Démarrer la bifurcation à partir du message cible par défaut",
"com_ui_fork_success": "Conversation bifurquée avec succès",
"com_ui_fork_visible": "Messages visibles uniquement",
"com_ui_go_back": "Retourner",
"com_ui_generate_backup": "Générer des codes de sauvegarde",
"com_ui_generate_qrcode": "Générer un QR Code",
"com_ui_generating": "Génération en cours...",
"com_ui_generation_settings": "Réglages de génération",
"com_ui_getting_started": "Commencer",
"com_ui_global_group": "Groupe global",
"com_ui_go_back": "Revenir en arrière",
"com_ui_go_to_conversation": "Aller à la conversation",
"com_ui_good_afternoon": "Bon après-midi",
"com_ui_good_evening": "Bonne soirée",
"com_ui_good_morning": "Bonjour",
"com_ui_happy_birthday": "C'est mon premier anniversaire !",
"com_ui_hide_qr": "Cacher le code QR",
"com_ui_hide_image_details": "Cacher les informations de l'images",
"com_ui_hide_qr": "Cacher le QR code",
"com_ui_high": "Elevé",
"com_ui_host": "Hôte",
"com_ui_icon": "Icône",
"com_ui_idea": "Idées",
"com_ui_image_created": "Image créée",
"com_ui_image_details": "Informations de l'image",
"com_ui_image_edited": "Image éditée",
"com_ui_image_gen": "Génération d'image",
"com_ui_import": "Importer",
"com_ui_import_conversation_error": "Une erreur s'est produite lors de l'importation de vos conversations",
"com_ui_import_conversation_file_type_error": "Type de fichier non pris en charge pour l'importation",
"com_ui_import_conversation_info": "Importer des conversations à partir d'un fichier JSON",
@@ -632,35 +819,82 @@
"com_ui_include_shadcnui": "Inclure les instructions des composants shadcn/ui",
"com_ui_input": "Entrée",
"com_ui_instructions": "Instructions",
"com_ui_key": "Clé",
"com_ui_late_night": "Bonne nocturne",
"com_ui_latest_footer": "Chaque IA pour tout le monde.",
"com_ui_latest_production_version": "Dernière version de production",
"com_ui_latest_version": "Dernière version",
"com_ui_librechat_code_api_key": "Obtenir votre clé API pour l'interpréteur de code LibreChat",
"com_ui_librechat_code_api_subtitle": "Sécurisé. Multilingue. Fichiers d'entrée/sortie.",
"com_ui_librechat_code_api_title": "Exécuter le code IA",
"com_ui_loading": "Chargement en cours...",
"com_ui_locked": "Verrouillé",
"com_ui_logo": "Logo {{0}}",
"com_ui_low": "Faible",
"com_ui_manage": "Gérer",
"com_ui_max_tags": "Le nombre maximum autorisé est {{0}}, en utilisant les dernières valeurs.",
"com_ui_mcp_dialog_desc": "Veuillez saisir les informations importantes ci-dessous.",
"com_ui_mcp_enter_var": "Saisissez la valeur de {{0}}",
"com_ui_mcp_server_not_found": "Le serveur n'a pas été trouvé.",
"com_ui_mcp_servers": "Serveurs MCP",
"com_ui_mcp_url": "Adresse URL du serveur MCP",
"com_ui_medium": "Modéré",
"com_ui_memories": "Souvenirs",
"com_ui_memories_allow_create": "Autoriser la création de Souvenirs",
"com_ui_memories_allow_opt_out": "Autoriser les utilisateurs à désactiver les Souvenirs",
"com_ui_memories_allow_read": "Autoriser la lecture des Souvenirs",
"com_ui_memories_allow_update": "Autoriser la mise à jour des Souvenirs",
"com_ui_memories_allow_use": "Autoriser l'utilisation des Souvenirs",
"com_ui_memories_filter": "Filtrer les Souvenirs",
"com_ui_memory": "Souvenir",
"com_ui_memory_created": "Souvenir créé avec succès",
"com_ui_memory_deleted": "Souvenir supprimé",
"com_ui_memory_deleted_items": "Souvenirs supprimés",
"com_ui_memory_key_exists": "Un Souvenir existe déjà avec cette clé. Veuillez utiliser une autre clé.",
"com_ui_memory_updated": "Actualiser le Souvenir enregistré",
"com_ui_memory_updated_items": "Souvenirs enregistrés",
"com_ui_mention": "Mentionnez un point de terminaison, un assistant ou un préréglage pour basculer rapidement vers celui-ci",
"com_ui_min_tags": "Impossible de supprimer plus de valeurs, un minimum de {{0}} est requis.",
"com_ui_misc": "Divers",
"com_ui_model": "Modèle",
"com_ui_model_parameters": "Paramètres du modèle",
"com_ui_more_info": "Plus d'informations",
"com_ui_my_prompts": "Mes Prompts",
"com_ui_name": "Nom",
"com_ui_new": "Nouveau",
"com_ui_new_chat": "Nouvelle conversation",
"com_ui_new_conversation_title": "Nouveau titre de conversation",
"com_ui_next": "Suivant",
"com_ui_no": "Non",
"com_ui_no_backup_codes": "Aucun code de sauvegarde disponible. Veuillez générer de nouveaux codes.",
"com_ui_no_bookmarks": "Il semble que vous n'ayez pas encore de favoris. Cliquez sur une discussion pour en ajouter un",
"com_ui_no_category": "Aucune catégorie",
"com_ui_no_changes": "Aucune modification à mettre à jour",
"com_ui_no_data": "Aucune donnée",
"com_ui_no_personalization_available": "Aucune personnalisation disponible",
"com_ui_no_read_access": "Vous n'avez pas l'authorisation de voir les Souvenirs.",
"com_ui_no_terms_content": "Aucun contenu de conditions d'utilisation à afficher",
"com_ui_no_valid_items": "Aucun élément valide",
"com_ui_none": "Aucun",
"com_ui_not_used": "Inutilisé",
"com_ui_nothing_found": "Aucun résultat trouvé",
"com_ui_oauth": "OAuth",
"com_ui_oauth_connected_to": "Connecté à",
"com_ui_oauth_error_callback_failed": "Le rappel d'authentification a échoué. Veuillez réessayer.",
"com_ui_oauth_error_generic": "L'authentification a échoué. Veuillez réessayer.",
"com_ui_oauth_error_invalid_state": "Paramètre d'état invalide. Veuillez réessayer.",
"com_ui_oauth_error_missing_code": "Code d'authorisation manquant. Veuillez réessayer.",
"com_ui_oauth_error_missing_state": "Paramètre d'état manquant. Veuillez réessayer.",
"com_ui_oauth_error_title": "L'authentification a échoué.",
"com_ui_oauth_success_description": "Vous vous êtes authentifié avec succès. La fenêtre va se fermer.",
"com_ui_oauth_success_title": "Authentification réussie",
"com_ui_of": "des",
"com_ui_off": "Désactivé",
"com_ui_on": "Activé",
"com_ui_openai": "OpenAI",
"com_ui_optional": "(optionnel)",
"com_ui_page": "Page",
"com_ui_preferences_updated": "Préférences enregistrées avec succès",
"com_ui_prev": "Précédent",
"com_ui_preview": "Aperçu",
"com_ui_privacy_policy": "Politique de confidentialité",
@@ -678,11 +912,23 @@
"com_ui_prompts_allow_share_global": "Autoriser le partage des Prompts à tous les utilisateurs",
"com_ui_prompts_allow_use": "Autoriser l'utilisation de Prompts",
"com_ui_provider": "Fournisseur",
"com_ui_quality": "Qualité",
"com_ui_read_aloud": "Lire à haute voix",
"com_ui_redirecting_to_provider": "Redirection en cours vers {{0}}, veuillez patienter...",
"com_ui_reference_saved_memories": "Indexer les Souvenirs enregistrés",
"com_ui_reference_saved_memories_description": "Autoriser l'assistant à accéder aux index des Souvenirs enregistrés et à les utiliser pour répondre",
"com_ui_refresh_link": "Rafraîchir le lien",
"com_ui_regenerate": "Régénérer",
"com_ui_regenerate_backup": "Régénérer les codes de sauvegarde",
"com_ui_regenerating": "Régénération en cours...",
"com_ui_region": "Région",
"com_ui_rename": "Renommer",
"com_ui_rename_conversation": "Renommer la conversation",
"com_ui_rename_failed": "Le changement de nom de la conversation a échoué",
"com_ui_rename_prompt": "Renommer le message",
"com_ui_requires_auth": "Nécessite une authentification",
"com_ui_reset_var": "Réinitialiser {{0}}",
"com_ui_reset_zoom": "Réinitialiser le zoom",
"com_ui_result": "Résultat",
"com_ui_revoke": "Révoquer",
"com_ui_revoke_info": "Révoquer toutes les informations d'identification fournies par l'utilisateur",
@@ -691,13 +937,21 @@
"com_ui_revoke_keys": "Révoquer les clés",
"com_ui_revoke_keys_confirm": "Êtes-vous sûr de vouloir révoquer toutes les clés ?",
"com_ui_role_select": "Sélectionner un rôle",
"com_ui_roleplay": "Jeu de rôle",
"com_ui_run_code": "Exécuter le code",
"com_ui_run_code_error": "Une erreur s'est produite lors de l'exécution du code",
"com_ui_save": "Sauvegarder",
"com_ui_save_badge_changes": "Sauvegarder les changements de badges ?",
"com_ui_save_submit": "Enregistrer et Soumettre",
"com_ui_saved": "Enregistré!",
"com_ui_saving": "Sauvegarde en cours...",
"com_ui_schema": "Schéma",
"com_ui_scope": "Périmètre",
"com_ui_search": "Rechercher",
"com_ui_seconds": "secondes",
"com_ui_secret_key": "Clé secrète",
"com_ui_select": "Sélectionner",
"com_ui_select_all": "Tout sélectionner",
"com_ui_select_file": "Sélectionner un fichier",
"com_ui_select_model": "Sélectionner un modèle",
"com_ui_select_provider": "Sélectionner un fournisseur",
@@ -711,45 +965,108 @@
"com_ui_share_create_message": "Votre nom et tout message que vous ajoutez après le partage restent privés.",
"com_ui_share_delete_error": "Une erreur est survenue lors de la suppression du lien partagé.",
"com_ui_share_error": "Une erreur est survenue lors du partage du lien de la discussion",
"com_ui_share_form_description": "Partager la description du formulaire",
"com_ui_share_link_to_chat": "Partager le lien de la discussion",
"com_ui_share_to_all_users": "Partager à tous les utilisateurs",
"com_ui_share_update_message": "Votre nom, les instructions personnalisées et tout message que vous ajoutez après le partage restent privés.",
"com_ui_share_var": "Partager {{0}}",
"com_ui_shared_link_bulk_delete_success": "Suppression réussie des liens partagés",
"com_ui_shared_link_delete_success": "Suppression réussie du lien partagé",
"com_ui_shared_link_not_found": "Lien partagé introuvable",
"com_ui_shared_prompts": "Prompts partagés",
"com_ui_shop": "Faire des achats",
"com_ui_show": "Montrer",
"com_ui_show_all": "Tout afficher",
"com_ui_show_image_details": "Montrer les informations de l'image",
"com_ui_show_qr": "Afficher le QR code",
"com_ui_sign_in_to_domain": "S'identifier à {{0}}",
"com_ui_simple": "Simple",
"com_ui_size": "Taille",
"com_ui_special_var_current_date": "Date actuelle",
"com_ui_special_var_current_datetime": "Date et heure actuelle",
"com_ui_special_var_current_user": "Utilisateur actuel",
"com_ui_special_var_iso_datetime": "Date et heure ISO UTC",
"com_ui_special_variables": "Variables spéciales : Utilisez {{current_date}} pour la date actuelle, et {{current_user}} pour le nom de votre compte donné.",
"com_ui_special_variables_more_info": "Vous pouvez sélectionner des variables spéciales dans le menu déroulant : `{{current_date}}` (date et jour de la semaine d'aujourd'hui), `{{current_datetime}}` (date et heure locales actuelles), `{{utc_iso_datetime}}` (date et heure ISO UTC actuelle), et `{{current_user}}` (votre nom d'utilisateur).",
"com_ui_speech_while_submitting": "Impossible de soumettre un message vocal pendant la génération d'une réponse",
"com_ui_sr_actions_menu": "Ouvrir le menu des actions pour \"{{0}}\"",
"com_ui_stop": "Arrêt ",
"com_ui_storage": "Stockage",
"com_ui_submit": "Soumettre",
"com_ui_teach_or_explain": "Apprendre",
"com_ui_temporary": "Message éphémère",
"com_ui_terms_and_conditions": "Conditions d'utilisation",
"com_ui_terms_of_service": "Conditions d'utilisation",
"com_ui_thinking": "Réflexion en cours...",
"com_ui_thoughts": "Pensées",
"com_ui_token": "jeton",
"com_ui_token_exchange_method": "Méthode d'échange de jetons",
"com_ui_token_url": "Adresse URL du jeton",
"com_ui_tokens": "jetons",
"com_ui_tool_collection_prefix": "Une collection d'outils de",
"com_ui_tool_info": "Informations sur l'outils",
"com_ui_tool_more_info": "Plus d'informations sur cet outil",
"com_ui_tools": "Outils",
"com_ui_travel": "Voyage",
"com_ui_trust_app": "J'ai confiance en cette application",
"com_ui_unarchive": "Désarchiver",
"com_ui_unarchive_error": "Échec du désarchivage de la conversation",
"com_ui_unknown": "Inconnu",
"com_ui_untitled": "Sans titre",
"com_ui_update": "Mettre à jour",
"com_ui_update_mcp_error": "Une erreur est survenue lors de la création ou l'actualisation du MCP.",
"com_ui_update_mcp_success": "Création ou actualisation du MCP réussie",
"com_ui_upload": "Téléverser",
"com_ui_upload_code_files": "Téléverser pour l'Interpréteur de Code",
"com_ui_upload_delay": "Le téléversement de \"{{0}}\" prend plus de temps que prévu. Veuillez patienter pendant que le fichier termine son indexation pour la récupération.",
"com_ui_upload_error": "Une erreur s'est produite lors du téléversement de votre fichier",
"com_ui_upload_file_context": "Téléverser le contexte du fichier",
"com_ui_upload_file_search": "Téléverser pour la recherche de fichiers",
"com_ui_upload_files": "Téléverser des fichiers",
"com_ui_upload_image": "Téléverser une image",
"com_ui_upload_image_input": "Téléverser une image",
"com_ui_upload_invalid": "Fichier non valide pour le téléchargement. L'image ne doit pas dépasser la limite",
"com_ui_upload_invalid_var": "Fichier non valide pour le téléchargement. L'image ne doit pas dépasser {{0}} Mo",
"com_ui_upload_ocr_text": "Téléverser en tant que texte",
"com_ui_upload_success": "Fichier téléversé avec succès",
"com_ui_upload_type": "Sélectionner le type de téléversement",
"com_ui_usage": "Utilisation",
"com_ui_use_2fa_code": "Utiliser un code 2FA à la place",
"com_ui_use_backup_code": "Utiliser un code de sauvegarde à la place",
"com_ui_use_memory": "Utiliser le Souvenir",
"com_ui_use_micrphone": "Utiliser le microphone",
"com_ui_use_prompt": "Utiliser le prompt",
"com_ui_used": "Déjà utilisé",
"com_ui_value": "Valeur",
"com_ui_variables": "Variables",
"com_ui_variables_info": "Utilisez des doubles accolades dans votre texte pour créer des variables, par exemple {{exemple de variable}}, à remplir ultérieurement lors de l'utilisation du prompt.",
"com_ui_verify": "Vérifier",
"com_ui_version_var": "Version {{0}}",
"com_ui_versions": "Versions",
"com_ui_view_memory": "Voir le Souvenir",
"com_ui_view_source": "Voir le message d'origine",
"com_ui_web_search": "Recherche web",
"com_ui_web_search_cohere_key": "Entrez la clé API de Cohere",
"com_ui_web_search_firecrawl_url": "Adresse URL de Firecrawl (optionnel)",
"com_ui_web_search_jina_key": "Entrez la clé API de Jina",
"com_ui_web_search_processing": "Traitement des résultats",
"com_ui_web_search_provider": "Fournisseur de recherches web",
"com_ui_web_search_provider_serper": "API de Serper",
"com_ui_web_search_provider_serper_key": "Obtenez votre clé API pour Serper",
"com_ui_web_search_reading": "Lecture des résultats",
"com_ui_web_search_reranker": "Classeur sémantique",
"com_ui_web_search_reranker_cohere": "Cohere",
"com_ui_web_search_reranker_cohere_key": "Obtenez votre clé API pour Cohere",
"com_ui_web_search_reranker_jina": "Jina AI",
"com_ui_web_search_reranker_jina_key": "Obtenez votre clé API pour Jina",
"com_ui_web_search_scraper": "Extracteur (scraper)",
"com_ui_web_search_scraper_firecrawl": "API de Firecrawl",
"com_ui_web_search_scraper_firecrawl_key": "Obtenez votre clé API pour Firecrawl",
"com_ui_web_searching": "Rechercher sur le web",
"com_ui_web_searching_again": "Rechercher à nouveau sur le web",
"com_ui_weekend_morning": "Joyeux week-end",
"com_ui_write": "Ecriture",
"com_ui_x_selected": "{{0}} sélectionné",
"com_ui_yes": "Oui",
"com_ui_zoom": "Zoom",
"com_user_message": "Vous"

View File

@@ -17,10 +17,18 @@
"com_agents_file_search_disabled": "יש ליצור את הסוכן לפני העלאת קבצים לחיפוש",
"com_agents_file_search_info": "כאשר הסוכן מופעל הוא יקבל מידע על שמות הקבצים המפורטים להלן, כדי שהוא יוכל לאחזר את הקשר רלוונטי.",
"com_agents_instructions_placeholder": "הוראות המערכת שבהן ישתמש הסוכן",
"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_provider_model": "אנא בחר את הספק ואת הדגם לפני יצירת הסוכן.",
"com_agents_name_placeholder": "אופציונלי: שם הסוכן",
"com_agents_no_access": "אין לך גישה לערוך את הסוכן הזה.",
"com_agents_no_agent_id_error": "לא נמצא מזהה סוכן. אנא ודא שהסוכן נוצר תחילה",
"com_agents_not_available": "הסוכן לא זמין",
"com_agents_search_info": "כאשר אפשרות זו מופעלת, היא מאפשרת לסוכן שלך לחפש מידע עדכני באינטרנט. נדרש מפתח API תקף.",
"com_agents_search_name": "חפש סוכן לפי שם",
"com_agents_update_error": "אירעה שגיאה בעדכון הסוכן שלך.",
"com_assistants_action_attempt": "הסוכן מעוניין לתקשר עם {{0}}",
@@ -61,6 +69,7 @@
"com_assistants_non_retrieval_model": "חיפוש בקבצים אינו מופעל במודל הזה. אנא בחר מודל אחר",
"com_assistants_retrieval": "אחזור",
"com_assistants_running_action": "פעולות ריצה",
"com_assistants_running_var": "{{0}} בריצה",
"com_assistants_search_name": "חפש סייען לפי שם",
"com_assistants_update_actions_error": "אירעה שגיאה ביצירה או העדכון של הפעולה.",
"com_assistants_update_actions_success": "הפעולה נוצרה או עודכנה בהצלחה",
@@ -122,6 +131,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_sign_in": "כניסה",
"com_auth_sign_up": "הירשם",
"com_auth_submit_registration": "שלח רישום",
@@ -133,6 +143,8 @@
"com_auth_username_min_length": "שם משתמש חייב להיות לפחות 2 תווים",
"com_auth_verify_your_identity": "אמת את הזהות שלך",
"com_auth_welcome_back": "ברוכים הבאים",
"com_citation_more_details": "פרטים נוספים על {{label}}",
"com_citation_source": "מקור",
"com_click_to_download": "(לחץ כאן להורדה)",
"com_download_expired": "(פג תוקף ההורדה)",
"com_download_expires": "(לחץ כאן כדי להוריד - יפוג בעוד {{0}}) ",
@@ -193,8 +205,11 @@
"com_endpoint_google_custom_name_placeholder": "הגדר שם מותאם אישית עבור Google",
"com_endpoint_google_maxoutputtokens": " המספר המרבי של אסימונים שניתן להפיק בתגובה. ציין ערך נמוך יותר עבור תגובות קצרות יותר וערך גבוה יותר עבור תגובות ארוכות יותר.",
"com_endpoint_google_temp": "ערכים גבוהים יותר = יותר אקראיים, בעוד שערכים נמוכים יותר = יותר ממוקד ודטרמיניסטי. אנו ממליצים לשנות את זה או את Top P אבל לא את שניהם.",
"com_endpoint_google_thinking": "מאפשר או מבטל את החשיבה. הגדרה זו נתמכת רק על ידי דגמים מסוימים (סדרה 2.5). עבור דגמים ישנים יותר, ייתכן שלהגדרה זו לא תהיה השפעה.",
"com_endpoint_google_thinking_budget": "מנחה את מספר טוקני החשיבה שהמודל משתמש בהם. הכמות בפועל עלולה לחרוג או להיות נמוכה מערך זה בהתאם לפרומפט.\n\nהגדרה זו נתמכת רק על ידי מודלים מסוימים (סדרת 2.5). Gemini 2.5 Pro תומך ב-128-32,768 טוקנים. Gemini 2.5 Flash תומך ב-0-24,576 טוקנים. Gemini 2.5 Flash Lite תומך ב-512-24,576 טוקנים.\n\nהשאירו ריק או הגדירו ל-\"-1\" כדי לאפשר למודל להחליט באופן אוטומטי מתי וכמה לחשוב. כברירת מחדל, Gemini 2.5 Flash Lite לא חושב.",
"com_endpoint_google_topk": "Top-k משנה את האופן שבו המודל בוחר אסימונים לפלט. Top-k של 1 פירושו שהאסימון שנבחר הוא הסביר ביותר מבין כל האסימונים באוצר המילים של הדגם (נקרא גם פענוח חמדן), בעוד ש-top-k של 3 פירושו שהאסימון הבא נבחר מבין 3 הכי הרבה. אסימונים סבירים (באמצעות טמפרטורה).",
"com_endpoint_google_topp": "Top-p משנה את האופן שבו המודל בוחר אסימונים לפלט. אסימונים נבחרים מרוב K (ראה פרמטר topK) ככל הנראה לפחות עד ה-sum של ההסתברויות שלהם שווה לערך ה-p העליון.",
"com_endpoint_google_use_search_grounding": "השתמשו בחיפוש גוגל כדי לשפר את התגובות עם תוצאות חיפוש בזמן אמת. זה מאפשר למודלים לגשת למידע עדכני ולספק תשובות מדויקות ועדכניות יותר",
"com_endpoint_instructions_assistants": "עקוף הוראות",
"com_endpoint_instructions_assistants_placeholder": "עובר את הוראות הסייען. זה שימושי לשינוי ההתנהגות על בסיס ריצה.",
"com_endpoint_max_output_tokens": "אסימוני פלט מרבי",
@@ -211,7 +226,8 @@
"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": "במודלים o1 בלבד: מגביל את מאמץ ההנמקה במודלים של הגיון. הפחתת מאמץ החשיבה יכולה לגרום לתגובות מהירות יותר ולפחות טוקנים בשימוש בהנמקה בתגובה.",
"com_endpoint_openai_reasoning_effort": "במודלים o1 ו-o3 בלבד: מגביל את מאמץ ההנמקה במודלים של הגיון. הפחתת מאמץ החשיבה יכולה לגרום לתגובות מהירות יותר ולפחות טוקנים בשימוש בהנמקה בתגובה.",
"com_endpoint_openai_reasoning_summary": "Responses API בלבד: סיכום של החשיבה שבוצעה על ידי המודל. זה יכול להיות שימושי לאיתור שגיאות ולהבנת תהליך החשיבה של המודל. אפשרויות הגדרה: ללא, אוטומטי, תמציתי, מפורט.",
"com_endpoint_openai_resend": "שלח שוב את כל התמונות שצורפו בעבר. הערה: זה יכול להגדיל משמעותית את עלות האסימונים ואתה עלול להיתקל בשגיאות עם קבצים מצורפים רבים של תמונות.",
"com_endpoint_openai_resend_files": "שלח שוב את כל הקבצים שצורפו בעבר. הערה: זה יגדיל את עלות הטוקנים, ואתה עלול להיתקל בשגיאות עם קבצים מצורפים רבים.",
"com_endpoint_openai_stop": "עד 4 רצפים שבהם ה-API יפסיק לייצר טוקנים נוספים.",
@@ -270,7 +286,8 @@
"com_error_files_upload": "אירעה שגיאה בעת העלאת הקובץ",
"com_error_files_upload_canceled": "בקשת העלאת הקובץ בוטלה. הערה: ייתכן שהעלאת הקובץ עדיין בעיבוד ותצטרך למחוק אותו בצורה ידנית.",
"com_error_files_validation": "אירעה שגיאה במהלך אימות הקובץ.",
"com_error_input_length": "מספר הטוקנים של ההודעות האחרונות גבוה מדי, והוא חורג ממגבלת האסימונים ({{0}} בהתאמה). אנא קצר את ההודעה שלך, שנה את גודל ההקשר המקסימלי בפרמטרי השיחה, או התחל שיחה חדשה.",
"com_error_heic_conversion": "המרת התמונה בפורמט HEIC לפורמט JPEG נכשלה. אנא נסה להמיר את התמונה ידנית או להשתמש בפורמט אחר.",
"com_error_input_length": "מספר הטוקנים של ההודעות האחרונות גבוה מדי, והוא חורג ממגבלת האסימונים ({{0}} בהתאמה. אנא קצר את ההודעה שלך, שנה את גודל ההקשר המקסימלי בפרמטרי השיחה, או התחל שיחה חדשה.",
"com_error_invalid_agent_provider": "המודלים של \"{{0}}\" אינם זמינים לשימוש עם סוכנים. אנא עבור להגדרות הסוכן שלך ובחר ספק הזמין כרגע.",
"com_error_invalid_user_key": "מפתח שסופק אינו חוקי. אנא ספק מפתח חוקי ונסה שוב.",
"com_error_moderation": "נראה שהתוכן שנשלח סומן על ידי מערכת הניהול שלנו בגלל שהוא אינו תואם את הנחיות הקהילה שלנו. אנחנו לא יכולים להמשיך עם הנושא הספציפי הזה. אם יש לך שאלות או נושאים אחרים שתרצה לחקור, אנא ערוך את ההודעה שלך, או צור שיחה חדשה.",
@@ -282,29 +299,51 @@
"com_files_table": "השדה חייב להכיל תוכן, הוא אינו יכול להישאר ריק",
"com_generated_files": "קבצים שנוצרו:",
"com_hide_examples": "הסתר דוגמאות",
"com_info_heic_converting": "המרת התמונה מפורמט HEIC לפורמט JPEG...",
"com_nav_2fa": "אימות דו-שלבי (2FA)",
"com_nav_account_settings": "הגדרות חשבון",
"com_nav_always_make_prod": "ייצר תמיד גרסאות חדשות",
"com_nav_archive_created_at": "תאריך ייצור",
"com_nav_archive_name": "שם",
"com_nav_archived_chats": "שיחות מארכיון",
"com_nav_archived_chats": "שיחות בארכיון",
"com_nav_at_command": "@-פקודה",
"com_nav_at_command_description": "הפקודה \"@\" משמשת כמנגנון הפעלה/החלפה של נקודות קצה, מודלים, הגדרות קבועות מראש וכו'.",
"com_nav_audio_play_error": "שגיאה בהפעלת אודיו: {{0}}",
"com_nav_audio_process_error": "שגיאה בעיבוד האודיו: {{0}}",
"com_nav_auto_scroll": "Auto-s גלול אל הכי חדש בפתיחה",
"com_nav_auto_scroll": "בפתיחת צ׳אט גלול אוטומטית להודעה האחרונה",
"com_nav_auto_send_prompts": "שליחת הנחיות (פרומפטים) אוטומטית",
"com_nav_auto_send_text": "טקסט לשליחה אוטומטית",
"com_nav_auto_send_text_disabled": "הגדר -1 כדי להשבית",
"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_auto_refill_settings": "הגדרות מילוי אוטומטי",
"com_nav_balance_day": "יום",
"com_nav_balance_days": "ימי",
"com_nav_balance_every": "כל",
"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_center_chat_input": "מרכז תיבת הצ'אט במסך ברוכים הבאים",
"com_nav_change_picture": "שנה תמונה",
"com_nav_chat_commands": "פקודות צ'אט",
"com_nav_chat_commands_info": "פקודות אלו מופעלות על ידי הקלדת תווים ספציפיים בתחילת ההודעה. כל פקודה מופעלת על ידי הקידומת המיועדת לה. אתה יכול להשבית אותם אם אתה משתמש בתווים אלה לעתים קרובות כדי להתחיל הודעות.",
"com_nav_chat_direction": "כיוונון צ'אט",
"com_nav_chat_direction": "כיוון הכתיבה",
"com_nav_clear_all_chats": "נקה את כל השיחות",
"com_nav_clear_cache_confirm_message": "האם אתה בטוח שברצונך לנקות את המטמון?",
"com_nav_clear_conversation": "נקה שיחות",
@@ -322,7 +361,6 @@
"com_nav_delete_cache_storage": "מחק אחסון מטמון TTS",
"com_nav_delete_data_info": "כל הנתונים שלך יימחקו",
"com_nav_delete_warning": "אזהרה: פעולה זו תמחק לצמיתות את חשבונך.",
"com_nav_edit_chat_badges": "עריכת תוויות צ'אט",
"com_nav_enable_cache_tts": "אפשר מטמון ב- TTS",
"com_nav_enable_cloud_browser_voice": "השתמש בקולות מבוססי ענן",
"com_nav_enabled": "מופעל",
@@ -345,7 +383,8 @@
"com_nav_font_size_xl": "גדול מאוד",
"com_nav_font_size_xs": "קט מאוד",
"com_nav_help_faq": "עזרה ושאלות נפוצות",
"com_nav_hide_panel": "הסתר לוח הצד הימני ביותר",
"com_nav_hide_panel": "הסתר את לוח הצד הימני",
"com_nav_info_balance": "היתרה מראה כמה קרדיטים של אסימונים (טוקנים) נותרו לך להשתמש. זיכויים של אסימונים מתורגמים לערך כספי (לדוגמה, 1000 זיכויים = $0.001 USD)",
"com_nav_info_code_artifacts": "אפשר הצגה של רכיבי תצוגת קוד ניסיוניים לצד הצ'אט",
"com_nav_info_code_artifacts_agent": "אפשר שימוש ברכיבי תצוגת קוד עבור סוכן זה כברירת מחדל, מתווספות הוראות נוספות ספציפיות לשימוש ברכיבי התצוגה אלא אם \"מצב הנחיה מותאם אישית\" מופעל.",
"com_nav_info_custom_prompt_mode": "כאשר אפשרות זו מופעלת, הנחיית ברירת המחדל של מערכת רכיבי תצוגה לא תיכלל. כל ההוראות ליצירת רכיבי תצוגה יהיו חייבות להינתן באופן ידני במצב זה.",
@@ -390,6 +429,8 @@
"com_nav_log_out": "צא",
"com_nav_long_audio_warning": "העיבוד של טקסטים ארוכים ייקח יותר זמן.",
"com_nav_maximize_chat_space": "הגדל את שטח הצ'אט",
"com_nav_mcp_vars_update_error": "שגיאה בעדכון משתני משתמש מותאמים אישית של MCP: {{0}}",
"com_nav_mcp_vars_updated": "משתני משתמש מותאמים אישית של MCP עודכנו בהצלחה.",
"com_nav_modular_chat": "אפשר החלפת נקודות קצה באמצע שיחה",
"com_nav_my_files": "הקבצים שלי",
"com_nav_not_supported": "לא נתמך",
@@ -404,15 +445,18 @@
"com_nav_plus_command_description": "הפעל או בטל את הפקודה '+' כדי להוסיף הגדרת תגובות מרובות",
"com_nav_profile_picture": "תמונת פרופיל",
"com_nav_save_badges_state": "שמור מצב תגים",
"com_nav_save_drafts": "שמיר את האפצה באותו מחשב",
"com_nav_save_drafts": "שמור טיוטות באופן מקומי",
"com_nav_scroll_button": "לחצן לגלילה עד הסוף",
"com_nav_search_placeholder": "חפש הודעות",
"com_nav_send_message": "שלח הודעה",
"com_nav_setting_account": "חשבון",
"com_nav_setting_balance": "לאזן",
"com_nav_setting_beta": "תכונות ביטא",
"com_nav_setting_chat": "צ'אט",
"com_nav_setting_data": "בקרות נתונים",
"com_nav_setting_general": "כללי",
"com_nav_setting_mcp": "הגדרות MCP",
"com_nav_setting_personalization": "התאמה אישית",
"com_nav_setting_speech": "דיבור",
"com_nav_settings": "הגדרות",
"com_nav_shared_links": "קישורים משותפים",
@@ -423,7 +467,7 @@
"com_nav_speech_to_text": "דיבור לטקסט",
"com_nav_stop_generating": "עצור את היצירה",
"com_nav_text_to_speech": "טקסט לדיבור",
"com_nav_theme": "נושא",
"com_nav_theme": "ערכת נושא (בהיר/כהה)",
"com_nav_theme_dark": "כהה",
"com_nav_theme_light": "אור",
"com_nav_theme_system": "מערכת",
@@ -433,7 +477,7 @@
"com_nav_tool_remove": "הסר",
"com_nav_tool_search": "כלי חיפוש",
"com_nav_user": "משתמש",
"com_nav_user_msg_markdown": "הצגת הודעות משתמש כ-Markdown",
"com_nav_user_msg_markdown": "הצגת הודעות משתמש כמרקדאון (Markdown)",
"com_nav_user_name_display": "הצג שם משתמש בהודעות",
"com_nav_voice_select": "קול",
"com_show_agent_settings": "הצג הגדרות סוכן",
@@ -445,7 +489,16 @@
"com_sidepanel_conversation_tags": "סימניות",
"com_sidepanel_hide_panel": "הסתר פאנל",
"com_sidepanel_manage_files": "נהל קבצים",
"com_sidepanel_mcp_enter_value": "הזן ערך עבור {{0}}",
"com_sidepanel_mcp_no_servers_with_vars": "אין שרתי MCP עם משתנים הניתנים להגדרה.",
"com_sidepanel_mcp_variables_for": "משתני MCP עבור {{0}}",
"com_sidepanel_parameters": "פרמטרים",
"com_sources_image_alt": "תמונת תוצאות החיפוש",
"com_sources_more_sources": "+{{count}}} מקורות",
"com_sources_tab_all": "הכל",
"com_sources_tab_images": "תמונות",
"com_sources_tab_news": "חדשות",
"com_sources_title": "מקורות",
"com_ui_2fa_account_security": "אימות דו-שלבי מוסיף שכבת אבטחה נוספת לחשבון שלך",
"com_ui_2fa_disable": "השבת אימות דו-שלבי (2FA)",
"com_ui_2fa_disable_error": "התרחשה שגיאה בעת ביטול האימות הדו-שלבי",
@@ -457,9 +510,13 @@
"com_ui_2fa_setup": "הגדר אימות דו-שלבי (2FA)",
"com_ui_2fa_verified": "האימות הדו-שלבי אומת בהצלחה",
"com_ui_accept": "אני מקבל",
"com_ui_action_button": "לחצן פעולה",
"com_ui_add": "הוסף",
"com_ui_add_mcp": "הוסף MCP",
"com_ui_add_mcp_server": "הוסף שרת MCP",
"com_ui_add_model_preset": "הוספת מודל או הגדרה קבועה לתגובה נוספת",
"com_ui_add_multi_conversation": "הוספת תמיכה בשיחות מרובות",
"com_ui_adding_details": "הוספת פרטים",
"com_ui_admin": "אדמין",
"com_ui_admin_access_warning": "השבתת גישת המנהל לתכונה זו עלולה לגרום לבעיות בלתי צפויות בממשק המשתמש שידרשו רענון. אם השינוי נשמר, הדרך היחידה להחזיר את ההגדרה היא דרך הגדרת הממשק בקובץ librechat.yaml, שמשפיעה על כל התפקידים.",
"com_ui_admin_settings": "הגדרות אדמין",
@@ -478,6 +535,20 @@
"com_ui_agent_recursion_limit_info": "מגביל את מספר השלבים שהסוכן יכול לבצע בריצה לפני מתן תגובה סופית. ברירת המחדל היא 25 שלבים. שלב הוא בקשת API של בינה מלאכותית או סבב שימוש בכלי. לדוגמה, אינטראקציה בסיסית עם כלי לוקחת 3 שלבים: בקשה ראשונית, שימוש בכלי, ובקשת המשך.",
"com_ui_agent_shared_to_all": "השדה חייב להכיל תוכן, אי אפשר להשאיר אותו ריק",
"com_ui_agent_var": "{{0}} סוכנים",
"com_ui_agent_version": "גרסה",
"com_ui_agent_version_active": "גרסת הפעלה",
"com_ui_agent_version_duplicate": "זוהתה גרסה כפולה, פעולה זו תיצור גרסה זהה לגרסה {{versionIndex}}.",
"com_ui_agent_version_empty": "אין גרסאות זמינות",
"com_ui_agent_version_error": "שגיאה באחזור גרסאות",
"com_ui_agent_version_history": "היסטוריית גרסאות",
"com_ui_agent_version_no_agent": "לא נבחר סוכן. אנא בחר סוכן כדי להציג את היסטוריית הגרסאות.",
"com_ui_agent_version_no_date": "תאריך לא זמין",
"com_ui_agent_version_restore": "לשחזר",
"com_ui_agent_version_restore_confirm": "האם אתה בטוח שברצונך לשחזר גרסה זו?",
"com_ui_agent_version_restore_error": "שחזור הגרסה נכשל",
"com_ui_agent_version_restore_success": "הגרסה שוחזרה בהצלחה",
"com_ui_agent_version_title": "גרסה {{versionNumber}}",
"com_ui_agent_version_unknown_date": "תאריך לא ידוע",
"com_ui_agents": "סוכנים",
"com_ui_agents_allow_create": "אפשר יצירת סוכנים",
"com_ui_agents_allow_share_global": "אפשר שיתוף סוכנים לכל המשתמשים",
@@ -487,7 +558,7 @@
"com_ui_analyzing": "ניתוח",
"com_ui_analyzing_finished": "סיים ניתוח",
"com_ui_api_key": "מפתח API",
"com_ui_archive": "ארכיון",
"com_ui_archive": "לארכיון",
"com_ui_archive_delete_error": "מחיקת השיחה מהארכיון נכשלה",
"com_ui_archive_error": "אירעה שגיאה באירכוב השיחה",
"com_ui_artifact_click": "לחץ לפתיחה",
@@ -504,14 +575,17 @@
"com_ui_attach_error_openai": "לא ניתן לצרף את קבצי הסייען לנקודות קצה אחרות",
"com_ui_attach_error_size": "חרגת ממגבלת גודל הקובץ עבור נקודת הקצה:",
"com_ui_attach_error_type": "סוג קובץ לא נתמך עבור נקודת קצה:",
"com_ui_attach_remove": "הסר קובץ",
"com_ui_attach_remove": "הסר את הקובץ",
"com_ui_attach_warn_endpoint": "עשוי להתעלם מקבצים שאינם של הסייען שאין להם כלי תואם",
"com_ui_attachment": "קובץ מצורף",
"com_ui_auth_type": "סוג אישור",
"com_ui_auth_url": "כתובת URL לאימות הרשאה",
"com_ui_authentication": "אימות",
"com_ui_authentication_type": "סוג אימות",
"com_ui_auto": "אוטומטי",
"com_ui_available_tools": "כלים זמינים",
"com_ui_avatar": "אווטאר",
"com_ui_back": "חזור",
"com_ui_back_to_chat": "חזור לצ'אט",
"com_ui_back_to_prompts": "חזור להנחיות (פרומפטים)",
"com_ui_backup_codes": "קודי גיבוי",
@@ -541,6 +615,7 @@
"com_ui_bulk_delete_error": "מחיקת קישורים משותפים נכשלה",
"com_ui_callback_url": "כתובת URL להחזרת המידע",
"com_ui_cancel": "בטל",
"com_ui_cancelled": "בוטל",
"com_ui_category": "קָטֵגוֹרִיָה",
"com_ui_chat": "צ'אט",
"com_ui_chat_history": "נקה היסטוריה",
@@ -550,11 +625,13 @@
"com_ui_client_secret": "ב",
"com_ui_close": "סגור",
"com_ui_close_menu": "סגור תפריט",
"com_ui_close_window": "סגור חלון",
"com_ui_code": "קוד",
"com_ui_collapse_chat": "כווץ צ'אט",
"com_ui_command_placeholder": "אופציונלי: הזן פקודה להנחיה (פרומפט), או שיעשה שימוש בשם",
"com_ui_command_usage_placeholder": "בחר הנחיה (פרומפט) לפי פקודה או שם",
"com_ui_complete_setup": "ההגדרה הושלמה",
"com_ui_configure_mcp_variables_for": "הגדרת משתנים עבור {{0}}",
"com_ui_confirm_action": "אשר פעולה",
"com_ui_confirm_admin_use_change": "שינוי הגדרה זו יחסום גישה למנהלים, כולל אותך. האם אתה בטוח שברצונך להמשיך?",
"com_ui_confirm_change": "אשר את השינוי",
@@ -569,7 +646,10 @@
"com_ui_copy_to_clipboard": "העתק ללוח",
"com_ui_create": "צור",
"com_ui_create_link": "צור קישור",
"com_ui_create_memory": "צור זכרון",
"com_ui_create_prompt": "צור הנחיה (פרומפט)",
"com_ui_creating_image": "יוצר תמונה. ייתכן שייקח מספר רגעים.",
"com_ui_current": "נוכחי",
"com_ui_currently_production": "נוצר עכשיו",
"com_ui_custom": "מותאם אישית",
"com_ui_custom_header_name": "שם כותרת מותאם אישית",
@@ -602,13 +682,20 @@
"com_ui_delete_confirm": "זה ימחק",
"com_ui_delete_confirm_prompt_version_var": "פעולה זו תמחק את הגרסה שנבחרה עבור \"{{0}}\". אם לא קיימות גרסאות נוספות, ההנחיה תימחק.",
"com_ui_delete_conversation": "למחוק את השיחה (צאט)?",
"com_ui_delete_mcp": "מחק MCP",
"com_ui_delete_mcp_confirm": "האם אתה בטוח שברצונך למחוק את שרת ה-MCP הזה?",
"com_ui_delete_mcp_error": "מחיקת שרת MCP נכשלה",
"com_ui_delete_mcp_success": "שרת ה-MCP נמחק בהצלחה",
"com_ui_delete_memory": "מחק זיכרון",
"com_ui_delete_prompt": "מחק הנחיה (פרומפט)",
"com_ui_delete_shared_link": "מחק קישור שיתוף",
"com_ui_delete_tool": "מחק כלי",
"com_ui_delete_tool_confirm": "האת אתה בטוח שאתה רוצה למחוק את הכלי הזה?",
"com_ui_deleted": "נמחק",
"com_ui_descending": "תיאור",
"com_ui_description": "תיאור",
"com_ui_description_placeholder": "אופציונלי: הזן תיאור שיוצג עבור ההנחיה (פרומפט)",
"com_ui_deselect_all": "בטל את הבחירה של הכל",
"com_ui_disabling": "מבטל הפעלה...",
"com_ui_download": "הורדות",
"com_ui_download_artifact": "רכיב תצוגת הורדות",
@@ -623,21 +710,46 @@
"com_ui_duplication_processing": "משכפל את השיחה...",
"com_ui_duplication_success": "השיחה שוכפלה בהצלחה",
"com_ui_edit": "ערוך",
"com_ui_edit_editing_image": "עורך את התמונה",
"com_ui_edit_mcp_server": "עריכת שרת MCP",
"com_ui_edit_memory": "ערוך זכרון",
"com_ui_empty_category": "-",
"com_ui_endpoint": "נקודת קצה",
"com_ui_endpoint_menu": "תפריט נקודת קצה LLM",
"com_ui_enter": "Enter",
"com_ui_enter_api_key": "הכנס מפתח API",
"com_ui_enter_key": "מקש Enter",
"com_ui_enter_openapi_schema": "הזן כאן את סכימת OpenAPI שלך",
"com_ui_enter_value": "הזן ערך",
"com_ui_error": "שגיאה",
"com_ui_error_connection": "שגיאה בחיבור לשרת, נסה לרענן את הדף",
"com_ui_error_save_admin_settings": "אירעה שגיאה בשמירת הגדרות הניהול שלך",
"com_ui_error_updating_preferences": "אירעה שגיאה בעדכון העדפות",
"com_ui_examples": "דוגמאות",
"com_ui_expand_chat": "הרחב צ'אט",
"com_ui_export_convo_modal": "חלון ייצוא שיחה",
"com_ui_feedback_more": "יותר...",
"com_ui_feedback_more_information": "ספק משוב נוסף",
"com_ui_feedback_negative": "טעון שיפור",
"com_ui_feedback_placeholder": "אנא ספק כאן כל משוב נוסף",
"com_ui_feedback_positive": "אוהב את זה",
"com_ui_feedback_tag_accurate_reliable": "מדויק ואמין",
"com_ui_feedback_tag_attention_to_detail": "תשומת לב לפרטים",
"com_ui_feedback_tag_bad_style": "סגנון גרוע",
"com_ui_feedback_tag_clear_well_written": "כתוב ברור ומדויק",
"com_ui_feedback_tag_creative_solution": "פתרון יצירתי",
"com_ui_feedback_tag_inaccurate": "תשובה לא מדויקת או שגויה",
"com_ui_feedback_tag_missing_image": "ציפיתי לתמונה",
"com_ui_feedback_tag_not_helpful": "חסר מידע שימושי",
"com_ui_feedback_tag_not_matched": "לא מתאים לבקשה שלי",
"com_ui_feedback_tag_other": "בעיות אחרות",
"com_ui_feedback_tag_unjustified_refusal": "סורב ללא סיבה",
"com_ui_field_required": "שדה זה נדרש",
"com_ui_file_size": "גודל הקובץ",
"com_ui_files": "קבצים",
"com_ui_filter_prompts": "סינון הנחיות (פרומפטים)",
"com_ui_filter_prompts_name": "סינון הנחיות (פרומפטים) לפי שם",
"com_ui_final_touch": "גימור סופי",
"com_ui_finance": "פיננסי",
"com_ui_fork": "הסתעפות",
"com_ui_fork_all_target": "כלול את כל ההודעות שנשלחו/התקבלו מכאן.",
@@ -667,6 +779,8 @@
"com_ui_generate_backup": "צור קודי גיבוי",
"com_ui_generate_qrcode": "צור קוד QR",
"com_ui_generating": "יוצר...",
"com_ui_generation_settings": "הגדרות יצירה",
"com_ui_getting_started": "תחילת העבודה",
"com_ui_global_group": "שדה זה לא יכול להישאר ריק",
"com_ui_go_back": "חזור",
"com_ui_go_to_conversation": "חזור לצ'אט",
@@ -674,9 +788,14 @@
"com_ui_good_evening": "ערב ",
"com_ui_good_morning": "ערב טוב",
"com_ui_happy_birthday": "זה יום ההולדת הראשון שלי!",
"com_ui_hide_image_details": "הסתר פרטי תמונה",
"com_ui_hide_qr": "הסתר קוד QR",
"com_ui_host": "מארח",
"com_ui_icon": "אייקון",
"com_ui_idea": "רעיונות",
"com_ui_image_created": "התמונה נוצרה",
"com_ui_image_details": "פרטי התמונה",
"com_ui_image_edited": "התמונה נערכה",
"com_ui_image_gen": "מחולל תמונות",
"com_ui_import": "ייבוא",
"com_ui_import_conversation_error": "אירעה שגיאה בעת ייבוא השיחות שלך",
@@ -684,9 +803,9 @@
"com_ui_import_conversation_info": "ייבא שיחות מקובץ JSON",
"com_ui_import_conversation_success": "השיחות יובאו בהצלחה",
"com_ui_include_shadcnui": "יש לכלול הוראות לשימוש ברכיבי ממשק המשתמש של shadcn/ui",
"com_ui_include_shadcnui_agent": "יש לכלול הוראות שימוש ב-shadcn/ui",
"com_ui_input": "קלט",
"com_ui_instructions": "הוראות",
"com_ui_key": "מפתח",
"com_ui_late_night": "לילה טוב",
"com_ui_latest_footer": "גישה לכל הבינות המלאכותיות (AI) לכולם",
"com_ui_latest_production_version": "גרסת הפיתוח העדכנית ביותר",
@@ -699,7 +818,25 @@
"com_ui_logo": "\"לוגו {{0}}\"",
"com_ui_manage": "נהל",
"com_ui_max_tags": "המספר המקסימלי המותר על פי הערכים העדכניים הוא {{0}}.",
"com_ui_mcp_dialog_desc": "אנא הזן למטה את המידע הדרוש",
"com_ui_mcp_enter_var": "הזן ערך עבור {{0}}",
"com_ui_mcp_server_not_found": "נשרת לא נמצא",
"com_ui_mcp_servers": "שרתי MCP",
"com_ui_mcp_url": "קישור לשרת ה-MCP",
"com_ui_memories": "זכרונות",
"com_ui_memories_allow_create": "אפשר יצירת זיכרונות",
"com_ui_memories_allow_opt_out": "אפשר למשתמשים לבטל את הזיכרונות",
"com_ui_memories_allow_read": "אפשר קריאת זיכרונות",
"com_ui_memories_allow_update": "אפשר עדכון זיכרונות",
"com_ui_memories_allow_use": "אפשר שימוש בזיכרונות",
"com_ui_memories_filter": "סינון זיכרונות...",
"com_ui_memory": "זכרון",
"com_ui_memory_created": "הזיכרון נוצר בהצלחה",
"com_ui_memory_deleted": "הזיכרון נמחק",
"com_ui_memory_deleted_items": "זכרונות שנמחקו",
"com_ui_memory_key_exists": "זיכרון עם מפתח זה כבר קיים. אנא השתמש במפתח אחר.",
"com_ui_memory_updated": "זיכרון שמור מעודכן",
"com_ui_memory_updated_items": "זיכרונות מעודכנים",
"com_ui_mention": "ציין נקודת קצה, סייען, או הנחייה (פרופמט) כדי לעבור אליה במהירות",
"com_ui_min_tags": "לא ניתן למחוק ערכים נוספים, יש צורך במינימום {{0}} ערכים.",
"com_ui_misc": "כללי",
@@ -718,16 +855,29 @@
"com_ui_no_category": "אין קטגוריה",
"com_ui_no_changes": "אין שינויים לעדכן",
"com_ui_no_data": "השדה חייב להכיל תוכן, הוא לא יכול להישאר ריק",
"com_ui_no_personalization_available": "אין אפשרויות התאמה אישית זמינות כרגע",
"com_ui_no_read_access": "אין לך הרשאה לצפות בזיכרונות",
"com_ui_no_terms_content": "אין תוכן תנאים והגבלות להצגה",
"com_ui_no_valid_items": "השדה חייב להכיל תוכן, הוא לא יכול להישאר ריק",
"com_ui_none": "אף אחד",
"com_ui_not_used": "לא בשימוש",
"com_ui_nothing_found": "לא נמצא",
"com_ui_oauth": "פרוטוקול אימות פתוח (OAuth)",
"com_ui_oauth_connected_to": "מחובר ל",
"com_ui_oauth_error_callback_failed": "ניסיון האימות החוזר נכשל. אנא נסה שוב.",
"com_ui_oauth_error_generic": "האימות נכשל. אנא נסה שוב.",
"com_ui_oauth_error_invalid_state": "פרמטר המצב (state) אינו תקין. אנא נסו שוב",
"com_ui_oauth_error_missing_code": "פרמטר המצב (state) חסר. אנא נסה שוב.",
"com_ui_oauth_error_missing_state": "פרמטר המצב (state) חסר. אנא נסה שוב.",
"com_ui_oauth_error_title": "האימות נכשל",
"com_ui_oauth_success_description": "האימות בוצע בהצלחה. חלון זה ייסגר בעוד",
"com_ui_oauth_success_title": "האימות בוצע בהצלחה",
"com_ui_of": "של",
"com_ui_off": "של",
"com_ui_on": "פעיל",
"com_ui_optional": "(אופציונלי)",
"com_ui_page": "עמוד",
"com_ui_preferences_updated": "ההעדפות עודכנו בהצלחה",
"com_ui_prev": "הקודם",
"com_ui_preview": "תצוגה מקדימה",
"com_ui_privacy_policy": "מדיניות פרטיות",
@@ -745,8 +895,11 @@
"com_ui_prompts_allow_share_global": "אפשר שיתוף הנחיות (פרומפטים) עם כל המשתמשים",
"com_ui_prompts_allow_use": "אפשר שימוש בהנחיות (פרומפטים)",
"com_ui_provider": "ספק",
"com_ui_quality": "איכות",
"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": "צור קודי גיבוי מחדש",
@@ -758,6 +911,7 @@
"com_ui_rename_prompt": "שנה שם הנחיה (פרומפט)",
"com_ui_requires_auth": "נדרש אימות",
"com_ui_reset_var": "איפוס {{0}}",
"com_ui_reset_zoom": "איפוס זום",
"com_ui_result": "תוצאה",
"com_ui_revoke": "בטל",
"com_ui_revoke_info": "בטל את כל האישורים שסופקו על ידי המשתמש",
@@ -767,17 +921,20 @@
"com_ui_revoke_keys_confirm": "האם אתה בטוח שברצונך לבטל את כל המפתחות?",
"com_ui_role_select": "תפקיד",
"com_ui_roleplay": "משחק תפקידים",
"com_ui_run_code": "הרץ קו",
"com_ui_run_code": "הרץ קוד",
"com_ui_run_code_error": "אירעה שגיאה בהרצת הקוד",
"com_ui_save": "שמור",
"com_ui_save_badge_changes": "האם לשמור את השינויים בתגים?",
"com_ui_save_submit": "שמור ושלח",
"com_ui_saved": "שמור!",
"com_ui_saving": "שומר...",
"com_ui_schema": "סכמה",
"com_ui_scope": "תחום",
"com_ui_search": "חיפוש",
"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_provider": "בחר ספק",
@@ -803,6 +960,7 @@
"com_ui_shop": "קניות",
"com_ui_show": "הצג",
"com_ui_show_all": "הראה הכל",
"com_ui_show_image_details": "הצג את פרטי התמונה",
"com_ui_show_qr": "הראה קוד QR",
"com_ui_sign_in_to_domain": "היכנס אל {{0}}",
"com_ui_simple": "פשוט",
@@ -824,15 +982,23 @@
"com_ui_terms_of_service": "תנאי השירות",
"com_ui_thinking": "חושב...",
"com_ui_thoughts": "מחשבות",
"com_ui_token": "טוקן",
"com_ui_token_exchange_method": "שיטת החלפת טוקנים",
"com_ui_token_url": "קישור URL לטוקן",
"com_ui_tokens": "טוקנים",
"com_ui_tool_collection_prefix": "אוסף כלים מבית",
"com_ui_tool_info": "מידע על הכלי",
"com_ui_tool_more_info": "יותר מידע אודות הכלי הזה",
"com_ui_tools": "כלים",
"com_ui_travel": "מסע",
"com_ui_unarchive": "לארכיון",
"com_ui_unarchive_error": "אירעה שגיאה בארכיון השיחה",
"com_ui_trust_app": "אני סומך על האפליקציה הזו",
"com_ui_unarchive": "הוצא מהארכיון",
"com_ui_unarchive_error": "הוצאת השיחה מהארכיון נכשלה",
"com_ui_unknown": "לא ידוע",
"com_ui_untitled": "ללא כותרת",
"com_ui_untitled": "ללא כותר",
"com_ui_update": "עדכון",
"com_ui_update_mcp_error": "אירעה שגיאה ביצירה או עדכון של ה-MCP.",
"com_ui_update_mcp_success": "ה-MCP נוצר או עודכן בהצלחה",
"com_ui_upload": "העלה",
"com_ui_upload_code_files": "העלאה עבור מפענח הקוד",
"com_ui_upload_delay": "העלאת \"{{0}}\" לוקחת יותר זמן מהצפוי. אנא המתן בזמן שהקובץ מסיים את האינדוקס לאחזור.",
@@ -847,17 +1013,35 @@
"com_ui_upload_ocr_text": "העלה קובץ כקובץ טקסט",
"com_ui_upload_success": "הקובץ הועלה בהצלחה",
"com_ui_upload_type": "בחר סוג העלאה",
"com_ui_usage": "ניצול",
"com_ui_use_2fa_code": "השתמש בקוד אימות דו-שלבי (2FA) במקום",
"com_ui_use_backup_code": "השתמש בקוד גיבוי במקום",
"com_ui_use_memory": "השתמש בזיכרון",
"com_ui_use_micrphone": "שימוש במיקורפון",
"com_ui_use_prompt": "השתמש בהנחיה (פרומפט)",
"com_ui_used": "נוצל",
"com_ui_value": "ערך",
"com_ui_variables": "משתנים",
"com_ui_variables_info": "השתמש בסוגריים מסולסלות כפולות בטקסט שלך ליצירת משתנים, לדוגמא `{{example variable}}`, כדי למלא אותם מאוחר יותר בשימוש בהנחיה.",
"com_ui_verify": "אמת",
"com_ui_version_var": "גרסה {{0}}",
"com_ui_versions": "גרסה",
"com_ui_view_memory": "הצג זיכרון",
"com_ui_view_source": "הצג צ'אט מקורי",
"com_ui_web_search": "חיפוש ברשת",
"com_ui_web_search_cohere_key": "הכנס מפתח API של Cohere",
"com_ui_web_search_firecrawl_url": "כתובת URL של ממשק ה-API של Firecrawl (אופציונלי)",
"com_ui_web_search_jina_key": "הזן את מפתח ה-API של Jina",
"com_ui_web_search_processing": "עיבוד התוצאות",
"com_ui_web_search_provider": "ספק החיפוש",
"com_ui_web_search_provider_serper": "ממשק ה-API של Serper",
"com_ui_web_search_provider_serper_key": "קבל מפתח API של Serper ",
"com_ui_web_search_reading": "קריאת התוצאות",
"com_ui_web_search_reranker_cohere_key": "קבל מפתח API של Cohere",
"com_ui_web_search_reranker_jina_key": "קבל מפתח API של Jina",
"com_ui_web_search_scraper_firecrawl_key": "קבל מפתח API של Firecrawl",
"com_ui_web_searching": "חיפוש ברשת",
"com_ui_web_searching_again": "חיפוש נוסף ברשת",
"com_ui_weekend_morning": "סוף שבוע נעים!",
"com_ui_write": "כתיבה",
"com_ui_x_selected": "{{0}} נבחר",

View File

@@ -322,7 +322,6 @@
"com_nav_delete_cache_storage": "TTS gyorsítótár tárolásának törlése",
"com_nav_delete_data_info": "Minden adata törlésre kerül.",
"com_nav_delete_warning": "FIGYELEM: Ez véglegesen törli fiókját.",
"com_nav_edit_chat_badges": "Csevegőjelvények szerkesztése",
"com_nav_enable_cache_tts": "TTS gyorsítótár engedélyezése",
"com_nav_enable_cloud_browser_voice": "Felhőalapú hangok használata",
"com_nav_enabled": "Engedélyezve",
@@ -679,7 +678,6 @@
"com_ui_import_conversation_info": "Beszélgetések importálása JSON fájlból",
"com_ui_import_conversation_success": "Beszélgetések sikeresen importálva",
"com_ui_include_shadcnui": "shadcn/ui komponensek utasításainak belefoglalása",
"com_ui_include_shadcnui_agent": "shadcn/ui utasítások belefoglalása",
"com_ui_input": "Bevitel",
"com_ui_instructions": "Utasítások",
"com_ui_late_night": "Kellemes éjszakát",

View File

@@ -0,0 +1,169 @@
{
"chat_direction_left_to_right": "այստեղ ինչ-որ բան պետք է ավելացնել։ դատարկ էր։",
"chat_direction_right_to_left": "այստեղ ինչ-որ բան պետք է ավելացնել։ դատարկ էր։",
"com_a11y_ai_composing": "AI-ն դեռ շարունակում է գրել։",
"com_a11y_end": "AI-ն ավարտեց իր պատասխանը։",
"com_a11y_start": "AI-ն սկսել է պատասխանը։",
"com_agents_allow_editing": "Թույլ տալ այլ օգտատերերին խմբագրել ձեր գործակալին",
"com_agents_by_librechat": "LibreChat-ի կողմից",
"com_agents_code_interpreter": "Միացված լինելու դեպքում, թույլ է տալիս ձեր գործակալին օգտագործել LibreChat Code Interpreter API-ը՝ ստեղծված կոդը, այդ թվում՝ ֆայլերի մշակումը, անվտանգ կերպով գործարկելու համար: Պահանջվում է վավեր API բանալի:",
"com_agents_code_interpreter_title": "Կոդի մեկնաբանիչ API",
"com_agents_create_error": "Ձեր գործակալը ստեղծելիս սխալ է տեղի ունեցել։",
"com_agents_description_placeholder": "Կամընտրական: Այստեղ նկարագրեք ձեր գործակալին",
"com_agents_enable_file_search": "Միացնել ֆայլերի որոնումը",
"com_agents_file_context": "Ֆայլի ճանաչում (OCR)",
"com_agents_file_context_disabled": "Գործակալը պետք է ստեղծվի ֆայլերը վերբեռնելուց առաջ ֆայլերի ճանաչման համար:",
"com_agents_mcp_icon_size": "Նվազագույն չափը՝ 128 x 128 px",
"com_agents_mcp_name_placeholder": "Անհատական գործիք",
"com_agents_name_placeholder": "Կամընտրական: Գործակալի անունը",
"com_agents_no_access": "Դուք իրավունք չունեք խմբագրելու այս գործակալը։",
"com_agents_not_available": "Գործակալը հասանելի չէ",
"com_agents_search_name": "Որոնել գործակալներին ըստ անվան",
"com_assistants_actions": "Գործողություններ",
"com_assistants_add_actions": "Ավելացնել գործողություններ",
"com_assistants_add_tools": "Ավելացնել գործիքներ",
"com_assistants_allow_sites_you_trust": "Թույլ տալ միայն այն կայքերը, որոնց վստահում եք։",
"com_assistants_append_date": "Ավելացնել ընթացիկ ամսաթիվը և ժամը",
"com_assistants_available_actions": "Հասանելի գործողություններ",
"com_assistants_capabilities": "Հնարավորություններ",
"com_assistants_code_interpreter": "Կոդի մեկնաբանիչ",
"com_assistants_conversation_starters": "Զրույցի մեկնարկներ",
"com_assistants_conversation_starters_placeholder": "Մուտքագրեք զրույցի սկիզբը",
"com_assistants_create_success": "Հաջողությամբ ստեղծվեց",
"com_assistants_file_search": "Ֆայլերի որոնում",
"com_assistants_image_vision": "Պատկերի տեսողություն",
"com_assistants_knowledge": "Գիտելիքներ",
"com_assistants_name_placeholder": "Կամընտրական: Օգնականի անունը",
"com_assistants_retrieval": "Տվյալների ստացում",
"com_assistants_running_action": "Գործողությունը կատարվում է",
"com_assistants_search_name": "Որոնել օգնականներին ըստ անունի",
"com_assistants_update_success": "Հաջողությամբ թարմացվեց",
"com_auth_already_have_account": "Արդեն ունե՞ք ակաունթ։",
"com_auth_apple_login": "Մուտք գործել Apple-ի միջոցով",
"com_auth_back_to_login": "Վերադառնալ մուտքագրման էջ",
"com_auth_click": "Սեղմեք",
"com_auth_click_here": "Սեղմեք այստեղ",
"com_auth_continue": "Շարունակել",
"com_auth_create_account": "Ստեղծեք ձեր ակաունթը",
"com_auth_email": "Էլ․ հասցե",
"com_auth_email_address": "Էլ․ հասցե",
"com_auth_email_required": "Էլ․ հասցեն պարտադիր է",
"com_auth_email_resend_link": "Ուղարկել էլ․ հասցեն կրկին",
"com_auth_email_verification_failed": "Էլ․ փոստի հաստատումը ձախողվեց",
"com_auth_email_verification_success": "\"Էլ․ հասցեն հաջողությամբ հաստատվեց",
"com_auth_email_verifying_ellipsis": "Ստուգվում է...",
"com_auth_facebook_login": "Շարունակել Facebook-ի միջոցով",
"com_auth_full_name": "Լրիվ անուն",
"com_auth_github_login": "Շարունակել Github-ի միջոցով",
"com_auth_google_login": "Շարունակել Google-ի միջոցով",
"com_auth_here": "ԱՅՍՏԵՂ",
"com_auth_login": "Մուտք",
"com_auth_name_required": "Անունը պարտադիր է",
"com_auth_no_account": "Դեռ օգտվող չե՞ք։",
"com_auth_password": "Գաղտնաբառ",
"com_auth_password_confirm": "Հաստատել գաղտնաբառը",
"com_auth_password_forgot": "Մոռացե՞լ եք գաղտնաբառը",
"com_auth_password_not_match": "Գաղտնաբառերը չեն համընկնում",
"com_auth_password_required": "Գաղտնաբառը պարտադիր է",
"com_auth_registration_success_insecure": "Գրանցումն ավարտվել է հաջողությամբ։",
"com_auth_reset_password": "Վերականգնել գաղտնաբառը",
"com_auth_reset_password_link_sent": "Էլ․ նամակը ուղարկվել է",
"com_auth_reset_password_success": "Գաղտնաբառը հաջողությամբ վերականգնվեց",
"com_auth_sign_in": "Մուտք գործել",
"com_auth_sign_up": "Գրանցվել",
"com_auth_to_reset_your_password": "գաղտնաբառը վերականգնելու համար։",
"com_auth_to_try_again": "նորից փորձելու համար։",
"com_auth_username": "Օգտանուն (կամընտրական)",
"com_auth_welcome_back": "Բարի վերադարձ",
"com_citation_more_details": "Լրացուցիչ մանրամասներ {{label}}-ի մասին",
"com_citation_source": "Աղբյուր",
"com_click_to_download": "(սեղմեք այստեղ ներբեռնելու համար)",
"com_download_expired": "(ներբեռնելու ժամկետը սպառվել է)",
"com_download_expires": "(սեղմեք այստեղ ներբեռնելու համար կսպառվի {{0}} հետո)",
"com_endpoint": "Endpoint",
"com_endpoint_agent": "Գործակալ",
"com_endpoint_agent_placeholder": "Խնդրում ենք ընտրել գործակալին",
"com_endpoint_ai": "AI",
"com_endpoint_assistant": "Օգնական",
"com_endpoint_assistant_model": "Օգնականի մոդել",
"com_endpoint_completion": "Լրացում",
"com_endpoint_completion_model": "Լրացման մոդել (նախընտրելի է՝ GPT-4)",
"com_endpoint_config_click_here": "Սեղմեք այստեղ",
"com_endpoint_config_google_api_key": "Google API Key",
"com_endpoint_config_google_cloud_platform": "(Google Cloud Platform-ից)",
"com_endpoint_config_google_gemini_api": "(Gemini API)",
"com_endpoint_config_google_service_key": "Google Service Account Key",
"com_endpoint_config_key": "Մուտքագրեք API key-ը",
"com_endpoint_config_key_encryption": "Ձեր բանալին կգաղտնագրվի և կհեռացվի",
"com_endpoint_config_key_for": "Մուտքագրեք API key-ը՝",
"com_endpoint_config_key_google_need_to": "Դուք պետք է",
"com_endpoint_config_key_google_service_account": "Ստեղծել ծառայողական օգտահաշիվ",
"com_endpoint_config_key_google_vertex_ai": "Միացնել Vertex AI-ը",
"com_endpoint_config_key_name": "Key",
"com_endpoint_config_key_never_expires": "Ձեր key-ը երբեք չի սպառվի",
"com_endpoint_custom_name": "Անհատական անուն",
"com_endpoint_deprecated": "Հնացած",
"com_endpoint_examples": "Օրինակ",
"com_endpoint_export": "Արտահանել",
"com_endpoint_export_share": "Արտահանել/Կիսվել",
"com_endpoint_message": "Հաղորդագրություն",
"com_endpoint_message_new": "Հաղորդագրություն {{0}}",
"com_endpoint_my_preset": "Իմ պրեսեթը",
"com_endpoint_open_menu": "Բացել մենյուն",
"com_endpoint_output": "Ելք",
"com_endpoint_preset": "պրեսեթ",
"com_endpoint_preset_default_item": "Սկզբնական",
"com_endpoint_preset_default_none": "Սկզբնական նախադրված պրեսեթը ակտիվ չէ։",
"com_endpoint_preset_import": "Պրեսեթը ներմուծվեց։",
"com_endpoint_preset_name": "Պրեսեթի անուն",
"com_endpoint_preset_selected": "Պրեսեթը ակտիվ է։",
"com_endpoint_preset_selected_title": "Ակտիվ է։",
"com_endpoint_preset_title": "Պրեսեթ",
"com_endpoint_presets": "պրեսեթներ",
"com_endpoint_save_as_preset": "Պահպանել որպես պրեսեթ",
"com_endpoint_temperature": "Temperature",
"com_error_files_dupe": "Հայտնաբերվել է կրկնվող ֆայլ։",
"com_hide_examples": "Թաքցնել օրինակները",
"com_nav_2fa": "Երկուփուլային նույնականացում (2FA)",
"com_nav_account_settings": "Ակաունթի կարգավորումներ",
"com_nav_archive_name": "Անուն",
"com_nav_balance": "Հաշվեկշիռ",
"com_nav_balance_every": "Ամեն",
"com_nav_balance_refill_amount": "Լիցքավորման գումարը՝",
"com_nav_browser": "Բրաուզեր",
"com_nav_close_sidebar": "Փակել կողային վահանակը",
"com_nav_commands": "Հրամաններ",
"com_nav_delete_account": "Ջնջել ակաունթը",
"com_nav_delete_account_confirm": "Ջնջե՞լ ակաունթը։ Հաստատո՞ւմ եք։",
"com_nav_enabled": "Միացված է",
"com_nav_export": "Արտահանել",
"com_nav_font_size_base": "Միջին",
"com_nav_font_size_lg": "Մեծ",
"com_nav_font_size_sm": "Փոքր",
"com_nav_font_size_xl": "Շատ մեծ",
"com_nav_font_size_xs": "Շատ փոքր",
"com_nav_lang_arabic": "العربية",
"com_nav_lang_armenian": "Հայերեն",
"com_nav_lang_catalan": "Català",
"com_nav_lang_chinese": "中文",
"com_nav_lang_czech": "Čeština",
"com_nav_lang_danish": "Dansk",
"com_nav_lang_english": "English",
"com_nav_lang_french": "Français",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_persian": "فارسی",
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_uyghur": "Uyƣur tili",
"com_nav_lang_vietnamese": "Tiếng Việt",
"com_nav_language": "Լեզու",
"com_nav_log_out": "Ելք",
"com_nav_setting_account": "Ակաունթ",
"com_nav_setting_balance": "Հաշվեկշիռ",
"com_nav_tool_remove": "Հեռացնել",
"com_ui_2fa_enable": "Միացնել 2FA-ն"
}

View File

@@ -22,13 +22,16 @@ import translationJa from './ja/translation.json';
import translationKa from './ka/translation.json';
import translationSv from './sv/translation.json';
import translationKo from './ko/translation.json';
import translationLv from './lv/translation.json';
import translationTh from './th/translation.json';
import translationTr from './tr/translation.json';
import translationUg from './ug/translation.json';
import translationVi from './vi/translation.json';
import translationNl from './nl/translation.json';
import translationId from './id/translation.json';
import translationHe from './he/translation.json';
import translationHu from './hu/translation.json';
import translationHy from './hy/translation.json';
import translationFi from './fi/translation.json';
import translationZh_Hans from './zh-Hans/translation.json';
import translationZh_Hant from './zh-Hant/translation.json';
@@ -57,13 +60,16 @@ export const resources = {
ka: { translation: translationKa },
sv: { translation: translationSv },
ko: { translation: translationKo },
lv: { translation: translationLv },
th: { translation: translationTh },
tr: { translation: translationTr },
ug: { translation: translationUg },
vi: { translation: translationVi },
nl: { translation: translationNl },
id: { translation: translationId },
he: { translation: translationHe },
hu: { translation: translationHu },
hy: { translation: translationHy },
fi: { translation: translationFi },
} as const;

View File

@@ -314,7 +314,6 @@
"com_nav_delete_cache_storage": "Elimina cache TTS",
"com_nav_delete_data_info": "Tutti i tuoi dati verranno eliminati.",
"com_nav_delete_warning": "ATTENZIONE: Questa azione eliminerà definitivamente il tuo account.",
"com_nav_edit_chat_badges": "Modifica i badge della chat",
"com_nav_enable_cache_tts": "Abilita cache TTS",
"com_nav_enable_cloud_browser_voice": "Usa voci basate su cloud",
"com_nav_enabled": "Abilitato",
@@ -664,7 +663,6 @@
"com_ui_import_conversation_info": "Importa conversazioni da un file JSON",
"com_ui_import_conversation_success": "Conversazioni importate con successo",
"com_ui_include_shadcnui": "Includi istruzioni per i componenti shadcn/ui",
"com_ui_include_shadcnui_agent": "Includere istruzioni shadcn/ui",
"com_ui_input": "Input",
"com_ui_instructions": "Istruzioni",
"com_ui_late_night": "Buona tarda serata",

Some files were not shown because too many files have changed in this diff Show More