Compare commits

...

26 Commits

Author SHA1 Message Date
Ruben Talstra
e34503edce 🔧 feat: Localize redirect message to OpenID provider in Login component 2025-03-10 12:55:46 +01:00
Ruben Talstra
14bd9f03fa 🔧 refactor: Update getLoginError to use TranslationKeys for improved type safety 2025-03-10 12:48:42 +01:00
Ruben Talstra
17b0f35f93 🔧 feat: Implement custom logout redirect handling and enhance OpenID auto-redirect logic 2025-03-10 11:52:36 +01:00
Ruben Talstra
a2f953460b Merge branch 'main' into feat/oidc-auto-redirect 2025-03-10 09:35:49 +01:00
github-actions[bot]
9db00edfc4 🌍 i18n: Update translation.json with latest translations (#6241)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-09 18:19:50 -04:00
sh4shii
a53638c481 🐛 fix: Await saveMessage in abortMiddleware to ensure proper execution (#6248) 2025-03-09 18:07:31 -04:00
Danny Avila
d6ab769b80 ⚠️ refactor: Use Error Content Part Instead Of Throwing Error for Agents (#6262) 2025-03-09 18:06:34 -04:00
Ruben Talstra
3e3dfe5bad 🔏 fix: Enhance Two-Factor Authentication (#6247)
* 🌟 feat: Implement Two-Factor Authentication (2FA) functionality

* fix: Two-Factor Authentication Logic and State Management

* 🌟 feat: Add LICENSE file and update package version to 0.0.2 with MIT license
2025-03-08 15:28:27 -05:00
Ruben Talstra
cc661c95ee 🔧 fix: MeiliSearch Field Error and Patch Incorrect Import by #6210 (#6245)
* 📦 refactor: Update MeiliSearch integration and improve schema handling

* Update indexSync.js

* 📦 refactor: Update Conversation model import path in indexSync.js

* 📦 refactor: Update import paths for Conversation and Message models in indexSync.js
2025-03-08 14:37:33 -05:00
github-actions[bot]
6ea88e09a2 🌍 i18n: Update translation.json with latest translations (#6240)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-08 11:18:04 -05:00
Danny Avila
a846e898a2 🐛 fix: Avatar Type Definitions in Agent/Assistant Schemas (#6235)
* fix: Simplify avatar type definition in agent and assistant schemas

* fix: Update regex to correctly match OpenAI model identifiers
2025-03-08 10:55:06 -05:00
Danny Avila
dc8d5dee6a 📦 chore: Patch axios to address CVE-2025-27152 (#6222)
* 📦 chore: remove `langchain` (no longer used)

* chore: patch `axios` to address CVE-2025-27152
2025-03-07 12:45:31 -05:00
github-actions[bot]
f04ae65a75 🌍 i18n: Update translation.json with latest translations (#6220)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-07 12:43:40 -05:00
Danny Avila
0a4da06fe1 📦 ci: Update npm authentication token for publishing in workflow 2025-03-07 12:20:56 -05:00
Danny Avila
932474c44e 📦 ci: Refactor workflow to combine build and publish steps with version check for @librechat/data-schemas 2025-03-07 12:18:03 -05:00
Danny Avila
a2b7812033 📦 ci: Update workflow to publish @librechat/data-schemas to NPM with manual trigger option 2025-03-07 12:12:30 -05:00
Danny Avila
88d2920b06 📦 ci: npm publish access to public for data-schemas 2025-03-07 12:10:32 -05:00
Ruben Talstra
c5e012abc0 🌍 i18n: Add Thai Language Support and Update Translations (#6219)
* 🌍 i18n: Add Thai Language Support and Update Translations

* 📝 docs: Update Locize Logo in README.md
2025-03-07 11:57:57 -05:00
Ruben Talstra
b51cd21b3c 📦 refactor: Move DB Models to @librechat/data-schemas (#6210)
* 🚀 feat: Introduce data schemas and refactor models to use @librechat/data-schemas

* 🚀 feat: Add installation step for Data Schemas Package in backend review workflow

* chore: Add `data-schemas` package to update/rebuild packages scripts

* chore: Update Dockerfile to include data-schemas package build process

* fix: add missing @rollup/plugin-typescript package

* chore: Add GitHub Actions workflow for publishing data-schemas package

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2025-03-07 11:55:44 -05:00
Danny Avila
4d04904af3 v0.7.7 (#6206)
* v0.7.7

* chore: Bump librechat-mcp version to 1.1.0

* action: update Unreleased changelog

* Update CHANGELOG.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Ruben Talstra <RubenTalstra1211@outlook.com>
2025-03-06 14:33:33 -05:00
Danny Avila
8cb7f34f86 🚀 feat: Add Code API Proxy Support and Update MCP SDK (#6203)
* chore: bump mcp sdk

* feat: Add proxy support for file download and upload in Code Environment CRUD operations

* chore: remove unused files

* chore: change output format from CommonJS to ES module in server rollup config
2025-03-06 12:47:59 -05:00
Kaushik Iska
780fdf743a 🕒 feat: Add Configurable MCP Server Timeouts (#6199) 2025-03-06 12:02:43 -05:00
Danny Avila
c8f7588164 🪄 feat: Customize Sandpack bundlerURL for Artifacts (#6191) 2025-03-05 16:03:54 -05:00
Danny Avila
00b2d026c1 🚀 feat: Enhance Model Handling, Logging & xAI Agent Support (#6182)
* chore: update @librechat/agents to version 2.1.9

* feat: xAI standalone provider for agents

* chore: bump librechat-data-provider version to 0.7.6997

* fix: reorder import statements and enhance user listing output

* fix: Update Docker Compose commands to support v2 syntax with fallback

* 🔧 fix: drop `reasoning_effort` for o1-preview/mini models

* chore: requireLocalAuth logging

* fix: edge case artifact message editing logic to handle `new` conversation IDs

* fix: remove `temperature` from model options in OpenAIClient if o1-mini/preview

* fix: update type annotation for fetchPromisesMap to use Promise<string[]> instead of string[]

* feat: anthropic model fetching

* fix: update model name to use EModelEndpoint.openAI in fetchModels and fetchOpenAIModels

* fix: add error handling to modelController for loadModels

* fix: add error handling and logging for model fetching in loadDefaultModels

* ci: update getAnthropicModels tests to be asynchronous

* feat: add user ID to model options in OpenAI and custom endpoint initialization

---------

Co-authored-by: Andrei Berceanu <andreicberceanu@gmail.com>
Co-authored-by: KiGamji <maloyh44@gmail.com>
2025-03-05 12:04:26 -05:00
Danilo Pejakovic
bfc7179f16 Added Cooldown logic for OIDC auto redirect for failed login attempts 2025-02-27 10:58:52 +01:00
Danilo Pejakovic
caaadf2fdb added feature for oidc auto redirection 2025-02-26 15:39:55 +01:00
162 changed files with 6129 additions and 5265 deletions

View File

@@ -432,6 +432,9 @@ OPENID_NAME_CLAIM=
OPENID_BUTTON_LABEL=
OPENID_IMAGE_URL=
# Set to true to automatically redirect to the OpenID provider when a user visits the login page
# This will bypass the login form completely for users, only use this if OpenID is your only authentication method
OPENID_AUTO_REDIRECT=false
# LDAP
LDAP_URL=

View File

@@ -39,6 +39,9 @@ jobs:
- name: Install MCP Package
run: npm run build:mcp
- name: Install Data Schemas Package
run: npm run build:data-schemas
- name: Create empty auth.json file
run: |
mkdir -p api/data

58
.github/workflows/data-schemas.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Publish `@librechat/data-schemas` to NPM
on:
push:
branches:
- main
paths:
- 'packages/data-schemas/package.json'
workflow_dispatch:
inputs:
reason:
description: 'Reason for manual trigger'
required: false
default: 'Manual publish requested'
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
- name: Install dependencies
run: cd packages/data-schemas && npm ci
- name: Build
run: cd packages/data-schemas && npm run build
- name: Set up npm authentication
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.PUBLISH_NPM_TOKEN }}" > ~/.npmrc
- name: Check version change
id: check
working-directory: packages/data-schemas
run: |
PACKAGE_VERSION=$(node -p "require('./package.json').version")
PUBLISHED_VERSION=$(npm view @librechat/data-schemas version 2>/dev/null || echo "0.0.0")
if [ "$PACKAGE_VERSION" = "$PUBLISHED_VERSION" ]; then
echo "No version change, skipping publish"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "Version changed, proceeding with publish"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Pack package
if: steps.check.outputs.skip != 'true'
working-directory: packages/data-schemas
run: npm pack
- name: Publish
if: steps.check.outputs.skip != 'true'
working-directory: packages/data-schemas
run: npm publish *.tgz --access public

View File

@@ -84,11 +84,11 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
sign-commits: true
commit-message: "chore: update CHANGELOG for release ${GITHUB_REF##*/}"
commit-message: "chore: update CHANGELOG for release ${{ github.ref_name }}"
base: main
branch: "changelog/${GITHUB_REF##*/}"
branch: "changelog/${{ github.ref_name }}"
reviewers: danny-avila
title: "chore: update CHANGELOG for release ${GITHUB_REF##*/}"
title: "chore: update CHANGELOG for release ${{ github.ref_name }}"
body: |
**Description**:
- This PR updates the CHANGELOG.md by removing the "Unreleased" section and adding new release notes for release ${GITHUB_REF##*/} above previous releases.
- This PR updates the CHANGELOG.md by removing the "Unreleased" section and adding new release notes for release ${{ github.ref_name }} above previous releases.

16
CHANGELOG.md Normal file
View File

@@ -0,0 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file.
## [Unreleased]
### ✨ New Features
- 🪄 feat: Agent Artifacts by **@danny-avila** in [#5804](https://github.com/danny-avila/LibreChat/pull/5804)
### ⚙️ Other Changes
- 🔄 chore: Enforce 18next Language Keys by **@rubentalstra** in [#5803](https://github.com/danny-avila/LibreChat/pull/5803)
- 🔃 refactor: Parent Message ID Handling on Error, Update Translations, Bump Agents by **@danny-avila** in [#5833](https://github.com/danny-avila/LibreChat/pull/5833)
---

View File

@@ -1,4 +1,4 @@
# v0.7.7-rc1
# v0.7.7
# Base node image
FROM node:20-alpine AS node

View File

@@ -1,5 +1,5 @@
# Dockerfile.multi
# v0.7.7-rc1
# v0.7.7
# Base for all builds
FROM node:20-alpine AS base-min
@@ -11,6 +11,7 @@ RUN npm config set fetch-retry-maxtimeout 600000 && \
COPY package*.json ./
COPY packages/data-provider/package*.json ./packages/data-provider/
COPY packages/mcp/package*.json ./packages/mcp/
COPY packages/data-schemas/package*.json ./packages/data-schemas/
COPY client/package*.json ./client/
COPY api/package*.json ./api/
@@ -32,6 +33,13 @@ COPY packages/mcp ./
COPY --from=data-provider-build /app/packages/data-provider/dist /app/packages/data-provider/dist
RUN npm run build
# Build data-schemas
FROM base AS data-schemas-build
WORKDIR /app/packages/data-schemas
COPY packages/data-schemas ./
COPY --from=data-provider-build /app/packages/data-provider/dist /app/packages/data-provider/dist
RUN npm run build
# Client build
FROM base AS client-build
WORKDIR /app/client
@@ -49,8 +57,9 @@ COPY api ./api
COPY config ./config
COPY --from=data-provider-build /app/packages/data-provider/dist ./packages/data-provider/dist
COPY --from=mcp-build /app/packages/mcp/dist ./packages/mcp/dist
COPY --from=data-schemas-build /app/packages/data-schemas/dist ./packages/data-schemas/dist
COPY --from=client-build /app/client/dist ./client/dist
WORKDIR /app/api
EXPOSE 3080
ENV HOST=0.0.0.0
CMD ["node", "server/index.js"]
CMD ["node", "server/index.js"]

View File

@@ -197,6 +197,6 @@ We thank [Locize](https://locize.com) for their translation management tools tha
<p align="center">
<a href="https://locize.com" target="_blank" rel="noopener noreferrer">
<img src="https://locize.com/img/locize_color.svg" alt="Locize Logo" height="50">
<img src="https://github.com/user-attachments/assets/d6b70894-6064-475e-bb65-92a9e23e0077" alt="Locize Logo" height="50">
</a>
</p>

View File

@@ -1307,8 +1307,12 @@ ${convo}
) {
delete modelOptions.stream;
delete modelOptions.stop;
} else if (!this.isOmni && modelOptions.reasoning_effort != null) {
} else if (
(!this.isOmni || /^o1-(mini|preview)/i.test(modelOptions.model)) &&
modelOptions.reasoning_effort != null
) {
delete modelOptions.reasoning_effort;
delete modelOptions.temperature;
}
let reasoningKey = 'reasoning_content';

View File

@@ -325,4 +325,37 @@ describe('formatAgentMessages', () => {
);
expect(result[0].content).not.toContain('Analyzing the problem...');
});
it('should exclude ERROR type content parts', () => {
const payload = [
{
role: 'assistant',
content: [
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Hello there' },
{
type: ContentTypes.ERROR,
[ContentTypes.ERROR]:
'An error occurred while processing the request: Something went wrong',
},
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final answer' },
],
},
];
const result = formatAgentMessages(payload);
expect(result).toHaveLength(1);
expect(result[0]).toBeInstanceOf(AIMessage);
expect(result[0].content).toEqual([
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Hello there' },
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final answer' },
]);
// Make sure no error content exists in the result
const hasErrorContent = result[0].content.some(
(item) =>
item.type === ContentTypes.ERROR || JSON.stringify(item).includes('An error occurred'),
);
expect(hasErrorContent).toBe(false);
});
});

View File

@@ -211,6 +211,8 @@ const formatAgentMessages = (payload) => {
} else if (part.type === ContentTypes.THINK) {
hasReasoning = true;
continue;
} else if (part.type === ContentTypes.ERROR) {
continue;
} else {
currentContent.push(part);
}

View File

@@ -1,6 +1,6 @@
const { MeiliSearch } = require('meilisearch');
const Conversation = require('~/models/schema/convoSchema');
const Message = require('~/models/schema/messageSchema');
const { Conversation } = require('~/models/Conversation');
const { Message } = require('~/models/Message');
const { isEnabled } = require('~/server/utils');
const { logger } = require('~/config');

View File

@@ -1,5 +1,5 @@
const mongoose = require('mongoose');
const actionSchema = require('./schema/action');
const { actionSchema } = require('@librechat/data-schemas');
const Action = mongoose.model('action', actionSchema);

View File

@@ -9,7 +9,7 @@ const {
removeAgentFromAllProjects,
} = require('./Project');
const getLogStores = require('~/cache/getLogStores');
const agentSchema = require('./schema/agent');
const { agentSchema } = require('@librechat/data-schemas');
const Agent = mongoose.model('agent', agentSchema);

View File

@@ -1,5 +1,5 @@
const mongoose = require('mongoose');
const assistantSchema = require('./schema/assistant');
const { assistantSchema } = require('@librechat/data-schemas');
const Assistant = mongoose.model('assistant', assistantSchema);

View File

@@ -1,5 +1,5 @@
const mongoose = require('mongoose');
const balanceSchema = require('./schema/balance');
const { balanceSchema } = require('@librechat/data-schemas');
const { getMultiplier } = require('./tx');
const { logger } = require('~/config');

View File

@@ -1,5 +1,9 @@
const Banner = require('./schema/banner');
const mongoose = require('mongoose');
const logger = require('~/config/winston');
const { bannerSchema } = require('@librechat/data-schemas');
const Banner = mongoose.model('Banner', bannerSchema);
/**
* Retrieves the current active banner.
* @returns {Promise<Object|null>} The active banner object or null if no active banner is found.

View File

@@ -1,5 +1,4 @@
const { logger } = require('~/config');
// const { Categories } = require('./schema/categories');
const options = [
{

View File

@@ -1,7 +1,11 @@
const ConversationTag = require('./schema/conversationTagSchema');
const mongoose = require('mongoose');
const Conversation = require('./schema/convoSchema');
const logger = require('~/config/winston');
const { conversationTagSchema } = require('@librechat/data-schemas');
const ConversationTag = mongoose.model('ConversationTag', conversationTagSchema);
/**
* Retrieves all conversation tags for a user.
* @param {string} user - The user ID.

View File

@@ -1,5 +1,5 @@
const mongoose = require('mongoose');
const fileSchema = require('./schema/fileSchema');
const { fileSchema } = require('@librechat/data-schemas');
const File = mongoose.model('File', fileSchema);
@@ -7,7 +7,7 @@ const File = mongoose.model('File', fileSchema);
* Finds a file by its file_id with additional query options.
* @param {string} file_id - The unique identifier of the file.
* @param {object} options - Query options for filtering, projection, etc.
* @returns {Promise<MongoFile>} A promise that resolves to the file document or null.
* @returns {Promise<IMongoFile>} A promise that resolves to the file document or null.
*/
const findFileById = async (file_id, options = {}) => {
return await File.findOne({ file_id, ...options }).lean();
@@ -17,7 +17,7 @@ const findFileById = async (file_id, options = {}) => {
* Retrieves files matching a given filter, sorted by the most recently updated.
* @param {Object} filter - The filter criteria to apply.
* @param {Object} [_sortOptions] - Optional sort parameters.
* @returns {Promise<Array<MongoFile>>} A promise that resolves to an array of file documents.
* @returns {Promise<Array<IMongoFile>>} A promise that resolves to an array of file documents.
*/
const getFiles = async (filter, _sortOptions) => {
const sortOptions = { updatedAt: -1, ..._sortOptions };
@@ -26,9 +26,9 @@ const getFiles = async (filter, _sortOptions) => {
/**
* Creates a new file with a TTL of 1 hour.
* @param {MongoFile} data - The file data to be created, must contain file_id.
* @param {IMongoFile} data - The file data to be created, must contain file_id.
* @param {boolean} disableTTL - Whether to disable the TTL.
* @returns {Promise<MongoFile>} A promise that resolves to the created file document.
* @returns {Promise<IMongoFile>} A promise that resolves to the created file document.
*/
const createFile = async (data, disableTTL) => {
const fileData = {
@@ -48,8 +48,8 @@ const createFile = async (data, disableTTL) => {
/**
* Updates a file identified by file_id with new data and removes the TTL.
* @param {MongoFile} data - The data to update, must contain file_id.
* @returns {Promise<MongoFile>} A promise that resolves to the updated file document.
* @param {IMongoFile} data - The data to update, must contain file_id.
* @returns {Promise<IMongoFile>} A promise that resolves to the updated file document.
*/
const updateFile = async (data) => {
const { file_id, ...update } = data;
@@ -62,8 +62,8 @@ const updateFile = async (data) => {
/**
* Increments the usage of a file identified by file_id.
* @param {MongoFile} data - The data to update, must contain file_id and the increment value for usage.
* @returns {Promise<MongoFile>} A promise that resolves to the updated file document.
* @param {IMongoFile} data - The data to update, must contain file_id and the increment value for usage.
* @returns {Promise<IMongoFile>} A promise that resolves to the updated file document.
*/
const updateFileUsage = async (data) => {
const { file_id, inc = 1 } = data;
@@ -77,7 +77,7 @@ const updateFileUsage = async (data) => {
/**
* Deletes a file identified by file_id.
* @param {string} file_id - The unique identifier of the file to delete.
* @returns {Promise<MongoFile>} A promise that resolves to the deleted file document or null.
* @returns {Promise<IMongoFile>} A promise that resolves to the deleted file document or null.
*/
const deleteFile = async (file_id) => {
return await File.findOneAndDelete({ file_id }).lean();
@@ -86,7 +86,7 @@ const deleteFile = async (file_id) => {
/**
* Deletes a file identified by a filter.
* @param {object} filter - The filter criteria to apply.
* @returns {Promise<MongoFile>} A promise that resolves to the deleted file document or null.
* @returns {Promise<IMongoFile>} A promise that resolves to the deleted file document or null.
*/
const deleteFileByFilter = async (filter) => {
return await File.findOneAndDelete(filter).lean();

View File

@@ -1,4 +1,4 @@
const mongoose = require('mongoose');
const keySchema = require('./schema/key');
const { keySchema } = require('@librechat/data-schemas');
module.exports = mongoose.model('Key', keySchema);

View File

@@ -1,6 +1,6 @@
const { model } = require('mongoose');
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
const projectSchema = require('~/models/schema/projectSchema');
const { projectSchema } = require('@librechat/data-schemas');
const Project = model('Project', projectSchema);
@@ -9,7 +9,7 @@ const Project = model('Project', projectSchema);
*
* @param {string} projectId - The ID of the project to find and return as a plain object.
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
* @returns {Promise<MongoProject>} A plain object representing the project document, or `null` if no project is found.
* @returns {Promise<IMongoProject>} A plain object representing the project document, or `null` if no project is found.
*/
const getProjectById = async function (projectId, fieldsToSelect = null) {
const query = Project.findById(projectId);
@@ -27,7 +27,7 @@ const getProjectById = async function (projectId, fieldsToSelect = null) {
*
* @param {string} projectName - The name of the project to find or create.
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
* @returns {Promise<MongoProject>} A plain object representing the project document.
* @returns {Promise<IMongoProject>} A plain object representing the project document.
*/
const getProjectByName = async function (projectName, fieldsToSelect = null) {
const query = { name: projectName };
@@ -47,7 +47,7 @@ const getProjectByName = async function (projectName, fieldsToSelect = null) {
*
* @param {string} projectId - The ID of the project to update.
* @param {string[]} promptGroupIds - The array of prompt group IDs to add to the project.
* @returns {Promise<MongoProject>} The updated project document.
* @returns {Promise<IMongoProject>} The updated project document.
*/
const addGroupIdsToProject = async function (projectId, promptGroupIds) {
return await Project.findByIdAndUpdate(
@@ -62,7 +62,7 @@ const addGroupIdsToProject = async function (projectId, promptGroupIds) {
*
* @param {string} projectId - The ID of the project to update.
* @param {string[]} promptGroupIds - The array of prompt group IDs to remove from the project.
* @returns {Promise<MongoProject>} The updated project document.
* @returns {Promise<IMongoProject>} The updated project document.
*/
const removeGroupIdsFromProject = async function (projectId, promptGroupIds) {
return await Project.findByIdAndUpdate(
@@ -87,7 +87,7 @@ const removeGroupFromAllProjects = async (promptGroupId) => {
*
* @param {string} projectId - The ID of the project to update.
* @param {string[]} agentIds - The array of agent IDs to add to the project.
* @returns {Promise<MongoProject>} The updated project document.
* @returns {Promise<IMongoProject>} The updated project document.
*/
const addAgentIdsToProject = async function (projectId, agentIds) {
return await Project.findByIdAndUpdate(
@@ -102,7 +102,7 @@ const addAgentIdsToProject = async function (projectId, agentIds) {
*
* @param {string} projectId - The ID of the project to update.
* @param {string[]} agentIds - The array of agent IDs to remove from the project.
* @returns {Promise<MongoProject>} The updated project document.
* @returns {Promise<IMongoProject>} The updated project document.
*/
const removeAgentIdsFromProject = async function (projectId, agentIds) {
return await Project.findByIdAndUpdate(

View File

@@ -1,3 +1,4 @@
const mongoose = require('mongoose');
const { ObjectId } = require('mongodb');
const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider');
const {
@@ -6,10 +7,13 @@ const {
removeGroupIdsFromProject,
removeGroupFromAllProjects,
} = require('./Project');
const { Prompt, PromptGroup } = require('./schema/promptSchema');
const { promptGroupSchema, promptSchema } = require('@librechat/data-schemas');
const { escapeRegExp } = require('~/server/utils');
const { logger } = require('~/config');
const PromptGroup = mongoose.model('PromptGroup', promptGroupSchema);
const Prompt = mongoose.model('Prompt', promptSchema);
/**
* Create a pipeline for the aggregation to get prompt groups
* @param {Object} query

View File

@@ -1,3 +1,4 @@
const mongoose = require('mongoose');
const {
CacheKeys,
SystemRoles,
@@ -12,9 +13,11 @@ const {
temporaryChatPermissionsSchema,
} = require('librechat-data-provider');
const getLogStores = require('~/cache/getLogStores');
const Role = require('~/models/schema/roleSchema');
const { roleSchema } = require('@librechat/data-schemas');
const { logger } = require('~/config');
const Role = mongoose.model('Role', roleSchema);
/**
* Retrieve a role by name and convert the found role document to a plain object.
* If the role with the given name doesn't exist and the name is a system defined role, create it and return the lean version.
@@ -168,6 +171,7 @@ const initializeRoles = async function () {
}
};
module.exports = {
Role,
getRoleByName,
initializeRoles,
updateRoleByName,

View File

@@ -8,7 +8,7 @@ const {
} = require('librechat-data-provider');
const { updateAccessPermissions, initializeRoles } = require('~/models/Role');
const getLogStores = require('~/cache/getLogStores');
const Role = require('~/models/schema/roleSchema');
const { Role } = require('~/models/Role');
// Mock the cache
jest.mock('~/cache/getLogStores', () => {

View File

@@ -1,7 +1,7 @@
const mongoose = require('mongoose');
const signPayload = require('~/server/services/signPayload');
const { hashToken } = require('~/server/utils/crypto');
const sessionSchema = require('./schema/session');
const { sessionSchema } = require('@librechat/data-schemas');
const { logger } = require('~/config');
const Session = mongoose.model('Session', sessionSchema);

View File

@@ -1,7 +1,9 @@
const mongoose = require('mongoose');
const { nanoid } = require('nanoid');
const { Constants } = require('librechat-data-provider');
const { Conversation } = require('~/models/Conversation');
const SharedLink = require('./schema/shareSchema');
const { shareSchema } = require('@librechat/data-schemas');
const SharedLink = mongoose.model('SharedLink', shareSchema);
const { getMessages } = require('./Message');
const logger = require('~/config/winston');

View File

@@ -1,6 +1,6 @@
const mongoose = require('mongoose');
const { encryptV2 } = require('~/server/utils/crypto');
const tokenSchema = require('./schema/tokenSchema');
const { tokenSchema } = require('@librechat/data-schemas');
const { logger } = require('~/config');
/**

View File

@@ -1,9 +1,11 @@
const ToolCall = require('./schema/toolCallSchema');
const mongoose = require('mongoose');
const { toolCallSchema } = require('@librechat/data-schemas');
const ToolCall = mongoose.model('ToolCall', toolCallSchema);
/**
* Create a new tool call
* @param {ToolCallData} toolCallData - The tool call data
* @returns {Promise<ToolCallData>} The created tool call document
* @param {IToolCallData} toolCallData - The tool call data
* @returns {Promise<IToolCallData>} The created tool call document
*/
async function createToolCall(toolCallData) {
try {
@@ -16,7 +18,7 @@ async function createToolCall(toolCallData) {
/**
* Get a tool call by ID
* @param {string} id - The tool call document ID
* @returns {Promise<ToolCallData|null>} The tool call document or null if not found
* @returns {Promise<IToolCallData|null>} The tool call document or null if not found
*/
async function getToolCallById(id) {
try {
@@ -44,7 +46,7 @@ async function getToolCallsByMessage(messageId, userId) {
* Get tool calls by conversation ID and user
* @param {string} conversationId - The conversation ID
* @param {string} userId - The user's ObjectId
* @returns {Promise<ToolCallData[]>} Array of tool call documents
* @returns {Promise<IToolCallData[]>} Array of tool call documents
*/
async function getToolCallsByConvo(conversationId, userId) {
try {
@@ -57,8 +59,8 @@ async function getToolCallsByConvo(conversationId, userId) {
/**
* Update a tool call
* @param {string} id - The tool call document ID
* @param {Partial<ToolCallData>} updateData - The data to update
* @returns {Promise<ToolCallData|null>} The updated tool call document or null if not found
* @param {Partial<IToolCallData>} updateData - The data to update
* @returns {Promise<IToolCallData|null>} The updated tool call document or null if not found
*/
async function updateToolCall(id, updateData) {
try {

View File

@@ -1,6 +1,6 @@
const mongoose = require('mongoose');
const { isEnabled } = require('~/server/utils/handleText');
const transactionSchema = require('./schema/transaction');
const { transactionSchema } = require('@librechat/data-schemas');
const { getMultiplier, getCacheMultiplier } = require('./tx');
const { logger } = require('~/config');
const Balance = require('./Balance');

View File

@@ -1,5 +1,5 @@
const mongoose = require('mongoose');
const userSchema = require('~/models/schema/userSchema');
const { userSchema } = require('@librechat/data-schemas');
const User = mongoose.model('User', userSchema);

View File

@@ -4,9 +4,28 @@ const { MeiliSearch } = require('meilisearch');
const { cleanUpPrimaryKeyValue } = require('~/lib/utils/misc');
const logger = require('~/config/meiliLogger');
// Environment flags
/**
* Flag to indicate if search is enabled based on environment variables.
* @type {boolean}
*/
const searchEnabled = process.env.SEARCH && process.env.SEARCH.toLowerCase() === 'true';
/**
* Flag to indicate if MeiliSearch is enabled based on required environment variables.
* @type {boolean}
*/
const meiliEnabled = process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY && searchEnabled;
/**
* Validates the required options for configuring the mongoMeili plugin.
*
* @param {Object} options - The configuration options.
* @param {string} options.host - The MeiliSearch host.
* @param {string} options.apiKey - The MeiliSearch API key.
* @param {string} options.indexName - The name of the index.
* @throws {Error} Throws an error if any required option is missing.
*/
const validateOptions = function (options) {
const requiredKeys = ['host', 'apiKey', 'indexName'];
requiredKeys.forEach((key) => {
@@ -16,53 +35,64 @@ const validateOptions = function (options) {
});
};
// const createMeiliMongooseModel = function ({ index, indexName, client, attributesToIndex }) {
/**
* Factory function to create a MeiliMongooseModel class which extends a Mongoose model.
* This class contains static and instance methods to synchronize and manage the MeiliSearch index
* corresponding to the MongoDB collection.
*
* @param {Object} config - Configuration object.
* @param {Object} config.index - The MeiliSearch index object.
* @param {Array<string>} config.attributesToIndex - List of attributes to index.
* @returns {Function} A class definition that will be loaded into the Mongoose schema.
*/
const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
// The primary key is assumed to be the first attribute in the attributesToIndex array.
const primaryKey = attributesToIndex[0];
// MeiliMongooseModel is of type Mongoose.Model
class MeiliMongooseModel {
/**
* `syncWithMeili`: synchronizes the data between a MongoDB collection and a MeiliSearch index,
* only triggered if there's ever a discrepancy determined by `api\lib\db\indexSync.js`.
* Synchronizes the data between the MongoDB collection and the MeiliSearch index.
*
* 1. Fetches all documents from the MongoDB collection and the MeiliSearch index.
* 2. Compares the documents from both sources.
* 3. If a document exists in MeiliSearch but not in MongoDB, it's deleted from MeiliSearch.
* 4. If a document exists in MongoDB but not in MeiliSearch, it's added to MeiliSearch.
* 5. If a document exists in both but has different `text` or `title` fields (depending on the `primaryKey`), it's updated in MeiliSearch.
* 6. After all operations, it updates the `_meiliIndex` field in MongoDB to indicate whether the document is indexed in MeiliSearch.
* The synchronization process involves:
* 1. Fetching all documents from the MongoDB collection and MeiliSearch index.
* 2. Comparing documents from both sources.
* 3. Deleting documents from MeiliSearch that no longer exist in MongoDB.
* 4. Adding documents to MeiliSearch that exist in MongoDB but not in the index.
* 5. Updating documents in MeiliSearch if key fields (such as `text` or `title`) differ.
* 6. Updating the `_meiliIndex` field in MongoDB to indicate the indexing status.
*
* Note: This strategy does not use batch operations for Meilisearch as the `index.addDocuments` will discard
* the entire batch if there's an error with one document, and will not throw an error if there's an issue.
* Also, `index.getDocuments` needs an exact limit on the amount of documents to return, so we build the map in batches.
* Note: The function processes documents in batches because MeiliSearch's
* `index.getDocuments` requires an exact limit and `index.addDocuments` does not handle
* partial failures in a batch.
*
* @returns {Promise} A promise that resolves when the synchronization is complete.
*
* @throws {Error} Throws an error if there's an issue with adding a document to MeiliSearch.
* @returns {Promise<void>} Resolves when the synchronization is complete.
*/
static async syncWithMeili() {
try {
let moreDocuments = true;
// Retrieve all MongoDB documents from the collection as plain JavaScript objects.
const mongoDocuments = await this.find().lean();
const format = (doc) => _.pick(doc, attributesToIndex);
// Prepare for comparison
// Helper function to format a document by selecting only the attributes to index
// and omitting keys starting with '$'.
const format = (doc) =>
_.omitBy(_.pick(doc, attributesToIndex), (v, k) => k.startsWith('$'));
// Build a map of MongoDB documents for quick lookup based on the primary key.
const mongoMap = new Map(mongoDocuments.map((doc) => [doc[primaryKey], format(doc)]));
const indexMap = new Map();
let offset = 0;
const batchSize = 1000;
// Fetch documents from the MeiliSearch index in batches.
while (moreDocuments) {
const batch = await index.getDocuments({ limit: batchSize, offset });
if (batch.results.length === 0) {
moreDocuments = false;
}
for (const doc of batch.results) {
indexMap.set(doc[primaryKey], format(doc));
}
offset += batchSize;
}
@@ -70,13 +100,12 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
const updateOps = [];
// Iterate over Meili index documents
// Process documents present in the MeiliSearch index.
for (const [id, doc] of indexMap) {
const update = {};
update[primaryKey] = id;
if (mongoMap.has(id)) {
// Case: Update
// If document also exists in MongoDB, would be update case
// If document exists in MongoDB, check for discrepancies in key fields.
if (
(doc.text && doc.text !== mongoMap.get(id).text) ||
(doc.title && doc.title !== mongoMap.get(id).title)
@@ -92,8 +121,7 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
await index.addDocuments([doc]);
}
} else {
// Case: Delete
// If document does not exist in MongoDB, its a delete case from meili index
// If the document does not exist in MongoDB, delete it from MeiliSearch.
await index.deleteDocument(id);
updateOps.push({
updateOne: { filter: update, update: { $set: { _meiliIndex: false } } },
@@ -101,24 +129,25 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
}
}
// Iterate over MongoDB documents
// Process documents present in MongoDB.
for (const [id, doc] of mongoMap) {
const update = {};
update[primaryKey] = id;
// Case: Insert
// If document does not exist in Meili Index, Its an insert case
// If the document is missing in the Meili index, add it.
if (!indexMap.has(id)) {
await index.addDocuments([doc]);
updateOps.push({
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
});
} else if (doc._meiliIndex === false) {
// If the document exists but is marked as not indexed, update the flag.
updateOps.push({
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
});
}
}
// Execute bulk update operations in MongoDB to update the _meiliIndex flags.
if (updateOps.length > 0) {
await this.collection.bulkWrite(updateOps);
logger.debug(
@@ -132,34 +161,47 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
}
}
// Set one or more settings of the meili index
/**
* Updates settings for the MeiliSearch index.
*
* @param {Object} settings - The settings to update on the MeiliSearch index.
* @returns {Promise<Object>} Promise resolving to the update result.
*/
static async setMeiliIndexSettings(settings) {
return await index.updateSettings(settings);
}
// Search the index
/**
* Searches the MeiliSearch index and optionally populates the results with data from MongoDB.
*
* @param {string} q - The search query.
* @param {Object} params - Additional search parameters for MeiliSearch.
* @param {boolean} populate - Whether to populate search hits with full MongoDB documents.
* @returns {Promise<Object>} The search results with populated hits if requested.
*/
static async meiliSearch(q, params, populate) {
const data = await index.search(q, params);
// Populate hits with content from mongodb
if (populate) {
// Find objects into mongodb matching `objectID` from Meili search
// Build a query using the primary key values from the search hits.
const query = {};
// query[primaryKey] = { $in: _.map(data.hits, primaryKey) };
query[primaryKey] = _.map(data.hits, (hit) => cleanUpPrimaryKeyValue(hit[primaryKey]));
// logger.debug('query', query);
const hitsFromMongoose = await this.find(
query,
_.reduce(
this.schema.obj,
function (results, value, key) {
return { ...results, [key]: 1 };
},
{ _id: 1, __v: 1 },
),
).lean();
// Add additional data from mongodb into Meili search hits
// Build a projection object, including only keys that do not start with '$'.
const projection = Object.keys(this.schema.obj).reduce(
(results, key) => {
if (!key.startsWith('$')) {
results[key] = 1;
}
return results;
},
{ _id: 1, __v: 1 },
);
// Retrieve the full documents from MongoDB.
const hitsFromMongoose = await this.find(query, projection).lean();
// Merge the MongoDB documents with the search hits.
const populatedHits = data.hits.map(function (hit) {
const query = {};
query[primaryKey] = hit[primaryKey];
@@ -176,10 +218,21 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
return data;
}
/**
* Preprocesses the current document for indexing.
*
* This method:
* - Picks only the defined attributes to index.
* - Omits any keys starting with '$'.
* - Replaces pipe characters ('|') in `conversationId` with '--'.
* - Extracts and concatenates text from an array of content items.
*
* @returns {Object} The preprocessed object ready for indexing.
*/
preprocessObjectForIndex() {
const object = _.pick(this.toJSON(), attributesToIndex);
// NOTE: MeiliSearch does not allow | in primary key, so we replace it with - for Bing convoIds
// object.conversationId = object.conversationId.replace(/\|/g, '-');
const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) =>
k.startsWith('$'),
);
if (object.conversationId && object.conversationId.includes('|')) {
object.conversationId = object.conversationId.replace(/\|/g, '--');
}
@@ -195,32 +248,53 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
return object;
}
// Push new document to Meili
/**
* Adds the current document to the MeiliSearch index.
*
* The method preprocesses the document, adds it to MeiliSearch, and then updates
* the MongoDB document's `_meiliIndex` flag to true.
*
* @returns {Promise<void>}
*/
async addObjectToMeili() {
const object = this.preprocessObjectForIndex();
try {
// logger.debug('Adding document to Meili', object);
await index.addDocuments([object]);
} catch (error) {
// logger.debug('Error adding document to Meili');
// logger.error(error);
// Error handling can be enhanced as needed.
logger.error('[addObjectToMeili] Error adding document to Meili', error);
}
await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
}
// Update an existing document in Meili
/**
* Updates the current document in the MeiliSearch index.
*
* @returns {Promise<void>}
*/
async updateObjectToMeili() {
const object = _.pick(this.toJSON(), attributesToIndex);
const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) =>
k.startsWith('$'),
);
await index.updateDocuments([object]);
}
// Delete a document from Meili
/**
* Deletes the current document from the MeiliSearch index.
*
* @returns {Promise<void>}
*/
async deleteObjectFromMeili() {
await index.deleteDocument(this._id);
}
// * schema.post('save')
/**
* Post-save hook to synchronize the document with MeiliSearch.
*
* If the document is already indexed (i.e. `_meiliIndex` is true), it updates it;
* otherwise, it adds the document to the index.
*/
postSaveHook() {
if (this._meiliIndex) {
this.updateObjectToMeili();
@@ -229,14 +303,24 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
}
}
// * schema.post('update')
/**
* Post-update hook to update the document in MeiliSearch.
*
* This hook is triggered after a document update, ensuring that changes are
* propagated to the MeiliSearch index if the document is indexed.
*/
postUpdateHook() {
if (this._meiliIndex) {
this.updateObjectToMeili();
}
}
// * schema.post('remove')
/**
* Post-remove hook to delete the document from MeiliSearch.
*
* This hook is triggered after a document is removed, ensuring that the document
* is also removed from the MeiliSearch index if it was previously indexed.
*/
postRemoveHook() {
if (this._meiliIndex) {
this.deleteObjectFromMeili();
@@ -247,11 +331,27 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
return MeiliMongooseModel;
};
/**
* Mongoose plugin to synchronize MongoDB collections with a MeiliSearch index.
*
* This plugin:
* - Validates the provided options.
* - Adds a `_meiliIndex` field to the schema to track indexing status.
* - Sets up a MeiliSearch client and creates an index if it doesn't already exist.
* - Loads class methods for syncing, searching, and managing documents in MeiliSearch.
* - Registers Mongoose hooks (post-save, post-update, post-remove, etc.) to maintain index consistency.
*
* @param {mongoose.Schema} schema - The Mongoose schema to which the plugin is applied.
* @param {Object} options - Configuration options.
* @param {string} options.host - The MeiliSearch host.
* @param {string} options.apiKey - The MeiliSearch API key.
* @param {string} options.indexName - The name of the MeiliSearch index.
* @param {string} options.primaryKey - The primary key field for indexing.
*/
module.exports = function mongoMeili(schema, options) {
// Vaidate Options for mongoMeili
validateOptions(options);
// Add meiliIndex to schema
// Add _meiliIndex field to the schema to track if a document has been indexed in MeiliSearch.
schema.add({
_meiliIndex: {
type: Boolean,
@@ -263,69 +363,77 @@ module.exports = function mongoMeili(schema, options) {
const { host, apiKey, indexName, primaryKey } = options;
// Setup MeiliSearch Client
// Setup the MeiliSearch client.
const client = new MeiliSearch({ host, apiKey });
// Asynchronously create the index
// Create the index asynchronously if it doesn't exist.
client.createIndex(indexName, { primaryKey });
// Setup the index to search for this schema
// Setup the MeiliSearch index for this schema.
const index = client.index(indexName);
// Collect attributes from the schema that should be indexed.
const attributesToIndex = [
..._.reduce(
schema.obj,
function (results, value, key) {
return value.meiliIndex ? [...results, key] : results;
// }, []), '_id'];
},
[],
),
];
// Load the class methods into the schema.
schema.loadClass(createMeiliMongooseModel({ index, indexName, client, attributesToIndex }));
// Register hooks
// Register Mongoose hooks to synchronize with MeiliSearch.
// Post-save: synchronize after a document is saved.
schema.post('save', function (doc) {
doc.postSaveHook();
});
// Post-update: synchronize after a document is updated.
schema.post('update', function (doc) {
doc.postUpdateHook();
});
// Post-remove: synchronize after a document is removed.
schema.post('remove', function (doc) {
doc.postRemoveHook();
});
// Pre-deleteMany hook: remove corresponding documents from MeiliSearch when multiple documents are deleted.
schema.pre('deleteMany', async function (next) {
if (!meiliEnabled) {
next();
return next();
}
try {
// Check if the schema has a "messages" field to determine if it's a conversation schema.
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
const convoIndex = client.index('convos');
const deletedConvos = await mongoose.model('Conversation').find(this._conditions).lean();
let promises = [];
for (const convo of deletedConvos) {
promises.push(convoIndex.deleteDocument(convo.conversationId));
}
const promises = deletedConvos.map((convo) =>
convoIndex.deleteDocument(convo.conversationId),
);
await Promise.all(promises);
}
// Check if the schema has a "messageId" field to determine if it's a message schema.
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
const messageIndex = client.index('messages');
const deletedMessages = await mongoose.model('Message').find(this._conditions).lean();
let promises = [];
for (const message of deletedMessages) {
promises.push(messageIndex.deleteDocument(message.messageId));
}
const promises = deletedMessages.map((message) =>
messageIndex.deleteDocument(message.messageId),
);
await Promise.all(promises);
}
return next();
} catch (error) {
if (meiliEnabled) {
logger.error(
'[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion, next startup may be slow due to syncing',
'[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may be slow due to syncing.',
error,
);
}
@@ -333,17 +441,19 @@ module.exports = function mongoMeili(schema, options) {
}
});
// Post-findOneAndUpdate hook: update MeiliSearch index after a document is updated via findOneAndUpdate.
schema.post('findOneAndUpdate', async function (doc) {
if (!meiliEnabled) {
return;
}
// If the document is unfinished, do not update the index.
if (doc.unfinished) {
return;
}
let meiliDoc;
// Doc is a Conversation
// For conversation documents, try to fetch the document from the "convos" index.
if (doc.messages) {
try {
meiliDoc = await client.index('convos').getDocument(doc.conversationId);
@@ -356,10 +466,12 @@ module.exports = function mongoMeili(schema, options) {
}
}
// If the MeiliSearch document exists and the title is unchanged, do nothing.
if (meiliDoc && meiliDoc.title === doc.title) {
return;
}
// Otherwise, trigger a post-save hook to synchronize the document.
doc.postSaveHook();
});
};

View File

@@ -1,60 +0,0 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const AuthSchema = new Schema(
{
authorization_type: String,
custom_auth_header: String,
type: {
type: String,
enum: ['service_http', 'oauth', 'none'],
},
authorization_content_type: String,
authorization_url: String,
client_url: String,
scope: String,
token_exchange_method: {
type: String,
enum: ['default_post', 'basic_auth_header', null],
},
},
{ _id: false },
);
const actionSchema = new Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
action_id: {
type: String,
index: true,
required: true,
},
type: {
type: String,
default: 'action_prototype',
},
settings: Schema.Types.Mixed,
agent_id: String,
assistant_id: String,
metadata: {
api_key: String, // private, encrypted
auth: AuthSchema,
domain: {
type: String,
required: true,
},
// json_schema: Schema.Types.Mixed,
privacy_policy_url: String,
raw_spec: String,
oauth_client_id: String, // private, encrypted
oauth_client_secret: String, // private, encrypted
},
});
// }, { minimize: false }); // Prevent removal of empty objects
module.exports = actionSchema;

View File

@@ -1,17 +0,0 @@
const mongoose = require('mongoose');
const balanceSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
// 1000 tokenCredits = 1 mill ($0.001 USD)
tokenCredits: {
type: Number,
default: 0,
},
});
module.exports = balanceSchema;

View File

@@ -1,19 +0,0 @@
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const categoriesSchema = new Schema({
label: {
type: String,
required: true,
unique: true,
},
value: {
type: String,
required: true,
unique: true,
},
});
const categories = mongoose.model('categories', categoriesSchema);
module.exports = { Categories: categories };

View File

@@ -1,32 +0,0 @@
const mongoose = require('mongoose');
const conversationTagSchema = mongoose.Schema(
{
tag: {
type: String,
index: true,
},
user: {
type: String,
index: true,
},
description: {
type: String,
index: true,
},
count: {
type: Number,
default: 0,
},
position: {
type: Number,
default: 0,
index: true,
},
},
{ timestamps: true },
);
conversationTagSchema.index({ tag: 1, user: 1 }, { unique: true });
module.exports = mongoose.model('ConversationTag', conversationTagSchema);

View File

@@ -1,46 +1,7 @@
const mongoose = require('mongoose');
const mongoMeili = require('../plugins/mongoMeili');
const { conversationPreset } = require('./defaults');
const convoSchema = mongoose.Schema(
{
conversationId: {
type: String,
unique: true,
required: true,
index: true,
meiliIndex: true,
},
title: {
type: String,
default: 'New Chat',
meiliIndex: true,
},
user: {
type: String,
index: true,
},
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
agentOptions: {
type: mongoose.Schema.Types.Mixed,
},
...conversationPreset,
agent_id: {
type: String,
},
tags: {
type: [String],
default: [],
meiliIndex: true,
},
files: {
type: [String],
},
expiredAt: {
type: Date,
},
},
{ timestamps: true },
);
const { convoSchema } = require('@librechat/data-schemas');
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
convoSchema.plugin(mongoMeili, {
@@ -52,10 +13,6 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
});
}
convoSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 });
convoSchema.index({ createdAt: 1, updatedAt: 1 });
convoSchema.index({ conversationId: 1, user: 1 }, { unique: true });
const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
module.exports = Conversation;

View File

@@ -1,111 +0,0 @@
const { FileSources } = require('librechat-data-provider');
const mongoose = require('mongoose');
/**
* @typedef {Object} MongoFile
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {number} [__v] - MongoDB Version Key
* @property {ObjectId} user - User ID
* @property {string} [conversationId] - Optional conversation ID
* @property {string} file_id - File identifier
* @property {string} [temp_file_id] - Temporary File identifier
* @property {number} bytes - Size of the file in bytes
* @property {string} filename - Name of the file
* @property {string} filepath - Location of the file
* @property {'file'} object - Type of object, always 'file'
* @property {string} type - Type of file
* @property {number} [usage=0] - Number of uses of the file
* @property {string} [context] - Context of the file origin
* @property {boolean} [embedded=false] - Whether or not the file is embedded in vector db
* @property {string} [model] - The model to identify the group region of the file (for Azure OpenAI hosting)
* @property {string} [source] - The source of the file (e.g., from FileSources)
* @property {number} [width] - Optional width of the file
* @property {number} [height] - Optional height of the file
* @property {Object} [metadata] - Metadata related to the file
* @property {string} [metadata.fileIdentifier] - Unique identifier for the file in metadata
* @property {Date} [expiresAt] - Optional expiration date of the file
* @property {Date} [createdAt] - Date when the file was created
* @property {Date} [updatedAt] - Date when the file was updated
*/
/** @type {MongooseSchema<MongoFile>} */
const fileSchema = mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
conversationId: {
type: String,
ref: 'Conversation',
index: true,
},
file_id: {
type: String,
// required: true,
index: true,
},
temp_file_id: {
type: String,
// required: true,
},
bytes: {
type: Number,
required: true,
},
filename: {
type: String,
required: true,
},
filepath: {
type: String,
required: true,
},
object: {
type: String,
required: true,
default: 'file',
},
embedded: {
type: Boolean,
},
type: {
type: String,
required: true,
},
context: {
type: String,
// required: true,
},
usage: {
type: Number,
required: true,
default: 0,
},
source: {
type: String,
default: FileSources.local,
},
model: {
type: String,
},
width: Number,
height: Number,
metadata: {
fileIdentifier: String,
},
expiresAt: {
type: Date,
expires: 3600, // 1 hour in seconds
},
},
{
timestamps: true,
},
);
fileSchema.index({ createdAt: 1, updatedAt: 1 });
module.exports = fileSchema;

View File

@@ -1,145 +1,6 @@
const mongoose = require('mongoose');
const mongoMeili = require('~/models/plugins/mongoMeili');
const messageSchema = mongoose.Schema(
{
messageId: {
type: String,
unique: true,
required: true,
index: true,
meiliIndex: true,
},
conversationId: {
type: String,
index: true,
required: true,
meiliIndex: true,
},
user: {
type: String,
index: true,
required: true,
default: null,
},
model: {
type: String,
default: null,
},
endpoint: {
type: String,
},
conversationSignature: {
type: String,
},
clientId: {
type: String,
},
invocationId: {
type: Number,
},
parentMessageId: {
type: String,
},
tokenCount: {
type: Number,
},
summaryTokenCount: {
type: Number,
},
sender: {
type: String,
meiliIndex: true,
},
text: {
type: String,
meiliIndex: true,
},
summary: {
type: String,
},
isCreatedByUser: {
type: Boolean,
required: true,
default: false,
},
unfinished: {
type: Boolean,
default: false,
},
error: {
type: Boolean,
default: false,
},
finish_reason: {
type: String,
},
_meiliIndex: {
type: Boolean,
required: false,
select: false,
default: false,
},
files: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
plugin: {
type: {
latest: {
type: String,
required: false,
},
inputs: {
type: [mongoose.Schema.Types.Mixed],
required: false,
default: undefined,
},
outputs: {
type: String,
required: false,
},
},
default: undefined,
},
plugins: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
content: {
type: [{ type: mongoose.Schema.Types.Mixed }],
default: undefined,
meiliIndex: true,
},
thread_id: {
type: String,
},
/* frontend components */
iconURL: {
type: String,
},
attachments: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
/*
attachments: {
type: [
{
file_id: String,
filename: String,
filepath: String,
expiresAt: Date,
width: Number,
height: Number,
type: String,
conversationId: String,
messageId: {
type: String,
required: true,
},
toolCallId: String,
},
],
default: undefined,
},
*/
expiredAt: {
type: Date,
},
},
{ timestamps: true },
);
const { messageSchema } = require('@librechat/data-schemas');
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
messageSchema.plugin(mongoMeili, {
@@ -149,11 +10,7 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
primaryKey: 'messageId',
});
}
messageSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 });
messageSchema.index({ createdAt: 1 });
messageSchema.index({ messageId: 1, user: 1 }, { unique: true });
/** @type {mongoose.Model<TMessage>} */
const Message = mongoose.models.Message || mongoose.model('Message', messageSchema);
module.exports = Message;

View File

@@ -1,25 +1,5 @@
const mongoose = require('mongoose');
const pluginAuthSchema = mongoose.Schema(
{
authField: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
userId: {
type: String,
required: true,
},
pluginKey: {
type: String,
},
},
{ timestamps: true },
);
const { pluginAuthSchema } = require('@librechat/data-schemas');
const PluginAuth = mongoose.models.Plugin || mongoose.model('PluginAuth', pluginAuthSchema);

View File

@@ -1,36 +1,5 @@
const mongoose = require('mongoose');
const { conversationPreset } = require('./defaults');
const presetSchema = mongoose.Schema(
{
presetId: {
type: String,
unique: true,
required: true,
index: true,
},
title: {
type: String,
default: 'New Chat',
meiliIndex: true,
},
user: {
type: String,
default: null,
},
defaultPreset: {
type: Boolean,
},
order: {
type: Number,
},
...conversationPreset,
agentOptions: {
type: mongoose.Schema.Types.Mixed,
default: null,
},
},
{ timestamps: true },
);
const { presetSchema } = require('@librechat/data-schemas');
const Preset = mongoose.models.Preset || mongoose.model('Preset', presetSchema);

View File

@@ -1,35 +0,0 @@
const { Schema } = require('mongoose');
/**
* @typedef {Object} MongoProject
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {string} name - The name of the project
* @property {ObjectId[]} promptGroupIds - Array of PromptGroup IDs associated with the project
* @property {Date} [createdAt] - Date when the project was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the project was last updated (added by timestamps)
*/
const projectSchema = new Schema(
{
name: {
type: String,
required: true,
index: true,
},
promptGroupIds: {
type: [Schema.Types.ObjectId],
ref: 'PromptGroup',
default: [],
},
agentIds: {
type: [String],
ref: 'Agent',
default: [],
},
},
{
timestamps: true,
},
);
module.exports = projectSchema;

View File

@@ -1,118 +0,0 @@
const mongoose = require('mongoose');
const { Constants } = require('librechat-data-provider');
const Schema = mongoose.Schema;
/**
* @typedef {Object} MongoPromptGroup
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {string} name - The name of the prompt group
* @property {ObjectId} author - The author of the prompt group
* @property {ObjectId} [projectId=null] - The project ID of the prompt group
* @property {ObjectId} [productionId=null] - The project ID of the prompt group
* @property {string} authorName - The name of the author of the prompt group
* @property {number} [numberOfGenerations=0] - Number of generations the prompt group has
* @property {string} [oneliner=''] - Oneliner description of the prompt group
* @property {string} [category=''] - Category of the prompt group
* @property {string} [command] - Command for the prompt group
* @property {Date} [createdAt] - Date when the prompt group was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the prompt group was last updated (added by timestamps)
*/
const promptGroupSchema = new Schema(
{
name: {
type: String,
required: true,
index: true,
},
numberOfGenerations: {
type: Number,
default: 0,
},
oneliner: {
type: String,
default: '',
},
category: {
type: String,
default: '',
index: true,
},
projectIds: {
type: [Schema.Types.ObjectId],
ref: 'Project',
index: true,
},
productionId: {
type: Schema.Types.ObjectId,
ref: 'Prompt',
required: true,
index: true,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true,
},
authorName: {
type: String,
required: true,
},
command: {
type: String,
index: true,
validate: {
validator: function (v) {
return v === undefined || v === null || v === '' || /^[a-z0-9-]+$/.test(v);
},
message: (props) =>
`${props.value} is not a valid command. Only lowercase alphanumeric characters and highfins (') are allowed.`,
},
maxlength: [
Constants.COMMANDS_MAX_LENGTH,
`Command cannot be longer than ${Constants.COMMANDS_MAX_LENGTH} characters`,
],
},
},
{
timestamps: true,
},
);
const PromptGroup = mongoose.model('PromptGroup', promptGroupSchema);
const promptSchema = new Schema(
{
groupId: {
type: Schema.Types.ObjectId,
ref: 'PromptGroup',
required: true,
index: true,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
prompt: {
type: String,
required: true,
},
type: {
type: String,
enum: ['text', 'chat'],
required: true,
},
},
{
timestamps: true,
},
);
const Prompt = mongoose.model('Prompt', promptSchema);
promptSchema.index({ createdAt: 1, updatedAt: 1 });
promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
module.exports = { Prompt, PromptGroup };

View File

@@ -1,20 +0,0 @@
const mongoose = require('mongoose');
const sessionSchema = mongoose.Schema({
refreshTokenHash: {
type: String,
required: true,
},
expiration: {
type: Date,
required: true,
expires: 0,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
});
module.exports = sessionSchema;

View File

@@ -1,54 +0,0 @@
const mongoose = require('mongoose');
/**
* @typedef {Object} ToolCallData
* @property {string} conversationId - The ID of the conversation
* @property {string} messageId - The ID of the message
* @property {string} toolId - The ID of the tool
* @property {string | ObjectId} user - The user's ObjectId
* @property {unknown} [result] - Optional result data
* @property {TAttachment[]} [attachments] - Optional attachments data
* @property {number} [blockIndex] - Optional code block index
* @property {number} [partIndex] - Optional part index
*/
/** @type {MongooseSchema<ToolCallData>} */
const toolCallSchema = mongoose.Schema(
{
conversationId: {
type: String,
required: true,
},
messageId: {
type: String,
required: true,
},
toolId: {
type: String,
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
result: {
type: mongoose.Schema.Types.Mixed,
},
attachments: {
type: mongoose.Schema.Types.Mixed,
},
blockIndex: {
type: Number,
},
partIndex: {
type: Number,
},
},
{ timestamps: true },
);
toolCallSchema.index({ messageId: 1, user: 1 });
toolCallSchema.index({ conversationId: 1, user: 1 });
module.exports = mongoose.model('ToolCall', toolCallSchema);

View File

@@ -1,151 +0,0 @@
const mongoose = require('mongoose');
const { SystemRoles } = require('librechat-data-provider');
/**
* @typedef {Object} MongoSession
* @property {string} [refreshToken] - The refresh token
*/
/**
* @typedef {Object} MongoUser
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {string} [name] - The user's name
* @property {string} [username] - The user's username, in lowercase
* @property {string} email - The user's email address
* @property {boolean} emailVerified - Whether the user's email is verified
* @property {string} [password] - The user's password, trimmed with 8-128 characters
* @property {string} [avatar] - The URL of the user's avatar
* @property {string} provider - The provider of the user's account (e.g., 'local', 'google')
* @property {string} [role='USER'] - The role of the user
* @property {string} [googleId] - Optional Google ID for the user
* @property {string} [facebookId] - Optional Facebook ID for the user
* @property {string} [openidId] - Optional OpenID ID for the user
* @property {string} [ldapId] - Optional LDAP ID for the user
* @property {string} [githubId] - Optional GitHub ID for the user
* @property {string} [discordId] - Optional Discord ID for the user
* @property {string} [appleId] - Optional Apple ID for the user
* @property {Array} [plugins=[]] - List of plugins used by the user
* @property {Array.<MongoSession>} [refreshToken] - List of sessions with refresh tokens
* @property {Date} [expiresAt] - Optional expiration date of the file
* @property {Date} [createdAt] - Date when the user was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the user was last updated (added by timestamps)
*/
/** @type {MongooseSchema<MongoSession>} */
const Session = mongoose.Schema({
refreshToken: {
type: String,
default: '',
},
});
const backupCodeSchema = mongoose.Schema({
codeHash: { type: String, required: true },
used: { type: Boolean, default: false },
usedAt: { type: Date, default: null },
});
/** @type {MongooseSchema<MongoUser>} */
const userSchema = mongoose.Schema(
{
name: {
type: String,
},
username: {
type: String,
lowercase: true,
default: '',
},
email: {
type: String,
required: [true, 'can\'t be blank'],
lowercase: true,
unique: true,
match: [/\S+@\S+\.\S+/, 'is invalid'],
index: true,
},
emailVerified: {
type: Boolean,
required: true,
default: false,
},
password: {
type: String,
trim: true,
minlength: 8,
maxlength: 128,
},
avatar: {
type: String,
required: false,
},
provider: {
type: String,
required: true,
default: 'local',
},
role: {
type: String,
default: SystemRoles.USER,
},
googleId: {
type: String,
unique: true,
sparse: true,
},
facebookId: {
type: String,
unique: true,
sparse: true,
},
openidId: {
type: String,
unique: true,
sparse: true,
},
ldapId: {
type: String,
unique: true,
sparse: true,
},
githubId: {
type: String,
unique: true,
sparse: true,
},
discordId: {
type: String,
unique: true,
sparse: true,
},
appleId: {
type: String,
unique: true,
sparse: true,
},
plugins: {
type: Array,
},
totpSecret: {
type: String,
},
backupCodes: {
type: [backupCodeSchema],
},
refreshToken: {
type: [Session],
},
expiresAt: {
type: Date,
expires: 604800, // 7 days in seconds
},
termsAccepted: {
type: Boolean,
default: false,
},
},
{ timestamps: true },
);
module.exports = userSchema;

View File

@@ -1,6 +1,6 @@
{
"name": "@librechat/backend",
"version": "v0.7.7-rc1",
"version": "v0.7.7",
"description": "",
"scripts": {
"start": "echo 'please run this from the root directory'",
@@ -40,14 +40,15 @@
"@googleapis/youtube": "^20.0.0",
"@keyv/mongo": "^2.1.8",
"@keyv/redis": "^2.8.1",
"@langchain/community": "^0.3.14",
"@langchain/community": "^0.3.34",
"@langchain/core": "^0.3.40",
"@langchain/google-genai": "^0.1.9",
"@langchain/google-vertexai": "^0.2.0",
"@langchain/textsplitters": "^0.1.0",
"@librechat/agents": "^2.1.8",
"@librechat/agents": "^2.2.0",
"@librechat/data-schemas": "*",
"@waylaidwanderer/fetch-event-source": "^3.0.1",
"axios": "1.7.8",
"axios": "^1.8.2",
"bcryptjs": "^2.4.3",
"cohere-ai": "^7.9.1",
"compression": "^1.7.4",
@@ -74,7 +75,6 @@
"keyv": "^4.5.4",
"keyv-file": "^0.2.0",
"klona": "^2.0.6",
"langchain": "^0.2.19",
"librechat-data-provider": "*",
"librechat-mcp": "*",
"lodash": "^4.17.21",

View File

@@ -1,6 +1,7 @@
const { CacheKeys } = require('librechat-data-provider');
const { loadDefaultModels, loadConfigModels } = require('~/server/services/Config');
const { getLogStores } = require('~/cache');
const { logger } = require('~/config');
/**
* @param {ServerRequest} req
@@ -36,8 +37,13 @@ async function loadModels(req) {
}
async function modelController(req, res) {
const modelConfig = await loadModels(req);
res.send(modelConfig);
try {
const modelConfig = await loadModels(req);
res.send(modelConfig);
} catch (error) {
logger.error('Error fetching models:', error);
res.status(500).send({ error: error.message });
}
}
module.exports = { modelController, loadModels, getModelsConfig };

View File

@@ -11,17 +11,19 @@ const { encryptV2 } = require('~/server/utils/crypto');
const enable2FAController = async (req, res) => {
const safeAppTitle = (process.env.APP_TITLE || 'LibreChat').replace(/\s+/g, '');
try {
const userId = req.user.id;
const secret = generateTOTPSecret();
const { plainCodes, codeObjects } = await generateBackupCodes();
const encryptedSecret = await encryptV2(secret);
const user = await updateUser(userId, { totpSecret: encryptedSecret, backupCodes: codeObjects });
// Set twoFactorEnabled to false until the user confirms 2FA.
const user = await updateUser(userId, {
totpSecret: encryptedSecret,
backupCodes: codeObjects,
twoFactorEnabled: false,
});
const otpauthUrl = `otpauth://totp/${safeAppTitle}:${user.email}?secret=${secret}&issuer=${safeAppTitle}`;
res.status(200).json({
otpauthUrl,
backupCodes: plainCodes,
@@ -37,6 +39,7 @@ const verify2FAController = async (req, res) => {
const userId = req.user.id;
const { token, backupCode } = req.body;
const user = await getUserById(userId);
// Ensure that 2FA is enabled for this user.
if (!user || !user.totpSecret) {
return res.status(400).json({ message: '2FA not initiated' });
}
@@ -52,7 +55,6 @@ const verify2FAController = async (req, res) => {
return res.status(200).json();
}
}
return res.status(400).json({ message: 'Invalid token.' });
} catch (err) {
logger.error('[verify2FAController]', err);
@@ -74,6 +76,8 @@ const confirm2FAController = async (req, res) => {
const secret = await getTOTPSecret(user.totpSecret);
if (await verifyTOTP(secret, token)) {
// Upon successful verification, enable 2FA.
await updateUser(userId, { twoFactorEnabled: true });
return res.status(200).json();
}
@@ -87,7 +91,7 @@ const confirm2FAController = async (req, res) => {
const disable2FAController = async (req, res) => {
try {
const userId = req.user.id;
await updateUser(userId, { totpSecret: null, backupCodes: [] });
await updateUser(userId, { totpSecret: null, backupCodes: [], twoFactorEnabled: false });
res.status(200).json();
} catch (err) {
logger.error('[disable2FAController]', err);

View File

@@ -812,7 +812,10 @@ class AgentClient extends BaseClient {
'[api/server/controllers/agents/client.js #sendCompletion] Unhandled error type',
err,
);
throw err;
this.contentParts.push({
type: ContentTypes.ERROR,
[ContentTypes.ERROR]: `An error occurred while processing the request${err?.message ? `: ${err.message}` : ''}`,
});
}
}
}

View File

@@ -8,7 +8,7 @@ const loginController = async (req, res) => {
return res.status(400).json({ message: 'Invalid credentials' });
}
if (req.user.backupCodes != null && req.user.backupCodes.length > 0) {
if (req.user.twoFactorEnabled) {
const tempToken = generate2FATempToken(req.user._id);
return res.status(200).json({ twoFAPending: true, tempToken });
}

View File

@@ -1,5 +1,9 @@
const jwt = require('jsonwebtoken');
const { verifyTOTP, verifyBackupCode, getTOTPSecret } = require('~/server/services/twoFactorService');
const {
verifyTOTP,
verifyBackupCode,
getTOTPSecret,
} = require('~/server/services/twoFactorService');
const { setAuthTokens } = require('~/server/services/AuthService');
const { getUserById } = require('~/models/userMethods');
const { logger } = require('~/config');
@@ -19,12 +23,12 @@ const verify2FA = async (req, res) => {
}
const user = await getUserById(payload.userId);
// Ensure that the user exists and has backup codes (i.e. 2FA enabled)
if (!user || !(user.backupCodes && user.backupCodes.length > 0)) {
// Ensure that the user exists and has 2FA enabled
if (!user || !user.twoFactorEnabled) {
return res.status(400).json({ message: '2FA is not enabled for this user' });
}
// Use the new getTOTPSecret function to retrieve (and decrypt if necessary) the TOTP secret.
// Retrieve (and decrypt if necessary) the TOTP secret.
const secret = await getTOTPSecret(user.totpSecret);
let verified = false;
@@ -39,9 +43,7 @@ const verify2FA = async (req, res) => {
}
// Prepare user data for response.
// If the user is a plain object (from lean queries), we create a shallow copy.
const userData = user.toObject ? user.toObject() : { ...user };
// Remove sensitive fields.
delete userData.password;
delete userData.__v;
delete userData.totpSecret;

View File

@@ -120,7 +120,7 @@ const createAbortController = (req, res, getAbortData, getReqData) => {
{ promptTokens, completionTokens },
);
saveMessage(
await saveMessage(
req,
{ ...responseMessage, user },
{ context: 'api/server/middleware/abortMiddleware.js' },

View File

@@ -1,32 +1,18 @@
const passport = require('passport');
const DebugControl = require('../../utils/debug.js');
function log({ title, parameters }) {
DebugControl.log.functionName(title);
if (parameters) {
DebugControl.log.parameters(parameters);
}
}
const { logger } = require('~/config');
const requireLocalAuth = (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
log({
title: '(requireLocalAuth) Error at passport.authenticate',
parameters: [{ name: 'error', value: err }],
});
logger.error('[requireLocalAuth] Error at passport.authenticate:', err);
return next(err);
}
if (!user) {
log({
title: '(requireLocalAuth) Error: No user',
});
logger.debug('[requireLocalAuth] Error: No user');
return res.status(404).send(info);
}
if (info && info.message) {
log({
title: '(requireLocalAuth) Error: ' + info.message,
});
logger.debug('[requireLocalAuth] Error: ' + info.message);
return res.status(422).send({ message: info.message });
}
req.user = user;

View File

@@ -18,6 +18,7 @@ afterEach(() => {
delete process.env.OPENID_ISSUER;
delete process.env.OPENID_SESSION_SECRET;
delete process.env.OPENID_BUTTON_LABEL;
delete process.env.OPENID_AUTO_REDIRECT;
delete process.env.OPENID_AUTH_URL;
delete process.env.GITHUB_CLIENT_ID;
delete process.env.GITHUB_CLIENT_SECRET;

View File

@@ -47,10 +47,10 @@ router.get('/', async function (req, res) {
githubLoginEnabled: !!process.env.GITHUB_CLIENT_ID && !!process.env.GITHUB_CLIENT_SECRET,
googleLoginEnabled: !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET,
appleLoginEnabled:
!!process.env.APPLE_CLIENT_ID &&
!!process.env.APPLE_TEAM_ID &&
!!process.env.APPLE_KEY_ID &&
!!process.env.APPLE_PRIVATE_KEY_PATH,
!!process.env.APPLE_CLIENT_ID &&
!!process.env.APPLE_TEAM_ID &&
!!process.env.APPLE_KEY_ID &&
!!process.env.APPLE_PRIVATE_KEY_PATH,
openidLoginEnabled:
!!process.env.OPENID_CLIENT_ID &&
!!process.env.OPENID_CLIENT_SECRET &&
@@ -58,6 +58,7 @@ router.get('/', async function (req, res) {
!!process.env.OPENID_SESSION_SECRET,
openidLabel: process.env.OPENID_BUTTON_LABEL || 'Continue with OpenID',
openidImageUrl: process.env.OPENID_IMAGE_URL,
openidAutoRedirect: isEnabled(process.env.OPENID_AUTO_REDIRECT),
serverDomain: process.env.DOMAIN_SERVER || 'http://localhost:3080',
emailLoginEnabled,
registrationEnabled: !ldap?.enabled && isEnabled(process.env.ALLOW_REGISTRATION),
@@ -80,6 +81,7 @@ router.get('/', async function (req, res) {
publicSharedLinksEnabled,
analyticsGtmId: process.env.ANALYTICS_GTM_ID,
instanceProjectId: instanceProject._id.toString(),
bundlerURL: process.env.SANDPACK_BUNDLER_URL,
};
if (ldap) {

View File

@@ -31,7 +31,9 @@ const oauthHandler = async (req, res) => {
router.get('/error', (req, res) => {
// A single error message is pushed by passport when authentication fails.
logger.error('Error in OAuth authentication:', { message: req.session.messages.pop() });
res.redirect(`${domains.client}/login`);
// Redirect to login page with auth_failed parameter to prevent infinite redirect loops
res.redirect(`${domains.client}/login?redirect=false`);
});
/**

View File

@@ -47,7 +47,7 @@ async function loadConfigModels(req) {
);
/**
* @type {Record<string, string[]>}
* @type {Record<string, Promise<string[]>>}
* Map for promises keyed by unique combination of baseURL and apiKey */
const fetchPromisesMap = {};
/**
@@ -102,7 +102,7 @@ async function loadConfigModels(req) {
for (const name of associatedNames) {
const endpoint = endpointsMap[name];
modelsConfig[name] = !modelData?.length ? endpoint.models.default ?? [] : modelData;
modelsConfig[name] = !modelData?.length ? (endpoint.models.default ?? []) : modelData;
}
}

View File

@@ -5,8 +5,8 @@ const {
getGoogleModels,
getBedrockModels,
getAnthropicModels,
getChatGPTBrowserModels,
} = require('~/server/services/ModelService');
const { logger } = require('~/config');
/**
* Loads the default models for the application.
@@ -15,31 +15,68 @@ const {
* @param {Express.Request} req - The Express request object.
*/
async function loadDefaultModels(req) {
const google = getGoogleModels();
const openAI = await getOpenAIModels({ user: req.user.id });
const anthropic = getAnthropicModels();
const chatGPTBrowser = getChatGPTBrowserModels();
const azureOpenAI = await getOpenAIModels({ user: req.user.id, azure: true });
const gptPlugins = await getOpenAIModels({
user: req.user.id,
azure: useAzurePlugins,
plugins: true,
});
const assistants = await getOpenAIModels({ assistants: true });
const azureAssistants = await getOpenAIModels({ azureAssistants: true });
try {
const [
openAI,
anthropic,
azureOpenAI,
gptPlugins,
assistants,
azureAssistants,
google,
bedrock,
] = await Promise.all([
getOpenAIModels({ user: req.user.id }).catch((error) => {
logger.error('Error fetching OpenAI models:', error);
return [];
}),
getAnthropicModels({ user: req.user.id }).catch((error) => {
logger.error('Error fetching Anthropic models:', error);
return [];
}),
getOpenAIModels({ user: req.user.id, azure: true }).catch((error) => {
logger.error('Error fetching Azure OpenAI models:', error);
return [];
}),
getOpenAIModels({ user: req.user.id, azure: useAzurePlugins, plugins: true }).catch(
(error) => {
logger.error('Error fetching Plugin models:', error);
return [];
},
),
getOpenAIModels({ assistants: true }).catch((error) => {
logger.error('Error fetching OpenAI Assistants API models:', error);
return [];
}),
getOpenAIModels({ azureAssistants: true }).catch((error) => {
logger.error('Error fetching Azure OpenAI Assistants API models:', error);
return [];
}),
Promise.resolve(getGoogleModels()).catch((error) => {
logger.error('Error getting Google models:', error);
return [];
}),
Promise.resolve(getBedrockModels()).catch((error) => {
logger.error('Error getting Bedrock models:', error);
return [];
}),
]);
return {
[EModelEndpoint.openAI]: openAI,
[EModelEndpoint.agents]: openAI,
[EModelEndpoint.google]: google,
[EModelEndpoint.anthropic]: anthropic,
[EModelEndpoint.gptPlugins]: gptPlugins,
[EModelEndpoint.azureOpenAI]: azureOpenAI,
[EModelEndpoint.chatGPTBrowser]: chatGPTBrowser,
[EModelEndpoint.assistants]: assistants,
[EModelEndpoint.azureAssistants]: azureAssistants,
[EModelEndpoint.bedrock]: getBedrockModels(),
};
return {
[EModelEndpoint.openAI]: openAI,
[EModelEndpoint.agents]: openAI,
[EModelEndpoint.google]: google,
[EModelEndpoint.anthropic]: anthropic,
[EModelEndpoint.gptPlugins]: gptPlugins,
[EModelEndpoint.azureOpenAI]: azureOpenAI,
[EModelEndpoint.assistants]: assistants,
[EModelEndpoint.azureAssistants]: azureAssistants,
[EModelEndpoint.bedrock]: bedrock,
};
} catch (error) {
logger.error('Error fetching default models:', error);
throw new Error(`Failed to load default models: ${error.message}`);
}
}
module.exports = loadDefaultModels;

View File

@@ -22,6 +22,7 @@ const { getAgent } = require('~/models/Agent');
const { logger } = require('~/config');
const providerConfigMap = {
[Providers.XAI]: initCustom,
[Providers.OLLAMA]: initCustom,
[Providers.DEEPSEEK]: initCustom,
[Providers.OPENROUTER]: initCustom,

View File

@@ -141,6 +141,7 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
},
clientOptions,
);
clientOptions.modelOptions.user = req.user.id;
const options = getLLMConfig(apiKey, clientOptions, endpoint);
if (!customOptions.streamRate) {
return options;

View File

@@ -141,6 +141,7 @@ const initializeClient = async ({
},
clientOptions,
);
clientOptions.modelOptions.user = req.user.id;
const options = getLLMConfig(apiKey, clientOptions);
if (!clientOptions.streamRate) {
return options;

View File

@@ -9,6 +9,7 @@ const { isEnabled } = require('~/server/utils');
* @param {Object} options - Additional options for configuring the LLM.
* @param {Object} [options.modelOptions] - Model-specific options.
* @param {string} [options.modelOptions.model] - The name of the model to use.
* @param {string} [options.modelOptions.user] - The user ID
* @param {number} [options.modelOptions.temperature] - Controls randomness in output generation (0-2).
* @param {number} [options.modelOptions.top_p] - Controls diversity via nucleus sampling (0-1).
* @param {number} [options.modelOptions.frequency_penalty] - Reduces repetition of token sequences (-2 to 2).

View File

@@ -1,4 +1,3 @@
// Code Files
const axios = require('axios');
const FormData = require('form-data');
const { getCodeBaseURL } = require('@librechat/agents');
@@ -16,7 +15,8 @@ const MAX_FILE_SIZE = 150 * 1024 * 1024;
async function getCodeOutputDownloadStream(fileIdentifier, apiKey) {
try {
const baseURL = getCodeBaseURL();
const response = await axios({
/** @type {import('axios').AxiosRequestConfig} */
const options = {
method: 'get',
url: `${baseURL}/download/${fileIdentifier}`,
responseType: 'stream',
@@ -25,10 +25,22 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) {
'X-API-Key': apiKey,
},
timeout: 15000,
});
};
if (process.env.PROXY) {
options.proxy = {
host: process.env.PROXY,
protocol: process.env.PROXY.startsWith('https') ? 'https' : 'http',
};
}
const response = await axios(options);
return response;
} catch (error) {
logAxiosError({
message: `Error downloading code environment file stream: ${error.message}`,
error,
});
throw new Error(`Error downloading file: ${error.message}`);
}
}
@@ -54,7 +66,8 @@ async function uploadCodeEnvFile({ req, stream, filename, apiKey, entity_id = ''
form.append('file', stream, filename);
const baseURL = getCodeBaseURL();
const response = await axios.post(`${baseURL}/upload`, form, {
/** @type {import('axios').AxiosRequestConfig} */
const options = {
headers: {
...form.getHeaders(),
'Content-Type': 'multipart/form-data',
@@ -64,7 +77,16 @@ async function uploadCodeEnvFile({ req, stream, filename, apiKey, entity_id = ''
},
maxContentLength: MAX_FILE_SIZE,
maxBodyLength: MAX_FILE_SIZE,
});
};
if (process.env.PROXY) {
options.proxy = {
host: process.env.PROXY,
protocol: process.env.PROXY.startsWith('https') ? 'https' : 'http',
};
}
const response = await axios.post(`${baseURL}/upload`, form, options);
/** @type {{ message: string; session_id: string; files: Array<{ fileId: string; filename: string }> }} */
const result = response.data;

View File

@@ -4,7 +4,9 @@ const { HttpsProxyAgent } = require('https-proxy-agent');
const { EModelEndpoint, defaultModels, CacheKeys } = require('librechat-data-provider');
const { inputSchema, logAxiosError, extractBaseURL, processModelData } = require('~/utils');
const { OllamaClient } = require('~/app/clients/OllamaClient');
const { isUserProvided } = require('~/server/utils');
const getLogStores = require('~/cache/getLogStores');
const { logger } = require('~/config');
/**
* Splits a string by commas and trims each resulting value.
@@ -42,7 +44,7 @@ const fetchModels = async ({
user,
apiKey,
baseURL,
name = 'OpenAI',
name = EModelEndpoint.openAI,
azure = false,
userIdQuery = false,
createTokenConfig = true,
@@ -64,12 +66,19 @@ const fetchModels = async ({
try {
const options = {
headers: {
Authorization: `Bearer ${apiKey}`,
},
headers: {},
timeout: 5000,
};
if (name === EModelEndpoint.anthropic) {
options.headers = {
'x-api-key': apiKey,
'anthropic-version': process.env.ANTHROPIC_VERSION || '2023-06-01',
};
} else {
options.headers.Authorization = `Bearer ${apiKey}`;
}
if (process.env.PROXY) {
options.httpsAgent = new HttpsProxyAgent(process.env.PROXY);
}
@@ -148,7 +157,7 @@ const fetchOpenAIModels = async (opts, _models = []) => {
baseURL,
azure: opts.azure,
user: opts.user,
name: baseURL,
name: EModelEndpoint.openAI,
});
}
@@ -157,7 +166,7 @@ const fetchOpenAIModels = async (opts, _models = []) => {
}
if (baseURL === openaiBaseURL) {
const regex = /(text-davinci-003|gpt-|o\d+-)/;
const regex = /(text-davinci-003|gpt-|o\d+)/;
const excludeRegex = /audio|realtime/;
models = models.filter((model) => regex.test(model) && !excludeRegex.test(model));
const instructModels = models.filter((model) => model.includes('instruct'));
@@ -231,13 +240,71 @@ const getChatGPTBrowserModels = () => {
return models;
};
const getAnthropicModels = () => {
/**
* Fetches models from the Anthropic API.
* @async
* @function
* @param {object} opts - The options for fetching the models.
* @param {string} opts.user - The user ID to send to the API.
* @param {string[]} [_models=[]] - The models to use as a fallback.
*/
const fetchAnthropicModels = async (opts, _models = []) => {
let models = _models.slice() ?? [];
let apiKey = process.env.ANTHROPIC_API_KEY;
const anthropicBaseURL = 'https://api.anthropic.com/v1';
let baseURL = anthropicBaseURL;
let reverseProxyUrl = process.env.ANTHROPIC_REVERSE_PROXY;
if (reverseProxyUrl) {
baseURL = extractBaseURL(reverseProxyUrl);
}
if (!apiKey) {
return models;
}
const modelsCache = getLogStores(CacheKeys.MODEL_QUERIES);
const cachedModels = await modelsCache.get(baseURL);
if (cachedModels) {
return cachedModels;
}
if (baseURL) {
models = await fetchModels({
apiKey,
baseURL,
user: opts.user,
name: EModelEndpoint.anthropic,
tokenKey: EModelEndpoint.anthropic,
});
}
if (models.length === 0) {
return _models;
}
await modelsCache.set(baseURL, models);
return models;
};
const getAnthropicModels = async (opts = {}) => {
let models = defaultModels[EModelEndpoint.anthropic];
if (process.env.ANTHROPIC_MODELS) {
models = splitAndTrim(process.env.ANTHROPIC_MODELS);
return models;
}
return models;
if (isUserProvided(process.env.ANTHROPIC_API_KEY)) {
return models;
}
try {
return await fetchAnthropicModels(opts, models);
} catch (error) {
logger.error('Error fetching Anthropic models:', error);
return models;
}
};
const getGoogleModels = () => {

View File

@@ -352,15 +352,15 @@ describe('splitAndTrim', () => {
});
describe('getAnthropicModels', () => {
it('returns default models when ANTHROPIC_MODELS is not set', () => {
it('returns default models when ANTHROPIC_MODELS is not set', async () => {
delete process.env.ANTHROPIC_MODELS;
const models = getAnthropicModels();
const models = await getAnthropicModels();
expect(models).toEqual(defaultModels[EModelEndpoint.anthropic]);
});
it('returns models from ANTHROPIC_MODELS when set', () => {
it('returns models from ANTHROPIC_MODELS when set', async () => {
process.env.ANTHROPIC_MODELS = 'claude-1, claude-2 ';
const models = getAnthropicModels();
const models = await getAnthropicModels();
expect(models).toEqual(['claude-1', 'claude-2']);
});
});

View File

@@ -766,36 +766,6 @@
* @memberof typedefs
*/
/**
* @exports MongoFile
* @typedef {import('~/models/schema/fileSchema.js').MongoFile} MongoFile
* @memberof typedefs
*/
/**
* @exports ToolCallData
* @typedef {import('~/models/schema/toolCallSchema.js').ToolCallData} ToolCallData
* @memberof typedefs
*/
/**
* @exports MongoUser
* @typedef {import('~/models/schema/userSchema.js').MongoUser} MongoUser
* @memberof typedefs
*/
/**
* @exports MongoProject
* @typedef {import('~/models/schema/projectSchema.js').MongoProject} MongoProject
* @memberof typedefs
*/
/**
* @exports MongoPromptGroup
* @typedef {import('~/models/schema/promptSchema.js').MongoPromptGroup} MongoPromptGroup
* @memberof typedefs
*/
/**
* @exports uploadImageBuffer
* @typedef {import('~/server/services/Files/process').uploadImageBuffer} uploadImageBuffer

View File

@@ -1,56 +0,0 @@
const levels = {
NONE: 0,
LOW: 1,
MEDIUM: 2,
HIGH: 3,
};
let level = levels.HIGH;
module.exports = {
levels,
setLevel: (l) => (level = l),
log: {
parameters: (parameters) => {
if (levels.HIGH > level) {
return;
}
console.group();
parameters.forEach((p) => console.log(`${p.name}:`, p.value));
console.groupEnd();
},
functionName: (name) => {
if (levels.MEDIUM > level) {
return;
}
console.log(`\nEXECUTING: ${name}\n`);
},
flow: (flow) => {
if (levels.LOW > level) {
return;
}
console.log(`\n\n\nBEGIN FLOW: ${flow}\n\n\n`);
},
variable: ({ name, value }) => {
if (levels.HIGH > level) {
return;
}
console.group();
console.group();
console.log(`VARIABLE ${name}:`, value);
console.groupEnd();
console.groupEnd();
},
request: () => (req, res, next) => {
if (levels.HIGH > level) {
return next();
}
console.log('Hit URL', req.url, 'with following:');
console.group();
console.log('Query:', req.query);
console.log('Body:', req.body);
console.groupEnd();
return next();
},
},
};

View File

@@ -1,6 +1,6 @@
{
"name": "@librechat/frontend",
"version": "v0.7.7-rc1",
"version": "v0.7.7",
"description": "",
"type": "module",
"scripts": {

View File

@@ -399,7 +399,7 @@ export type TAuthContext = {
isAuthenticated: boolean;
error: string | undefined;
login: (data: t.TLoginUser) => void;
logout: () => void;
logout: (redirect?: string) => void;
setError: React.Dispatch<React.SetStateAction<string | undefined>>;
roles?: Record<string, t.TRole | null | undefined>;
};

View File

@@ -8,8 +8,8 @@ import {
import { SandpackProviderProps } from '@codesandbox/sandpack-react/unstyled';
import type { CodeEditorRef } from '@codesandbox/sandpack-react';
import type { ArtifactFiles, Artifact } from '~/common';
import { useEditArtifact, useGetStartupConfig } from '~/data-provider';
import { sharedFiles, sharedOptions } from '~/utils/artifacts';
import { useEditArtifact } from '~/data-provider';
import { useEditorContext } from '~/Providers';
const createDebouncedMutation = (
@@ -124,6 +124,17 @@ export const ArtifactCodeEditor = memo(function ({
sharedProps: Partial<SandpackProviderProps>;
editorRef: React.MutableRefObject<CodeEditorRef>;
}) {
const { data: config } = useGetStartupConfig();
const options: typeof sharedOptions = useMemo(() => {
if (!config) {
return sharedOptions;
}
return {
...sharedOptions,
bundlerURL: config.bundlerURL,
};
}, [config]);
if (Object.keys(files).length === 0) {
return null;
}
@@ -135,7 +146,7 @@ export const ArtifactCodeEditor = memo(function ({
...files,
...sharedFiles,
}}
options={{ ...sharedOptions }}
options={options}
{...sharedProps}
template={template}
>

View File

@@ -7,6 +7,7 @@ import {
import type { SandpackPreviewRef } from '@codesandbox/sandpack-react/unstyled';
import type { ArtifactFiles } from '~/common';
import { sharedFiles, sharedOptions } from '~/utils/artifacts';
import { useGetStartupConfig } from '~/data-provider';
import { useEditorContext } from '~/Providers';
export const ArtifactPreview = memo(function ({
@@ -23,6 +24,8 @@ export const ArtifactPreview = memo(function ({
previewRef: React.MutableRefObject<SandpackPreviewRef>;
}) {
const { currentCode } = useEditorContext();
const { data: config } = useGetStartupConfig();
const artifactFiles = useMemo(() => {
if (Object.keys(files).length === 0) {
return files;
@@ -38,6 +41,17 @@ export const ArtifactPreview = memo(function ({
},
};
}, [currentCode, files, fileKey]);
const options: typeof sharedOptions = useMemo(() => {
if (!config) {
return sharedOptions;
}
return {
...sharedOptions,
bundlerURL: config.bundlerURL,
};
}, [config]);
if (Object.keys(artifactFiles).length === 0) {
return null;
}
@@ -48,7 +62,7 @@ export const ArtifactPreview = memo(function ({
...artifactFiles,
...sharedFiles,
}}
options={{ ...sharedOptions }}
options={options}
{...sharedProps}
template={template}
>

View File

@@ -1,16 +1,78 @@
import { useOutletContext } from 'react-router-dom';
import { useOutletContext, useSearchParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { useAuthContext } from '~/hooks/AuthContext';
import type { TLoginLayoutContext } from '~/common';
import { ErrorMessage } from '~/components/Auth/ErrorMessage';
import { getLoginError } from '~/utils';
import { useLocalize } from '~/hooks';
import LoginForm from './LoginForm';
import SocialButton from '~/components/Auth/SocialButton';
import { OpenIDIcon } from '~/components';
function Login() {
const localize = useLocalize();
const { error, setError, login } = useAuthContext();
const { startupConfig } = useOutletContext<TLoginLayoutContext>();
const [searchParams, setSearchParams] = useSearchParams();
// Determine if auto-redirect should be disabled based on the URL parameter
const disableAutoRedirect = searchParams.get('redirect') === 'false';
// Persist the disable flag locally so that once detected, auto-redirect stays disabled.
const [isAutoRedirectDisabled, setIsAutoRedirectDisabled] = useState(disableAutoRedirect);
// Once the disable flag is detected, update local state and remove the parameter from the URL.
useEffect(() => {
if (disableAutoRedirect) {
setIsAutoRedirectDisabled(true);
const newParams = new URLSearchParams(searchParams);
newParams.delete('redirect');
setSearchParams(newParams, { replace: true });
}
}, [disableAutoRedirect, searchParams, setSearchParams]);
// Determine whether we should auto-redirect to OpenID.
const shouldAutoRedirect =
startupConfig?.openidLoginEnabled &&
startupConfig?.openidAutoRedirect &&
startupConfig?.serverDomain &&
!isAutoRedirectDisabled;
useEffect(() => {
if (shouldAutoRedirect) {
console.log('Auto-redirecting to OpenID provider...');
window.location.href = `${startupConfig.serverDomain}/oauth/openid`;
}
}, [shouldAutoRedirect, startupConfig]);
// Render fallback UI if auto-redirect is active.
if (shouldAutoRedirect) {
return (
<div className="flex min-h-screen flex-col items-center justify-center p-4">
<p className="text-lg font-semibold">
{localize('com_ui_redirecting_to_provider', { 0: startupConfig.openidLabel })}
</p>
<div className="mt-4">
<SocialButton
key="openid"
enabled={startupConfig.openidLoginEnabled}
serverDomain={startupConfig.serverDomain}
oauthPath="openid"
Icon={() =>
startupConfig.openidImageUrl ? (
<img src={startupConfig.openidImageUrl} alt="OpenID Logo" className="h-5 w-5" />
) : (
<OpenIDIcon />
)
}
label={startupConfig.openidLabel}
id="openid"
/>
</div>
</div>
);
}
return (
<>
{error != null && <ErrorMessage>{localize(getLoginError(error))}</ErrorMessage>}

View File

@@ -32,7 +32,12 @@ const Part = memo(({ part, isSubmitting, attachments, showCursor, isCreatedByUse
}
if (part.type === ContentTypes.ERROR) {
return <ErrorMessage text={part[ContentTypes.TEXT].value} className="my-2" />;
return (
<ErrorMessage
text={part[ContentTypes.ERROR] ?? part[ContentTypes.TEXT]?.value}
className="my-2"
/>
);
} else if (part.type === ContentTypes.TEXT) {
const text = typeof part.text === 'string' ? part.text : part.text.value;

View File

@@ -22,7 +22,7 @@ function Account() {
<div className="pb-3">
<EnableTwoFactorItem />
</div>
{Array.isArray(user.user?.backupCodes) && user.user?.backupCodes.length > 0 && (
{user?.user?.twoFactorEnabled && (
<div className="pb-3">
<BackupCodesItem />
</div>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { motion } from 'framer-motion';
import { LockIcon, UnlockIcon } from 'lucide-react';
// import { motion } from 'framer-motion';
// import { LockIcon, UnlockIcon } from 'lucide-react';
import { Label, Button } from '~/components';
import { useLocalize } from '~/hooks';

View File

@@ -37,7 +37,7 @@ const TwoFactorAuthentication: React.FC = () => {
const [backupCodes, setBackupCodes] = useState<string[]>([]);
const [isDialogOpen, setDialogOpen] = useState<boolean>(false);
const [verificationToken, setVerificationToken] = useState<string>('');
const [phase, setPhase] = useState<Phase>(Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? 'disable' : 'setup');
const [phase, setPhase] = useState<Phase>(user?.twoFactorEnabled ? 'disable' : 'setup');
const { mutate: confirm2FAMutate } = useConfirmTwoFactorMutation();
const { mutate: enable2FAMutate, isLoading: isGenerating } = useEnableTwoFactorMutation();
@@ -56,7 +56,7 @@ const TwoFactorAuthentication: React.FC = () => {
const currentStep = steps.indexOf(phasesLabel[phase]);
const resetState = useCallback(() => {
if (Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 && otpauthUrl) {
if (user?.twoFactorEnabled && otpauthUrl) {
disable2FAMutate(undefined, {
onError: () =>
showToast({ message: localize('com_ui_2fa_disable_error'), status: 'error' }),
@@ -68,7 +68,7 @@ const TwoFactorAuthentication: React.FC = () => {
setBackupCodes([]);
setVerificationToken('');
setDisableToken('');
setPhase(Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? 'disable' : 'setup');
setPhase(user?.twoFactorEnabled ? 'disable' : 'setup');
setDownloaded(false);
}, [user, otpauthUrl, disable2FAMutate, localize, showToast]);
@@ -136,6 +136,7 @@ const TwoFactorAuthentication: React.FC = () => {
used: false,
usedAt: null,
})),
twoFactorEnabled: true,
}) as TUser,
);
}, [setUser, localize, showToast, backupCodes]);
@@ -171,6 +172,7 @@ const TwoFactorAuthentication: React.FC = () => {
...prev,
totpSecret: '',
backupCodes: [],
twoFactorEnabled: false,
}) as TUser,
);
setPhase('setup');
@@ -183,7 +185,7 @@ const TwoFactorAuthentication: React.FC = () => {
onError: () => showToast({ message: localize('com_ui_2fa_invalid'), status: 'error' }),
});
},
[disableToken, verify2FAMutate, disable2FAMutate, showToast, localize, setUser],
[verify2FAMutate, disable2FAMutate, showToast, localize, setUser],
);
return (
@@ -197,7 +199,7 @@ const TwoFactorAuthentication: React.FC = () => {
}}
>
<DisableTwoFactorToggle
enabled={Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0}
enabled={!!user?.twoFactorEnabled}
onChange={() => setDialogOpen(true)}
disabled={isVerifying || isDisabling || isGenerating}
/>
@@ -215,9 +217,11 @@ const TwoFactorAuthentication: React.FC = () => {
<OGDialogHeader>
<OGDialogTitle className="mb-2 flex items-center gap-3 text-2xl font-bold">
<SmartphoneIcon className="h-6 w-6 text-primary" />
{Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? localize('com_ui_2fa_disable') : localize('com_ui_2fa_setup')}
{user?.twoFactorEnabled
? localize('com_ui_2fa_disable')
: localize('com_ui_2fa_setup')}
</OGDialogTitle>
{Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 && phase !== 'disable' && (
{user?.twoFactorEnabled && phase !== 'disable' && (
<div className="mt-4 space-y-3">
<Progress
value={(steps.indexOf(phasesLabel[phase]) / (steps.length - 1)) * 100}

View File

@@ -74,7 +74,7 @@ export const DisablePhase: React.FC<DisablePhaseProps> = ({ onDisable, isDisabli
disabled={isDisabling || token.length !== (useBackup ? 8 : 6)}
className="w-full rounded-xl px-6 py-3 transition-all disabled:opacity-50"
>
{isDisabling === true && <Spinner className="mr-2" />}
{isDisabling && <Spinner className="mr-2" />}
{isDisabling ? localize('com_ui_disabling') : localize('com_ui_2fa_disable')}
</Button>
<button

View File

@@ -18,7 +18,7 @@ interface SetupPhaseProps {
onGenerate: () => void;
}
export const SetupPhase: React.FC<SetupPhaseProps> = ({ isGenerating, onGenerate, onNext }) => {
export const SetupPhase: React.FC<SetupPhaseProps> = ({ isGenerating, onGenerate }) => {
const localize = useLocalize();
return (

View File

@@ -68,6 +68,7 @@ export const LangSelector = ({
{ value: 'sv-SE', label: localize('com_nav_lang_swedish') },
{ value: 'ko-KR', label: localize('com_nav_lang_korean') },
{ 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: 'nl-NL', label: localize('com_nav_lang_dutch') },
{ value: 'id-ID', label: localize('com_nav_lang_indonesia') },

View File

@@ -1,7 +1,7 @@
import { dataService, QueryKeys } from 'librechat-data-provider';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type * as t from 'librechat-data-provider';
import { dataService, QueryKeys, Constants } from 'librechat-data-provider';
import type { UseMutationResult } from '@tanstack/react-query';
import type * as t from 'librechat-data-provider';
export const useEditArtifact = (
_options?: t.EditArtifactOptions,
@@ -11,33 +11,47 @@ export const useEditArtifact = (
return useMutation({
mutationFn: (variables: t.TEditArtifactRequest) => dataService.editArtifact(variables),
onSuccess: (data, vars, context) => {
queryClient.setQueryData<t.TMessage[]>([QueryKeys.messages, data.conversationId], (prev) => {
if (!prev) {
return prev;
let targetNotFound = true;
const setMessageData = (conversationId?: string | null) => {
if (!conversationId) {
return;
}
const newArray = [...prev];
let targetIndex: number | undefined;
for (let i = newArray.length - 1; i >= 0; i--) {
if (newArray[i].messageId === vars.messageId) {
targetIndex = i;
break;
queryClient.setQueryData<t.TMessage[]>([QueryKeys.messages, conversationId], (prev) => {
if (!prev) {
return prev;
}
}
if (targetIndex == null) {
return prev;
}
const newArray = [...prev];
let targetIndex: number | undefined;
newArray[targetIndex] = {
...newArray[targetIndex],
content: data.content,
text: data.text,
};
for (let i = newArray.length - 1; i >= 0; i--) {
if (newArray[i].messageId === vars.messageId) {
targetIndex = i;
targetNotFound = false;
break;
}
}
return newArray;
});
if (targetIndex == null) {
return prev;
}
newArray[targetIndex] = {
...newArray[targetIndex],
content: data.content,
text: data.text,
};
return newArray;
});
};
setMessageData(data.conversationId);
if (targetNotFound) {
console.warn(
'Edited Artifact Message not found in cache, trying `new` as `conversationId`',
);
setMessageData(Constants.NEW_CONVO);
}
onSuccess?.(data, vars, context);
},

View File

@@ -6,6 +6,7 @@ import {
useContext,
useCallback,
createContext,
useRef,
} from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilState } from 'recoil';
@@ -35,6 +36,8 @@ const AuthContextProvider = ({
const [token, setToken] = useState<string | undefined>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const logoutRedirectRef = useRef<string | undefined>(undefined);
const { data: userRole = null } = useGetRole(SystemRoles.USER, {
enabled: !!(isAuthenticated && (user?.role ?? '')),
});
@@ -52,16 +55,17 @@ const AuthContextProvider = ({
//@ts-ignore - ok for token to be undefined initially
setTokenHeader(token);
setIsAuthenticated(isAuthenticated);
if (redirect == null) {
// Use a custom redirect if set
const finalRedirect = logoutRedirectRef.current || redirect;
// Clear the stored redirect
logoutRedirectRef.current = undefined;
if (finalRedirect == null) {
return;
}
if (redirect.startsWith('http://') || redirect.startsWith('https://')) {
// For external links, use window.location
window.location.href = redirect;
// Or if you want to open in a new tab:
// window.open(redirect, '_blank');
if (finalRedirect.startsWith('http://') || finalRedirect.startsWith('https://')) {
window.location.href = finalRedirect;
} else {
navigate(redirect, { replace: true });
navigate(finalRedirect, { replace: true });
}
},
[navigate, setUser],
@@ -106,7 +110,16 @@ const AuthContextProvider = ({
});
const refreshToken = useRefreshTokenMutation();
const logout = useCallback(() => logoutUser.mutate(undefined), [logoutUser]);
const logout = useCallback(
(redirect?: string) => {
if (redirect) {
logoutRedirectRef.current = redirect;
}
logoutUser.mutate(undefined);
},
[logoutUser],
);
const userQuery = useGetUserQuery({ enabled: !!(token ?? '') });
const login = (data: t.TLoginUser) => {

View File

@@ -337,6 +337,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -348,6 +349,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -0,0 +1,744 @@
{
"com_a11y_ai_composing": "AI stále tvoří odpověď.",
"com_a11y_end": "AI dokončila svou odpověď.",
"com_a11y_start": "AI začala tvořit odpověď.",
"com_agents_allow_editing": "Povolit ostatním uživatelům upravovat vašeho agenta",
"com_agents_by_librechat": "od LibreChat",
"com_agents_code_interpreter": "Při povolení může váš agent využívat LibreChat Code Interpreter API ke spuštění generovaného kódu, včetně zpracování souborů, bezpečně. Vyžaduje platný API klíč.",
"com_agents_code_interpreter_title": "API pro interpretaci kódu",
"com_agents_create_error": "Při vytváření agenta došlo k chybě.",
"com_agents_description_placeholder": "Volitelné: Popište zde svého agenta",
"com_agents_enable_file_search": "Povolit vyhledávání souborů",
"com_agents_file_search_disabled": "Než nahrajete soubory pro vyhledávání, musíte vytvořit agenta.",
"com_agents_file_search_info": "Při povolení bude agent informován o přesných názvech souborů uvedených níže, což mu umožní získat relevantní kontext z těchto souborů.",
"com_agents_instructions_placeholder": "Systémové instrukce, které agent používá",
"com_agents_missing_provider_model": "Před vytvořením agenta vyberte poskytovatele a model.",
"com_agents_name_placeholder": "Volitelné: Název agenta",
"com_agents_no_access": "Nemáte oprávnění upravovat tohoto agenta.",
"com_agents_not_available": "Agent není k dispozici",
"com_agents_search_name": "Hledat agenty podle jména",
"com_agents_update_error": "Při aktualizaci agenta došlo k chybě.",
"com_assistants_action_attempt": "Asistent se chce spojit s {{0}}",
"com_assistants_actions": "Akce",
"com_assistants_actions_disabled": "Než přidáte akce, musíte vytvořit asistenta.",
"com_assistants_actions_info": "Umožněte asistentovi získávat informace nebo provádět akce přes API",
"com_assistants_add_actions": "Přidat akce",
"com_assistants_add_tools": "Přidat nástroje",
"com_assistants_allow_sites_you_trust": "Povolte pouze weby, kterým důvěřujete.",
"com_assistants_append_date": "Připojit aktuální datum a čas",
"com_assistants_append_date_tooltip": "Při povolení se k systémovým instrukcím asistenta připojí aktuální datum a čas klienta.",
"com_assistants_attempt_info": "Asistent chce odeslat následující:",
"com_assistants_available_actions": "Dostupné akce",
"com_assistants_capabilities": "Schopnosti",
"com_assistants_code_interpreter": "Interpret kódu",
"com_assistants_code_interpreter_files": "Soubory níže jsou určeny pouze pro interpret kódu:",
"com_assistants_code_interpreter_info": "Interpret kódu umožňuje asistentovi psát a spouštět kód. Tento nástroj dokáže zpracovat soubory s různými daty a formáty a generovat soubory, například grafy.",
"com_assistants_completed_action": "Spojil se s {{0}}",
"com_assistants_completed_function": "Spustil {{0}}",
"com_assistants_conversation_starters": "Témata konverzace",
"com_assistants_conversation_starters_placeholder": "Zadejte téma konverzace",
"com_assistants_create_error": "Při vytváření asistenta došlo k chybě.",
"com_assistants_create_success": "Úspěšně vytvořeno",
"com_assistants_delete_actions_error": "Při mazání akce došlo k chybě.",
"com_assistants_delete_actions_success": "Akce byla úspěšně odstraněna z asistenta",
"com_assistants_description_placeholder": "Volitelné: Popište zde svého asistenta",
"com_assistants_domain_info": "Asistent poslal tyto informace: {{0}}",
"com_assistants_file_search": "Vyhledávání souborů",
"com_assistants_file_search_info": "Vyhledávání souborů umožňuje asistentovi pracovat se znalostmi z nahraných souborů. Jakmile je soubor nahrán, asistent automaticky rozhodne, kdy získat jeho obsah na základě požadavků uživatelů. Připojování vektorových úložišť pro vyhledávání není zatím podporováno.",
"com_auth_already_have_account": "Už máte účet?",
"com_auth_apple_login": "Přihlásit se přes Apple",
"com_auth_back_to_login": "Zpět k přihlášení",
"com_auth_click": "Klikněte",
"com_auth_click_here": "Klikněte zde",
"com_auth_continue": "Pokračovat",
"com_auth_create_account": "Vytvořit účet",
"com_auth_discord_login": "Pokračovat přes Discord",
"com_auth_email": "E-mail",
"com_auth_email_address": "E-mailová adresa",
"com_auth_email_verification_invalid": "Neplatné ověření e-mailu",
"com_auth_email_verification_redirecting": "Přesměrování za {{0}} sekund...",
"com_auth_email_verification_resend_prompt": "Nedostali jste e-mail?",
"com_auth_email_verification_success": "E-mail byl úspěšně ověřen",
"com_auth_email_verifying_ellipsis": "Ověřování...",
"com_auth_error_create": "Při registraci účtu došlo k chybě. Zkuste to prosím znovu.",
"com_auth_error_invalid_reset_token": "Tento resetovací token hesla již není platný.",
"com_auth_error_login": "Nelze se přihlásit s poskytnutými údaji. Zkontrolujte své přihlašovací údaje a zkuste to znovu.",
"com_auth_error_login_ban": "Váš účet byl dočasně zablokován kvůli porušení našich pravidel.",
"com_auth_error_login_rl": "Příliš mnoho pokusů o přihlášení v krátkém čase. Zkuste to prosím později.",
"com_auth_error_login_server": "Došlo k interní chybě serveru. Počkejte několik okamžiků a zkuste to znovu.",
"com_auth_error_login_unverified": "Váš účet nebyl ověřen. Zkontrolujte svůj e-mail a najděte ověřovací odkaz.",
"com_auth_facebook_login": "Pokračovat přes Facebook",
"com_auth_full_name": "Celé jméno",
"com_auth_github_login": "Pokračovat přes Github",
"com_auth_google_login": "Pokračovat přes Google",
"com_auth_here": "ZDE",
"com_auth_login": "Přihlášení",
"com_auth_login_with_new_password": "Nyní se můžete přihlásit s novým heslem.",
"com_auth_name_max_length": "Jméno musí mít méně než 80 znaků",
"com_auth_name_min_length": "Jméno musí mít alespoň 3 znaky",
"com_auth_name_required": "Jméno je povinné",
"com_auth_no_account": "Nemáte účet?",
"com_auth_password": "Heslo",
"com_auth_password_confirm": "Potvrdit heslo",
"com_auth_password_forgot": "Zapomněli jste heslo?",
"com_auth_password_max_length": "Heslo musí mít méně než 128 znaků",
"com_auth_password_min_length": "Heslo musí mít alespoň 8 znaků",
"com_auth_password_not_match": "Hesla se neshodují",
"com_auth_password_required": "Heslo je povinné",
"com_auth_registration_success_generic": "Zkontrolujte svůj e-mail pro ověření vaší e-mailové adresy.",
"com_auth_registration_success_insecure": "Registrace byla úspěšná.",
"com_auth_reset_password": "Obnovit heslo",
"com_auth_reset_password_if_email_exists": "Pokud účet s touto e-mailovou adresou existuje, byl odeslán e-mail s instrukcemi pro resetování hesla. Nezapomeňte zkontrolovat složku spamu.",
"com_auth_reset_password_link_sent": "E-mail odeslán",
"com_auth_reset_password_success": "Obnova hesla úspěšná",
"com_auth_sign_in": "Přihlásit se",
"com_auth_sign_up": "Registrovat se",
"com_auth_submit_registration": "Odeslat registraci",
"com_auth_to_reset_your_password": "pro obnovení hesla.",
"com_auth_to_try_again": "zkusit znovu.",
"com_auth_two_factor": "Zkontrolujte vaši preferovanou aplikaci pro jednorázové heslo a zadejte kód",
"com_auth_username": "Uživatelské jméno (volitelné)",
"com_auth_username_max_length": "Uživatelské jméno musí mít méně než 20 znaků",
"com_auth_username_min_length": "Uživatelské jméno musí mít alespoň 2 znaky",
"com_auth_verify_your_identity": "Ověřte svou identitu",
"com_auth_welcome_back": "Vítejte zpět",
"com_click_to_download": "(klikněte zde pro stažení)",
"com_download_expired": "(platnost stažení vypršela)",
"com_download_expires": "(klikněte zde pro stažení - platnost vyprší {{0}})",
"com_endpoint": "Koncový bod",
"com_endpoint_agent": "Agent",
"com_endpoint_agent_model": "Model agenta (Doporučeno: GPT-3.5)",
"com_endpoint_agent_placeholder": "Vyberte prosím agenta",
"com_endpoint_ai": "AI",
"com_endpoint_anthropic_maxoutputtokens": "Maximální počet tokenů, které lze v odpovědi vygenerovat. Zadejte nižší hodnotu pro kratší odpovědi a vyšší hodnotu pro delší odpovědi. Poznámka: modely mohou skončit před dosažením tohoto maxima.",
"com_endpoint_anthropic_prompt_cache": "Ukládání výzev umožňuje znovu použít rozsáhlý kontext nebo instrukce napříč API voláními, čímž se snižují náklady a latence.",
"com_endpoint_anthropic_temp": "Hodnoty od 0 do 1. Použijte hodnotu blíže k 0 pro analytické / výběrové úlohy a blíže k 1 pro kreativní a generativní úkoly.",
"com_endpoint_anthropic_thinking": "Povoluje interní uvažování u podporovaných modelů Claude (3.7 Sonnet). Poznámka: vyžaduje nastavení \"Thinking Budget\" na nižší hodnotu než \"Max Output Tokens\".",
"com_endpoint_anthropic_thinking_budget": "Určuje maximální počet tokenů, které může Claude použít pro svůj interní proces uvažování.",
"com_endpoint_assistant": "Asistent",
"com_endpoint_assistant_model": "Model asistenta",
"com_endpoint_completion": "Dokončení",
"com_endpoint_completion_model": "Model dokončení (Doporučeno: GPT-4)",
"com_endpoint_config_key_name": "Klíč",
"com_endpoint_config_key_never_expires": "Váš klíč nikdy nevyprší",
"com_endpoint_config_placeholder": "Nastavte svůj klíč v nabídce záhlaví pro chat.",
"com_endpoint_config_value": "Zadejte hodnotu pro",
"com_endpoint_context": "Kontext",
"com_endpoint_context_info": "Maximální počet tokenů, které lze použít pro kontext. Použijte toto pro kontrolu počtu tokenů odeslaných na požadavek. Pokud není uvedeno, použije se výchozí nastavení systému podle velikosti kontextu známých modelů. Nastavení vyšších hodnot může vést k chybám a/nebo vyšším nákladům na tokeny.",
"com_endpoint_context_tokens": "Maximální počet kontextových tokenů",
"com_endpoint_custom_name": "Vlastní název",
"com_endpoint_default": "výchozí",
"com_endpoint_default_blank": "výchozí: prázdné",
"com_endpoint_default_empty": "výchozí: prázdné",
"com_endpoint_default_with_num": "výchozí: {{0}}",
"com_endpoint_examples": "Předvolby",
"com_endpoint_export": "Exportovat",
"com_endpoint_export_share": "Exportovat/Sdílet",
"com_endpoint_frequency_penalty": "Postih za časté opakování",
"com_endpoint_func_hover": "Povolit použití pluginů jako funkcí OpenAI",
"com_endpoint_google_custom_name_placeholder": "Nastavit vlastní název pro Google",
"com_endpoint_google_maxoutputtokens": "Maximální počet tokenů, které lze v odpovědi vygenerovat. Nižší hodnota pro kratší odpovědi, vyšší hodnota pro delší odpovědi. Poznámka: modely mohou skončit před dosažením tohoto maxima.",
"com_endpoint_google_temp": "Vyšší hodnoty = větší náhodnost, nižší hodnoty = soustředěnější a deterministický výstup. Doporučujeme upravit buď toto, nebo Top P, ale ne obojí.",
"com_endpoint_google_topk": "Top-k mění způsob, jakým model vybírá tokeny pro výstup. Top-k 1 znamená, že vybraný token je nejpravděpodobnější ze všech v modelové slovní zásobě (také nazývané chamtivé dekódování), zatímco top-k 3 znamená, že další token je vybrán ze tří nejpravděpodobnějších (s použitím teploty).",
"com_endpoint_google_topp": "Top-p mění způsob, jakým model vybírá tokeny pro výstup. Tokeny jsou vybírány od nejpravděpodobnějších, dokud součet jejich pravděpodobností nedosáhne hodnoty top-p.",
"com_endpoint_instructions_assistants": "Přepsat instrukce",
"com_endpoint_instructions_assistants_placeholder": "Přepíše instrukce asistenta. Užitečné pro úpravu chování v jednotlivých bězích.",
"com_endpoint_max_output_tokens": "Maximální počet výstupních tokenů",
"com_endpoint_message": "Zpráva",
"com_endpoint_message_new": "Zpráva {{0}}",
"com_endpoint_message_not_appendable": "Upravte svou zprávu nebo ji znovu vygenerujte.",
"com_endpoint_my_preset": "Moje předvolba",
"com_endpoint_no_presets": "Žádné předvolby zatím nejsou, použijte tlačítko nastavení k vytvoření jedné",
"com_endpoint_open_menu": "Otevřít nabídku",
"com_endpoint_openai_custom_name_placeholder": "Nastavit vlastní název pro AI",
"com_endpoint_openai_temp": "Vyšší hodnoty = větší náhodnost, nižší hodnoty = soustředěnější a deterministický výstup.",
"com_endpoint_openai_topp": "Alternativa k vzorkování s teplotou, tzv. nucleus sampling, kde model zvažuje výsledky tokenů s nejvyšší pravděpodobností. Například hodnota 0.1 znamená, že se berou v úvahu pouze tokeny tvořící 10 % nejvyšší pravděpodobnosti.",
"com_endpoint_output": "Výstup",
"com_endpoint_plug_image_detail": "Detail obrazu",
"com_endpoint_plug_resend_files": "Znovu odeslat soubory",
"com_endpoint_prompt_cache": "Použít cache výzev",
"com_endpoint_prompt_prefix": "Vlastní instrukce",
"com_endpoint_prompt_prefix_assistants": "Další instrukce",
"com_endpoint_reasoning_effort": "Úroveň úsilí při uvažování",
"com_endpoint_save_as_preset": "Uložit jako předvolbu",
"com_endpoint_search": "Hledat koncový bod podle názvu",
"com_endpoint_stop": "Zastavit sekvence",
"com_endpoint_temperature": "Teplota",
"com_endpoint_thinking": "Přemýšlení",
"com_endpoint_thinking_budget": "Rozpočet na přemýšlení",
"com_endpoint_top_k": "Top K",
"com_endpoint_top_p": "Top P",
"com_endpoint_use_active_assistant": "Použít aktivního asistenta",
"com_error_expired_user_key": "Poskytnutý klíč pro {{0}} vypršel v {{1}}. Zadejte nový klíč a zkuste to znovu.",
"com_error_files_dupe": "Byl zjištěn duplicitní soubor.",
"com_error_files_empty": "Prázdné soubory nejsou povoleny.",
"com_error_files_process": "Při zpracování souboru došlo k chybě.",
"com_error_files_unsupported_capability": "Nejsou povoleny žádné funkce podporující tento typ souboru.",
"com_error_files_upload": "Při nahrávání souboru došlo k chybě.",
"com_error_files_upload_canceled": "Požadavek na nahrání souboru byl zrušen. Poznámka: nahrávání souboru může stále probíhat a bude nutné jej ručně smazat.",
"com_error_files_validation": "Při ověřování souboru došlo k chybě.",
"com_error_input_length": "Počet tokenů v poslední zprávě je příliš dlouhý a přesahuje limit tokenů ({{0}}). Zkraťte svou zprávu, upravte maximální velikost kontextu v parametrech konverzace nebo rozdělte konverzaci.",
"com_error_invalid_user_key": "Zadaný klíč je neplatný. Zadejte platný klíč a zkuste to znovu.",
"com_error_moderation": "Zdá se, že obsah vaší zprávy byl označen naším moderovacím systémem, protože neodpovídá našim komunitním zásadám. Nemůžeme v této záležitosti pokračovat. Pokud máte jiné otázky nebo témata, která chcete prozkoumat, upravte svou zprávu nebo vytvořte novou konverzaci.",
"com_error_no_base_url": "Nebyla nalezena základní URL. Zadejte ji a zkuste to znovu.",
"com_error_no_user_key": "Nebyl nalezen žádný klíč. Zadejte klíč a zkuste to znovu.",
"com_files_filter": "Filtrovat soubory...",
"com_files_no_results": "Žádné výsledky.",
"com_files_number_selected": "Vybráno {{0}} z {{1}} položek",
"com_files_table": "něco sem musí přijít. bylo prázdné",
"com_generated_files": "Vygenerované soubory:",
"com_hide_examples": "Skrýt příklady",
"com_nav_2fa": "Dvoufaktorové ověřování (2FA)",
"com_nav_account_settings": "Nastavení účtu",
"com_nav_always_make_prod": "Vždy nastavovat nové verze jako produkční",
"com_nav_archive_created_at": "Datum archivace",
"com_nav_archive_name": "Název",
"com_nav_archived_chats": "Archivované chaty",
"com_nav_archived_chats_empty": "Nemáte žádné archivované konverzace.",
"com_nav_at_command": "@-Příkaz",
"com_nav_at_command_description": "Přepínání příkazů \"@\" pro přepínání koncových bodů, modelů, předvoleb atd.",
"com_nav_audio_play_error": "Chyba při přehrávání zvuku: {{0}}",
"com_nav_audio_process_error": "Chyba při zpracování zvuku: {{0}}",
"com_nav_auto_scroll": "Automaticky rolovat na nejnovější zprávu po otevření chatu",
"com_nav_auto_send_prompts": "Automatické odesílání výzev",
"com_nav_auto_send_text": "Automatické odesílání textu",
"com_nav_auto_send_text_disabled": "nastavte -1 pro deaktivaci",
"com_nav_auto_transcribe_audio": "Automaticky přepisovat zvuk",
"com_nav_automatic_playback": "Automatické přehrávání poslední zprávy",
"com_nav_balance": "Zůstatek",
"com_nav_browser": "Prohlížeč",
"com_nav_buffer_append_error": "Problém s přenosem zvuku. Přehrávání může být přerušeno.",
"com_nav_change_picture": "Změnit obrázek",
"com_nav_chat_commands": "Příkazy chatu",
"com_nav_chat_commands_info": "Tyto příkazy se aktivují zadáním specifických znaků na začátku vaší zprávy. Každý příkaz je spuštěn svým určeným prefixem. Můžete je deaktivovat, pokud tyto znaky často používáte na začátku zpráv.",
"com_nav_chat_direction": "Směr chatu",
"com_nav_clear_all_chats": "Vymazat všechny chaty",
"com_nav_clear_cache_confirm_message": "Opravdu chcete vymazat mezipaměť?",
"com_nav_clear_conversation": "Vymazat konverzace",
"com_nav_clear_conversation_confirm_message": "Opravdu chcete vymazat všechny konverzace? Tuto akci nelze vrátit zpět.",
"com_nav_close_sidebar": "Zavřít boční panel",
"com_nav_commands": "Příkazy",
"com_nav_confirm_clear": "Potvrdit vymazání",
"com_nav_conversation_mode": "Režim konverzace",
"com_nav_convo_menu_options": "Možnosti menu konverzace",
"com_nav_db_sensitivity": "Citlivost decibelů",
"com_nav_delete_account": "Smazat účet",
"com_nav_delete_account_button": "Trvale smazat můj účet",
"com_nav_delete_account_confirm": "Smazat účet - jste si jisti?",
"com_nav_delete_account_email_placeholder": "Zadejte e-mail vašeho účtu",
"com_nav_delete_cache_storage": "Smazat úložiště mezipaměti TTS",
"com_nav_delete_data_info": "Všechna vaše data budou smazána.",
"com_nav_delete_warning": "VAROVÁNÍ: Tato akce trvale smaže váš účet.",
"com_nav_edge": "Edge",
"com_nav_enable_cache_tts": "Povolit mezipaměť TTS",
"com_nav_enable_cloud_browser_voice": "Používat cloudové hlasy",
"com_nav_enabled": "Povoleno",
"com_nav_engine": "Engine",
"com_nav_enter_to_send": "Stiskněte Enter pro odeslání zprávy",
"com_nav_export": "Exportovat",
"com_nav_export_all_message_branches": "Exportovat všechny větve zpráv",
"com_nav_export_conversation": "Exportovat konverzaci",
"com_nav_export_filename": "Název souboru",
"com_nav_export_filename_placeholder": "Zadejte název souboru",
"com_nav_export_include_endpoint_options": "Zahrnout možnosti koncového bodu",
"com_nav_export_recursive": "Rekurzivní",
"com_nav_export_recursive_or_sequential": "Rekurzivní nebo sekvenční?",
"com_nav_export_type": "Typ",
"com_nav_external": "Externí",
"com_nav_font_size": "Velikost písma zprávy",
"com_nav_font_size_base": "Střední",
"com_nav_font_size_lg": "Velká",
"com_nav_font_size_sm": "Malá",
"com_nav_font_size_xl": "Extra velká",
"com_nav_font_size_xs": "Extra malá",
"com_nav_help_faq": "Nápověda a FAQ",
"com_nav_hide_panel": "Skrýt pravý panel",
"com_nav_info_code_artifacts": "Povoluje zobrazování experimentálních kódových artefaktů vedle chatu",
"com_nav_info_code_artifacts_agent": "Povoluje použití kódových artefaktů pro tohoto agenta. Ve výchozím nastavení jsou přidány další instrukce specifické pro použití artefaktů, pokud není povolen režim \"Vlastní výzva\".",
"com_nav_info_custom_prompt_mode": "Při povolení nebude zahrnuta výchozí systémová výzva pro artefakty. Všechny instrukce pro generování artefaktů musí být v tomto režimu poskytnuty ručně.",
"com_nav_info_enter_to_send": "Při povolení odešle stisk `ENTER` zprávu. Při deaktivaci přidá Enter nový řádek a zprávu odešlete stiskem `CTRL + ENTER` / `⌘ + ENTER`.",
"com_nav_info_fork_change_default": "`Viditelné zprávy pouze` zahrnuje pouze přímou cestu k vybrané zprávě. `Zahrnout související větve` přidá větve podél cesty. `Zahrnout vše od/do` zahrnuje všechny propojené zprávy a větve.",
"com_nav_info_fork_split_target_setting": "Při povolení začne větvení od cílové zprávy až po nejnovější zprávu v konverzaci podle zvoleného chování.",
"com_nav_info_include_shadcnui": "Při povolení budou zahrnuty instrukce pro použití komponent shadcn/ui.",
"com_nav_info_latex_parsing": "Při povolení bude LaTeX kód v zprávách vykreslen jako matematické rovnice.",
"com_nav_info_save_draft": "Při povolení se text a přílohy, které zadáte do chatu, automaticky ukládají jako koncepty.",
"com_nav_info_show_thinking": "Při povolení se automaticky zobrazí rozbalovací nabídky uvažování AI.",
"com_nav_info_user_name_display": "Při povolení se nad každou vaší zprávou zobrazí uživatelské jméno.",
"com_nav_lang_arabic": "العربية",
"com_nav_lang_auto": "Automatické rozpoznání",
"com_nav_lang_brazilian_portuguese": "Português Brasileiro",
"com_nav_lang_chinese": "中文",
"com_nav_lang_dutch": "Nederlands",
"com_nav_lang_english": "English",
"com_nav_lang_french": "Français ",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_italian": "Italiano",
"com_nav_lang_japanese": "日本語",
"com_nav_lang_korean": "한국어",
"com_nav_lang_polish": "Polski",
"com_nav_lang_portuguese": "Português",
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",
"com_nav_language": "Jazyk",
"com_nav_latex_parsing": "Zpracování LaTeXu v zprávách",
"com_nav_log_out": "Odhlásit se",
"com_nav_maximize_chat_space": "Maximalizovat prostor chatu",
"com_nav_modular_chat": "Povolit přepínání koncových bodů během konverzace",
"com_nav_my_files": "Moje soubory",
"com_nav_no_search_results": "Nebyl nalezen žádný výsledek",
"com_nav_not_supported": "Nepodporováno",
"com_nav_open_sidebar": "Otevřít boční panel",
"com_nav_playback_rate": "Rychlost přehrávání zvuku",
"com_nav_plugin_auth_error": "Při ověřování pluginu došlo k chybě.",
"com_nav_plugin_install": "Instalovat",
"com_nav_plugin_search": "Hledat pluginy",
"com_nav_plugin_store": "Obchod s pluginy",
"com_nav_plugin_uninstall": "Odinstalovat",
"com_nav_plus_command": "+-Příkaz",
"com_nav_plus_command_description": "Přepnutí příkazu \"+\" pro přidání nastavení více odpovědí",
"com_nav_profile_picture": "Profilový obrázek",
"com_nav_save_drafts": "Ukládat koncepty lokálně",
"com_nav_scroll_button": "Tlačítko pro posun na konec",
"com_nav_search_placeholder": "Hledat zprávy",
"com_nav_send_message": "Odeslat zprávu",
"com_nav_setting_account": "Účet",
"com_nav_setting_beta": "Beta funkce",
"com_nav_setting_chat": "Chat",
"com_nav_setting_data": "Ovládání dat",
"com_nav_setting_general": "Obecné",
"com_nav_setting_speech": "Hlas",
"com_nav_settings": "Nastavení",
"com_nav_shared_links": "Sdílené odkazy",
"com_nav_show_code": "Vždy zobrazit kód při použití interpretace kódu",
"com_nav_show_thinking": "Otevřít uvažovací nabídky ve výchozím nastavení",
"com_nav_slash_command": "/-Příkaz",
"com_nav_slash_command_description": "Přepnutí příkazu \"/\" pro výběr výzvy pomocí klávesnice",
"com_nav_source_buffer_error": "Chyba při nastavení přehrávání zvuku. Obnovte stránku.",
"com_nav_speech_cancel_error": "Nelze zastavit přehrávání zvuku. Možná budete muset stránku obnovit.",
"com_nav_speech_to_text": "Převod řeči na text",
"com_nav_stop_generating": "Zastavit generování",
"com_nav_text_to_speech": "Převod textu na řeč",
"com_nav_theme": "Motiv",
"com_nav_theme_dark": "Tmavý",
"com_nav_theme_light": "Světlý",
"com_nav_theme_system": "Systémový",
"com_nav_tool_dialog": "Nástroje asistenta",
"com_nav_tool_dialog_agents": "Nástroje agenta",
"com_nav_tool_dialog_description": "Asistent musí být uložen, aby výběr nástrojů přetrval.",
"com_nav_tool_remove": "Odstranit",
"com_nav_tool_search": "Hledat nástroje",
"com_nav_tts_init_error": "Nepodařilo se inicializovat převod textu na řeč: {{0}}",
"com_nav_tts_unsupported_error": "Převod textu na řeč pro vybraný engine není v tomto prohlížeči podporován.",
"com_nav_user": "UŽIVATEL",
"com_nav_user_msg_markdown": "Zobrazit uživatelské zprávy ve formátu Markdown",
"com_nav_user_name_display": "Zobrazit uživatelské jméno ve zprávách",
"com_nav_voice_select": "Hlas",
"com_nav_voices_fetch_error": "Nepodařilo se načíst možnosti hlasu. Zkontrolujte připojení k internetu.",
"com_nav_welcome_agent": "Vyberte agenta",
"com_nav_welcome_assistant": "Vyberte asistenta",
"com_nav_welcome_message": "Jak vám mohu dnes pomoci?",
"com_show_agent_settings": "Zobrazit nastavení agenta",
"com_show_completion_settings": "Zobrazit nastavení dokončení",
"com_show_examples": "Zobrazit příklady",
"com_sidepanel_agent_builder": "Tvůrce agentů",
"com_sidepanel_assistant_builder": "Tvůrce asistentů",
"com_sidepanel_attach_files": "Připojit soubory",
"com_sidepanel_conversation_tags": "Záložky",
"com_sidepanel_hide_panel": "Skrýt panel",
"com_sidepanel_manage_files": "Správa souborů",
"com_sidepanel_parameters": "Parametry",
"com_sidepanel_select_agent": "Vybrat agenta",
"com_sidepanel_select_assistant": "Vybrat asistenta",
"com_ui_2fa_account_security": "Dvoufaktorové ověřování přidává další vrstvu zabezpečení vašeho účtu",
"com_ui_2fa_disable": "Zakázat 2FA",
"com_ui_2fa_disable_error": "Při deaktivaci dvoufaktorového ověřování došlo k chybě",
"com_ui_2fa_disabled": "2FA bylo deaktivováno",
"com_ui_2fa_enable": "Povolit 2FA",
"com_ui_2fa_enabled": "2FA bylo povoleno",
"com_ui_2fa_generate_error": "Při generování nastavení 2FA došlo k chybě",
"com_ui_2fa_invalid": "Neplatný kód dvoufaktorového ověřování",
"com_ui_2fa_setup": "Nastavit 2FA",
"com_ui_2fa_verified": "Dvoufaktorové ověřování úspěšně ověřeno",
"com_ui_accept": "Přijímám",
"com_ui_add": "Přidat",
"com_ui_add_model_preset": "Přidat model nebo předvolbu pro další odpověď",
"com_ui_add_multi_conversation": "Přidat více konverzací",
"com_ui_admin": "Administrátor",
"com_ui_admin_access_warning": "Zakázání přístupu správce k této funkci může způsobit problémy v uživatelském rozhraní.",
"com_ui_admin_settings": "Nastavení správce",
"com_ui_advanced": "Pokročilé",
"com_ui_agent": "Agent",
"com_ui_agent_delete_error": "Při mazání agenta došlo k chybě",
"com_ui_agent_deleted": "Agent byl úspěšně smazán",
"com_ui_agent_duplicate_error": "Při duplikaci agenta došlo k chybě",
"com_ui_agent_duplicated": "Agent byl úspěšně duplikován",
"com_ui_agents": "Agenti",
"com_ui_agents_allow_create": "Povolit vytváření agentů",
"com_ui_agents_allow_share_global": "Povolit sdílení agentů všem uživatelům",
"com_ui_agents_allow_use": "Povolit používání agentů",
"com_ui_all": "vše",
"com_ui_all_proper": "Vše",
"com_ui_analyzing": "Analýza",
"com_ui_analyzing_finished": "Analýza dokončena",
"com_ui_api_key": "API klíč",
"com_ui_archive": "Archivovat",
"com_ui_archive_error": "Nepodařilo se archivovat konverzaci",
"com_ui_artifact_click": "Klikněte pro otevření",
"com_ui_artifacts": "Artefakty",
"com_ui_artifacts_toggle": "Přepnout uživatelské rozhraní artefaktů",
"com_ui_artifacts_toggle_agent": "Povolit artefakty",
"com_ui_ascending": "Vzestupně",
"com_ui_assistant": "Asistent",
"com_ui_assistant_delete_error": "Při mazání asistenta došlo k chybě",
"com_ui_assistant_deleted": "Asistent byl úspěšně smazán",
"com_ui_assistants": "Asistenti",
"com_ui_assistants_output": "Výstup asistentů",
"com_ui_attach_error": "Nelze připojit soubor. Vytvořte nebo vyberte konverzaci.",
"com_ui_attach_error_openai": "Nelze připojit soubory asistenta k jiným koncovým bodům",
"com_ui_attach_error_size": "Překročena velikost souboru pro koncový bod:",
"com_ui_attach_error_type": "Nepodporovaný typ souboru pro koncový bod:",
"com_ui_attach_warn_endpoint": "Nepodporované soubory mohou být ignorovány",
"com_ui_attachment": "Příloha",
"com_ui_auth_type": "Typ ověření",
"com_ui_auth_url": "Autorizační URL",
"com_ui_authentication": "Ověření",
"com_ui_authentication_type": "Typ ověření",
"com_ui_avatar": "Avatar",
"com_ui_azure": "Azure",
"com_ui_back_to_chat": "Zpět do chatu",
"com_ui_back_to_prompts": "Zpět na výzvy",
"com_ui_backup_codes": "Záložní kódy",
"com_ui_backup_codes_regenerate_error": "Při generování záložních kódů došlo k chybě",
"com_ui_backup_codes_regenerated": "Záložní kódy byly úspěšně vygenerovány",
"com_ui_basic": "Základní",
"com_ui_basic_auth_header": "Základní autorizační hlavička",
"com_ui_bearer": "Bearer",
"com_ui_bookmark_delete_confirm": "Opravdu chcete smazat tuto záložku?",
"com_ui_bookmarks": "Záložky",
"com_ui_bookmarks_add": "Přidat záložky",
"com_ui_bookmarks_add_to_conversation": "Přidat do aktuální konverzace",
"com_ui_bookmarks_count": "Počet",
"com_ui_bookmarks_create_error": "Při vytváření záložky došlo k chybě",
"com_ui_bookmarks_create_exists": "Tato záložka již existuje",
"com_ui_bookmarks_create_success": "Záložka byla úspěšně vytvořena",
"com_ui_bookmarks_delete": "Smazat záložku",
"com_ui_bookmarks_delete_error": "Při mazání záložky došlo k chybě",
"com_ui_bookmarks_delete_success": "Záložka byla úspěšně smazána",
"com_ui_bookmarks_description": "Popis",
"com_ui_bookmarks_edit": "Upravit záložku",
"com_ui_bookmarks_filter": "Filtrovat záložky...",
"com_ui_bookmarks_new": "Nová záložka",
"com_ui_bookmarks_title": "Název",
"com_ui_bookmarks_update_error": "Při aktualizaci záložky došlo k chybě",
"com_ui_bookmarks_update_success": "Záložka byla úspěšně aktualizována",
"com_ui_bulk_delete_error": "Nepodařilo se smazat sdílené odkazy",
"com_ui_callback_url": "Callback URL",
"com_ui_cancel": "Zrušit",
"com_ui_chat": "Chat",
"com_ui_chat_history": "Historie chatu",
"com_ui_clear": "Vymazat",
"com_ui_clear_all": "Vymazat vše",
"com_ui_client_id": "ID klienta",
"com_ui_client_secret": "Tajný klíč klienta",
"com_ui_close": "Zavřít",
"com_ui_close_menu": "Zavřít nabídku",
"com_ui_code": "Kód",
"com_ui_collapse_chat": "Sbalit chat",
"com_ui_command_placeholder": "Volitelné: Zadejte příkaz pro výzvu, jinak se použije název",
"com_ui_command_usage_placeholder": "Vybrat výzvu podle příkazu nebo názvu",
"com_ui_complete_setup": "Dokončit nastavení",
"com_ui_confirm_action": "Potvrdit akci",
"com_ui_confirm_admin_use_change": "Změna tohoto nastavení zablokuje přístup správcům, včetně vás. Opravdu chcete pokračovat?",
"com_ui_confirm_change": "Potvrdit změnu",
"com_ui_context": "Kontext",
"com_ui_continue": "Pokračovat",
"com_ui_controls": "Ovládání",
"com_ui_copied": "Zkopírováno!",
"com_ui_copied_to_clipboard": "Zkopírováno do schránky",
"com_ui_copy_code": "Kopírovat kód",
"com_ui_copy_link": "Kopírovat odkaz",
"com_ui_copy_to_clipboard": "Kopírovat do schránky",
"com_ui_create": "Vytvořit",
"com_ui_create_link": "Vytvořit odkaz",
"com_ui_create_prompt": "Vytvořit výzvu",
"com_ui_currently_production": "Aktuálně ve výrobě",
"com_ui_custom": "Vlastní",
"com_ui_custom_header_name": "Vlastní název hlavičky",
"com_ui_custom_prompt_mode": "Režim vlastní výzvy",
"com_ui_dashboard": "Dashboard",
"com_ui_date": "Datum",
"com_ui_date_april": "Duben",
"com_ui_date_august": "Srpen",
"com_ui_date_december": "Prosinec",
"com_ui_date_february": "Únor",
"com_ui_date_january": "Leden",
"com_ui_date_july": "Červenec",
"com_ui_date_june": "Červen",
"com_ui_date_march": "Březen",
"com_ui_date_may": "Květen",
"com_ui_date_november": "Listopad",
"com_ui_date_october": "Říjen",
"com_ui_date_previous_30_days": "Předchozích 30 dní",
"com_ui_date_previous_7_days": "Předchozích 7 dní",
"com_ui_date_september": "Září",
"com_ui_date_today": "Dnes",
"com_ui_date_yesterday": "Včera",
"com_ui_decline": "Nepřijímám",
"com_ui_default_post_request": "Výchozí (POST request)",
"com_ui_delete": "Smazat",
"com_ui_delete_action": "Smazat akci",
"com_ui_delete_action_confirm": "Opravdu chcete tuto akci smazat?",
"com_ui_delete_agent_confirm": "Opravdu chcete tohoto agenta smazat?",
"com_ui_delete_assistant_confirm": "Opravdu chcete tohoto asistenta smazat? Tuto akci nelze vrátit zpět.",
"com_ui_delete_confirm": "Tímto smažete",
"com_ui_delete_confirm_prompt_version_var": "Tímto smažete vybranou verzi pro \"{{0}}.\" Pokud neexistují žádné další verze, výzva bude smazána.",
"com_ui_delete_conversation": "Smazat chat?",
"com_ui_delete_prompt": "Smazat výzvu?",
"com_ui_delete_shared_link": "Smazat sdílený odkaz?",
"com_ui_delete_tool": "Smazat nástroj",
"com_ui_delete_tool_confirm": "Opravdu chcete tento nástroj smazat?",
"com_ui_descending": "Sestupně",
"com_ui_description": "Popis",
"com_ui_description_placeholder": "Volitelné: Zadejte popis pro zobrazení výzvy",
"com_ui_disabling": "Deaktivace...",
"com_ui_download": "Stáhnout",
"com_ui_download_artifact": "Stáhnout artefakt",
"com_ui_download_backup": "Stáhnout záložní kódy",
"com_ui_download_backup_tooltip": "Před pokračováním si stáhněte záložní kódy. Budete je potřebovat k opětovnému přístupu v případě ztráty autentizačního zařízení.",
"com_ui_download_error": "Chyba při stahování souboru. Soubor mohl být smazán.",
"com_ui_drag_drop": "něco sem musí přijít. bylo prázdné",
"com_ui_dropdown_variables": "Proměnné rozevírací nabídky:",
"com_ui_dropdown_variables_info": "Vytvořte vlastní rozevírací nabídky pro vaše výzvy: `{{variable_name:option1|option2|option3}}`",
"com_ui_duplicate": "Duplikovat",
"com_ui_duplication_error": "Při duplikaci konverzace došlo k chybě",
"com_ui_duplication_processing": "Duplikuji konverzaci...",
"com_ui_duplication_success": "Konverzace úspěšně duplikována",
"com_ui_edit": "Upravit",
"com_ui_empty_category": "-",
"com_ui_endpoint": "Koncový bod",
"com_ui_endpoint_menu": "Nabídka LLM koncových bodů",
"com_ui_endpoints_available": "Dostupné koncové body",
"com_ui_enter": "Enter",
"com_ui_enter_api_key": "Zadejte API klíč",
"com_ui_enter_openapi_schema": "Zadejte svůj OpenAPI schéma zde",
"com_ui_enter_var": "Zadejte {{0}}",
"com_ui_error": "Chyba",
"com_ui_error_connection": "Chyba při připojení k serveru, zkuste obnovit stránku.",
"com_ui_error_save_admin_settings": "Při ukládání nastavení správce došlo k chybě.",
"com_ui_examples": "Příklady",
"com_ui_export_convo_modal": "Exportovat konverzaci",
"com_ui_field_required": "Toto pole je povinné",
"com_ui_filter_prompts": "Filtrovat výzvy",
"com_ui_filter_prompts_name": "Filtrovat výzvy podle názvu",
"com_ui_finance": "Finance",
"com_ui_fork": "Rozdělit",
"com_ui_fork_all_target": "Zahrnout vše od/do",
"com_ui_fork_branches": "Zahrnout související větve",
"com_ui_fork_change_default": "Výchozí možnost rozdělení",
"com_ui_fork_default": "Použít výchozí možnost rozdělení",
"com_ui_fork_error": "Při rozdělování konverzace došlo k chybě",
"com_ui_fork_from_message": "Vyberte možnost rozdělení",
"com_ui_fork_info_1": "Použijte toto nastavení pro rozdělení zpráv podle požadovaného chování.",
"com_ui_fork_info_2": "\"Rozdělení\" znamená vytvoření nové konverzace začínající/končící u určitých zpráv v aktuální konverzaci, čímž se vytvoří kopie dle vybraných možností.",
"com_ui_fork_info_3": "\"Cílová zpráva\" označuje buď zprávu, ze které bylo okno otevřeno, nebo pokud zaškrtnete \"{{0}}\", nejnovější zprávu v konverzaci.",
"com_ui_fork_info_branches": "Tato možnost rozděluje viditelné zprávy spolu se souvisejícími větvemi; jinými slovy, přímou cestu k cílové zprávě včetně větví na této cestě.",
"com_ui_fork_info_remember": "Zaškrtnutím si zapamatujete vybrané možnosti pro budoucí použití, což urychlí rozdělování konverzací.",
"com_ui_fork_info_start": "Pokud zaškrtnuto, rozdělení začne od této zprávy až po nejnovější zprávu v konverzaci dle zvoleného chování.",
"com_ui_fork_info_target": "Tato možnost rozděluje všechny zprávy vedoucí k cílové zprávě, včetně sousedních; jinými slovy, zahrnuje všechny větve zpráv.",
"com_ui_fork_info_visible": "Tato možnost rozděluje pouze viditelné zprávy; jinými slovy, přímou cestu k cílové zprávě bez větví.",
"com_ui_fork_processing": "Rozděluji konverzaci...",
"com_ui_fork_remember": "Zapamatovat",
"com_ui_fork_remember_checked": "Vaše volba bude zapamatována. Můžete ji kdykoli změnit v nastavení.",
"com_ui_fork_split_target": "Začít rozdělení zde",
"com_ui_fork_split_target_setting": "Výchozí rozdělení od cílové zprávy",
"com_ui_fork_success": "Konverzace úspěšně rozdělena",
"com_ui_fork_visible": "Pouze viditelné zprávy",
"com_ui_generate_backup": "Generovat záložní kódy",
"com_ui_generate_qrcode": "Generovat QR kód",
"com_ui_generating": "Generuji...",
"com_ui_global_group": "něco sem musí přijít. bylo prázdné",
"com_ui_go_back": "Zpět",
"com_ui_go_to_conversation": "Přejít na konverzaci",
"com_ui_happy_birthday": "Mám 1. narozeniny!",
"com_ui_hide_qr": "Skrýt QR kód",
"com_ui_host": "Hostitel",
"com_ui_idea": "Nápady",
"com_ui_image_gen": "Generování obrázků",
"com_ui_import": "Importovat",
"com_ui_import_conversation_error": "Při importu konverzací došlo k chybě",
"com_ui_import_conversation_file_type_error": "Nepodporovaný typ souboru pro import",
"com_ui_import_conversation_info": "Importovat konverzace ze souboru JSON",
"com_ui_import_conversation_success": "Konverzace úspěšně importovány",
"com_ui_include_shadcnui": "Zahrnout instrukce pro shadcn/ui",
"com_ui_input": "Vstup",
"com_ui_instructions": "Instrukce",
"com_ui_latest_footer": "AICon se může plést. Vždy kontrolujte důležité informace.",
"com_ui_latest_production_version": "Nejnovější produkční verze",
"com_ui_latest_version": "Nejnovější verze",
"com_ui_librechat_code_api_key": "Získejte svůj API klíč pro LibreChat Code Interpreter",
"com_ui_librechat_code_api_subtitle": "Bezpečné. Vícejazyčné. Vstupní/Výstupní soubory.",
"com_ui_librechat_code_api_title": "Spustit AI kód",
"com_ui_llm_menu": "Nabídka LLM",
"com_ui_llms_available": "Dostupné LLM modely",
"com_ui_loading": "Načítání...",
"com_ui_locked": "Zamčeno",
"com_ui_logo": "Logo {{0}}",
"com_ui_manage": "Spravovat",
"com_ui_max_tags": "Maximální povolený počet je {{0}}, používám nejnovější hodnoty.",
"com_ui_mention": "Zmiňte koncový bod, asistenta nebo předvolbu pro rychlé přepnutí",
"com_ui_min_tags": "Nelze odebrat další hodnoty, minimální počet je {{0}}.",
"com_ui_misc": "Různé",
"com_ui_model": "Model",
"com_ui_model_parameters": "Parametry modelu",
"com_ui_more_info": "Více informací",
"com_ui_my_prompts": "Moje výzvy",
"com_ui_name": "Název",
"com_ui_new": "Nový",
"com_ui_new_chat": "Nový chat",
"com_ui_next": "Další",
"com_ui_no": "Ne",
"com_ui_no_backup_codes": "Nejsou k dispozici žádné záložní kódy. Vygenerujte nové.",
"com_ui_no_bookmarks": "Zdá se, že zatím nemáte žádné záložky. Klikněte na chat a přidejte novou.",
"com_ui_no_category": "Žádná kategorie",
"com_ui_no_changes": "Žádné změny k aktualizaci",
"com_ui_no_data": "něco sem musí přijít. bylo prázdné",
"com_ui_no_terms_content": "Žádný obsah podmínek a pravidel k zobrazení",
"com_ui_no_valid_items": "něco sem musí přijít. bylo prázdné",
"com_ui_none": "Žádné",
"com_ui_none_selected": "Nic nevybráno",
"com_ui_not_used": "Nepoužito",
"com_ui_nothing_found": "Nic nenalezeno",
"com_ui_oauth": "OAuth",
"com_ui_of": "z",
"com_ui_off": "Vypnuto",
"com_ui_on": "Zapnuto",
"com_ui_openai": "OpenAI",
"com_ui_page": "Stránka",
"com_ui_prev": "Předchozí",
"com_ui_preview": "Náhled",
"com_ui_privacy_policy": "Zásady ochrany osobních údajů",
"com_ui_privacy_policy_url": "URL zásad ochrany osobních údajů",
"com_ui_prompt": "Výzva",
"com_ui_prompt_already_shared_to_all": "Tato výzva je již sdílena se všemi uživateli",
"com_ui_prompt_name": "Název výzvy",
"com_ui_prompt_name_required": "Název výzvy je povinný",
"com_ui_prompt_preview_not_shared": "Autor neumožnil spolupráci na této výzvě.",
"com_ui_prompt_text": "Text",
"com_ui_prompt_text_required": "Text je povinný",
"com_ui_prompt_update_error": "Při aktualizaci výzvy došlo k chybě",
"com_ui_prompts": "Výzvy",
"com_ui_prompts_allow_create": "Povolit vytváření výzev",
"com_ui_prompts_allow_share_global": "Povolit sdílení výzev všem uživatelům",
"com_ui_prompts_allow_use": "Povolit používání výzev",
"com_ui_provider": "Poskytovatel",
"com_ui_read_aloud": "Přečíst nahlas",
"com_ui_refresh_link": "Obnovit odkaz",
"com_ui_regenerate": "Znovu vygenerovat",
"com_ui_regenerate_backup": "Znovu vygenerovat záložní kódy",
"com_ui_regenerating": "Generuji znovu...",
"com_ui_region": "Oblast",
"com_ui_rename": "Přejmenovat",
"com_ui_rename_prompt": "Přejmenovat výzvu",
"com_ui_requires_auth": "Vyžaduje ověření",
"com_ui_reset_var": "Obnovit {{0}}",
"com_ui_result": "Výsledek",
"com_ui_revoke": "Odvolat",
"com_ui_revoke_info": "Odvolat všechna uživatelem poskytnutá pověření",
"com_ui_revoke_key_confirm": "Opravdu chcete odvolat tento klíč?",
"com_ui_revoke_key_endpoint": "Odvolat klíč pro {{0}}",
"com_ui_revoke_keys": "Odvolat klíče",
"com_ui_revoke_keys_confirm": "Opravdu chcete odvolat všechny klíče?",
"com_ui_role_select": "Role",
"com_ui_roleplay": "Roleplay",
"com_ui_run_code": "Spustit kód",
"com_ui_run_code_error": "Při spouštění kódu došlo k chybě",
"com_ui_save": "Uložit",
"com_ui_save_submit": "Uložit a odeslat",
"com_ui_saved": "Uloženo!",
"com_ui_schema": "Schéma",
"com_ui_scope": "Rozsah",
"com_ui_search": "Hledat",
"com_ui_secret_key": "Tajný klíč",
"com_ui_select": "Vybrat",
"com_ui_select_file": "Vyberte soubor",
"com_ui_select_model": "Vyberte model",
"com_ui_select_provider": "Vyberte poskytovatele",
"com_ui_select_provider_first": "Nejprve vyberte poskytovatele",
"com_ui_select_region": "Vyberte oblast",
"com_ui_select_search_model": "Hledat model podle názvu",
"com_ui_select_search_plugin": "Hledat plugin podle názvu",
"com_ui_select_search_provider": "Hledat poskytovatele podle názvu",
"com_ui_select_search_region": "Hledat oblast podle názvu",
"com_ui_share": "Sdílet",
"com_ui_share_create_message": "Vaše jméno a zprávy, které přidáte po sdílení, zůstanou soukromé.",
"com_ui_share_delete_error": "Při mazání sdíleného odkazu došlo k chybě",
"com_ui_share_error": "Při sdílení odkazu na chat došlo k chybě",
"com_ui_share_form_description": "něco sem musí přijít. bylo prázdné",
"com_ui_share_link_to_chat": "Sdílet odkaz na chat",
"com_ui_share_to_all_users": "Sdílet se všemi uživateli",
"com_ui_share_update_message": "Vaše jméno, vlastní instrukce a zprávy přidané po sdílení zůstanou soukromé.",
"com_ui_share_var": "Sdílet {{0}}",
"com_ui_shared_link_bulk_delete_success": "Sdílené odkazy byly úspěšně smazány",
"com_ui_shared_link_delete_success": "Sdílený odkaz byl úspěšně smazán",
"com_ui_shared_link_not_found": "Sdílený odkaz nebyl nalezen",
"com_ui_shared_prompts": "Sdílené výzvy",
"com_ui_shop": "Nakupování",
"com_ui_show": "Zobrazit",
"com_ui_show_all": "Zobrazit vše",
"com_ui_show_qr": "Zobrazit QR kód",
"com_ui_sign_in_to_domain": "Přihlásit se do {{0}}",
"com_ui_simple": "Jednoduché",
"com_ui_size": "Velikost",
"com_ui_special_variables": "Speciální proměnné:",
"com_ui_special_variables_info": "Použijte `{{current_date}}` pro aktuální datum a `{{current_user}}` pro vaše uživatelské jméno.",
"com_ui_speech_while_submitting": "Nelze odeslat hlasový vstup, zatímco se generuje odpověď",
"com_ui_stop": "Zastavit",
"com_ui_storage": "Úložiště",
"com_ui_submit": "Odeslat",
"com_ui_teach_or_explain": "Učení",
"com_ui_temporary_chat": "Dočasný chat",
"com_ui_terms_and_conditions": "Obchodní podmínky",
"com_ui_terms_of_service": "Podmínky služby",
"com_ui_thinking": "Přemýšlení...",
"com_ui_thoughts": "Myšlenky",
"com_ui_token_exchange_method": "Metoda výměny tokenů",
"com_ui_token_url": "URL tokenu",
"com_ui_tools": "Nástroje",
"com_ui_travel": "Cestování",
"com_ui_unarchive": "Obnovit archiv",
"com_ui_unarchive_error": "Nepodařilo se obnovit archivovanou konverzaci",
"com_ui_unknown": "Neznámé",
"com_ui_update": "Aktualizovat",
"com_ui_upload": "Nahrát",
"com_ui_upload_code_files": "Nahrát soubory pro interpret kódu",
"com_ui_upload_delay": "Nahrávání \"{{0}}\" trvá déle než obvykle. Počkejte, než bude soubor indexován.",
"com_ui_upload_error": "Při nahrávání souboru došlo k chybě",
"com_ui_upload_file_search": "Nahrát pro vyhledávání v souborech",
"com_ui_upload_files": "Nahrát soubory",
"com_ui_upload_image": "Nahrát obrázek",
"com_ui_upload_image_input": "Nahrát obrázek",
"com_ui_upload_invalid": "Neplatný soubor pro nahrání. Musí to být obrázek nepřesahující limit.",
"com_ui_upload_invalid_var": "Neplatný soubor pro nahrání. Musí to být obrázek nepřesahující {{0}} MB",
"com_ui_upload_success": "Soubor byl úspěšně nahrán",
"com_ui_upload_type": "Vyberte typ nahrávání",
"com_ui_use_2fa_code": "Použít kód 2FA",
"com_ui_use_backup_code": "Použít záložní kód",
"com_ui_use_micrphone": "Použít mikrofon",
"com_ui_use_prompt": "Použít výzvu",
"com_ui_used": "Použito",
"com_ui_variables": "Proměnné",
"com_ui_variables_info": "Použijte dvojité složené závorky k vytvoření proměnných, např. `{{příklad proměnné}}`, které lze vyplnit při použití výzvy.",
"com_ui_verify": "Ověřit",
"com_ui_version_var": "Verze {{0}}",
"com_ui_versions": "Verze",
"com_ui_view_source": "Zobrazit zdrojový chat",
"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."
}

View File

@@ -191,7 +191,7 @@
"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",
"com_endpoint_message": "Nachricht an",
"com_endpoint_message_new": "Nachricht {{0}}",
"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",
@@ -353,6 +353,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -364,6 +365,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",
@@ -597,6 +599,7 @@
"com_ui_download_backup": "Backup-Codes herunterladen",
"com_ui_download_backup_tooltip": "Bevor Sie fortfahren, laden Sie bitte Ihre Backup-Codes herunter. Sie benötigen sie, um den Zugang wiederherzustellen, falls Sie Ihr Authentifizierungsgerät verlieren.",
"com_ui_download_error": "Fehler beim Herunterladen der Datei. Die Datei wurde möglicherweise gelöscht.",
"com_ui_drag_drop": "Ziehen und Ablegen",
"com_ui_dropdown_variables": "Dropdown-Variablen:",
"com_ui_dropdown_variables_info": "Erstellen Sie benutzerdefinierte Dropdown-Menüs für Ihre Eingabeaufforderungen: `{{variable_name:option1|option2|option3}}`",
"com_ui_duplicate": "Duplizieren",
@@ -604,6 +607,7 @@
"com_ui_duplication_processing": "Konversation wird dupliziert...",
"com_ui_duplication_success": "Unterhaltung erfolgreich dupliziert",
"com_ui_edit": "Bearbeiten",
"com_ui_empty_category": "-",
"com_ui_endpoint": "Endpunkt",
"com_ui_endpoint_menu": "LLM-Endpunkt-Menü",
"com_ui_endpoints_available": "Verfügbare Endpunkte",

View File

@@ -101,6 +101,7 @@
"com_auth_google_login": "Continue with Google",
"com_auth_here": "HERE",
"com_auth_login": "Login",
"com_ui_redirecting_to_provider": "Redirecting to {{0}}, please wait...",
"com_auth_login_with_new_password": "You may now login with your new password.",
"com_auth_name_max_length": "Name must be less than 80 characters",
"com_auth_name_min_length": "Name must be at least 3 characters",
@@ -369,6 +370,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",
@@ -834,4 +836,4 @@
"com_ui_zoom": "Zoom",
"com_user_message": "You",
"com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint."
}
}

View File

@@ -345,6 +345,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -356,6 +357,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -353,6 +353,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -364,6 +365,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -274,6 +274,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -285,6 +286,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -341,6 +341,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -352,6 +353,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -356,6 +356,7 @@
"com_nav_lang_estonian": "אסטונית (Eesti keel)",
"com_nav_lang_finnish": "פינית (Suomi)",
"com_nav_lang_french": "צרפתית (Français)",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "גרמנית (Deutsch)",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "אינדונזית (Indonesia)",
@@ -367,6 +368,7 @@
"com_nav_lang_russian": "רוסית (Русский)",
"com_nav_lang_spanish": "ספרדית (Español)",
"com_nav_lang_swedish": "שוודית (Svenska)",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "סינית מסורתית (繁體中文)",
"com_nav_lang_turkish": "טורקית (Türkçe)",
"com_nav_lang_vietnamese": "וייטנאמית (Tiếng Việt)",

View File

@@ -18,8 +18,9 @@ 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 translationVi from './vi/translation.json';
import translationTh from './th/translation.json';
import translationTr from './tr/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';
@@ -47,8 +48,9 @@ export const resources = {
ka: { translation: translationKa },
sv: { translation: translationSv },
ko: { translation: translationKo },
vi: { translation: translationVi },
th: { translation: translationTh },
tr: { translation: translationTr },
vi: { translation: translationVi },
nl: { translation: translationNl },
id: { translation: translationId },
he: { translation: translationHe },
@@ -62,7 +64,7 @@ i18n
fallbackLng: {
'zh-TW': ['zh-Hant', 'en'],
'zh-HK': ['zh-Hant', 'en'],
'zh': ['zh-Hans', 'en'],
zh: ['zh-Hans', 'en'],
default: ['en'],
},
fallbackNS: 'translation',
@@ -73,4 +75,4 @@ i18n
interpolation: { escapeValue: false },
});
export default i18n;
export default i18n;

View File

@@ -170,6 +170,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -181,6 +182,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -341,6 +341,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -352,6 +353,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -337,6 +337,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -348,6 +349,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -1 +1,28 @@
{}
{
"com_nav_lang_arabic": "العربية",
"com_nav_lang_auto": "ავტომატური ამოცნობა",
"com_nav_lang_brazilian_portuguese": "Português Brasileiro",
"com_nav_lang_chinese": "中文",
"com_nav_lang_dutch": "Nederlands",
"com_nav_lang_english": "English",
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
"com_nav_lang_italian": "Italiano",
"com_nav_lang_japanese": "日本語",
"com_nav_lang_korean": "한국어",
"com_nav_lang_polish": "Polski",
"com_nav_lang_portuguese": "Português",
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",
"com_nav_language": "ენა"
}

View File

@@ -337,6 +337,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -348,6 +349,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -154,6 +154,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -165,9 +166,11 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",
"com_nav_language": "Taal",
"com_nav_log_out": "Uitloggen",
"com_nav_not_supported": "Niet ondersteund",
"com_nav_open_sidebar": "Zijbalk openen",

View File

@@ -309,6 +309,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "ქართული",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -320,6 +321,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "ไทย",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",

View File

@@ -11,17 +11,25 @@
"com_agents_create_error": "Houve um erro ao criar seu agente.",
"com_agents_description_placeholder": "Opcional: Descreva seu Agente aqui",
"com_agents_enable_file_search": "Habilitar pesquisa de arquivos",
"com_agents_file_search_disabled": "O agente deve ser criado antes de carregar arquivos para Pesquisa de Arquivos.",
"com_agents_file_search_info": "Quando ativado, o agente será informado dos nomes exatos dos arquivos listados abaixo, permitindo que ele recupere o contexto relevante desses arquivos.",
"com_agents_instructions_placeholder": "As instruções do sistema que o agente usa",
"com_agents_missing_provider_model": "Selecione um provedor e um modelo antes de criar um agente.\n",
"com_agents_name_placeholder": "Opcional: O nome do agente",
"com_agents_no_access": "Você não tem acesso para editar este agente.",
"com_agents_not_available": "Agente não disponível",
"com_agents_search_name": "Pesquisar agentes por nome",
"com_agents_update_error": "Houve um erro ao atualizar seu agente.",
"com_assistants_action_attempt": "O Assistente quer falar com {{0}}",
"com_assistants_actions": "Ações",
"com_assistants_actions_disabled": "Você precisa criar um assistente antes de adicionar ações.",
"com_assistants_actions_info": "Permita que seu Assistente recupere informações ou execute ações via API's",
"com_assistants_add_actions": "Adicionar Ações",
"com_assistants_add_tools": "Adicionar Ferramentas",
"com_assistants_allow_sites_you_trust": "Permitir apenas sites em que confia.",
"com_assistants_append_date": "Anexar Data e Hora Atual",
"com_assistants_append_date_tooltip": "Quando ativado, a data e hora atual do cliente serão anexadas às instruções do sistema do assistente.",
"com_assistants_attempt_info": "O Assistente deseja enviar o seguinte:",
"com_assistants_available_actions": "Ações Disponíveis",
"com_assistants_capabilities": "Capacidades",
"com_assistants_code_interpreter": "Interpretador de Código",
@@ -56,6 +64,7 @@
"com_assistants_update_error": "Houve um erro ao atualizar seu assistente.",
"com_assistants_update_success": "Atualizado com sucesso",
"com_auth_already_have_account": "Já tem uma conta?",
"com_auth_apple_login": "Iniciar sessão com a Apple",
"com_auth_back_to_login": "Voltar para Login",
"com_auth_click": "Clique",
"com_auth_click_here": "Clique aqui",
@@ -78,6 +87,7 @@
"com_auth_email_verification_redirecting": "Redirecionando em {{0}} segundos...",
"com_auth_email_verification_resend_prompt": "Não recebeu o e-mail?",
"com_auth_email_verification_success": "E-mail verificado com sucesso",
"com_auth_email_verifying_ellipsis": "Verificando...",
"com_auth_error_create": "Houve um erro ao tentar registrar sua conta. Por favor, tente novamente.",
"com_auth_error_invalid_reset_token": "Este token de redefinição de senha não é mais válido.",
"com_auth_error_login": "Não foi possível fazer login com as informações fornecidas. Por favor, verifique suas credenciais e tente novamente.",
@@ -114,16 +124,25 @@
"com_auth_submit_registration": "Enviar registro",
"com_auth_to_reset_your_password": "para redefinir sua senha.",
"com_auth_to_try_again": "para tentar novamente.",
"com_auth_two_factor": "Consulte a sua aplicação de senha de uso único preferida para obter um código",
"com_auth_username": "Nome de usuário (opcional)",
"com_auth_username_max_length": "O nome de usuário deve ter menos de 20 caracteres",
"com_auth_username_min_length": "O nome de usuário deve ter pelo menos 2 caracteres",
"com_auth_verify_your_identity": "Verificar a sua identidade",
"com_auth_welcome_back": "Bem-vindo de volta",
"com_click_to_download": "(clique aqui para download)",
"com_download_expired": "(download expirado)",
"com_download_expires": "(clique aqui para download - expira {{0}})",
"com_endpoint": "Endpoint",
"com_endpoint_agent": "Agente",
"com_endpoint_agent_model": "Modelo de Agente (Recomendado: GPT-3.5)",
"com_endpoint_agent_placeholder": "Selecione um agente",
"com_endpoint_ai": "AI",
"com_endpoint_anthropic_maxoutputtokens": "Número máximo de tokens que podem ser gerados na resposta. Especifique um valor mais baixo para respostas mais curtas e um valor mais alto para respostas mais longas. Nota: os modelos podem parar antes de atingir esse máximo.",
"com_endpoint_anthropic_prompt_cache": "O cache de prompt permite reutilizar um grande contexto ou instruções em chamadas de API, reduzindo custos e latência",
"com_endpoint_anthropic_temp": "Varia de 0 a 1. Use temperatura mais próxima de 0 para tarefas analíticas / de múltipla escolha, e mais próxima de 1 para tarefas criativas e generativas. Recomendamos alterar isso ou Top P, mas não ambos.",
"com_endpoint_anthropic_thinking": "Permite o raciocínio interno para os modelos Claude suportados (3.7 Sonnet). Nota: requer que o \"Orçamento de raciocínio\" esteja definido e seja inferior ao \"Máximo de tokens de saída\"",
"com_endpoint_anthropic_thinking_budget": "Determina o número máximo de tokens que o Claude pode utilizar para o seu processo de raciocínio interno. Orçamentos maiores podem melhorar a qualidade da resposta, permitindo uma análise mais completa para problemas complexos, embora o Claude possa não usar todo o orçamento alocado, especialmente em intervalos acima de 32K. Essa configuração deve ser menor que \"Máximo de tokens de saída\".",
"com_endpoint_anthropic_topk": "Top-k altera como o modelo seleciona tokens para saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).",
"com_endpoint_anthropic_topp": "Top-p altera como o modelo seleciona tokens para saída. Os tokens são selecionados dos mais prováveis (veja o parâmetro topK) até os menos prováveis até que a soma de suas probabilidades atinja o valor top-p.",
"com_endpoint_assistant": "Assistente",
@@ -174,6 +193,7 @@
"com_endpoint_instructions_assistants_placeholder": "Substitui as instruções do assistente. Isso é útil para modificar o comportamento em uma base por execução.",
"com_endpoint_max_output_tokens": "Máximo de Tokens de Saída",
"com_endpoint_message": "Mensagem",
"com_endpoint_message_new": "Mensagem {{0}}",
"com_endpoint_message_not_appendable": "Edite sua mensagem ou Regenerar.",
"com_endpoint_my_preset": "Meu Preset",
"com_endpoint_no_presets": "Ainda não há presets, use o botão de configurações para criar um",
@@ -185,6 +205,7 @@
"com_endpoint_openai_max_tokens": "Campo opcional `max_tokens`, representando o número máximo de tokens que podem ser gerados na conclusão do chat. O comprimento total dos tokens de entrada e dos tokens gerados é limitado pelo comprimento do contexto dos modelos. Você pode experimentar erros se esse número exceder o máximo de tokens de contexto.",
"com_endpoint_openai_pres": "Número entre -2.0 e 2.0. Valores positivos penalizam novos tokens com base em sua presença no texto até agora, aumentando a probabilidade do modelo de falar sobre novos tópicos.",
"com_endpoint_openai_prompt_prefix_placeholder": "Defina instruções personalizadas para incluir na Mensagem do Sistema. Padrão: nenhuma",
"com_endpoint_openai_reasoning_effort": "apenas modelos o1: limita o esforço de raciocínio para modelos de raciocínio. Reduzir o esforço de raciocínio pode resultar em respostas mais rápidas e em menos tokens utilizados no raciocínio de uma resposta.",
"com_endpoint_openai_resend": "Reenviar todas as imagens anexadas anteriormente. Nota: isso pode aumentar significativamente o custo de tokens e você pode experimentar erros com muitos anexos de imagem.",
"com_endpoint_openai_resend_files": "Reenviar todos os arquivos anexados anteriormente. Nota: isso aumentará o custo de tokens e você pode experimentar erros com muitos anexos.",
"com_endpoint_openai_stop": "Até 4 sequências onde a API parará de gerar mais tokens.",
@@ -198,6 +219,7 @@
"com_endpoint_plug_use_functions": "Usar Funções",
"com_endpoint_presence_penalty": "Penalidade de Presença",
"com_endpoint_preset": "preset",
"com_endpoint_preset_custom_name_placeholder": "algo precisa ir aqui. esta vazio",
"com_endpoint_preset_default": "é agora o preset padrão.",
"com_endpoint_preset_default_item": "Padrão:",
"com_endpoint_preset_default_none": "Nenhum preset padrão ativo.",
@@ -218,16 +240,27 @@
"com_endpoint_prompt_prefix_assistants": "Instruções Adicionais",
"com_endpoint_prompt_prefix_assistants_placeholder": "Defina instruções ou contexto adicionais além das instruções principais do Assistente. Ignorado se vazio.",
"com_endpoint_prompt_prefix_placeholder": "Defina instruções ou contexto personalizados. Ignorado se vazio.",
"com_endpoint_reasoning_effort": "Esforço de raciocínio",
"com_endpoint_save_as_preset": "Salvar Como Preset",
"com_endpoint_search": "Procurar endpoint por nome",
"com_endpoint_set_custom_name": "Defina um nome personalizado, caso você possa encontrar este preset",
"com_endpoint_skip_hover": "Habilitar pular a etapa de conclusão, que revisa a resposta final e os passos gerados",
"com_endpoint_stop": "Sequências de Parada",
"com_endpoint_stop_placeholder": "Separe os valores pressionando `Enter`",
"com_endpoint_temperature": "Temperatura",
"com_endpoint_thinking": "Pensamento",
"com_endpoint_thinking_budget": "Pensar no orçamento",
"com_endpoint_top_k": "Top K",
"com_endpoint_top_p": "Top P",
"com_endpoint_use_active_assistant": "Usar Assistente Ativo",
"com_error_expired_user_key": "A chave fornecida para {{0}} expirou em {{1}}. Por favor, forneça uma nova chave e tente novamente.",
"com_error_files_dupe": "Foi detectado um arquivo duplicado.",
"com_error_files_empty": "Pensamento",
"com_error_files_process": "Ocorreu um erro ao processar o arquivo.",
"com_error_files_unsupported_capability": "Não existem capacidades ativadas que suportem este tipo de arquivo.",
"com_error_files_upload": "Ocorreu um erro ao carregar o arquivo.",
"com_error_files_upload_canceled": "O pedido de carregamento de arquivos foi cancelado. Nota: o carregamento de arquivo pode ainda estar a ser processado e terá de ser eliminado manualmente.",
"com_error_files_validation": "Ocorreu um erro durante a validação do arquivo.",
"com_error_input_length": "A contagem de tokens da última mensagem é muito longa, excedendo o limite de tokens ({{0}} respectivamente). Por favor, encurte sua mensagem, ajuste o tamanho máximo do contexto nos parâmetros da conversa ou divida a conversa para continuar.",
"com_error_invalid_user_key": "Chave fornecida inválida. Por favor, forneça uma chave válida e tente novamente.",
"com_error_moderation": "Parece que o conteúdo enviado foi sinalizado pelo nosso sistema de moderação por não estar alinhado com nossas diretrizes da comunidade. Não podemos prosseguir com este tópico específico. Se você tiver outras perguntas ou tópicos que gostaria de explorar, edite sua mensagem ou crie uma nova conversa.",
@@ -236,7 +269,10 @@
"com_files_filter": "Filtrar arquivos...",
"com_files_no_results": "Nenhum resultado.",
"com_files_number_selected": "{{0}} de {{1}} arquivo(s) selecionado(s)",
"com_files_table": "algo precisa ir aqui. esta vazio",
"com_generated_files": "Arquivos gerados:",
"com_hide_examples": "Ocultar Exemplos",
"com_nav_2fa": "Autenticação de dois fatores (2FA)",
"com_nav_account_settings": "Configurações da Conta",
"com_nav_always_make_prod": "Sempre tornar novas versões produção",
"com_nav_archive_created_at": "Data de Arquivamento",
@@ -257,8 +293,11 @@
"com_nav_browser": "Navegador",
"com_nav_buffer_append_error": "Problema com o streaming de áudio. A reprodução pode ser interrompida.",
"com_nav_change_picture": "Mudar foto",
"com_nav_chat_commands": "Comandos do chat",
"com_nav_chat_commands_info": "Estes comandos são ativados digitando caracteres específicos no início da sua mensagem. Cada comando é acionado pelo seu prefixo designado. Pode desativá-los se utilizar frequentemente estes caracteres para iniciar mensagens.",
"com_nav_chat_direction": "Direção do chat",
"com_nav_clear_all_chats": "Limpar todos os chats",
"com_nav_clear_cache_confirm_message": "Tem a certeza de que quer limpar a cache?",
"com_nav_clear_conversation": "Limpar conversas",
"com_nav_clear_conversation_confirm_message": "Tem certeza de que deseja limpar todas as conversas? Isso é irreversível.",
"com_nav_close_sidebar": "Fechar barra lateral",
@@ -299,6 +338,7 @@
"com_nav_help_faq": "Ajuda & FAQ",
"com_nav_hide_panel": "Ocultar painel mais à direita",
"com_nav_info_code_artifacts": "Habilita a exibição de artefatos de código experimental ao lado do chat",
"com_nav_info_code_artifacts_agent": "Ativa a utilização de artefatos de código para este agente. Por predefinição, são adicionadas instruções adicionais específicas para a utilização de artefatos, a menos que o \"Modo de aviso personalizado\" esteja ativado.",
"com_nav_info_custom_prompt_mode": "Quando habilitado, o prompt padrão do sistema de artefatos não será incluído. Todas as instruções de geração de artefatos devem ser fornecidas manualmente neste modo.",
"com_nav_info_enter_to_send": "Quando habilitado, pressionar `ENTER` enviará sua mensagem. Quando desabilitado, pressionar Enter adicionará uma nova linha, e você precisará pressionar `CTRL + ENTER` / `⌘ + ENTER` para enviar sua mensagem.",
"com_nav_info_fork_change_default": "`Apenas mensagens visíveis` inclui apenas o caminho direto para a mensagem selecionada. `Incluir ramos relacionados` adiciona ramos ao longo do caminho. `Incluir tudo de/para aqui` inclui todas as mensagens e ramos conectados.",
@@ -306,6 +346,7 @@
"com_nav_info_include_shadcnui": "Quando habilitado, as instruções para usar componentes shadcn/ui serão incluídas. shadcn/ui é uma coleção de componentes reutilizáveis construídos usando Radix UI e Tailwind CSS. Nota: estas são instruções longas, você deve habilitar apenas se for importante informar o LLM sobre as importações e componentes corretos. Para mais informações sobre esses componentes, visite: https://ui.shadcn.com/",
"com_nav_info_latex_parsing": "Quando habilitado, o código LaTeX nas mensagens será renderizado como equações matemáticas. Desabilitar isso pode melhorar o desempenho se você não precisar de renderização LaTeX.",
"com_nav_info_save_draft": "Quando habilitado, o texto e os anexos que você inserir no formulário de chat serão salvos automaticamente localmente como rascunhos. Esses rascunhos estarão disponíveis mesmo se você recarregar a página ou mudar para uma conversa diferente. Os rascunhos são armazenados localmente no seu dispositivo e são excluídos uma vez que a mensagem é enviada.",
"com_nav_info_show_thinking": "Quando ativado, o chat apresentará os menus pendentes de raciocínio abertos por predefinição, permitindo-lhe ver o raciocínio da IA em tempo real. Quando desativado, os menus suspensos de raciocínio permanecerão fechados por predefinição para uma interface mais limpa e simplificada",
"com_nav_info_user_name_display": "Quando habilitado, o nome de usuário do remetente será mostrado acima de cada mensagem que você enviar. Quando desabilitado, você verá apenas \"Você\" acima de suas mensagens.",
"com_nav_lang_arabic": "العربية",
"com_nav_lang_auto": "Detecção automática",
@@ -316,6 +357,7 @@
"com_nav_lang_estonian": "Eesti keel",
"com_nav_lang_finnish": "Suomi",
"com_nav_lang_french": "Français ",
"com_nav_lang_georgian": "Georgiano",
"com_nav_lang_german": "Deutsch",
"com_nav_lang_hebrew": "עברית",
"com_nav_lang_indonesia": "Indonesia",
@@ -327,6 +369,7 @@
"com_nav_lang_russian": "Русский",
"com_nav_lang_spanish": "Español",
"com_nav_lang_swedish": "Svenska",
"com_nav_lang_thai": "Tailandês",
"com_nav_lang_traditional_chinese": "繁體中文",
"com_nav_lang_turkish": "Türkçe",
"com_nav_lang_vietnamese": "Tiếng Việt",
@@ -334,8 +377,10 @@
"com_nav_latex_parsing": "Análise de LaTeX em mensagens (pode afetar o desempenho)",
"com_nav_log_out": "Sair",
"com_nav_long_audio_warning": "Textos mais longos levarão mais tempo para processar.",
"com_nav_maximize_chat_space": "Maximizar o espaço de conversa",
"com_nav_modular_chat": "Habilitar troca de Endpoints no meio da conversa",
"com_nav_my_files": "Meus Arquivos",
"com_nav_no_search_results": "Não foram encontrados resultados de pesquisa",
"com_nav_not_supported": "Não Suportado",
"com_nav_open_sidebar": "Abrir barra lateral",
"com_nav_playback_rate": "Taxa de Reprodução de Áudio",
@@ -348,6 +393,7 @@
"com_nav_plus_command_description": "Alternar comando \"+\" para adicionar uma configuração de resposta múltipla",
"com_nav_profile_picture": "Foto de Perfil",
"com_nav_save_drafts": "Salvar rascunhos localmente",
"com_nav_scroll_button": "Ir para o botão final",
"com_nav_search_placeholder": "Buscar mensagens",
"com_nav_send_message": "Enviar mensagem",
"com_nav_setting_account": "Conta",
@@ -359,26 +405,31 @@
"com_nav_settings": "Configurações",
"com_nav_shared_links": "Links compartilhados",
"com_nav_show_code": "Sempre mostrar código ao usar o interpretador de código",
"com_nav_show_thinking": "Menus suspensos de pensamento aberto por padrão",
"com_nav_slash_command": "Comando /",
"com_nav_slash_command_description": "Alternar comando \"/\" para selecionar um prompt via teclado",
"com_nav_source_buffer_error": "Erro ao configurar a reprodução de áudio. Por favor, atualize a página.",
"com_nav_speech_cancel_error": "Não foi possível parar a reprodução de áudio. Você pode precisar atualizar a página.",
"com_nav_speech_to_text": "Fala para Texto",
"com_nav_stop_generating": "Pare de gerar",
"com_nav_text_to_speech": "Texto para Fala",
"com_nav_theme": "Tema",
"com_nav_theme_dark": "Escuro",
"com_nav_theme_light": "Claro",
"com_nav_theme_system": "Sistema",
"com_nav_tool_dialog": "Ferramentas do Assistente",
"com_nav_tool_dialog_agents": "Ferramentas do agente",
"com_nav_tool_dialog_description": "O assistente deve ser salvo para persistir as seleções de ferramentas.",
"com_nav_tool_remove": "Remover",
"com_nav_tool_search": "Buscar ferramentas",
"com_nav_tts_init_error": "Falha ao inicializar texto-para-fala: {{0}}",
"com_nav_tts_unsupported_error": "Texto-para-fala para o mecanismo selecionado não é suportado neste navegador.",
"com_nav_user": "USUÁRIO",
"com_nav_user_msg_markdown": "Renderizar mensagens do usuário como favoritos",
"com_nav_user_name_display": "Exibir nome de usuário nas mensagens",
"com_nav_voice_select": "Voz",
"com_nav_voices_fetch_error": "Não foi possível recuperar as opções de voz. Por favor, verifique sua conexão com a internet.",
"com_nav_welcome_agent": "Selecione um agente",
"com_nav_welcome_assistant": "Por favor, Selecione um Assistente",
"com_nav_welcome_message": "Como posso ajudar você hoje?",
"com_show_agent_settings": "Mostrar Configurações do Agente",
@@ -391,24 +442,48 @@
"com_sidepanel_hide_panel": "Ocultar Painel",
"com_sidepanel_manage_files": "Gerenciar Arquivos",
"com_sidepanel_parameters": "Parâmetros",
"com_sidepanel_select_agent": "Selecione um agente",
"com_sidepanel_select_assistant": "Selecionar um Assistente",
"com_ui_2fa_account_security": "A autenticação de dois fatores acrescenta uma camada extra de segurança à sua conta",
"com_ui_2fa_disable": "Desabilitar 2FA",
"com_ui_2fa_disable_error": "Ocorreu um erro ao desativar a autenticação de dois fatores",
"com_ui_2fa_disabled": "A 2FA foi desativada",
"com_ui_2fa_enable": "Ativar 2FA",
"com_ui_2fa_enabled": "A 2FA foi ativada",
"com_ui_2fa_generate_error": "Ocorreu um erro ao gerar as configurações de autenticação de dois fatores",
"com_ui_2fa_invalid": "Código de autenticação de dois fatores inválido",
"com_ui_2fa_setup": "Configurar 2FA",
"com_ui_2fa_verified": "Autenticação de dois fatores verificada com sucesso",
"com_ui_accept": "Eu aceito",
"com_ui_add": "Adicionar",
"com_ui_add_model_preset": "Adicionar um modelo ou predefinição para uma resposta adicional",
"com_ui_add_multi_conversation": "Adicionar multi-conversação",
"com_ui_admin": "Admin",
"com_ui_admin_access_warning": "Desabilitar o acesso de Admin a esse recurso pode causar problemas inesperados na IU que exigem atualização. Se salvo, a única maneira de reverter é por meio da configuração de interface na configuração librechat.yaml que afeta todas as funções.",
"com_ui_admin_settings": "Configurações de Admin",
"com_ui_advanced": "Avançado",
"com_ui_agent": "Agente",
"com_ui_agent_delete_error": "Houve um erro ao excluir o agente",
"com_ui_agent_deleted": "Agente excluído com sucesso",
"com_ui_agent_duplicate_error": "Ocorreu um erro ao duplicar o agente",
"com_ui_agent_duplicated": "Agente duplicado com sucesso",
"com_ui_agent_editing_allowed": "Outros usuários já podem editar este agente",
"com_ui_agent_shared_to_all": "algo precisa ir aqui. esta vazio",
"com_ui_agents": "Agentes",
"com_ui_agents_allow_create": "Permitir a criação de agentes",
"com_ui_agents_allow_share_global": "Permitir compartilhamento de agentes para todos os usuários",
"com_ui_agents_allow_use": "Permitir o uso de agentes",
"com_ui_all": "todos",
"com_ui_all_proper": "Todos",
"com_ui_analyzing": "Analisando",
"com_ui_analyzing_finished": "Análise concluída",
"com_ui_api_key": "Chave API",
"com_ui_archive": "Arquivar",
"com_ui_archive_error": "Falha ao arquivar conversa",
"com_ui_artifact_click": "Clique para abrir",
"com_ui_artifacts": "Artefatos",
"com_ui_artifacts_toggle": "Alternar UI de Artefatos",
"com_ui_artifacts_toggle_agent": "Habilitar artefatos",
"com_ui_ascending": "Asc",
"com_ui_assistant": "Assistente",
"com_ui_assistant_delete_error": "Houve um erro ao excluir o assistente",
@@ -421,35 +496,57 @@
"com_ui_attach_error_type": "Tipo de arquivo não suportado para o endpoint:",
"com_ui_attach_warn_endpoint": "Arquivos não compatíveis podem ser ignorados sem uma ferramenta compatível",
"com_ui_attachment": "Anexo",
"com_ui_auth_type": "Tipo de autenticação",
"com_ui_auth_url": "URL de autorização",
"com_ui_authentication": "Autenticação",
"com_ui_authentication_type": "Tipo de Autenticação",
"com_ui_avatar": "Avatar",
"com_ui_azure": "Azure",
"com_ui_back_to_chat": "Voltar ao Chat",
"com_ui_back_to_prompts": "Voltar aos Prompts",
"com_ui_backup_codes": "Códigos de Backup",
"com_ui_backup_codes_regenerate_error": "Ocorreu um erro ao regerar os códigos de backup",
"com_ui_backup_codes_regenerated": "Os códigos de backup foram regerados com sucesso",
"com_ui_basic": "Básico",
"com_ui_basic_auth_header": "Cabeçalho de autorização básico",
"com_ui_bearer": "Portador",
"com_ui_bookmark_delete_confirm": "Tem certeza de que deseja excluir este favorito?",
"com_ui_bookmarks": "Favoritos",
"com_ui_bookmarks_add": "Adicionar marcadores",
"com_ui_bookmarks_add_to_conversation": "Adicionar à conversa atual",
"com_ui_bookmarks_count": "Contagem",
"com_ui_bookmarks_create_error": "Houve um erro ao criar o favorito",
"com_ui_bookmarks_create_exists": "Este favorito já existe",
"com_ui_bookmarks_create_success": "Favorito criado com sucesso",
"com_ui_bookmarks_delete": "Deletar marcadores",
"com_ui_bookmarks_delete_error": "Houve um erro ao excluir o favorito",
"com_ui_bookmarks_delete_success": "Favorito excluído com sucesso",
"com_ui_bookmarks_description": "Descrição",
"com_ui_bookmarks_edit": "Editar marcadores",
"com_ui_bookmarks_filter": "Filtrar favoritos...",
"com_ui_bookmarks_new": "Novo Favorito",
"com_ui_bookmarks_title": "Título",
"com_ui_bookmarks_update_error": "Houve um erro ao atualizar o favorito",
"com_ui_bookmarks_update_success": "Favorito atualizado com sucesso",
"com_ui_bulk_delete_error": "Falha ao excluir links compartilhados",
"com_ui_callback_url": "URL de retorno de chamada",
"com_ui_cancel": "Cancelar",
"com_ui_chat": "Chat",
"com_ui_chat_history": "Histórico de Chat",
"com_ui_clear": "Limpar",
"com_ui_clear_all": "Limpar tudo",
"com_ui_client_id": "ID do cliente",
"com_ui_client_secret": "Segredo do cliente",
"com_ui_close": "Fechar",
"com_ui_close_menu": "Fechar Menu",
"com_ui_code": "Código",
"com_ui_collapse_chat": "Recolher bate-papo",
"com_ui_command_placeholder": "Opcional: Insira um comando para o prompt ou o nome será usado.",
"com_ui_command_usage_placeholder": "Selecione um Prompt por comando ou nome",
"com_ui_complete_setup": "Configuração completa",
"com_ui_confirm_action": "Confirmar Ação",
"com_ui_confirm_admin_use_change": "Alterar esta configuração bloqueará o acesso para administradores, incluindo você. Tem certeza de que deseja prosseguir?",
"com_ui_confirm_change": "Confirmar alteração",
"com_ui_context": "Contexto",
"com_ui_continue": "Continuar",
"com_ui_controls": "Controles",
@@ -461,6 +558,9 @@
"com_ui_create": "Criar",
"com_ui_create_link": "Criar link",
"com_ui_create_prompt": "Criar Prompt",
"com_ui_currently_production": "Atualmente em produção",
"com_ui_custom": "Personalizado",
"com_ui_custom_header_name": "Nome do cabeçalho personalizado",
"com_ui_custom_prompt_mode": "Modo de Prompt Personalizado",
"com_ui_dashboard": "Painel",
"com_ui_date": "Data",
@@ -481,6 +581,7 @@
"com_ui_date_today": "Hoje",
"com_ui_date_yesterday": "Ontem",
"com_ui_decline": "Eu não aceito",
"com_ui_default_post_request": "Padrão (solicitação POST)",
"com_ui_delete": "Excluir",
"com_ui_delete_action": "Excluir Ação",
"com_ui_delete_action_confirm": "Tem certeza de que deseja excluir esta ação?",
@@ -490,24 +591,43 @@
"com_ui_delete_confirm_prompt_version_var": "Isso excluirá a versão selecionada para \"{{0}}\". Se não houver outras versões, o prompt será excluído.",
"com_ui_delete_conversation": "Excluir chat?",
"com_ui_delete_prompt": "Excluir Prompt?",
"com_ui_delete_shared_link": "Excluir link compartilhado?",
"com_ui_delete_tool": "Excluir Ferramenta",
"com_ui_delete_tool_confirm": "Tem certeza de que deseja excluir esta ferramenta?",
"com_ui_descending": "Desc",
"com_ui_description": "Descrição",
"com_ui_description_placeholder": "Opcional: Insira uma descrição para exibir para o prompt",
"com_ui_disabling": "Desativando...",
"com_ui_download": "Download",
"com_ui_download_artifact": "Download artefato",
"com_ui_download_backup": "Baixar códigos de backup",
"com_ui_download_backup_tooltip": "Antes de continuar, baixe seus códigos de backup. Você precisará deles para recuperar o acesso se perder seu dispositivo autenticador",
"com_ui_download_error": "Erro ao baixar o arquivo. O arquivo pode ter sido excluído.",
"com_ui_drag_drop": "algo precisa ir aqui. estava vazio",
"com_ui_dropdown_variables": "Variáveis de dropdown:",
"com_ui_dropdown_variables_info": "Crie menus dropdown personalizados para seus prompts: `{{nome_da_variável:opção1|opção2|opção3}}`",
"com_ui_duplicate": "Duplicado",
"com_ui_duplication_error": "Ocorreu um erro ao duplicar a conversa",
"com_ui_duplication_processing": "Duplicando conversa...",
"com_ui_duplication_success": "Conversa duplicada com sucesso",
"com_ui_edit": "Editar",
"com_ui_empty_category": "-",
"com_ui_endpoint": "Endpoint",
"com_ui_endpoint_menu": "Menu endpoint LLM",
"com_ui_endpoints_available": "Endpoints disponíveis",
"com_ui_enter": "Entrar",
"com_ui_enter_api_key": "Insira a chave da API",
"com_ui_enter_openapi_schema": "Insira seu esquema OpenAPI aqui",
"com_ui_enter_var": "Inserir {{0}}",
"com_ui_error": "Erro",
"com_ui_error_connection": "Erro ao conectar ao servidor, tente atualizar a página.",
"com_ui_error_save_admin_settings": "Houve um erro ao salvar suas configurações de admin.",
"com_ui_examples": "Exemplos",
"com_ui_export_convo_modal": "Exportar Modal de Conversação",
"com_ui_field_required": "Este campo é obrigatório",
"com_ui_filter_prompts": "Filtrar prompts",
"com_ui_filter_prompts_name": "Filtrar prompts por nome",
"com_ui_finance": "Financiar",
"com_ui_fork": "Bifurcar",
"com_ui_fork_all_target": "Incluir todos para/de aqui",
"com_ui_fork_branches": "Incluir ramificações relacionadas",
@@ -530,41 +650,72 @@
"com_ui_fork_split_target_setting": "Iniciar bifurcação a partir da mensagem alvo por padrão",
"com_ui_fork_success": "Conversa bifurcada com sucesso",
"com_ui_fork_visible": "Apenas mensagens visíveis",
"com_ui_generate_backup": "Gerar códigos de backup",
"com_ui_generate_qrcode": "Gerar QR Code",
"com_ui_generating": "Gerando...",
"com_ui_global_group": "algo precisa ir aqui. estava vazio",
"com_ui_go_back": "Volte",
"com_ui_go_to_conversation": "Ir para a conversa",
"com_ui_happy_birthday": "É meu 1º aniversário!",
"com_ui_hide_qr": "Ocultar QR Code",
"com_ui_host": "Host",
"com_ui_idea": "Ideias",
"com_ui_image_gen": "Geração de Imagem",
"com_ui_import": "Importar",
"com_ui_import_conversation_error": "Houve um erro ao importar suas conversas",
"com_ui_import_conversation_file_type_error": "Tipo de importação não suportado",
"com_ui_import_conversation_info": "Importar conversas de um arquivo JSON",
"com_ui_import_conversation_success": "Conversas importadas com sucesso",
"com_ui_include_shadcnui": "Incluir instruções de componentes shadcn/ui",
"com_ui_include_shadcnui_agent": "Incluir instruções shadcn/ui",
"com_ui_input": "Entrada",
"com_ui_instructions": "Instruções",
"com_ui_latest_footer": "Toda IA para Todos.",
"com_ui_latest_production_version": "Última versão de produção",
"com_ui_latest_version": "Ultima versão",
"com_ui_librechat_code_api_key": "Obtenha sua chave de API do LibreChat Code Interpreter",
"com_ui_librechat_code_api_subtitle": "Seguro. Multi-idioma. Arquivos de entrada/saída.",
"com_ui_librechat_code_api_title": "Execute o código AI",
"com_ui_llm_menu": "Menu LLM",
"com_ui_llms_available": "LLMs disponíveis",
"com_ui_loading": "Carregando",
"com_ui_locked": "Bloqueado",
"com_ui_logo": "{{0}} Logo",
"com_ui_manage": "Gerenciar",
"com_ui_max_tags": "O número máximo permitido é {{0}}, usando os valores mais recentes.",
"com_ui_mention": "Mencione um endpoint, assistente ou predefinição para alternar rapidamente para ele",
"com_ui_min_tags": "Não é possível remover mais valores, um mínimo de {{0}} é necessário.",
"com_ui_misc": "Diversos",
"com_ui_model": "Modelo",
"com_ui_model_parameters": "Parâmetros do Modelo",
"com_ui_more_info": "Mais informações",
"com_ui_my_prompts": "Meus Prompts",
"com_ui_name": "Nome",
"com_ui_new": "Novo",
"com_ui_new_chat": "Novo chat",
"com_ui_next": "Próximo",
"com_ui_no": "Não",
"com_ui_no_backup_codes": "Nenhum código de backup disponível. Por favor, gere novos",
"com_ui_no_bookmarks": "Parece que você ainda não tem favoritos. Clique em um chat e adicione um novo",
"com_ui_no_category": "Sem categoria",
"com_ui_no_changes": "Sem alterações para atualizar",
"com_ui_no_data": "algo precisa ir aqui. estava vazio",
"com_ui_no_terms_content": "Nenhum conteúdo de termos e condições para exibir",
"com_ui_no_valid_items": "algo precisa ir aqui. estava vazio",
"com_ui_none": "Nenhum",
"com_ui_none_selected": "Nenhum selecionado",
"com_ui_not_used": "Não usado",
"com_ui_nothing_found": "Nada encontrado",
"com_ui_oauth": "OAuth",
"com_ui_of": "de",
"com_ui_off": "Desligado",
"com_ui_on": "Ligado",
"com_ui_openai": "OpenAI",
"com_ui_page": "Página",
"com_ui_prev": "Anterior",
"com_ui_preview": "Pré-visualizar",
"com_ui_privacy_policy": "Política de Privacidade",
"com_ui_privacy_policy_url": "URL da Política de Privacidade",
"com_ui_prompt": "Prompt",
"com_ui_prompt_already_shared_to_all": "Este prompt já está compartilhado com todos os usuários",
"com_ui_prompt_name": "Nome do Prompt",
@@ -579,62 +730,109 @@
"com_ui_prompts_allow_use": "Permitir uso de Prompts",
"com_ui_provider": "Provedor",
"com_ui_read_aloud": "Ler em voz alta",
"com_ui_refresh_link": "Atualizar link",
"com_ui_regenerate": "Regenerar",
"com_ui_regenerate_backup": "Regerar código de backup",
"com_ui_regenerating": "Regerando...",
"com_ui_region": "Região",
"com_ui_rename": "Renomear",
"com_ui_rename_prompt": "Renomear prompt",
"com_ui_requires_auth": "Requer autenticação",
"com_ui_reset_var": "Redefinir {{0}}",
"com_ui_result": "Resultado",
"com_ui_revoke": "Revogar",
"com_ui_revoke_info": "Revogar todas as credenciais fornecidas pelo usuário",
"com_ui_revoke_key_confirm": "Tem certeza de que deseja revogar esta chave?",
"com_ui_revoke_key_endpoint": "Revogar chave para {{0}}",
"com_ui_revoke_keys": "Revogar chaves",
"com_ui_revoke_keys_confirm": "Tem certeza de que deseja revogar todas as chaves?",
"com_ui_role_select": "Papel",
"com_ui_roleplay": "RPG",
"com_ui_run_code": "Executar código",
"com_ui_run_code_error": "Ocorreu um erro ao executar o código",
"com_ui_save": "Salvar",
"com_ui_save_submit": "Salvar & Enviar",
"com_ui_saved": "Salvo!",
"com_ui_schema": "Esquema",
"com_ui_scope": "Escopo",
"com_ui_search": "Pesquisar",
"com_ui_secret_key": "Chave secreta",
"com_ui_select": "Selecionar",
"com_ui_select_file": "Selecionar um arquivo",
"com_ui_select_model": "Selecionar um modelo",
"com_ui_select_provider": "Selecionar um provedor",
"com_ui_select_provider_first": "Selecione um provedor primeiro",
"com_ui_select_region": "Selecione uma região",
"com_ui_select_search_model": "Pesquisar modelo por nome",
"com_ui_select_search_plugin": "Pesquisar plugin por nome",
"com_ui_select_search_provider": "Pesquisar provedor por nome",
"com_ui_select_search_region": "Pesquisar região por nome",
"com_ui_share": "Compartilhar",
"com_ui_share_create_message": "Seu nome e quaisquer mensagens que você adicionar após o compartilhamento permanecerão privadas.",
"com_ui_share_delete_error": "Houve um erro ao excluir o link compartilhado",
"com_ui_share_error": "Houve um erro ao compartilhar o link do chat",
"com_ui_share_form_description": "algo precisa ir aqui. esta vazio",
"com_ui_share_link_to_chat": "Compartilhar link para o chat",
"com_ui_share_to_all_users": "Compartilhar com todos os usuários",
"com_ui_share_update_message": "Seu nome, instruções personalizadas e quaisquer mensagens que você adicionar após o compartilhamento permanecerão privadas.",
"com_ui_share_var": "Compartilhar {{0}}",
"com_ui_shared_link_bulk_delete_success": "Links compartilhados excluídos com sucesso",
"com_ui_shared_link_delete_success": "Link compartilhado excluído com sucesso",
"com_ui_shared_link_not_found": "Link compartilhado não encontrado",
"com_ui_shared_prompts": "Prompts Compartilhados",
"com_ui_shop": "Shopping",
"com_ui_show": "Mostrar",
"com_ui_show_all": "Mostrar Todos",
"com_ui_show_qr": "Mostrar QR Code",
"com_ui_sign_in_to_domain": "Entre em {{0}}",
"com_ui_simple": "Simples",
"com_ui_size": "Tamanho",
"com_ui_special_variables": "Variáveis especiais:",
"com_ui_special_variables_info": "Use `{{current_date}}` para a data atual, e `{{current_user}}` para o nome da sua conta.",
"com_ui_speech_while_submitting": "Não é possível enviar a fala enquanto uma resposta está sendo gerada",
"com_ui_stop": "Parar",
"com_ui_storage": "Armazenamento",
"com_ui_submit": "Enviar",
"com_ui_teach_or_explain": "Aprendizado",
"com_ui_temporary_chat": "Chat temporário",
"com_ui_terms_and_conditions": "Termos e Condições",
"com_ui_terms_of_service": "Termos de Serviço",
"com_ui_thinking": "Pensando...",
"com_ui_thoughts": "Pensamentos",
"com_ui_token_exchange_method": "Método de troca de tokens",
"com_ui_token_url": "URL do token",
"com_ui_tools": "Ferramentas",
"com_ui_travel": "Viagem",
"com_ui_unarchive": "Desarquivar",
"com_ui_unarchive_error": "Falha ao desarquivar conversa",
"com_ui_unknown": "Desconhecido",
"com_ui_update": "Atualizar",
"com_ui_upload": "Carregar",
"com_ui_upload_code_files": "Carregar para o interpretador de código",
"com_ui_upload_delay": "O upload de \"{{0}}\" está demorando mais do que o esperado. Por favor, aguarde enquanto o arquivo termina de ser indexado para recuperação.",
"com_ui_upload_error": "Houve um erro ao carregar seu arquivo",
"com_ui_upload_file_search": "Upload para pesquisa de arquivos",
"com_ui_upload_files": "Carregar arquivos",
"com_ui_upload_image": "Carregar uma imagem",
"com_ui_upload_image_input": "Upload de imagem",
"com_ui_upload_invalid": "Arquivo inválido para upload. Deve ser uma imagem não excedendo o limite",
"com_ui_upload_invalid_var": "Arquivo inválido para upload. Deve ser uma imagem não excedendo {{0}} MB",
"com_ui_upload_success": "Arquivo carregado com sucesso",
"com_ui_upload_type": "Selecione o tipo de upload",
"com_ui_use_2fa_code": "Use o código 2FA em vez disso",
"com_ui_use_backup_code": "Use o código de backup",
"com_ui_use_micrphone": "Usar microfone",
"com_ui_use_prompt": "Usar prompt",
"com_ui_used": "Usado",
"com_ui_variables": "Variáveis",
"com_ui_variables_info": "Use chaves duplas no seu texto para criar variáveis, por exemplo, `{{exemplo de variável}}`, para preencher posteriormente ao usar o prompt.",
"com_ui_verify": "Verificar",
"com_ui_version_var": "Versão {{0}}",
"com_ui_versions": "Versões",
"com_ui_view_source": "Ver chat de origem",
"com_ui_write": "Escrevendo",
"com_ui_yes": "Sim",
"com_ui_zoom": "Zoom",
"com_user_message": "Você"
"com_user_message": "Você",
"com_warning_resubmit_unsupported": "O reenvio da mensagem de IA não é suportado para este endpoint."
}

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