Compare commits
39 Commits
feat/group
...
feat/segme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcfb0f47f9 | ||
|
|
f4d97e1672 | ||
|
|
035fa081c1 | ||
|
|
aecf8f19a6 | ||
|
|
35f548a94d | ||
|
|
e60c0cf201 | ||
|
|
5b392f9cb0 | ||
|
|
e0f468da20 | ||
|
|
91a2df4759 | ||
|
|
97a99985fa | ||
|
|
3554625a06 | ||
|
|
a37bf6719c | ||
|
|
e513f50c08 | ||
|
|
f5511e4a4e | ||
|
|
a288ad1d9c | ||
|
|
458580ec87 | ||
|
|
4285d5841c | ||
|
|
5ee55cda4f | ||
|
|
404d40cbef | ||
|
|
f4680b016c | ||
|
|
077224b351 | ||
|
|
9c70d1db96 | ||
|
|
543281da6c | ||
|
|
24800bfbeb | ||
|
|
07e08143e4 | ||
|
|
8ba61a86f4 | ||
|
|
56ad92fb1c | ||
|
|
1ceb52d2b5 | ||
|
|
5d267aa8e2 | ||
|
|
59d00e99f3 | ||
|
|
738d04fac4 | ||
|
|
8a5dbac0f9 | ||
|
|
434289fe92 | ||
|
|
a648ad3d13 | ||
|
|
55d63caaf4 | ||
|
|
313539d1ed | ||
|
|
f869d772f7 | ||
|
|
20100e120b | ||
|
|
3f3cfefc52 |
@@ -1,4 +1,4 @@
|
||||
# v0.7.8
|
||||
# v0.7.9-rc1
|
||||
|
||||
# Base node image
|
||||
FROM node:20-alpine AS node
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Dockerfile.multi
|
||||
# v0.7.8
|
||||
# v0.7.9-rc1
|
||||
|
||||
# Base for all builds
|
||||
FROM node:20-alpine AS base-min
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,6 @@ const {
|
||||
const { getMessages, saveMessage, updateMessage, saveConvo, getConvo } = require('~/models');
|
||||
const { checkBalance } = require('~/models/balanceMethods');
|
||||
const { truncateToolCallOutputs } = require('./prompts');
|
||||
const { addSpaceIfNeeded } = require('~/server/utils');
|
||||
const { getFiles } = require('~/models/File');
|
||||
const TextStream = require('./TextStream');
|
||||
const { logger } = require('~/config');
|
||||
@@ -572,7 +571,7 @@ class BaseClient {
|
||||
});
|
||||
}
|
||||
|
||||
const { generation = '' } = opts;
|
||||
const { editedContent } = opts;
|
||||
|
||||
// It's not necessary to push to currentMessages
|
||||
// depending on subclass implementation of handling messages
|
||||
@@ -587,11 +586,21 @@ class BaseClient {
|
||||
isCreatedByUser: false,
|
||||
model: this.modelOptions?.model ?? this.model,
|
||||
sender: this.sender,
|
||||
text: generation,
|
||||
};
|
||||
this.currentMessages.push(userMessage, latestMessage);
|
||||
} else {
|
||||
latestMessage.text = generation;
|
||||
} else if (editedContent != null) {
|
||||
// Handle editedContent for content parts
|
||||
if (editedContent && latestMessage.content && Array.isArray(latestMessage.content)) {
|
||||
const { index, text, type } = editedContent;
|
||||
if (index >= 0 && index < latestMessage.content.length) {
|
||||
const contentPart = latestMessage.content[index];
|
||||
if (type === ContentTypes.THINK && contentPart.type === ContentTypes.THINK) {
|
||||
contentPart[ContentTypes.THINK] = text;
|
||||
} else if (type === ContentTypes.TEXT && contentPart.type === ContentTypes.TEXT) {
|
||||
contentPart[ContentTypes.TEXT] = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.continued = true;
|
||||
} else {
|
||||
@@ -672,16 +681,32 @@ class BaseClient {
|
||||
};
|
||||
|
||||
if (typeof completion === 'string') {
|
||||
responseMessage.text = addSpaceIfNeeded(generation) + completion;
|
||||
responseMessage.text = completion;
|
||||
} else if (
|
||||
Array.isArray(completion) &&
|
||||
(this.clientName === EModelEndpoint.agents ||
|
||||
isParamEndpoint(this.options.endpoint, this.options.endpointType))
|
||||
) {
|
||||
responseMessage.text = '';
|
||||
responseMessage.content = completion;
|
||||
|
||||
if (!opts.editedContent || this.currentMessages.length === 0) {
|
||||
responseMessage.content = completion;
|
||||
} else {
|
||||
const latestMessage = this.currentMessages[this.currentMessages.length - 1];
|
||||
if (!latestMessage?.content) {
|
||||
responseMessage.content = completion;
|
||||
} else {
|
||||
const existingContent = [...latestMessage.content];
|
||||
const { type: editedType } = opts.editedContent;
|
||||
responseMessage.content = this.mergeEditedContent(
|
||||
existingContent,
|
||||
completion,
|
||||
editedType,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(completion)) {
|
||||
responseMessage.text = addSpaceIfNeeded(generation) + completion.join('');
|
||||
responseMessage.text = completion.join('');
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1095,6 +1120,50 @@ class BaseClient {
|
||||
return numTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges completion content with existing content when editing TEXT or THINK types
|
||||
* @param {Array} existingContent - The existing content array
|
||||
* @param {Array} newCompletion - The new completion content
|
||||
* @param {string} editedType - The type of content being edited
|
||||
* @returns {Array} The merged content array
|
||||
*/
|
||||
mergeEditedContent(existingContent, newCompletion, editedType) {
|
||||
if (!newCompletion.length) {
|
||||
return existingContent.concat(newCompletion);
|
||||
}
|
||||
|
||||
if (editedType !== ContentTypes.TEXT && editedType !== ContentTypes.THINK) {
|
||||
return existingContent.concat(newCompletion);
|
||||
}
|
||||
|
||||
const lastIndex = existingContent.length - 1;
|
||||
const lastExisting = existingContent[lastIndex];
|
||||
const firstNew = newCompletion[0];
|
||||
|
||||
if (lastExisting?.type !== firstNew?.type || firstNew?.type !== editedType) {
|
||||
return existingContent.concat(newCompletion);
|
||||
}
|
||||
|
||||
const mergedContent = [...existingContent];
|
||||
if (editedType === ContentTypes.TEXT) {
|
||||
mergedContent[lastIndex] = {
|
||||
...mergedContent[lastIndex],
|
||||
[ContentTypes.TEXT]:
|
||||
(mergedContent[lastIndex][ContentTypes.TEXT] || '') + (firstNew[ContentTypes.TEXT] || ''),
|
||||
};
|
||||
} else {
|
||||
mergedContent[lastIndex] = {
|
||||
...mergedContent[lastIndex],
|
||||
[ContentTypes.THINK]:
|
||||
(mergedContent[lastIndex][ContentTypes.THINK] || '') +
|
||||
(firstNew[ContentTypes.THINK] || ''),
|
||||
};
|
||||
}
|
||||
|
||||
// Add remaining completion items
|
||||
return mergedContent.concat(newCompletion.slice(1));
|
||||
}
|
||||
|
||||
async sendPayload(payload, opts = {}) {
|
||||
if (opts && typeof opts === 'object') {
|
||||
this.setOptions(opts);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@librechat/backend",
|
||||
"version": "v0.7.8",
|
||||
"version": "v0.7.9-rc1",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"start": "echo 'please run this from the root directory'",
|
||||
@@ -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.46",
|
||||
"@librechat/agents": "^2.4.56",
|
||||
"@librechat/api": "*",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@node-saml/passport-saml": "^5.0.0",
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
};
|
||||
|
||||
241
api/server/controllers/ErrorController.spec.js
Normal file
241
api/server/controllers/ErrorController.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -525,7 +525,10 @@ class AgentClient extends BaseClient {
|
||||
messagesToProcess = [...messages.slice(-messageWindowSize)];
|
||||
}
|
||||
}
|
||||
return await this.processMemory(messagesToProcess);
|
||||
|
||||
const bufferString = getBufferString(messagesToProcess);
|
||||
const bufferMessage = new HumanMessage(`# Current Chat:\n\n${bufferString}`);
|
||||
return await this.processMemory([bufferMessage]);
|
||||
} catch (error) {
|
||||
logger.error('Memory Agent failed to process memory', error);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,11 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||
text,
|
||||
endpointOption,
|
||||
conversationId,
|
||||
isContinued = false,
|
||||
editedContent = null,
|
||||
parentMessageId = null,
|
||||
overrideParentMessageId = null,
|
||||
responseMessageId: editedResponseMessageId = null,
|
||||
} = req.body;
|
||||
|
||||
let sender;
|
||||
@@ -67,7 +70,7 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||
handler();
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore cleanup errors
|
||||
logger.error('[AgentController] Error in cleanup handler', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,7 +158,7 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||
try {
|
||||
res.removeListener('close', closeHandler);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
logger.error('[AgentController] Error removing close listener', e);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -163,10 +166,14 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||
user: userId,
|
||||
onStart,
|
||||
getReqData,
|
||||
isContinued,
|
||||
editedContent,
|
||||
conversationId,
|
||||
parentMessageId,
|
||||
abortController,
|
||||
overrideParentMessageId,
|
||||
isEdited: !!editedContent,
|
||||
responseMessageId: editedResponseMessageId,
|
||||
progressOptions: {
|
||||
res,
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
659
api/server/controllers/agents/v1.spec.js
Normal file
659
api/server/controllers/agents/v1.spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
88
api/server/middleware/limiters/forkLimiters.js
Normal file
88
api/server/middleware/limiters/forkLimiters.js
Normal file
@@ -0,0 +1,88 @@
|
||||
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 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,
|
||||
};
|
||||
};
|
||||
|
||||
const createForkHandler = (ip = true) => {
|
||||
const { forkIpMax, forkIpWindowInMinutes, forkUserMax, 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);
|
||||
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 };
|
||||
@@ -1,10 +1,10 @@
|
||||
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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -477,7 +477,9 @@ describe('Multer Configuration', () => {
|
||||
done(new Error('Expected mkdirSync to throw an error but no error was thrown'));
|
||||
} catch (error) {
|
||||
// This is the expected behavior - mkdirSync throws synchronously for invalid paths
|
||||
expect(error.code).toBe('EACCES');
|
||||
// On Linux, this typically returns EACCES (permission denied)
|
||||
// On macOS/Darwin, this returns ENOENT (no such file or directory)
|
||||
expect(['EACCES', 'ENOENT']).toContain(error.code);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -235,12 +235,13 @@ router.put('/:conversationId/:messageId', validateMessageReq, async (req, res) =
|
||||
return res.status(400).json({ error: 'Content part not found' });
|
||||
}
|
||||
|
||||
if (updatedContent[index].type !== ContentTypes.TEXT) {
|
||||
const currentPartType = updatedContent[index].type;
|
||||
if (currentPartType !== ContentTypes.TEXT && currentPartType !== ContentTypes.THINK) {
|
||||
return res.status(400).json({ error: 'Cannot update non-text content' });
|
||||
}
|
||||
|
||||
const oldText = updatedContent[index].text;
|
||||
updatedContent[index] = { type: ContentTypes.TEXT, text };
|
||||
const oldText = updatedContent[index][currentPartType];
|
||||
updatedContent[index] = { type: currentPartType, [currentPartType]: text };
|
||||
|
||||
let tokenCount = message.tokenCount;
|
||||
if (tokenCount !== undefined) {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const path = require('path');
|
||||
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;
|
||||
@@ -9,37 +11,42 @@ const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, go
|
||||
* @param {Express.Request} req - The request object
|
||||
*/
|
||||
async function loadAsyncEndpoints(req) {
|
||||
let i = 0;
|
||||
let serviceKey, googleUserProvides;
|
||||
try {
|
||||
serviceKey = require('~/data/auth.json');
|
||||
} catch (e) {
|
||||
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 ||
|
||||
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 =
|
||||
useAzure || openAIApiKey || azureOpenAIApiKey
|
||||
? {
|
||||
availableAgents: ['classic', 'functions'],
|
||||
userProvide: useAzure ? false : userProvidedOpenAI,
|
||||
userProvideURL: useAzure
|
||||
? false
|
||||
: config[EModelEndpoint.openAI]?.userProvideURL ||
|
||||
availableAgents: ['classic', 'functions'],
|
||||
userProvide: useAzure ? false : userProvidedOpenAI,
|
||||
userProvideURL: useAzure
|
||||
? false
|
||||
: config[EModelEndpoint.openAI]?.userProvideURL ||
|
||||
config[EModelEndpoint.azureOpenAI]?.userProvideURL,
|
||||
azure: useAzurePlugins || useAzure,
|
||||
}
|
||||
azure: useAzurePlugins || useAzure,
|
||||
}
|
||||
: false;
|
||||
|
||||
return { google, gptPlugins };
|
||||
|
||||
@@ -85,7 +85,7 @@ const initializeAgent = async ({
|
||||
});
|
||||
|
||||
const provider = agent.provider;
|
||||
const { tools, toolContextMap } =
|
||||
const { tools: structuredTools, toolContextMap } =
|
||||
(await loadTools?.({
|
||||
req,
|
||||
res,
|
||||
@@ -140,6 +140,24 @@ const initializeAgent = async ({
|
||||
agent.provider = options.provider;
|
||||
}
|
||||
|
||||
/** @type {import('@librechat/agents').GenericTool[]} */
|
||||
let tools = options.tools?.length ? options.tools : structuredTools;
|
||||
if (
|
||||
(agent.provider === Providers.GOOGLE || agent.provider === Providers.VERTEXAI) &&
|
||||
options.tools?.length &&
|
||||
structuredTools?.length
|
||||
) {
|
||||
throw new Error(`{ "type": "${ErrorTypes.GOOGLE_TOOL_CONFLICT}"}`);
|
||||
} else if (
|
||||
(agent.provider === Providers.OPENAI ||
|
||||
agent.provider === Providers.AZURE ||
|
||||
agent.provider === Providers.ANTHROPIC) &&
|
||||
options.tools?.length &&
|
||||
structuredTools?.length
|
||||
) {
|
||||
tools = structuredTools.concat(options.tools);
|
||||
}
|
||||
|
||||
/** @type {import('@librechat/agents').ClientOptions} */
|
||||
agent.model_parameters = { ...options.llmConfig };
|
||||
if (options.configOptions) {
|
||||
@@ -162,10 +180,10 @@ const initializeAgent = async ({
|
||||
|
||||
return {
|
||||
...agent,
|
||||
tools,
|
||||
attachments,
|
||||
resendFiles,
|
||||
toolContextMap,
|
||||
tools,
|
||||
maxContextTokens: (agentMaxContextTokens - maxTokens) * 0.9,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { getGoogleConfig, isEnabled } = require('@librechat/api');
|
||||
const path = require('path');
|
||||
const { EModelEndpoint, AuthKeys } = require('librechat-data-provider');
|
||||
const { getGoogleConfig, isEnabled, loadServiceKey } = require('@librechat/api');
|
||||
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||
const { GoogleClient } = require('~/app');
|
||||
|
||||
@@ -15,10 +16,25 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
|
||||
}
|
||||
|
||||
let serviceKey = {};
|
||||
try {
|
||||
serviceKey = require('~/data/auth.json');
|
||||
} catch (_e) {
|
||||
// Do nothing
|
||||
|
||||
/** 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 ||
|
||||
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 = {};
|
||||
}
|
||||
}
|
||||
|
||||
const credentials = isUserProvided
|
||||
|
||||
@@ -7,6 +7,16 @@ const initCustom = require('~/server/services/Endpoints/custom/initialize');
|
||||
const initGoogle = require('~/server/services/Endpoints/google/initialize');
|
||||
const { getCustomEndpointConfig } = require('~/server/services/Config');
|
||||
|
||||
/** Check if the provider is a known custom provider
|
||||
* @param {string | undefined} [provider] - The provider string
|
||||
* @returns {boolean} - True if the provider is a known custom provider, false otherwise
|
||||
*/
|
||||
function isKnownCustomProvider(provider) {
|
||||
return [Providers.XAI, Providers.OLLAMA, Providers.DEEPSEEK, Providers.OPENROUTER].includes(
|
||||
provider?.toLowerCase() || '',
|
||||
);
|
||||
}
|
||||
|
||||
const providerConfigMap = {
|
||||
[Providers.XAI]: initCustom,
|
||||
[Providers.OLLAMA]: initCustom,
|
||||
@@ -46,6 +56,13 @@ async function getProviderConfig(provider) {
|
||||
overrideProvider = Providers.OPENAI;
|
||||
}
|
||||
|
||||
if (isKnownCustomProvider(overrideProvider || provider) && !customEndpointConfig) {
|
||||
customEndpointConfig = await getCustomEndpointConfig(provider);
|
||||
if (!customEndpointConfig) {
|
||||
throw new Error(`Provider ${provider} not supported`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getOptions,
|
||||
overrideProvider,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
const { FileSources } = require('librechat-data-provider');
|
||||
const { uploadMistralOCR, uploadAzureMistralOCR } = require('@librechat/api');
|
||||
const {
|
||||
uploadMistralOCR,
|
||||
uploadAzureMistralOCR,
|
||||
uploadGoogleVertexMistralOCR,
|
||||
} = require('@librechat/api');
|
||||
const {
|
||||
getFirebaseURL,
|
||||
prepareImageURL,
|
||||
@@ -222,6 +226,26 @@ const azureMistralOCRStrategy = () => ({
|
||||
handleFileUpload: uploadAzureMistralOCR,
|
||||
});
|
||||
|
||||
const vertexMistralOCRStrategy = () => ({
|
||||
/** @type {typeof saveFileFromURL | null} */
|
||||
saveURL: null,
|
||||
/** @type {typeof getLocalFileURL | null} */
|
||||
getFileURL: null,
|
||||
/** @type {typeof saveLocalBuffer | null} */
|
||||
saveBuffer: null,
|
||||
/** @type {typeof processLocalAvatar | null} */
|
||||
processAvatar: null,
|
||||
/** @type {typeof uploadLocalImage | null} */
|
||||
handleImageUpload: null,
|
||||
/** @type {typeof prepareImagesLocal | null} */
|
||||
prepareImagePayload: null,
|
||||
/** @type {typeof deleteLocalFile | null} */
|
||||
deleteFile: null,
|
||||
/** @type {typeof getLocalFileStream | null} */
|
||||
getDownloadStream: null,
|
||||
handleFileUpload: uploadGoogleVertexMistralOCR,
|
||||
});
|
||||
|
||||
// Strategy Selector
|
||||
const getStrategyFunctions = (fileSource) => {
|
||||
if (fileSource === FileSources.firebase) {
|
||||
@@ -244,6 +268,8 @@ const getStrategyFunctions = (fileSource) => {
|
||||
return mistralOCRStrategy();
|
||||
} else if (fileSource === FileSources.azure_mistral_ocr) {
|
||||
return azureMistralOCRStrategy();
|
||||
} else if (fileSource === FileSources.vertexai_mistral_ocr) {
|
||||
return vertexMistralOCRStrategy();
|
||||
} else {
|
||||
throw new Error('Invalid file source');
|
||||
}
|
||||
|
||||
280
api/server/utils/import/importers-timestamp.spec.js
Normal file
280
api/server/utils/import/importers-timestamp.spec.js
Normal 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(),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const { SystemRoles } = require('librechat-data-provider');
|
||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
|
||||
const { updateUser, findUser } = require('~/models');
|
||||
const { logger } = require('~/config');
|
||||
@@ -13,17 +14,23 @@ const { isEnabled } = require('~/server/utils');
|
||||
* The strategy extracts the JWT from the Authorization header as a Bearer token.
|
||||
* The JWT is then verified using the signing key, and the user is retrieved from the database.
|
||||
*/
|
||||
const openIdJwtLogin = (openIdConfig) =>
|
||||
new JwtStrategy(
|
||||
const openIdJwtLogin = (openIdConfig) => {
|
||||
let jwksRsaOptions = {
|
||||
cache: isEnabled(process.env.OPENID_JWKS_URL_CACHE_ENABLED) || true,
|
||||
cacheMaxAge: process.env.OPENID_JWKS_URL_CACHE_TIME
|
||||
? eval(process.env.OPENID_JWKS_URL_CACHE_TIME)
|
||||
: 60000,
|
||||
jwksUri: openIdConfig.serverMetadata().jwks_uri,
|
||||
};
|
||||
|
||||
if (process.env.PROXY) {
|
||||
jwksRsaOptions.requestAgent = new HttpsProxyAgent(process.env.PROXY);
|
||||
}
|
||||
|
||||
return new JwtStrategy(
|
||||
{
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKeyProvider: jwksRsa.passportJwtSecret({
|
||||
cache: isEnabled(process.env.OPENID_JWKS_URL_CACHE_ENABLED) || true,
|
||||
cacheMaxAge: process.env.OPENID_JWKS_URL_CACHE_TIME
|
||||
? eval(process.env.OPENID_JWKS_URL_CACHE_TIME)
|
||||
: 60000,
|
||||
jwksUri: openIdConfig.serverMetadata().jwks_uri,
|
||||
}),
|
||||
secretOrKeyProvider: jwksRsa.passportJwtSecret(jwksRsaOptions),
|
||||
},
|
||||
async (payload, done) => {
|
||||
try {
|
||||
@@ -48,5 +55,6 @@ const openIdJwtLogin = (openIdConfig) =>
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = openIdJwtLogin;
|
||||
|
||||
@@ -49,7 +49,7 @@ async function customFetch(url, options) {
|
||||
logger.info(`[openidStrategy] proxy agent configured: ${process.env.PROXY}`);
|
||||
fetchOptions = {
|
||||
...options,
|
||||
dispatcher: new HttpsProxyAgent(process.env.PROXY),
|
||||
dispatcher: new undici.ProxyAgent(process.env.PROXY),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@librechat/frontend",
|
||||
"version": "v0.7.8",
|
||||
"version": "v0.7.9-rc1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
@@ -336,6 +336,11 @@ export type TAskProps = {
|
||||
export type TOptions = {
|
||||
editedMessageId?: string | null;
|
||||
editedText?: string | null;
|
||||
editedContent?: {
|
||||
index: number;
|
||||
text: string;
|
||||
type: 'text' | 'think';
|
||||
};
|
||||
isRegenerate?: boolean;
|
||||
isContinued?: boolean;
|
||||
isEdited?: boolean;
|
||||
|
||||
152
client/src/components/Chat/Input/Artifacts.tsx
Normal file
152
client/src/components/Chat/Input/Artifacts.tsx
Normal 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);
|
||||
147
client/src/components/Chat/Input/ArtifactsSubMenu.tsx
Normal file
147
client/src/components/Chat/Input/ArtifactsSubMenu.tsx
Normal 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);
|
||||
@@ -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 />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -4,18 +4,21 @@ import {
|
||||
supportsFiles,
|
||||
mergeFileConfig,
|
||||
isAgentsEndpoint,
|
||||
isAssistantsEndpoint,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { EndpointFileConfig } from 'librechat-data-provider';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
import AttachFileMenu from './AttachFileMenu';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import AttachFile from './AttachFile';
|
||||
|
||||
function AttachFileChat({ disableInputs }: { disableInputs: boolean }) {
|
||||
const { conversation } = useChatContext();
|
||||
const conversationId = conversation?.conversationId ?? Constants.NEW_CONVO;
|
||||
const { endpoint, endpointType } = conversation ?? { endpoint: null };
|
||||
const isAgents = useMemo(() => isAgentsEndpoint(endpoint), [endpoint]);
|
||||
const isAssistants = useMemo(() => isAssistantsEndpoint(endpoint), [endpoint]);
|
||||
|
||||
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
|
||||
select: (data) => mergeFileConfig(data),
|
||||
@@ -25,7 +28,9 @@ function AttachFileChat({ disableInputs }: { disableInputs: boolean }) {
|
||||
const endpointSupportsFiles: boolean = supportsFiles[endpointType ?? endpoint ?? ''] ?? false;
|
||||
const isUploadDisabled = (disableInputs || endpointFileConfig?.disabled) ?? false;
|
||||
|
||||
if (isAgents || (endpointSupportsFiles && !isUploadDisabled)) {
|
||||
if (isAssistants && endpointSupportsFiles && !isUploadDisabled) {
|
||||
return <AttachFile disabled={disableInputs} />;
|
||||
} else if (isAgents || (endpointSupportsFiles && !isUploadDisabled)) {
|
||||
return (
|
||||
<AttachFileMenu
|
||||
disabled={disableInputs}
|
||||
@@ -34,7 +39,6 @@ function AttachFileChat({ disableInputs }: { disableInputs: boolean }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -81,14 +81,23 @@ const ContentParts = memo(
|
||||
return (
|
||||
<>
|
||||
{content.map((part, idx) => {
|
||||
if (part?.type !== ContentTypes.TEXT || typeof part.text !== 'string') {
|
||||
if (!part) {
|
||||
return null;
|
||||
}
|
||||
const isTextPart =
|
||||
part?.type === ContentTypes.TEXT ||
|
||||
typeof (part as unknown as Agents.MessageContentText)?.text !== 'string';
|
||||
const isThinkPart =
|
||||
part?.type === ContentTypes.THINK ||
|
||||
typeof (part as unknown as Agents.ReasoningDeltaUpdate)?.think !== 'string';
|
||||
if (!isTextPart && !isThinkPart) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EditTextPart
|
||||
index={idx}
|
||||
text={part.text}
|
||||
part={part as Agents.MessageContentText | Agents.ReasoningDeltaUpdate}
|
||||
messageId={messageId}
|
||||
isSubmitting={isSubmitting}
|
||||
enterEdit={enterEdit}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ContentTypes } from 'librechat-data-provider';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useUpdateMessageContentMutation } from 'librechat-data-provider/react-query';
|
||||
import type { Agents } from 'librechat-data-provider';
|
||||
import type { TEditProps } from '~/common';
|
||||
import Container from '~/components/Chat/Messages/Content/Container';
|
||||
import { useChatContext, useAddedChatContext } from '~/Providers';
|
||||
@@ -12,18 +13,19 @@ import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
const EditTextPart = ({
|
||||
text,
|
||||
part,
|
||||
index,
|
||||
messageId,
|
||||
isSubmitting,
|
||||
enterEdit,
|
||||
}: Omit<TEditProps, 'message' | 'ask'> & {
|
||||
}: Omit<TEditProps, 'message' | 'ask' | 'text'> & {
|
||||
index: number;
|
||||
messageId: string;
|
||||
part: Agents.MessageContentText | Agents.ReasoningDeltaUpdate;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const { addedIndex } = useAddedChatContext();
|
||||
const { getMessages, setMessages, conversation } = useChatContext();
|
||||
const { ask, getMessages, setMessages, conversation } = useChatContext();
|
||||
const [latestMultiMessage, setLatestMultiMessage] = useRecoilState(
|
||||
store.latestMessageFamily(addedIndex),
|
||||
);
|
||||
@@ -34,15 +36,16 @@ const EditTextPart = ({
|
||||
[getMessages, messageId],
|
||||
);
|
||||
|
||||
const chatDirection = useRecoilValue(store.chatDirection);
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const updateMessageContentMutation = useUpdateMessageContentMutation(conversationId ?? '');
|
||||
|
||||
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
|
||||
const isRTL = chatDirection === 'rtl';
|
||||
const isRTL = chatDirection?.toLowerCase() === 'rtl';
|
||||
|
||||
const { register, handleSubmit, setValue } = useForm({
|
||||
defaultValues: {
|
||||
text: text ?? '',
|
||||
text: (ContentTypes.THINK in part ? part.think : part.text) || '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -55,15 +58,7 @@ const EditTextPart = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
/*
|
||||
const resubmitMessage = () => {
|
||||
showToast({
|
||||
status: 'warning',
|
||||
message: localize('com_warning_resubmit_unsupported'),
|
||||
});
|
||||
|
||||
// const resubmitMessage = (data: { text: string }) => {
|
||||
// Not supported by AWS Bedrock
|
||||
const resubmitMessage = (data: { text: string }) => {
|
||||
const messages = getMessages();
|
||||
const parentMessage = messages?.find((msg) => msg.messageId === message?.parentMessageId);
|
||||
|
||||
@@ -73,17 +68,19 @@ const EditTextPart = ({
|
||||
ask(
|
||||
{ ...parentMessage },
|
||||
{
|
||||
editedText: data.text,
|
||||
editedContent: {
|
||||
index,
|
||||
text: data.text,
|
||||
type: part.type,
|
||||
},
|
||||
editedMessageId: messageId,
|
||||
isRegenerate: true,
|
||||
isEdited: true,
|
||||
},
|
||||
);
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
enterEdit(true);
|
||||
};
|
||||
*/
|
||||
|
||||
const updateMessage = (data: { text: string }) => {
|
||||
const messages = getMessages();
|
||||
@@ -167,13 +164,13 @@ const EditTextPart = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex w-full justify-center text-center">
|
||||
{/* <button
|
||||
<button
|
||||
className="btn btn-primary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={handleSubmit(resubmitMessage)}
|
||||
>
|
||||
{localize('com_ui_save_submit')}
|
||||
</button> */}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
},
|
||||
|
||||
@@ -62,6 +62,7 @@ const errorMessages = {
|
||||
const { info } = json;
|
||||
return info;
|
||||
},
|
||||
[ErrorTypes.GOOGLE_TOOL_CONFLICT]: 'com_error_google_tool_conflict',
|
||||
[ViolationTypes.BAN]:
|
||||
'Your account has been temporarily banned due to violations of our service.',
|
||||
invalid_api_key:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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') },
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ export default function AgentTool({
|
||||
}}
|
||||
className={cn(
|
||||
'h-4 w-4 rounded border border-gray-300 transition-all duration-200 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500',
|
||||
isExpanded ? 'opacity-100' : 'opacity-0',
|
||||
isExpanded ? 'visible' : 'pointer-events-none invisible',
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => {
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
144
client/src/components/SidePanel/Agents/Search/InputSection.tsx
Normal file
144
client/src/components/SidePanel/Agents/Search/InputSection.tsx
Normal 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 };
|
||||
130
client/src/components/SidePanel/Parameters/DynamicSegment.tsx
Normal file
130
client/src/components/SidePanel/Parameters/DynamicSegment.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, HoverCard, HoverCardTrigger, SegmentedControl } from '~/components/ui';
|
||||
import { TranslationKeys, useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
function DynamicSegment({
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
options,
|
||||
enumMappings,
|
||||
readonly = false,
|
||||
showLabel = true,
|
||||
showDefault = false,
|
||||
labelCode = false,
|
||||
descriptionCode = false,
|
||||
conversation,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const [inputValue, setInputValue] = useState<string | null>(null);
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}, [conversation, defaultValue, optionType, settingKey, inputValue]);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setInputValue(value);
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(value);
|
||||
};
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: true,
|
||||
});
|
||||
|
||||
if (!options || options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert options to SegmentedControl format with proper localization
|
||||
const segmentOptions =
|
||||
options?.map((option) => {
|
||||
const optionValue = typeof option === 'string' ? option : String(option);
|
||||
const optionLabel = typeof option === 'string' ? option : String(option);
|
||||
|
||||
// Use enum mappings for localization if available
|
||||
const localizedLabel = enumMappings?.[optionValue]
|
||||
? localize(enumMappings[optionValue] as TranslationKeys) ||
|
||||
String(enumMappings[optionValue])
|
||||
: optionLabel;
|
||||
|
||||
return {
|
||||
label: String(localizedLabel),
|
||||
value: optionValue,
|
||||
disabled: false,
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-start gap-6',
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
)}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
{showLabel === true && (
|
||||
<div className="flex w-full justify-between">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-segment`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
<SegmentedControl
|
||||
options={segmentOptions}
|
||||
value={selectedValue}
|
||||
onValueChange={handleChange}
|
||||
disabled={readonly}
|
||||
className="w-full min-w-0"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={
|
||||
descriptionCode
|
||||
? (localize(description as TranslationKeys) ?? description)
|
||||
: description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
</HoverCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DynamicSegment;
|
||||
@@ -18,6 +18,7 @@ function DynamicSlider({
|
||||
setOption,
|
||||
optionType,
|
||||
options,
|
||||
enumMappings,
|
||||
readonly = false,
|
||||
showDefault = false,
|
||||
includeInput = true,
|
||||
@@ -60,24 +61,68 @@ function DynamicSlider({
|
||||
|
||||
const enumToNumeric = useMemo(() => {
|
||||
if (isEnum && options) {
|
||||
return options.reduce((acc, mapping, index) => {
|
||||
acc[mapping] = index;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
return options.reduce(
|
||||
(acc, mapping, index) => {
|
||||
acc[mapping] = index;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}, [isEnum, options]);
|
||||
|
||||
const valueToEnumOption = useMemo(() => {
|
||||
if (isEnum && options) {
|
||||
return options.reduce((acc, option, index) => {
|
||||
acc[index] = option;
|
||||
return acc;
|
||||
}, {} as Record<number, string>);
|
||||
return options.reduce(
|
||||
(acc, option, index) => {
|
||||
acc[index] = option;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<number, string>,
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}, [isEnum, options]);
|
||||
|
||||
const getDisplayValue = useCallback(
|
||||
(value: string | number | undefined | null): string => {
|
||||
if (isEnum && enumMappings && value != null) {
|
||||
const stringValue = String(value);
|
||||
// Check if the value exists in enumMappings
|
||||
if (stringValue in enumMappings) {
|
||||
const mappedValue = String(enumMappings[stringValue]);
|
||||
// Check if the mapped value is a localization key
|
||||
if (mappedValue.startsWith('com_')) {
|
||||
return localize(mappedValue as TranslationKeys) ?? mappedValue;
|
||||
}
|
||||
return mappedValue;
|
||||
}
|
||||
}
|
||||
// Always return a string for Input component compatibility
|
||||
if (value != null) {
|
||||
return String(value);
|
||||
}
|
||||
return String(defaultValue ?? '');
|
||||
},
|
||||
[isEnum, enumMappings, defaultValue, localize],
|
||||
);
|
||||
|
||||
const getDefaultDisplayValue = useCallback((): string => {
|
||||
if (defaultValue != null && enumMappings) {
|
||||
const stringDefault = String(defaultValue);
|
||||
if (stringDefault in enumMappings) {
|
||||
const mappedValue = String(enumMappings[stringDefault]);
|
||||
// Check if the mapped value is a localization key
|
||||
if (mappedValue.startsWith('com_')) {
|
||||
return localize(mappedValue as TranslationKeys) ?? mappedValue;
|
||||
}
|
||||
return mappedValue;
|
||||
}
|
||||
}
|
||||
return String(defaultValue ?? '');
|
||||
}, [defaultValue, enumMappings, localize]);
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(value: number) => {
|
||||
if (isEnum) {
|
||||
@@ -115,12 +160,12 @@ function DynamicSlider({
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-setting`}
|
||||
className="text-left text-sm font-medium"
|
||||
className="break-words text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label as TranslationKeys) ?? label : label || settingKey}{' '}
|
||||
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
({localize('com_endpoint_default')}: {getDefaultDisplayValue()})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
@@ -132,13 +177,13 @@ function DynamicSlider({
|
||||
onChange={(value) => setInputValue(Number(value))}
|
||||
max={range ? range.max : (options?.length ?? 0) - 1}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
step={range ? (range.step ?? 1) : 1}
|
||||
controls={false}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 py-1 text-xs group-hover/temp:border-gray-200',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
@@ -146,13 +191,13 @@ function DynamicSlider({
|
||||
<Input
|
||||
id={`${settingKey}-dynamic-setting-input`}
|
||||
disabled={readonly}
|
||||
value={selectedValue ?? defaultValue}
|
||||
value={getDisplayValue(selectedValue)}
|
||||
onChange={() => ({})}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
'reset-rc-number-input h-auto w-14 border-0 py-1 pl-1 text-center text-xs group-hover/temp:border-gray-200',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
@@ -164,19 +209,23 @@ function DynamicSlider({
|
||||
value={[
|
||||
isEnum
|
||||
? enumToNumeric[(selectedValue as number) ?? '']
|
||||
: (inputValue as number) ?? (defaultValue as number),
|
||||
: ((inputValue as number) ?? (defaultValue as number)),
|
||||
]}
|
||||
onValueChange={(value) => handleValueChange(value[0])}
|
||||
onDoubleClick={() => setInputValue(defaultValue as string | number)}
|
||||
max={max}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
step={range ? (range.step ?? 1) : 1}
|
||||
className="flex h-4 w-full"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description as TranslationKeys) ?? description : description}
|
||||
description={
|
||||
descriptionCode
|
||||
? (localize(description as TranslationKeys) ?? description)
|
||||
: description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -50,7 +50,7 @@ function DynamicSwitch({
|
||||
<div className="flex justify-between">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-switch`}
|
||||
className="text-left text-sm font-medium"
|
||||
className="break-words text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
DynamicSwitch,
|
||||
DynamicInput,
|
||||
DynamicTags,
|
||||
DynamicSegment,
|
||||
} from './';
|
||||
|
||||
export const componentMapping: Record<
|
||||
@@ -23,4 +24,5 @@ export const componentMapping: Record<
|
||||
[ComponentTypes.Checkbox]: DynamicCheckbox,
|
||||
[ComponentTypes.Tags]: DynamicTags,
|
||||
[ComponentTypes.Combobox]: DynamicCombobox,
|
||||
[ComponentTypes.Segment]: DynamicSegment,
|
||||
};
|
||||
|
||||
@@ -6,4 +6,5 @@ export { default as DynamicSlider } from './DynamicSlider';
|
||||
export { default as DynamicSwitch } from './DynamicSwitch';
|
||||
export { default as DynamicInput } from './DynamicInput';
|
||||
export { default as DynamicTags } from './DynamicTags';
|
||||
export { default as DynamicSegment } from './DynamicSegment';
|
||||
export { default as OptionHoverAlt } from './OptionHover';
|
||||
|
||||
@@ -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
|
||||
|
||||
169
client/src/components/ui/SegmentedControl.tsx
Normal file
169
client/src/components/ui/SegmentedControl.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import { forwardRef, useEffect, useRef, useState } from 'react';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export interface SegmentedControlOption {
|
||||
label: string;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface SegmentedControlProps {
|
||||
options: SegmentedControlOption[];
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
name?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
|
||||
({ options, value, onValueChange, name, className, disabled }, ref) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [indicatorStyle, setIndicatorStyle] = useState({ width: 0, height: 0, left: 0, top: 0 });
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
const [useGrid, setUseGrid] = useState(false);
|
||||
|
||||
// Ensure we always have a current value
|
||||
const currentValue = value !== undefined ? value : options[0]?.value;
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
if (disabled) return;
|
||||
onValueChange?.(newValue);
|
||||
};
|
||||
|
||||
const updateIndicator = () => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const selector = currentValue === '' ? '[data-value=""]' : `[data-value="${currentValue}"]`;
|
||||
const activeButton = containerRef.current.querySelector(selector) as HTMLButtonElement;
|
||||
|
||||
if (activeButton) {
|
||||
const containerRect = containerRef.current.getBoundingClientRect();
|
||||
const buttonRect = activeButton.getBoundingClientRect();
|
||||
|
||||
if (useGrid) {
|
||||
// 2x2 grid layout - use full button dimensions
|
||||
setIndicatorStyle({
|
||||
width: buttonRect.width,
|
||||
height: buttonRect.height,
|
||||
left: buttonRect.left - containerRect.left,
|
||||
top: buttonRect.top - containerRect.top,
|
||||
});
|
||||
} else {
|
||||
// 1-row layout - account for flex-1 distribution
|
||||
const containerPadding = 4; // p-1 = 4px
|
||||
setIndicatorStyle({
|
||||
width: buttonRect.width,
|
||||
height: buttonRect.height,
|
||||
left: buttonRect.left - containerRect.left - containerPadding,
|
||||
top: buttonRect.top - containerRect.top - containerPadding,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isInitialized) {
|
||||
setIsInitialized(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check if text is being truncated and switch to grid if needed
|
||||
const checkLayout = () => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const buttons = containerRef.current.querySelectorAll('button');
|
||||
let needsGrid = false;
|
||||
|
||||
buttons.forEach((button) => {
|
||||
if (button.scrollWidth > button.clientWidth) {
|
||||
needsGrid = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (needsGrid !== useGrid) {
|
||||
setUseGrid(needsGrid);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize and handle resize
|
||||
useEffect(() => {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
requestAnimationFrame(() => {
|
||||
checkLayout();
|
||||
updateIndicator();
|
||||
});
|
||||
});
|
||||
|
||||
if (containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current);
|
||||
// Initial check
|
||||
setTimeout(() => {
|
||||
checkLayout();
|
||||
updateIndicator();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return () => resizeObserver.disconnect();
|
||||
}, []);
|
||||
|
||||
// Update indicator when value changes
|
||||
useEffect(() => {
|
||||
updateIndicator();
|
||||
}, [currentValue, options]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn(
|
||||
'relative rounded-lg bg-surface-secondary p-1',
|
||||
useGrid ? 'grid grid-cols-2 gap-1' : 'flex items-center',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
className,
|
||||
)}
|
||||
role="radiogroup"
|
||||
>
|
||||
{/* Sliding background indicator */}
|
||||
<div
|
||||
className={cn(
|
||||
'ring-border-light/20 absolute rounded-md bg-surface-primary shadow-sm ring-1 transition-all duration-300 ease-out',
|
||||
!isInitialized && 'opacity-0',
|
||||
)}
|
||||
style={{
|
||||
width: indicatorStyle.width,
|
||||
height: indicatorStyle.height,
|
||||
transform: `translate(${indicatorStyle.left}px, ${indicatorStyle.top}px)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{options.map((option) => {
|
||||
const isActive = currentValue === option.value;
|
||||
const isDisabled = disabled || option.disabled;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={isActive}
|
||||
disabled={isDisabled}
|
||||
data-value={option.value}
|
||||
onClick={() => handleChange(option.value)}
|
||||
className={cn(
|
||||
'relative z-10 px-2 py-1.5 text-xs font-medium transition-colors duration-200 ease-out',
|
||||
'rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'min-w-0 truncate',
|
||||
useGrid ? 'w-full' : 'flex-1',
|
||||
isActive ? 'text-text-primary' : 'text-text-secondary hover:text-text-primary',
|
||||
!isDisabled && 'cursor-pointer',
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SegmentedControl.displayName = 'SegmentedControl';
|
||||
@@ -46,3 +46,4 @@ export { default as InputWithDropdown } from './InputWithDropDown';
|
||||
export { default as SelectDropDownPop } from './SelectDropDownPop';
|
||||
export { default as AnimatedSearchInput } from './AnimatedSearchInput';
|
||||
export { default as MultiSelectDropDown } from './MultiSelectDropDown';
|
||||
export { SegmentedControl } from './SegmentedControl';
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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';
|
||||
|
||||
61
client/src/hooks/Agents/useAgentCapabilities.ts
Normal file
61
client/src/hooks/Agents/useAgentCapabilities.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
35
client/src/hooks/Agents/useGetAgentsConfig.ts
Normal file
35
client/src/hooks/Agents/useGetAgentsConfig.ts
Normal 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 };
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
QueryKeys,
|
||||
ContentTypes,
|
||||
EModelEndpoint,
|
||||
isAgentsEndpoint,
|
||||
parseCompactConvo,
|
||||
replaceSpecialVars,
|
||||
isAssistantsEndpoint,
|
||||
@@ -24,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';
|
||||
@@ -36,15 +36,6 @@ const logChatRequest = (request: Record<string, unknown>) => {
|
||||
logger.log('=====================================');
|
||||
};
|
||||
|
||||
const usesContentStream = (endpoint: EModelEndpoint | undefined, endpointType?: string) => {
|
||||
if (endpointType === EModelEndpoint.custom) {
|
||||
return true;
|
||||
}
|
||||
if (endpoint === EModelEndpoint.openAI || endpoint === EModelEndpoint.azureOpenAI) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export default function useChatFunctions({
|
||||
index = 0,
|
||||
files,
|
||||
@@ -76,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));
|
||||
@@ -93,7 +81,7 @@ export default function useChatFunctions({
|
||||
messageId = null,
|
||||
},
|
||||
{
|
||||
editedText = null,
|
||||
editedContent = null,
|
||||
editedMessageId = null,
|
||||
isResubmission = false,
|
||||
isRegenerate = false,
|
||||
@@ -195,10 +183,6 @@ export default function useChatFunctions({
|
||||
endpointType,
|
||||
overrideConvoId,
|
||||
overrideUserMessageId,
|
||||
artifacts:
|
||||
endpoint !== EModelEndpoint.agents
|
||||
? getArtifactsMode({ codeArtifacts, includeShadcnui, customPromptMode })
|
||||
: undefined,
|
||||
},
|
||||
convo,
|
||||
) as TEndpointOption;
|
||||
@@ -245,14 +229,11 @@ export default function useChatFunctions({
|
||||
setFilesToDelete({});
|
||||
}
|
||||
|
||||
const generation = editedText ?? latestMessage?.text ?? '';
|
||||
const responseText = isEditOrContinue ? generation : '';
|
||||
|
||||
const responseMessageId =
|
||||
editedMessageId ?? (latestMessage?.messageId ? latestMessage?.messageId + '_' : null) ?? null;
|
||||
const initialResponse: TMessage = {
|
||||
sender: responseSender,
|
||||
text: responseText,
|
||||
text: '',
|
||||
endpoint: endpoint ?? '',
|
||||
parentMessageId: isRegenerate ? messageId : intermediateId,
|
||||
messageId: responseMessageId ?? `${isRegenerate ? messageId : intermediateId}_`,
|
||||
@@ -272,34 +253,37 @@ export default function useChatFunctions({
|
||||
{
|
||||
type: ContentTypes.TEXT,
|
||||
[ContentTypes.TEXT]: {
|
||||
value: responseText,
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
];
|
||||
} else if (endpoint === EModelEndpoint.agents) {
|
||||
initialResponse.model = conversation?.agent_id ?? '';
|
||||
} else if (endpoint != null) {
|
||||
initialResponse.model = isAgentsEndpoint(endpoint)
|
||||
? (conversation?.agent_id ?? '')
|
||||
: (conversation?.model ?? '');
|
||||
initialResponse.text = '';
|
||||
initialResponse.content = [
|
||||
{
|
||||
type: ContentTypes.TEXT,
|
||||
[ContentTypes.TEXT]: {
|
||||
value: responseText,
|
||||
|
||||
if (editedContent && latestMessage?.content) {
|
||||
initialResponse.content = cloneDeep(latestMessage.content);
|
||||
const { index, text, type } = editedContent;
|
||||
if (initialResponse.content && index >= 0 && index < initialResponse.content.length) {
|
||||
const contentPart = initialResponse.content[index];
|
||||
if (type === ContentTypes.THINK && contentPart.type === ContentTypes.THINK) {
|
||||
contentPart[ContentTypes.THINK] = text;
|
||||
} else if (type === ContentTypes.TEXT && contentPart.type === ContentTypes.TEXT) {
|
||||
contentPart[ContentTypes.TEXT] = text;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
initialResponse.content = [
|
||||
{
|
||||
type: ContentTypes.TEXT,
|
||||
[ContentTypes.TEXT]: {
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
setShowStopButton(true);
|
||||
} else if (usesContentStream(endpoint, endpointType)) {
|
||||
initialResponse.text = '';
|
||||
initialResponse.content = [
|
||||
{
|
||||
type: ContentTypes.TEXT,
|
||||
[ContentTypes.TEXT]: {
|
||||
value: responseText,
|
||||
},
|
||||
},
|
||||
];
|
||||
setShowStopButton(true);
|
||||
} else {
|
||||
];
|
||||
}
|
||||
setShowStopButton(true);
|
||||
}
|
||||
|
||||
@@ -316,7 +300,6 @@ export default function useChatFunctions({
|
||||
endpointOption,
|
||||
userMessage: {
|
||||
...currentMsg,
|
||||
generation,
|
||||
responseMessageId,
|
||||
overrideParentMessageId: isRegenerate ? messageId : null,
|
||||
},
|
||||
@@ -328,6 +311,7 @@ export default function useChatFunctions({
|
||||
initialResponse,
|
||||
isTemporary,
|
||||
ephemeralAgent,
|
||||
editedContent,
|
||||
};
|
||||
|
||||
if (isRegenerate) {
|
||||
|
||||
@@ -30,6 +30,14 @@ const useSetIndexOptions: TUseSetOptions = (preset = false) => {
|
||||
};
|
||||
}
|
||||
|
||||
// Auto-enable Responses API when web search is enabled
|
||||
if (param === 'web_search' && newValue === true) {
|
||||
const currentUseResponsesApi = conversation?.useResponsesApi ?? false;
|
||||
if (!currentUseResponsesApi) {
|
||||
update['useResponsesApi'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
setConversation(
|
||||
(prevState) =>
|
||||
tConvoUpdateSchema.parse({
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -55,6 +55,26 @@ export default function useStepHandler({
|
||||
const messageMap = useRef(new Map<string, TMessage>());
|
||||
const stepMap = useRef(new Map<string, Agents.RunStep>());
|
||||
|
||||
const calculateContentIndex = (
|
||||
baseIndex: number,
|
||||
initialContent: TMessageContentParts[],
|
||||
incomingContentType: string,
|
||||
existingContent?: TMessageContentParts[],
|
||||
): number => {
|
||||
/** Only apply -1 adjustment for TEXT or THINK types when they match existing content */
|
||||
if (
|
||||
initialContent.length > 0 &&
|
||||
(incomingContentType === ContentTypes.TEXT || incomingContentType === ContentTypes.THINK)
|
||||
) {
|
||||
const targetIndex = baseIndex + initialContent.length - 1;
|
||||
const existingType = existingContent?.[targetIndex]?.type;
|
||||
if (existingType === incomingContentType) {
|
||||
return targetIndex;
|
||||
}
|
||||
}
|
||||
return baseIndex + initialContent.length;
|
||||
};
|
||||
|
||||
const updateContent = (
|
||||
message: TMessage,
|
||||
index: number,
|
||||
@@ -170,6 +190,11 @@ export default function useStepHandler({
|
||||
lastAnnouncementTimeRef.current = currentTime;
|
||||
}
|
||||
|
||||
let initialContent: TMessageContentParts[] = [];
|
||||
if (submission?.editedContent != null) {
|
||||
initialContent = submission?.initialResponse?.content ?? initialContent;
|
||||
}
|
||||
|
||||
if (event === 'on_run_step') {
|
||||
const runStep = data as Agents.RunStep;
|
||||
const responseMessageId = runStep.runId ?? '';
|
||||
@@ -189,7 +214,7 @@ export default function useStepHandler({
|
||||
parentMessageId: userMessage.messageId,
|
||||
conversationId: userMessage.conversationId,
|
||||
messageId: responseMessageId,
|
||||
content: [],
|
||||
content: initialContent,
|
||||
};
|
||||
|
||||
messageMap.current.set(responseMessageId, response);
|
||||
@@ -214,7 +239,9 @@ export default function useStepHandler({
|
||||
},
|
||||
};
|
||||
|
||||
updatedResponse = updateContent(updatedResponse, runStep.index, contentPart);
|
||||
/** Tool calls don't need index adjustment */
|
||||
const currentIndex = runStep.index + initialContent.length;
|
||||
updatedResponse = updateContent(updatedResponse, currentIndex, contentPart);
|
||||
});
|
||||
|
||||
messageMap.current.set(responseMessageId, updatedResponse);
|
||||
@@ -234,7 +261,9 @@ export default function useStepHandler({
|
||||
|
||||
const response = messageMap.current.get(responseMessageId);
|
||||
if (response) {
|
||||
const updatedResponse = updateContent(response, agent_update.index, data);
|
||||
// Agent updates don't need index adjustment
|
||||
const currentIndex = agent_update.index + initialContent.length;
|
||||
const updatedResponse = updateContent(response, currentIndex, data);
|
||||
messageMap.current.set(responseMessageId, updatedResponse);
|
||||
const currentMessages = getMessages() || [];
|
||||
setMessages([...currentMessages.slice(0, -1), updatedResponse]);
|
||||
@@ -255,7 +284,13 @@ export default function useStepHandler({
|
||||
? messageDelta.delta.content[0]
|
||||
: messageDelta.delta.content;
|
||||
|
||||
const updatedResponse = updateContent(response, runStep.index, contentPart);
|
||||
const currentIndex = calculateContentIndex(
|
||||
runStep.index,
|
||||
initialContent,
|
||||
contentPart.type || '',
|
||||
response.content,
|
||||
);
|
||||
const updatedResponse = updateContent(response, currentIndex, contentPart);
|
||||
|
||||
messageMap.current.set(responseMessageId, updatedResponse);
|
||||
const currentMessages = getMessages() || [];
|
||||
@@ -277,7 +312,13 @@ export default function useStepHandler({
|
||||
? reasoningDelta.delta.content[0]
|
||||
: reasoningDelta.delta.content;
|
||||
|
||||
const updatedResponse = updateContent(response, runStep.index, contentPart);
|
||||
const currentIndex = calculateContentIndex(
|
||||
runStep.index,
|
||||
initialContent,
|
||||
contentPart.type || '',
|
||||
response.content,
|
||||
);
|
||||
const updatedResponse = updateContent(response, currentIndex, contentPart);
|
||||
|
||||
messageMap.current.set(responseMessageId, updatedResponse);
|
||||
const currentMessages = getMessages() || [];
|
||||
@@ -318,7 +359,9 @@ export default function useStepHandler({
|
||||
contentPart.tool_call.expires_at = runStepDelta.delta.expires_at;
|
||||
}
|
||||
|
||||
updatedResponse = updateContent(updatedResponse, runStep.index, contentPart);
|
||||
/** Tool calls don't need index adjustment */
|
||||
const currentIndex = runStep.index + initialContent.length;
|
||||
updatedResponse = updateContent(updatedResponse, currentIndex, contentPart);
|
||||
});
|
||||
|
||||
messageMap.current.set(responseMessageId, updatedResponse);
|
||||
@@ -350,7 +393,9 @@ export default function useStepHandler({
|
||||
tool_call: result.tool_call,
|
||||
};
|
||||
|
||||
updatedResponse = updateContent(updatedResponse, runStep.index, contentPart, true);
|
||||
/** Tool calls don't need index adjustment */
|
||||
const currentIndex = runStep.index + initialContent.length;
|
||||
updatedResponse = updateContent(updatedResponse, currentIndex, contentPart, true);
|
||||
|
||||
messageMap.current.set(responseMessageId, updatedResponse);
|
||||
const updatedMessages = messages.map((msg) =>
|
||||
|
||||
@@ -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": "أدوات المساعدين",
|
||||
@@ -695,6 +730,5 @@
|
||||
"com_ui_versions": "الإصدارات",
|
||||
"com_ui_yes": "نعم",
|
||||
"com_ui_zoom": "تكبير",
|
||||
"com_user_message": "أنت",
|
||||
"com_warning_resubmit_unsupported": "إعادة إرسال رسالة الذكاء الاصطناعي غير مدعومة لنقطة النهاية هذه"
|
||||
"com_user_message": "أنت"
|
||||
}
|
||||
@@ -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",
|
||||
@@ -868,6 +866,5 @@
|
||||
"com_ui_x_selected": "{{0}} seleccionats",
|
||||
"com_ui_yes": "Sí",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_user_message": "Tu",
|
||||
"com_warning_resubmit_unsupported": "Tornar a enviar el missatge de la IA no està suportat per aquest endpoint."
|
||||
"com_user_message": "Tu"
|
||||
}
|
||||
@@ -720,6 +720,5 @@
|
||||
"com_ui_write": "Psát",
|
||||
"com_ui_yes": "Ano",
|
||||
"com_ui_zoom": "Přiblížit",
|
||||
"com_user_message": "Vy",
|
||||
"com_warning_resubmit_unsupported": "Opětovné odeslání AI zprávy není pro tento koncový bod podporováno."
|
||||
"com_user_message": "Vy"
|
||||
}
|
||||
@@ -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,11 +917,30 @@
|
||||
"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_api_subtitle": "Søg på nettet efter opdateret information",
|
||||
"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",
|
||||
"com_ui_yes": "Ja",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_user_message": "Du",
|
||||
"com_warning_resubmit_unsupported": "Genindsendelse af AI-beskeden understøttes ikke for dette slutpunkt."
|
||||
"com_user_message": "Du"
|
||||
}
|
||||
@@ -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 128–32.768 Token. Gemini 2.5 Flash unterstützt 0–24.576 Token. Gemini 2.5 Flash Lite unterstützt 512–24.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,38 @@
|
||||
"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": "Web-Suche\n",
|
||||
"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_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",
|
||||
@@ -917,6 +1062,5 @@
|
||||
"com_ui_x_selected": "{{0}} ausgewählt",
|
||||
"com_ui_yes": "Ja",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_user_message": "Du",
|
||||
"com_warning_resubmit_unsupported": "Das erneute Senden der KI-Nachricht wird für diesen Endpunkt nicht unterstützt."
|
||||
"com_user_message": "Du"
|
||||
}
|
||||
@@ -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",
|
||||
@@ -209,6 +210,7 @@
|
||||
"com_endpoint_google_thinking_budget": "Guides the number of thinking tokens the model uses. The actual amount may exceed or fall below this value depending on the prompt.\n\nThis setting is only supported by certain models (2.5 series). Gemini 2.5 Pro supports 128-32,768 tokens. Gemini 2.5 Flash supports 0-24,576 tokens. Gemini 2.5 Flash Lite supports 512-24,576 tokens.\n\nLeave blank or set to \"-1\" to let the model automatically decide when and how much to think. By default, Gemini 2.5 Flash Lite does not think.",
|
||||
"com_endpoint_google_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_google_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_google_use_search_grounding": "Use Google's search grounding feature to enhance responses with real-time web search results. This enables models to access current information and provide more accurate, up-to-date answers.",
|
||||
"com_endpoint_instructions_assistants": "Override Instructions",
|
||||
"com_endpoint_instructions_assistants_placeholder": "Overrides the instructions of the assistant. This is useful for modifying the behavior on a per-run basis.",
|
||||
"com_endpoint_max_output_tokens": "Max Output Tokens",
|
||||
@@ -226,11 +228,14 @@
|
||||
"com_endpoint_openai_pres": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.",
|
||||
"com_endpoint_openai_prompt_prefix_placeholder": "Set custom instructions to include in System Message. Default: none",
|
||||
"com_endpoint_openai_reasoning_effort": "o1 and o3 models only: constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.",
|
||||
"com_endpoint_openai_reasoning_summary": "Responses API only: A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. Set to none,auto, concise, or detailed.",
|
||||
"com_endpoint_openai_resend": "Resend all previously attached images. Note: this can significantly increase token cost and you may experience errors with many image attachments.",
|
||||
"com_endpoint_openai_resend_files": "Resend all previously attached files. Note: this will increase token cost and you may experience errors with many attachments.",
|
||||
"com_endpoint_openai_stop": "Up to 4 sequences where the API will stop generating further tokens.",
|
||||
"com_endpoint_openai_temp": "Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.",
|
||||
"com_endpoint_openai_topp": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We recommend altering this or temperature but not both.",
|
||||
"com_endpoint_openai_use_responses_api": "Use the Responses API instead of Chat Completions, which includes extended features from OpenAI. Required for o1-pro, o3-pro, and to enable reasoning summaries.",
|
||||
"com_endpoint_openai_use_web_search": "Enable web search functionality using OpenAI'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_output": "Output",
|
||||
"com_endpoint_plug_image_detail": "Image Detail",
|
||||
"com_endpoint_plug_resend_files": "Resend Files",
|
||||
@@ -261,6 +266,7 @@
|
||||
"com_endpoint_prompt_prefix_assistants_placeholder": "Set additional instructions or context on top of the Assistant's main instructions. Ignored if empty.",
|
||||
"com_endpoint_prompt_prefix_placeholder": "Set custom instructions or context. Ignored if empty.",
|
||||
"com_endpoint_reasoning_effort": "Reasoning Effort",
|
||||
"com_endpoint_reasoning_summary": "Reasoning Summary",
|
||||
"com_endpoint_save_as_preset": "Save As Preset",
|
||||
"com_endpoint_search": "Search endpoint by name",
|
||||
"com_endpoint_search_endpoint_models": "Search {{0}} models...",
|
||||
@@ -276,6 +282,8 @@
|
||||
"com_endpoint_top_k": "Top K",
|
||||
"com_endpoint_top_p": "Top P",
|
||||
"com_endpoint_use_active_assistant": "Use Active Assistant",
|
||||
"com_endpoint_use_responses_api": "Use Responses API",
|
||||
"com_endpoint_use_search_grounding": "Grounding with Google Search",
|
||||
"com_error_expired_user_key": "Provided key for {{0}} expired at {{1}}. Please provide a new key and try again.",
|
||||
"com_error_files_dupe": "Duplicate file detected.",
|
||||
"com_error_files_empty": "Empty files are not allowed.",
|
||||
@@ -284,6 +292,7 @@
|
||||
"com_error_files_upload": "An error occurred while uploading the file.",
|
||||
"com_error_files_upload_canceled": "The file upload request was canceled. Note: the file upload may still be processing and will need to be manually deleted.",
|
||||
"com_error_files_validation": "An error occurred while validating the file.",
|
||||
"com_error_google_tool_conflict": "Usage of built-in Google tools are not supported with external tools. Please disable either the built-in tools or the external tools.",
|
||||
"com_error_heic_conversion": "Failed to convert HEIC image to JPEG. Please try converting the image manually or use a different format.",
|
||||
"com_error_input_length": "The latest message token count is too long, exceeding the token limit, or your token limit parameters are misconfigured, adversely affecting the context window. More info: {{0}}. Please shorten your message, adjust the max context size from the conversation parameters, or fork the conversation to continue.",
|
||||
"com_error_invalid_agent_provider": "The \"{{0}}\" provider is not available for use with Agents. Please go to your agent's settings and select a currently available provider.",
|
||||
@@ -359,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",
|
||||
@@ -397,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à",
|
||||
@@ -416,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",
|
||||
@@ -425,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)",
|
||||
@@ -565,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",
|
||||
@@ -634,6 +646,7 @@
|
||||
"com_ui_command_placeholder": "Optional: Enter a command for the prompt or name will be used",
|
||||
"com_ui_command_usage_placeholder": "Select a Prompt by command or name",
|
||||
"com_ui_complete_setup": "Complete Setup",
|
||||
"com_ui_concise": "Concise",
|
||||
"com_ui_configure_mcp_variables_for": "Configure Variables for {{0}}",
|
||||
"com_ui_confirm_action": "Confirm Action",
|
||||
"com_ui_confirm_admin_use_change": "Changing this setting will block access for admins, including yourself. Are you sure you want to proceed?",
|
||||
@@ -699,6 +712,7 @@
|
||||
"com_ui_description": "Description",
|
||||
"com_ui_description_placeholder": "Optional: Enter a description to display for the prompt",
|
||||
"com_ui_deselect_all": "Deselect All",
|
||||
"com_ui_detailed": "Detailed",
|
||||
"com_ui_disabling": "Disabling...",
|
||||
"com_ui_download": "Download",
|
||||
"com_ui_download_artifact": "Download Artifact",
|
||||
@@ -760,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.",
|
||||
@@ -793,6 +808,7 @@
|
||||
"com_ui_happy_birthday": "It's my 1st birthday!",
|
||||
"com_ui_hide_image_details": "Hide Image Details",
|
||||
"com_ui_hide_qr": "Hide QR Code",
|
||||
"com_ui_high": "High",
|
||||
"com_ui_host": "Host",
|
||||
"com_ui_icon": "Icon",
|
||||
"com_ui_idea": "Ideas",
|
||||
@@ -805,8 +821,7 @@
|
||||
"com_ui_import_conversation_file_type_error": "Unsupported import type",
|
||||
"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_include_shadcnui": "Include shadcn/ui",
|
||||
"com_ui_input": "Input",
|
||||
"com_ui_instructions": "Instructions",
|
||||
"com_ui_key": "Key",
|
||||
@@ -820,6 +835,7 @@
|
||||
"com_ui_loading": "Loading...",
|
||||
"com_ui_locked": "Locked",
|
||||
"com_ui_logo": "{{0}} Logo",
|
||||
"com_ui_low": "Low",
|
||||
"com_ui_manage": "Manage",
|
||||
"com_ui_max_tags": "Maximum number allowed is {{0}}, using latest values.",
|
||||
"com_ui_mcp_dialog_desc": "Please enter the necessary information below.",
|
||||
@@ -827,6 +843,7 @@
|
||||
"com_ui_mcp_server_not_found": "Server not found.",
|
||||
"com_ui_mcp_servers": "MCP Servers",
|
||||
"com_ui_mcp_url": "MCP Server URL",
|
||||
"com_ui_medium": "Medium",
|
||||
"com_ui_memories": "Memories",
|
||||
"com_ui_memories_allow_create": "Allow creating Memories",
|
||||
"com_ui_memories_allow_opt_out": "Allow users to opt out of Memories",
|
||||
@@ -1034,13 +1051,15 @@
|
||||
"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_serper": "Serper API",
|
||||
"com_ui_web_search_provider_searxng": "SearXNG",
|
||||
"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_search_provider_serper_key": "Get your Serper API key",
|
||||
"com_ui_web_search_reading": "Reading results",
|
||||
"com_ui_web_search_reranker": "Reranker",
|
||||
@@ -1059,5 +1078,6 @@
|
||||
"com_ui_yes": "Yes",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_user_message": "You",
|
||||
"com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint."
|
||||
}
|
||||
"com_ui_show_password": "Show password",
|
||||
"com_ui_hide_password": "Hide password"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -752,6 +751,5 @@
|
||||
"com_ui_x_selected": "{{0}} seleccionado",
|
||||
"com_ui_yes": "Sí",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_user_message": "Usted",
|
||||
"com_warning_resubmit_unsupported": "No se admite el reenvío del mensaje de IA para este punto de conexión."
|
||||
"com_user_message": "Usted"
|
||||
}
|
||||
@@ -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,11 +939,30 @@
|
||||
"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_api_subtitle": "Otsi veebist ajakohast teavet",
|
||||
"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",
|
||||
"com_ui_yes": "Jah",
|
||||
"com_ui_zoom": "Suumi",
|
||||
"com_user_message": "Sina",
|
||||
"com_warning_resubmit_unsupported": "AI sõnumi uuesti esitamine pole selle otspunkti jaoks toetatud."
|
||||
"com_user_message": "Sina"
|
||||
}
|
||||
@@ -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": "آخر شب مبارک",
|
||||
@@ -847,6 +845,5 @@
|
||||
"com_ui_write": "نوشتن",
|
||||
"com_ui_yes": "بله",
|
||||
"com_ui_zoom": "بزرگنمایی ضربه بزنید؛",
|
||||
"com_user_message": "شما",
|
||||
"com_warning_resubmit_unsupported": "ارسال مجدد پیام هوش مصنوعی برای این نقطه پایانی پشتیبانی نمی شود."
|
||||
"com_user_message": "شما"
|
||||
}
|
||||
@@ -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,47 +965,110 @@
|
||||
"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_api_subtitle": "Rechercher des informations actualisées sur le 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",
|
||||
"com_warning_resubmit_unsupported": "La resoumission du message IA n'est pas prise en charge pour ce point de terminaison."
|
||||
"com_user_message": "Vous"
|
||||
}
|
||||
@@ -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,22 +1013,40 @@
|
||||
"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_api_subtitle": "חפש מידע עדכני ברשת",
|
||||
"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}} נבחר",
|
||||
"com_ui_yes": "כן",
|
||||
"com_ui_zoom": "זום",
|
||||
"com_user_message": "אתה",
|
||||
"com_warning_resubmit_unsupported": "שליחת הודעה מחדש אינה נתמכת עבור נקודת קצה זו."
|
||||
"com_user_message": "אתה"
|
||||
}
|
||||
@@ -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",
|
||||
@@ -847,6 +845,5 @@
|
||||
"com_ui_write": "Írás",
|
||||
"com_ui_yes": "Igen",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_user_message": "Ön",
|
||||
"com_warning_resubmit_unsupported": "Az AI üzenet újraküldése nem támogatott ennél a végpontnál."
|
||||
"com_user_message": "Ön"
|
||||
}
|
||||
155
client/src/locales/hy/translation.json
Normal file
155
client/src/locales/hy/translation.json
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"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_source": "Աղբյուր",
|
||||
"com_click_to_download": "(սեղմեք այստեղ ներբեռնելու համար)",
|
||||
"com_download_expired": "(ներբեռնելու ժամկետը սպառվել է)",
|
||||
"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_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_open_menu": "Բացել մենյուն",
|
||||
"com_endpoint_output": "Ելք",
|
||||
"com_endpoint_preset": "պրեսեթ",
|
||||
"com_endpoint_preset_default_item": "Սկզբնական",
|
||||
"com_endpoint_preset_default_none": "Սկզբնական նախադրված պրեսեթը ակտիվ չէ։",
|
||||
"com_endpoint_preset_name": "Պրեսեթի անուն",
|
||||
"com_endpoint_preset_selected": "Պրեսեթը ակտիվ է։",
|
||||
"com_endpoint_preset_selected_title": "Ակտիվ է։",
|
||||
"com_endpoint_preset_title": "Պրեսեթ",
|
||||
"com_endpoint_presets": "պրեսեթներ",
|
||||
"com_endpoint_temperature": "Temperature",
|
||||
"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_commands": "Հրամաններ",
|
||||
"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-ն"
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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",
|
||||
@@ -829,6 +827,5 @@
|
||||
"com_ui_write": "Scrittura",
|
||||
"com_ui_yes": "Sì",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_user_message": "Mostra nome utente nei messaggi",
|
||||
"com_warning_resubmit_unsupported": "Il reinvio del messaggio AI non è supportato per questo endpoint."
|
||||
"com_user_message": "Mostra nome utente nei messaggi"
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
"com_agents_name_placeholder": "オプション: エージェントの名前",
|
||||
"com_agents_no_access": "このエージェントを編集する権限がありません。",
|
||||
"com_agents_not_available": "エージェントは利用できません",
|
||||
"com_agents_search_info": "有効にすると、エージェントが最新の情報を取得するためにWEB検索を行うようになります。有効なAPIキーが必要です。",
|
||||
"com_agents_search_name": "名前でエージェントを検索",
|
||||
"com_agents_update_error": "エージェントの更新中にエラーが発生しました。",
|
||||
"com_assistants_action_attempt": "アシスタントは{{0}}と話したいです",
|
||||
@@ -322,7 +323,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": "有効化",
|
||||
@@ -688,7 +688,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": "遅い夜を楽しんで",
|
||||
@@ -868,6 +867,5 @@
|
||||
"com_ui_x_selected": "{{0}}が選択された",
|
||||
"com_ui_yes": "はい",
|
||||
"com_ui_zoom": "ズーム",
|
||||
"com_user_message": "あなた",
|
||||
"com_warning_resubmit_unsupported": "このエンドポイントではAIメッセージの再送信はサポートされていません"
|
||||
"com_user_message": "あなた"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user