Compare commits

...

14 Commits

Author SHA1 Message Date
Danny Avila
4a32d7466a v0.7.2 (#2667)
* v0.7.2

* chore: uninstall hnswlib-node

* bump package provider

* bump librechat-data-provider in lockfile

* README: ross index

* chore: center star history
2024-05-10 16:29:27 -04:00
Danny Avila
2ec821ea4c 🌍 : Updated Translations & AI Generation Scripts (#2666)
* chore: bun scripts

* feat: comparisons

* refactor: move scripts to own folder

* feat: generated prompts script and Es output

* feat: generated prompts

* created prompts

* feat: Russian localization prompts

* translation setup

* additional ES translations

* additional ES translations

* translation services

* feat: additional translations

* fix regex for parseParamPrompt

* RU translations

* remove stores from git

* update gitignore

* update gitignore

* ZH translations

* move gen prompt output location

* ZH traditional translations

* AR translations

* chore: rename

* JP

* cleanup scripts

* add additional instruction prompts

* fix translation prompt and add DE

* FR translations (rate limited so not complete)

* chore: update translation comparisons

* chore: remove unused AnthropicClient changes

* refactor: use compositional styling for archive/delete buttons, fix manage archive table styling
2024-05-10 15:56:25 -04:00
Marco Beretta
978009787c 📝 docs(ai_endpoints): update HF key link (#2664) 2024-05-10 12:48:39 -04:00
Danny Avila
27e7621b6a 🪟 fix: Windows Vite Build Issue (#2663)
* fix: Windows CSS VITE build issue: peer-focus:dark => dark:peer-focus

* ci: add windows env step for frontend ci/cd
2024-05-10 12:44:43 -04:00
Danny Avila
2b37a44b8d 🔧 fix: Preset Dialog Styling and Values (#2657)
* style: preset dialog styling

* refactor: coerce number input for convo schema

* refactor: replace dynamic input number with static component
2024-05-10 03:05:45 -04:00
Danny Avila
98c96cd020 🦙 fix: Ollama System Message order (#2655) 2024-05-10 00:50:02 -04:00
Anirudh
8f20fb28e5 🪟 fix+feat: General UI Enhancements (#2619)
* feat: Minor design changes to mimic OpenAI's latest login page

* fix: Optimize ThemeSelector for mobile

* fix: Use a svg for the logo for transperency in dark mode

* feat: Update styles for Registration

* feat: Update error colors for login & registration

* fix: remove medium font

* wip: Dropdown menu

* feat: Update dropdown to match ChatGPT

* feat: Improve rounding and padding

* feat: Add UI Updates to RequestPasswordReset, PasswordRest and increase width for theme dropdown

* fix: Modify the My Files modal's width to not touch the screen

* feat: fix scrolling for dropdown, and make border width lighter

* feat: Match popup menu design to OpenAI (p1/2)

* fix+feat: fix dark mode, add user email, add lighter borders

* fix: Add border color on focus of chat input.

* feat: Move Export Conversation to a seperate button (testing)

* fix: Properly center Login, Registration, Reset Password Flow

* fix: Border colors on dark mode for settings modal

* feat: Improve wording for settings menu

* fix: Optimize settings modal for mobile and fix height for modal

* feat: Optimize for desktop

* fix: make TooltipTrigger asChild of button, improve settings mobile responsiveness

* feat: Handle dropdowns properly
TODO: Make height dynamic, fix dark mode colors

* fix: input styles
fix: make endpoint icon smaller

* feat: Update UI to Match ChatGPT Style

- Updated the dropdown styles to match the aesthetic of ChatGPT.
- Decreased spacing within the conversation area for cleanliness.
- Replaced the current archive icon with the ChatGPT's icon.

* fix: fix colors for EditMenuButton & ArchiveButton for dark mode and light mode

* fix: ui fixes

* fix: Fix Conversation UI Bugs

* fix: transparency of HoverToggle to make buttons not visible

* fix: dark mode HoverToggle & compress menu item spacing

* fix: responsiveness of export icon

* fix: first mentionitem is set to always be highlighted

* fix: improve hover state to text instead of bg

* feat: Update icons to ChatGPT Style

* fix: dark mode hover for PanelFileCell

* fix: change navlinks z-index to 100

* fix: hover states for DataTable

* feat: Move ExportButton to seperate component

* chore: remove unused imports
2024-05-09 17:46:16 -04:00
Fuegovic
d73ea8e1f2 🤗 feat: Known Endpoints: HuggingFace (#2646)
* endpoints: huggingface

* Update ai_endpoints.md

* huggingface: update icon
2024-05-09 14:26:47 -04:00
Marco Beretta
83bae9e9d9 🔧 fix: android keyboard @ popover issue (#2647) 2024-05-09 13:31:55 -04:00
Danny Avila
6ba7f60eec 🪙 feat: Configure Max Context and Output Tokens (#2648)
* chore: make frequent 'error' log into 'debug' log

* feat: add maxContextTokens as a conversation field

* refactor(settings): increase popover height

* feat: add DynamicInputNumber and maxContextTokens to all endpoints that support it (frontend), fix schema

* feat: maxContextTokens handling (backend)

* style: revert popover height

* feat: max tokens

* fix: Ollama Vision firebase compatibility

* fix: Ollama Vision, use message_file_map to determine multimodal request

* refactor: bring back MobileNav and improve title styling
2024-05-09 13:27:13 -04:00
Danny Avila
5293b73b6d 🤖 feat(google): Add safety settings configuration (#2644)
* 🤖 feat(google): Add safety settings configuration

- Implement safety settings configuration in GoogleClient.js
- Add safety settings variables in .env.example
- Update documentation to explain safety settings and clarify model usage

* fix(google): Apply safety settings only to Gemini models

Previously, the safety settings were being applied to all models, regardless of whether they were Gemini models or not. This commit ensures that the safety settings are only applied to models that contain the "gemini" string in their name.

The changes include:

- Extracting the model name from `payload.parameters.model`
- Checking if the model name exists and contains the "gemini" string
- Only applying the safety settings if the model name contains "gemini"
- Ignoring the safety settings for non-Gemini models

This fix ensures that the safety settings are only used for the intended Gemini models, and not applied to other models where they may not be applicable.

* Update GoogleClient.js

* fix(google): Apply safety settings only to Gemini models

---------

Co-authored-by: Oliver Faust <oliver@f4ust.de>
2024-05-08 21:32:23 -04:00
Walber Cardoso
b6d1f5fa53 🎨 style: Convo fade effect (#2642)
* style: Improve fade effect into convos

* style: Improve fade effect into convos. WIP code

* 🔧 fix: Convo fade effect

* 🔧 fix: Convo fade effect: removed listeners
2024-05-08 21:21:55 -04:00
Danny Avila
c94278be85 🦙 feat: Ollama Vision Support (#2643)
* refactor: checkVisionRequest, search availableModels for valid vision model instead of using default

* feat: install ollama-js, add typedefs

* feat: Ollama Vision Support

* ci: fix test
2024-05-08 20:24:40 -04:00
Danny Avila
3c5fa40435 📶 fix: Mobile Stylings (#2639)
* chore: remove unused mobile nav

* fix: mobile nav fix for 'more' and 'archive' buttons div

* refactor(useTextarea): rewrite handleKeyUp for backwards compatibility

refactor(useTextarea): rewrite handleKeyUp for backwards compatibility

* experimental: add processing delay to azure streams for better performance/UX

* experiemental: adjust gpt-3 azureDelay

* fix: perplexity titles
2024-05-08 16:40:20 -04:00
154 changed files with 53554 additions and 789 deletions

View File

@@ -68,6 +68,7 @@ PROXY=
# APIPIE_API_KEY=
# FIREWORKS_API_KEY=
# GROQ_API_KEY=
# HUGGINGFACE_TOKEN=
# MISTRAL_API_KEY=
# OPENROUTER_KEY=
# PERPLEXITY_API_KEY=
@@ -122,6 +123,20 @@ GOOGLE_KEY=user_provided
# Vertex AI
# GOOGLE_MODELS=gemini-1.5-pro-preview-0409,gemini-1.0-pro-vision-001,gemini-pro,gemini-pro-vision,chat-bison,chat-bison-32k,codechat-bison,codechat-bison-32k,text-bison,text-bison-32k,text-unicorn,code-gecko,code-bison,code-bison-32k
# Google Gemini Safety Settings
# NOTE (Vertex AI): You do not have access to the BLOCK_NONE setting by default.
# To use this restricted HarmBlockThreshold setting, you will need to either:
#
# (a) Get access through an allowlist via your Google account team
# (b) Switch your account type to monthly invoiced billing following this instruction:
# https://cloud.google.com/billing/docs/how-to/invoiced-billing
#
# GOOGLE_SAFETY_SEXUALLY_EXPLICIT=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_HATE_SPEECH=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_HARASSMENT=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_DANGEROUS_CONTENT=BLOCK_ONLY_HIGH
#============#
# OpenAI #
#============#

View File

@@ -1,11 +1,6 @@
#github action to run unit tests for frontend with jest
name: Frontend Unit Tests
on:
# push:
# branches:
# - main
# - dev
# - release/*
pull_request:
branches:
- main
@@ -14,9 +9,10 @@ on:
paths:
- 'client/**'
- 'packages/**'
jobs:
tests_frontend:
name: Run frontend unit tests
tests_frontend_ubuntu:
name: Run frontend unit tests on Ubuntu
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
@@ -35,4 +31,26 @@ jobs:
- name: Run unit tests
run: npm run test:ci --verbose
working-directory: client
working-directory: client
tests_frontend_windows:
name: Run frontend unit tests on Windows
timeout-minutes: 60
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build Client
run: npm run frontend:ci
- name: Run unit tests
run: npm run test:ci --verbose
working-directory: client

6
.gitignore vendored
View File

@@ -21,6 +21,10 @@ coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# translation services
config/translations/stores/*
client/src/localization/languages/*_missing_keys.json
# Compiled Dirs (http://nodejs.org/api/addons.html)
build/
dist/
@@ -69,6 +73,8 @@ src/style - official.css
/playwright/.cache/
.DS_Store
*.code-workspace
.idx
monospace.json
.idea
*.iml
*.pem

View File

@@ -1,4 +1,4 @@
# v0.7.1
# v0.7.2
# Base node image
FROM node:18-alpine3.18 AS node

View File

@@ -1,4 +1,4 @@
# v0.7.1
# v0.7.2
# Build API, Client and Data Provider
FROM node:20-alpine AS base

View File

@@ -103,11 +103,23 @@ Please consult the breaking changes before updating.
<p align="center">
<a href="https://trendshift.io/repositories/4685" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4685" alt="danny-avila%2FLibreChat | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<a
href="https://runacap.com/ross-index/q1-24/"
target="_blank"
rel="noopener"
>
<img
style="width: 260px; height: 56px"
src="https://runacap.com/wp-content/uploads/2024/04/ROSS_badge_white_Q1_2024.svg"
alt="ROSS Index - Fastest Growing Open-Source Startups in Q1 2024 | Runa Capital"
width="260"
height="56"
/>
</a>
<a href="https://star-history.com/#danny-avila/LibreChat&Date">
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date&theme=dark" onerror="this.src='https://api.star-history.com/svg?repos=danny-avila/LibreChat&type=Date'" />
</a>
</p>
---

View File

@@ -7,10 +7,10 @@ const {
} = require('librechat-data-provider');
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
const {
titleFunctionPrompt,
parseTitleFromPrompt,
truncateText,
formatMessage,
titleFunctionPrompt,
parseParamFromPrompt,
createContextHandlers,
} = require('./prompts');
const spendTokens = require('~/models/spendTokens');
@@ -75,7 +75,9 @@ class AnthropicClient extends BaseClient {
this.options.attachments?.then((attachments) => this.checkVisionRequest(attachments));
this.maxContextTokens =
getModelMaxTokens(this.modelOptions.model, EModelEndpoint.anthropic) ?? 100000;
this.options.maxContextTokens ??
getModelMaxTokens(this.modelOptions.model, EModelEndpoint.anthropic) ??
100000;
this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1500;
this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
@@ -652,6 +654,7 @@ class AnthropicClient extends BaseClient {
getSaveOptions() {
return {
maxContextTokens: this.options.maxContextTokens,
promptPrefix: this.options.promptPrefix,
modelLabel: this.options.modelLabel,
resendFiles: this.options.resendFiles,
@@ -745,7 +748,7 @@ class AnthropicClient extends BaseClient {
context: 'title',
});
const text = response.content[0].text;
title = parseTitleFromPrompt(text);
title = parseParamFromPrompt(text, 'title');
} catch (e) {
logger.error('[AnthropicClient] There was an issue generating the title', e);
}

View File

@@ -138,7 +138,10 @@ class GoogleClient extends BaseClient {
!isGenerativeModel && !isChatModel && /code|text/.test(this.modelOptions.model);
const { isTextModel } = this;
this.maxContextTokens = getModelMaxTokens(this.modelOptions.model, EModelEndpoint.google);
this.maxContextTokens =
this.options.maxContextTokens ??
getModelMaxTokens(this.modelOptions.model, EModelEndpoint.google);
// The max prompt tokens is determined by the max context tokens minus the max response tokens.
// Earlier messages will be dropped until the prompt is within the limit.
this.maxResponseTokens = this.modelOptions.maxOutputTokens || settings.maxOutputTokens.default;
@@ -677,6 +680,9 @@ class GoogleClient extends BaseClient {
};
}
const safetySettings = _payload.safetySettings;
requestOptions.safetySettings = safetySettings;
const result = await client.generateContentStream(requestOptions);
for await (const chunk of result.stream) {
const chunkText = chunk.text();
@@ -688,9 +694,11 @@ class GoogleClient extends BaseClient {
return reply;
}
const safetySettings = _payload.safetySettings;
const stream = await model.stream(messages, {
signal: abortController.signal,
timeout: 7000,
safetySettings: safetySettings,
});
for await (const chunk of stream) {
@@ -720,6 +728,33 @@ class GoogleClient extends BaseClient {
}
async sendCompletion(payload, opts = {}) {
const modelName = payload.parameters?.model;
if (modelName && modelName.toLowerCase().includes('gemini')) {
const safetySettings = [
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold:
process.env.GOOGLE_SAFETY_SEXUALLY_EXPLICIT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: process.env.GOOGLE_SAFETY_HATE_SPEECH || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: process.env.GOOGLE_SAFETY_HARASSMENT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold:
process.env.GOOGLE_SAFETY_DANGEROUS_CONTENT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
];
payload.safetySettings = safetySettings;
}
let reply = '';
reply = await this.getCompletion(payload, opts);
return reply.trim();

View File

@@ -0,0 +1,154 @@
const { z } = require('zod');
const axios = require('axios');
const { Ollama } = require('ollama');
const { deriveBaseURL } = require('~/utils');
const { logger } = require('~/config');
const ollamaPayloadSchema = z.object({
mirostat: z.number().optional(),
mirostat_eta: z.number().optional(),
mirostat_tau: z.number().optional(),
num_ctx: z.number().optional(),
repeat_last_n: z.number().optional(),
repeat_penalty: z.number().optional(),
temperature: z.number().optional(),
seed: z.number().nullable().optional(),
stop: z.array(z.string()).optional(),
tfs_z: z.number().optional(),
num_predict: z.number().optional(),
top_k: z.number().optional(),
top_p: z.number().optional(),
stream: z.optional(z.boolean()),
model: z.string(),
});
/**
* @param {string} imageUrl
* @returns {string}
* @throws {Error}
*/
const getValidBase64 = (imageUrl) => {
const parts = imageUrl.split(';base64,');
if (parts.length === 2) {
return parts[1];
} else {
logger.error('Invalid or no Base64 string found in URL.');
}
};
class OllamaClient {
constructor(options = {}) {
const host = deriveBaseURL(options.baseURL ?? 'http://localhost:11434');
/** @type {Ollama} */
this.client = new Ollama({ host });
}
/**
* Fetches Ollama models from the specified base API path.
* @param {string} baseURL
* @returns {Promise<string[]>} The Ollama models.
*/
static async fetchModels(baseURL) {
let models = [];
if (!baseURL) {
return models;
}
try {
const ollamaEndpoint = deriveBaseURL(baseURL);
/** @type {Promise<AxiosResponse<OllamaListResponse>>} */
const response = await axios.get(`${ollamaEndpoint}/api/tags`);
models = response.data.models.map((tag) => tag.name);
return models;
} catch (error) {
const logMessage =
'Failed to fetch models from Ollama API. If you are not using Ollama directly, and instead, through some aggregator or reverse proxy that handles fetching via OpenAI spec, ensure the name of the endpoint doesn\'t start with `ollama` (case-insensitive).';
logger.error(logMessage, error);
return [];
}
}
/**
* @param {ChatCompletionMessage[]} messages
* @returns {OllamaMessage[]}
*/
static formatOpenAIMessages(messages) {
const ollamaMessages = [];
for (const message of messages) {
if (typeof message.content === 'string') {
ollamaMessages.push({
role: message.role,
content: message.content,
});
continue;
}
let aggregatedText = '';
let imageUrls = [];
for (const content of message.content) {
if (content.type === 'text') {
aggregatedText += content.text + ' ';
} else if (content.type === 'image_url') {
imageUrls.push(getValidBase64(content.image_url.url));
}
}
const ollamaMessage = {
role: message.role,
content: aggregatedText.trim(),
};
if (imageUrls.length > 0) {
ollamaMessage.images = imageUrls;
}
ollamaMessages.push(ollamaMessage);
}
return ollamaMessages;
}
/***
* @param {Object} params
* @param {ChatCompletionPayload} params.payload
* @param {onTokenProgress} params.onProgress
* @param {AbortController} params.abortController
*/
async chatCompletion({ payload, onProgress, abortController = null }) {
let intermediateReply = '';
const parameters = ollamaPayloadSchema.parse(payload);
const messages = OllamaClient.formatOpenAIMessages(payload.messages);
if (parameters.stream) {
const stream = await this.client.chat({
messages,
...parameters,
});
for await (const chunk of stream) {
const token = chunk.message.content;
intermediateReply += token;
onProgress(token);
if (abortController.signal.aborted) {
stream.controller.abort();
break;
}
}
}
// TODO: regular completion
else {
// const generation = await this.client.generate(payload);
}
return intermediateReply;
}
catch(err) {
logger.error('[OllamaClient.chatCompletion]', err);
throw err;
}
}
module.exports = { OllamaClient, ollamaPayloadSchema };

View File

@@ -1,4 +1,5 @@
const OpenAI = require('openai');
const { OllamaClient } = require('./OllamaClient');
const { HttpsProxyAgent } = require('https-proxy-agent');
const {
Constants,
@@ -26,11 +27,11 @@ const {
createContextHandlers,
} = require('./prompts');
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
const { isEnabled, sleep } = require('~/server/utils');
const { handleOpenAIErrors } = require('./tools/util');
const spendTokens = require('~/models/spendTokens');
const { createLLM, RunManager } = require('./llm');
const ChatGPTClient = require('./ChatGPTClient');
const { isEnabled } = require('~/server/utils');
const { summaryBuffer } = require('./memory');
const { runTitleChain } = require('./chains');
const { tokenSplit } = require('./document');
@@ -128,6 +129,10 @@ class OpenAIClient extends BaseClient {
this.useOpenRouter = true;
}
if (this.options.endpoint?.toLowerCase() === 'ollama') {
this.isOllama = true;
}
this.FORCE_PROMPT =
isEnabled(OPENAI_FORCE_PROMPT) ||
(reverseProxy && reverseProxy.includes('completions') && !reverseProxy.includes('chat'));
@@ -160,11 +165,13 @@ class OpenAIClient extends BaseClient {
model.startsWith('text-chat') || model.startsWith('text-davinci-002-render');
this.maxContextTokens =
this.options.maxContextTokens ??
getModelMaxTokens(
model,
this.options.endpointType ?? this.options.endpoint,
this.options.endpointTokenConfig,
) ?? 4095; // 1 less than maximum
) ??
4095; // 1 less than maximum
if (this.shouldSummarize) {
this.maxContextTokens = Math.floor(this.maxContextTokens / 2);
@@ -234,23 +241,52 @@ class OpenAIClient extends BaseClient {
* @param {MongoFile[]} attachments
*/
checkVisionRequest(attachments) {
const availableModels = this.options.modelsConfig?.[this.options.endpoint];
this.isVisionModel = validateVisionModel({ model: this.modelOptions.model, availableModels });
const visionModelAvailable = availableModels?.includes(this.defaultVisionModel);
if (
attachments &&
attachments.some((file) => file?.type && file?.type?.includes('image')) &&
visionModelAvailable &&
!this.isVisionModel
) {
this.modelOptions.model = this.defaultVisionModel;
this.isVisionModel = true;
if (!attachments) {
return;
}
const availableModels = this.options.modelsConfig?.[this.options.endpoint];
if (!availableModels) {
return;
}
let visionRequestDetected = false;
for (const file of attachments) {
if (file?.type?.includes('image')) {
visionRequestDetected = true;
break;
}
}
if (!visionRequestDetected) {
return;
}
this.isVisionModel = validateVisionModel({ model: this.modelOptions.model, availableModels });
if (this.isVisionModel) {
delete this.modelOptions.stop;
return;
}
for (const model of availableModels) {
if (!validateVisionModel({ model, availableModels })) {
continue;
}
this.modelOptions.model = model;
this.isVisionModel = true;
delete this.modelOptions.stop;
return;
}
if (!availableModels.includes(this.defaultVisionModel)) {
return;
}
if (!validateVisionModel({ model: this.defaultVisionModel, availableModels })) {
return;
}
this.modelOptions.model = this.defaultVisionModel;
this.isVisionModel = true;
delete this.modelOptions.stop;
}
setupTokens() {
@@ -377,6 +413,7 @@ class OpenAIClient extends BaseClient {
getSaveOptions() {
return {
maxContextTokens: this.options.maxContextTokens,
chatGptLabel: this.options.chatGptLabel,
promptPrefix: this.options.promptPrefix,
resendFiles: this.options.resendFiles,
@@ -405,7 +442,11 @@ class OpenAIClient extends BaseClient {
* @returns {Promise<MongoFile[]>}
*/
async addImageURLs(message, attachments) {
const { files, image_urls } = await encodeAndFormat(this.options.req, attachments);
const { files, image_urls } = await encodeAndFormat(
this.options.req,
attachments,
this.options.endpoint,
);
message.image_urls = image_urls.length ? image_urls : undefined;
return files;
}
@@ -715,6 +756,10 @@ class OpenAIClient extends BaseClient {
* In case of failure, it will return the default title, "New Chat".
*/
async titleConvo({ text, conversationId, responseText = '' }) {
if (this.options.attachments) {
delete this.options.attachments;
}
let title = 'New Chat';
const convo = `||>User:
"${truncateText(text)}"
@@ -1079,11 +1124,8 @@ ${convo}
...opts,
});
/* hacky fixes for Mistral AI API:
- Re-orders system message to the top of the messages payload, as not allowed anywhere else
- If there is only one message and it's a system message, change the role to user
*/
if (opts.baseURL.includes('https://api.mistral.ai/v1') && modelOptions.messages) {
/* Re-orders system message to the top of the messages payload, as not allowed anywhere else */
if (modelOptions.messages && (opts.baseURL.includes('api.mistral.ai') || this.isOllama)) {
const { messages } = modelOptions;
const systemMessageIndex = messages.findIndex((msg) => msg.role === 'system');
@@ -1094,10 +1136,16 @@ ${convo}
}
modelOptions.messages = messages;
}
if (messages.length === 1 && messages[0].role === 'system') {
modelOptions.messages[0].role = 'user';
}
/* If there is only one message and it's a system message, change the role to user */
if (
(opts.baseURL.includes('api.mistral.ai') || opts.baseURL.includes('api.perplexity.ai')) &&
modelOptions.messages &&
modelOptions.messages.length === 1 &&
modelOptions.messages[0]?.role === 'system'
) {
modelOptions.messages[0].role = 'user';
}
if (this.options.addParams && typeof this.options.addParams === 'object') {
@@ -1121,6 +1169,15 @@ ${convo}
});
}
if (this.message_file_map && this.isOllama) {
const ollamaClient = new OllamaClient({ baseURL });
return await ollamaClient.chatCompletion({
payload: modelOptions,
onProgress,
abortController,
});
}
let UnexpectedRoleError = false;
if (modelOptions.stream) {
const stream = await openai.beta.chat.completions
@@ -1151,6 +1208,7 @@ ${convo}
}
});
const azureDelay = this.modelOptions.model?.includes('gpt-4') ? 30 : 17;
for await (const chunk of stream) {
const token = chunk.choices[0]?.delta?.content || '';
intermediateReply += token;
@@ -1159,6 +1217,10 @@ ${convo}
stream.controller.abort();
break;
}
if (this.azure) {
await sleep(azureDelay);
}
}
if (!UnexpectedRoleError) {

View File

@@ -59,25 +59,57 @@ Submit a brief title in the conversation's language, following the parameter des
</tool_description>
</tools>`;
const genTranslationPrompt = (
translationPrompt,
) => `In this environment you have access to a set of tools you can use to translate text.
You may call them like this:
<function_calls>
<invoke>
<tool_name>$TOOL_NAME</tool_name>
<parameters>
<$PARAMETER_NAME>$PARAMETER_VALUE</$PARAMETER_NAME>
...
</parameters>
</invoke>
</function_calls>
Here are the tools available:
<tools>
<tool_description>
<tool_name>submit_translation</tool_name>
<description>
Submit a translation in the target language, following the parameter description and its language closely.
</description>
<parameters>
<parameter>
<name>translation</name>
<type>string</type>
<description>${translationPrompt}
ONLY include the generated translation without quotations, nor its related key</description>
</parameter>
</parameters>
</tool_description>
</tools>`;
/**
* Parses titles from title functions based on the provided prompt.
* @param {string} prompt - The prompt containing the title function.
* @returns {string} The parsed title. "New Chat" if no title is found.
* Parses specified parameter from the provided prompt.
* @param {string} prompt - The prompt containing the desired parameter.
* @param {string} paramName - The name of the parameter to extract.
* @returns {string} The parsed parameter's value or a default value if not found.
*/
function parseTitleFromPrompt(prompt) {
const titleRegex = /<title>(.+?)<\/title>/;
const titleMatch = prompt.match(titleRegex);
function parseParamFromPrompt(prompt, paramName) {
const paramRegex = new RegExp(`<${paramName}>([\\s\\S]+?)</${paramName}>`);
const paramMatch = prompt.match(paramRegex);
if (titleMatch && titleMatch[1]) {
const title = titleMatch[1].trim();
// // Capitalize the first letter of each word; Note: unnecessary due to title case prompting
// const capitalizedTitle = title.replace(/\b\w/g, (char) => char.toUpperCase());
return title;
if (paramMatch && paramMatch[1]) {
return paramMatch[1].trim();
}
return 'New Chat';
if (prompt && prompt.length) {
return `NO TOOL INVOCATION: ${prompt}`;
}
return `No ${paramName} provided`;
}
module.exports = {
@@ -85,5 +117,6 @@ module.exports = {
titleInstruction,
createTitlePrompt,
titleFunctionPrompt,
parseTitleFromPrompt,
parseParamFromPrompt,
genTranslationPrompt,
};

View File

@@ -40,7 +40,8 @@ class FakeClient extends BaseClient {
};
}
this.maxContextTokens = getModelMaxTokens(this.modelOptions.model) ?? 4097;
this.maxContextTokens =
this.options.maxContextTokens ?? getModelMaxTokens(this.modelOptions.model) ?? 4097;
}
buildMessages() {}
getTokenCount(str) {

View File

@@ -157,12 +157,19 @@ describe('OpenAIClient', () => {
azureOpenAIApiVersion: '2020-07-01-preview',
};
let originalWarn;
beforeAll(() => {
jest.spyOn(console, 'warn').mockImplementation(() => {});
originalWarn = console.warn;
console.warn = jest.fn();
});
afterAll(() => {
console.warn.mockRestore();
console.warn = originalWarn;
});
beforeEach(() => {
console.warn.mockClear();
});
beforeEach(() => {
@@ -662,4 +669,35 @@ describe('OpenAIClient', () => {
expect(constructorArgs.baseURL).toBe(expectedURL);
});
});
describe('checkVisionRequest functionality', () => {
let client;
const attachments = [{ type: 'image/png' }];
beforeEach(() => {
client = new OpenAIClient('test-api-key', {
endpoint: 'ollama',
modelOptions: {
model: 'initial-model',
},
modelsConfig: {
ollama: ['initial-model', 'llava', 'other-model'],
},
});
client.defaultVisionModel = 'non-valid-default-model';
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should set "llava" as the model if it is the first valid model when default validation fails', () => {
client.checkVisionRequest(attachments);
expect(client.modelOptions.model).toBe('llava');
expect(client.isVisionModel).toBeTruthy();
expect(client.modelOptions.stop).toBeUndefined();
});
});
});

View File

@@ -348,7 +348,7 @@ module.exports = function mongoMeili(schema, options) {
try {
meiliDoc = await client.index('convos').getDocument(doc.conversationId);
} catch (error) {
logger.error(
logger.debug(
'[MeiliMongooseModel.findOneAndUpdate] Convo not found in MeiliSearch and will index ' +
doc.conversationId,
error,

View File

@@ -104,6 +104,12 @@ const conversationPreset = {
type: String,
},
tools: { type: [{ type: String }], default: undefined },
maxContextTokens: {
type: Number,
},
max_tokens: {
type: Number,
},
};
const agentOptions = {

View File

@@ -1,6 +1,6 @@
{
"name": "@librechat/backend",
"version": "0.7.1",
"version": "0.7.2",
"description": "",
"scripts": {
"start": "echo 'please run this from the root directory'",
@@ -75,6 +75,7 @@
"multer": "^1.4.5-lts.1",
"nodejs-gpt": "^1.37.4",
"nodemailer": "^6.9.4",
"ollama": "^0.5.0",
"openai": "4.36.0",
"openai-chat-tokens": "^0.2.8",
"openid-client": "^5.4.2",

View File

@@ -1,5 +1,14 @@
const buildOptions = (endpoint, parsedBody) => {
const { modelLabel, promptPrefix, resendFiles, iconURL, greeting, spec, ...rest } = parsedBody;
const {
modelLabel,
promptPrefix,
maxContextTokens,
resendFiles,
iconURL,
greeting,
spec,
...rest
} = parsedBody;
const endpointOption = {
endpoint,
modelLabel,
@@ -8,6 +17,7 @@ const buildOptions = (endpoint, parsedBody) => {
iconURL,
greeting,
spec,
maxContextTokens,
modelOptions: {
...rest,
},

View File

@@ -1,6 +1,15 @@
const buildOptions = (endpoint, parsedBody, endpointType) => {
const { chatGptLabel, promptPrefix, resendFiles, imageDetail, iconURL, greeting, spec, ...rest } =
parsedBody;
const {
chatGptLabel,
promptPrefix,
maxContextTokens,
resendFiles,
imageDetail,
iconURL,
greeting,
spec,
...rest
} = parsedBody;
const endpointOption = {
endpoint,
endpointType,
@@ -11,6 +20,7 @@ const buildOptions = (endpoint, parsedBody, endpointType) => {
iconURL,
greeting,
spec,
maxContextTokens,
modelOptions: {
...rest,
},

View File

@@ -7,6 +7,7 @@ const buildOptions = (endpoint, parsedBody) => {
iconURL,
greeting,
spec,
maxContextTokens,
...modelOptions
} = parsedBody;
const endpointOption = {
@@ -21,6 +22,7 @@ const buildOptions = (endpoint, parsedBody) => {
iconURL,
greeting,
spec,
maxContextTokens,
modelOptions,
};

View File

@@ -1,6 +1,15 @@
const buildOptions = (endpoint, parsedBody) => {
const { chatGptLabel, promptPrefix, resendFiles, imageDetail, iconURL, greeting, spec, ...rest } =
parsedBody;
const {
chatGptLabel,
promptPrefix,
maxContextTokens,
resendFiles,
imageDetail,
iconURL,
greeting,
spec,
...rest
} = parsedBody;
const endpointOption = {
endpoint,
chatGptLabel,
@@ -10,6 +19,7 @@ const buildOptions = (endpoint, parsedBody) => {
iconURL,
greeting,
spec,
maxContextTokens,
modelOptions: {
...rest,
},

View File

@@ -23,7 +23,7 @@ async function fetchImageToBase64(url) {
}
}
const base64Only = new Set([EModelEndpoint.google, EModelEndpoint.anthropic]);
const base64Only = new Set([EModelEndpoint.google, EModelEndpoint.anthropic, 'Ollama', 'ollama']);
/**
* Encodes and formats the given files.

View File

@@ -2,60 +2,11 @@ const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { EModelEndpoint, defaultModels, CacheKeys } = require('librechat-data-provider');
const { extractBaseURL, inputSchema, processModelData, logAxiosError } = require('~/utils');
const { OllamaClient } = require('~/app/clients/OllamaClient');
const getLogStores = require('~/cache/getLogStores');
const { logger } = require('~/config');
const { openAIApiKey, userProvidedOpenAI } = require('./Config/EndpointService').config;
/**
* Extracts the base URL from the provided URL.
* @param {string} fullURL - The full URL.
* @returns {string} The base URL.
*/
function deriveBaseURL(fullURL) {
try {
const parsedUrl = new URL(fullURL);
const protocol = parsedUrl.protocol;
const hostname = parsedUrl.hostname;
const port = parsedUrl.port;
// Check if the parsed URL components are meaningful
if (!protocol || !hostname) {
return fullURL;
}
// Reconstruct the base URL
return `${protocol}//${hostname}${port ? `:${port}` : ''}`;
} catch (error) {
logger.error('Failed to derive base URL', error);
return fullURL; // Return the original URL in case of any exception
}
}
/**
* Fetches Ollama models from the specified base API path.
* @param {string} baseURL
* @returns {Promise<string[]>} The Ollama models.
*/
const fetchOllamaModels = async (baseURL) => {
let models = [];
if (!baseURL) {
return models;
}
try {
const ollamaEndpoint = deriveBaseURL(baseURL);
/** @type {Promise<AxiosResponse<OllamaListResponse>>} */
const response = await axios.get(`${ollamaEndpoint}/api/tags`);
models = response.data.models.map((tag) => tag.name);
return models;
} catch (error) {
const logMessage =
'Failed to fetch models from Ollama API. If you are not using Ollama directly, and instead, through some aggregator or reverse proxy that handles fetching via OpenAI spec, ensure the name of the endpoint doesn\'t start with `ollama` (case-insensitive).';
logger.error(logMessage, error);
return [];
}
};
/**
* Fetches OpenAI models from the specified base API path or Azure, based on the provided configuration.
*
@@ -92,7 +43,7 @@ const fetchModels = async ({
}
if (name && name.toLowerCase().startsWith('ollama')) {
return await fetchOllamaModels(baseURL);
return await OllamaClient.fetchModels(baseURL);
}
try {
@@ -281,7 +232,6 @@ const getGoogleModels = () => {
module.exports = {
fetchModels,
deriveBaseURL,
getOpenAIModels,
getChatGPTBrowserModels,
getAnthropicModels,

View File

@@ -1,7 +1,7 @@
const axios = require('axios');
const { logger } = require('~/config');
const { fetchModels, getOpenAIModels, deriveBaseURL } = require('./ModelService');
const { fetchModels, getOpenAIModels } = require('./ModelService');
jest.mock('~/utils', () => {
const originalUtils = jest.requireActual('~/utils');
return {
@@ -329,47 +329,3 @@ describe('fetchModels with Ollama specific logic', () => {
);
});
});
describe('deriveBaseURL', () => {
it('should extract the base URL correctly from a full URL with a port', () => {
const fullURL = 'https://example.com:8080/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('https://example.com:8080');
});
it('should extract the base URL correctly from a full URL without a port', () => {
const fullURL = 'https://example.com/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('https://example.com');
});
it('should handle URLs using the HTTP protocol', () => {
const fullURL = 'http://example.com:3000/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('http://example.com:3000');
});
it('should return only the protocol and hostname if no port is specified', () => {
const fullURL = 'http://example.com/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('http://example.com');
});
it('should handle URLs with uncommon protocols', () => {
const fullURL = 'ftp://example.com:2121/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('ftp://example.com:2121');
});
it('should handle edge case where URL ends with a slash', () => {
const fullURL = 'https://example.com/';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('https://example.com');
});
it('should return the original URL if the URL is invalid', () => {
const invalidURL = 'htp:/example.com:8080';
const result = deriveBaseURL(invalidURL);
expect(result).toBe(invalidURL);
});
});

View File

@@ -7,6 +7,13 @@
* @typedef {import('openai').OpenAI} OpenAI
* @memberof typedefs
*/
/**
* @exports Ollama
* @typedef {import('ollama').Ollama} Ollama
* @memberof typedefs
*/
/**
* @exports AxiosResponse
* @typedef {import('axios').AxiosResponse} AxiosResponse
@@ -62,8 +69,14 @@
*/
/**
* @exports ChatCompletionMessages
* @typedef {import('openai').OpenAI.ChatCompletionMessageParam} ChatCompletionMessages
* @exports OllamaMessage
* @typedef {import('ollama').Message} OllamaMessage
* @memberof typedefs
*/
/**
* @exports ChatCompletionMessage
* @typedef {import('openai').OpenAI.ChatCompletionMessageParam} ChatCompletionMessage
* @memberof typedefs
*/
@@ -1153,7 +1166,7 @@
/**
* Main entrypoint for API completion calls
* @callback sendCompletion
* @param {Array<ChatCompletionMessages> | string} payload - The messages or prompt to send to the model
* @param {Array<ChatCompletionMessage> | string} payload - The messages or prompt to send to the model
* @param {object} opts - Options for the completion
* @param {onTokenProgress} opts.onProgress - Callback function to handle token progress
* @param {AbortController} opts.abortController - AbortController instance
@@ -1164,7 +1177,7 @@
/**
* Legacy completion handler for OpenAI API.
* @callback getCompletion
* @param {Array<ChatCompletionMessages> | string} input - Array of messages or a single prompt string
* @param {Array<ChatCompletionMessage> | string} input - Array of messages or a single prompt string
* @param {(event: object | string) => Promise<void>} onProgress - SSE progress handler
* @param {onTokenProgress} onTokenProgress - Token progress handler
* @param {AbortController} [abortController] - AbortController instance

View File

@@ -0,0 +1,28 @@
const { logger } = require('~/config');
/**
* Extracts the base URL from the provided URL.
* @param {string} fullURL - The full URL.
* @returns {string} The base URL.
*/
function deriveBaseURL(fullURL) {
try {
const parsedUrl = new URL(fullURL);
const protocol = parsedUrl.protocol;
const hostname = parsedUrl.hostname;
const port = parsedUrl.port;
// Check if the parsed URL components are meaningful
if (!protocol || !hostname) {
return fullURL;
}
// Reconstruct the base URL
return `${protocol}//${hostname}${port ? `:${port}` : ''}`;
} catch (error) {
logger.error('Failed to derive base URL', error);
return fullURL; // Return the original URL in case of any exception
}
}
module.exports = deriveBaseURL;

View File

@@ -0,0 +1,74 @@
const axios = require('axios');
const deriveBaseURL = require('./deriveBaseURL');
jest.mock('~/utils', () => {
const originalUtils = jest.requireActual('~/utils');
return {
...originalUtils,
processModelData: jest.fn((...args) => {
return originalUtils.processModelData(...args);
}),
};
});
jest.mock('axios');
jest.mock('~/cache/getLogStores', () =>
jest.fn().mockImplementation(() => ({
get: jest.fn().mockResolvedValue(undefined),
set: jest.fn().mockResolvedValue(true),
})),
);
jest.mock('~/config', () => ({
logger: {
error: jest.fn(),
},
}));
axios.get.mockResolvedValue({
data: {
data: [{ id: 'model-1' }, { id: 'model-2' }],
},
});
describe('deriveBaseURL', () => {
it('should extract the base URL correctly from a full URL with a port', () => {
const fullURL = 'https://example.com:8080/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('https://example.com:8080');
});
it('should extract the base URL correctly from a full URL without a port', () => {
const fullURL = 'https://example.com/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('https://example.com');
});
it('should handle URLs using the HTTP protocol', () => {
const fullURL = 'http://example.com:3000/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('http://example.com:3000');
});
it('should return only the protocol and hostname if no port is specified', () => {
const fullURL = 'http://example.com/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('http://example.com');
});
it('should handle URLs with uncommon protocols', () => {
const fullURL = 'ftp://example.com:2121/path?query=123';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('ftp://example.com:2121');
});
it('should handle edge case where URL ends with a slash', () => {
const fullURL = 'https://example.com/';
const baseURL = deriveBaseURL(fullURL);
expect(baseURL).toEqual('https://example.com');
});
it('should return the original URL if the URL is invalid', () => {
const invalidURL = 'htp:/example.com:8080';
const result = deriveBaseURL(invalidURL);
expect(result).toBe(invalidURL);
});
});

View File

@@ -1,6 +1,7 @@
const loadYaml = require('./loadYaml');
const tokenHelpers = require('./tokens');
const azureUtils = require('./azureUtils');
const deriveBaseURL = require('./deriveBaseURL');
const logAxiosError = require('./logAxiosError');
const extractBaseURL = require('./extractBaseURL');
const findMessageContent = require('./findMessageContent');
@@ -9,6 +10,7 @@ module.exports = {
loadYaml,
...tokenHelpers,
...azureUtils,
deriveBaseURL,
logAxiosError,
extractBaseURL,
findMessageContent,

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "@librechat/frontend",
"version": "0.7.1",
"version": "0.7.2",
"description": "",
"type": "module",
"scripts": {

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,32 @@
<svg width="512" height="512" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="linearGradient22708">
<stop stop-color="#21facf" offset="0"/>
<stop stop-color="#0970ef" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient6949" x1="68.454" x2="198.59" y1="246.73" y2="96.35" gradientTransform="translate(-5.754,-56.594)" gradientUnits="userSpaceOnUse">
<stop stop-color="#72004e" offset="0"/>
<stop stop-color="#0015b1" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient22718" x1="56.735" x2="155.2" y1="246.96" y2="58.575" gradientUnits="userSpaceOnUse">
<stop stop-color="#4f00da" offset="0"/>
<stop stop-color="#e5311b" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient23463" x1="68.454" x2="198.59" y1="246.73" y2="96.35" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient22708"/>
<linearGradient id="linearGradient903" x1="54.478" x2="192.1" y1="247.56" y2="9.8095" gradientTransform="matrix(.87923 0 0 .87923 -9.551 48.787)" gradientUnits="userSpaceOnUse">
<stop stop-color="#dc180d" offset="0"/>
<stop stop-color="#f96e20" offset=".5"/>
<stop stop-color="#f4ce41" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient918" x1="39.468" x2="154.99" y1="204.22" y2="124.47" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient22708"/>
</defs>
<g transform="matrix(2.473 0 0 2.473 -4.8978 -4.8812)">
<path transform="translate(-5.5496,-57.412)" d="m148.16 59.393c-7.7098 9.3985-19.951 42.888-20.696 49.204-0.16994 4.6737 1.3731 14.231 0.67182 15.805-0.71909 1.6134-5.117-9.4461-7.2151-6.3266-12.219 18.168-10.7 17.731-15.582 31.378-1.8357 5.1315-0.42447 21.99-1.5666 23.773-1.273 1.9866-3.962-12.31-6.8063-9.236-11.603 12.54-16.279 20.379-22.336 30.607-3.3589 5.6725-2.1817 23.33-3.506 24.674-1.3023 1.3215-3.8566-18.326-7.6437-14.309-8.5193 9.038-14.054 13.441-18.946 19.252-5.1981 6.1739-0.78251 17.584-5.0672 35.383l0.1448 0.22073c77.447-50.308 101.52-127.16 107.61-181.19-0.68051 63.93-29.41 142.78-105.33 184.65l0.1127 0.17141c20.241-2.181 22.307 10.458 44.562-4.2837 55.792-48.277 81.856-124.29 61.593-199.78z" display="none" fill="url(#linearGradient903)"/>
<path transform="translate(-5.5498,-57.412)" d="m148.16 59.393c-7.7098 9.3985-19.951 42.888-20.696 49.204-0.16994 4.6737 1.3731 14.231 0.67182 15.805-0.71909 1.6134-5.117-9.4461-7.2151-6.3266-12.219 18.168-10.7 17.731-15.582 31.378-1.8357 5.1315-0.42447 21.99-1.5666 23.773-1.273 1.9866-3.962-12.31-6.8063-9.236-11.603 12.54-16.279 20.379-22.336 30.607-3.3589 5.6725-2.1817 23.33-3.506 24.674-1.3023 1.3215-3.8566-18.326-7.6437-14.309-8.5193 9.038-14.054 13.441-18.946 19.252-5.1981 6.1739-0.78251 17.584-5.0672 35.383l0.1448 0.22073c77.447-50.308 101.52-127.16 107.61-181.19-0.68051 63.93-29.41 142.78-105.33 184.65l0.1127 0.17141c20.241-2.181 22.307 10.458 44.562-4.2837 55.792-48.277 81.856-124.29 61.593-199.78z" fill="url(#linearGradient918)"/>
<g transform="translate(0 2.0218e-5)">
<path transform="translate(-5.7543,-56.594)" d="m111.25 81.024c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" display="none" fill="url(#linearGradient22718)"/>
<path transform="translate(-5.754,-56.594)" d="m111.25 81.024c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" display="none" fill="url(#linearGradient23463)"/>
<path d="m105.5 24.43c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" fill="url(#linearGradient6949)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -1,4 +1,5 @@
import { FileSources } from 'librechat-data-provider';
import type * as InputNumberPrimitive from 'rc-input-number';
import type { ColumnDef } from '@tanstack/react-table';
import type { SetterOrUpdater } from 'recoil';
import type {
@@ -115,6 +116,8 @@ export type TSetExample = (
newValue: number | string | boolean | null,
) => void;
export type OnInputNumberChange = InputNumberPrimitive.InputNumberProps['onChange'];
export const defaultDebouncedDelay = 450;
export enum ESide {

View File

@@ -96,7 +96,7 @@ function Login() {
const privacyPolicyRender = privacyPolicy?.externalUrl && (
<a
className="text-xs font-medium text-green-500"
className="text-sm text-green-500"
href={privacyPolicy.externalUrl}
target={privacyPolicy.openNewTab ? '_blank' : undefined}
rel="noreferrer"
@@ -107,7 +107,7 @@ function Login() {
const termsOfServiceRender = termsOfService?.externalUrl && (
<a
className="text-xs font-medium text-green-500"
className="text-sm text-green-500"
href={termsOfService.externalUrl}
target={termsOfService.openNewTab ? '_blank' : undefined}
rel="noreferrer"
@@ -117,57 +117,62 @@ function Login() {
);
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0">
<div className="absolute bottom-0 left-0 m-4">
<div className="relative flex min-h-screen flex-col bg-white dark:bg-gray-900">
<div className="mt-12 h-24 w-full bg-cover">
<img src="/assets/logo.svg" className="h-full w-full object-contain" alt="Logo" />
</div>
<div className="absolute bottom-0 left-0 md:m-4">
<ThemeSelector />
</div>
<div className="mt-6 w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
<h1
className="mb-4 text-center text-3xl font-semibold text-black dark:text-white"
style={{ userSelect: 'none' }}
>
{localize('com_auth_welcome_back')}
</h1>
{error && (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
<div className="flex flex-grow items-center justify-center">
<div className="w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
<h1
className="mb-4 text-center text-3xl font-semibold text-black dark:text-white"
style={{ userSelect: 'none' }}
>
{localize(getLoginError(error))}
</div>
)}
{startupConfig.emailLoginEnabled && <LoginForm onSubmit={login} />}
{startupConfig.registrationEnabled && (
<p className="my-4 text-center text-sm font-light text-gray-700 dark:text-white">
{' '}
{localize('com_auth_no_account')}{' '}
<a href="/register" className="p-1 font-medium text-green-500">
{localize('com_auth_sign_up')}
</a>
</p>
)}
{startupConfig.socialLoginEnabled && (
<>
{startupConfig.emailLoginEnabled && (
<>
<div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase">
<div className="absolute bg-white px-3 text-xs text-black dark:bg-gray-900 dark:text-white">
Or
</div>
</div>
<div className="mt-8" />
</>
)}
<div className="mt-2">
{socialLogins.map((provider) => providerComponents[provider] || null)}
{localize('com_auth_welcome_back')}
</h1>
{error && (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
>
{localize(getLoginError(error))}
</div>
</>
)}
)}
{startupConfig.emailLoginEnabled && <LoginForm onSubmit={login} />}
{startupConfig.registrationEnabled && (
<p className="my-4 text-center text-sm font-light text-gray-700 dark:text-white">
{' '}
{localize('com_auth_no_account')}{' '}
<a href="/register" className="p-1 text-green-500">
{localize('com_auth_sign_up')}
</a>
</p>
)}
{startupConfig.socialLoginEnabled && (
<>
{startupConfig.emailLoginEnabled && (
<>
<div className="relative mt-6 flex w-full items-center justify-center border border-t border-gray-300 uppercase dark:border-gray-600">
<div className="absolute bg-white px-3 text-xs text-black dark:bg-gray-900 dark:text-white">
Or
</div>
</div>
<div className="mt-8" />
</>
)}
<div className="mt-2">
{socialLogins.map((provider) => providerComponents[provider] || null)}
</div>
</>
)}
</div>
</div>
<div className="flex justify-center gap-4 align-middle">
<div className="align-end m-4 flex justify-center gap-2">
{privacyPolicyRender}
{privacyPolicyRender && termsOfServiceRender && (
<div className="border-r-[1px] border-gray-300" />
<div className="border-r-[1px] border-gray-300 dark:border-gray-600" />
)}
{termsOfServiceRender}
</div>

View File

@@ -18,7 +18,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit }) => {
const renderError = (fieldName: string) => {
const errorMessage = errors[fieldName]?.message;
return errorMessage ? (
<span role="alert" className="mt-1 text-sm text-black dark:text-white">
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{String(errorMessage)}
</span>
) : null;
@@ -44,12 +44,12 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit }) => {
pattern: { value: /\S+@\S+\.\S+/, message: localize('com_auth_email_pattern') },
})}
aria-invalid={!!errors.email}
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500"
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
placeholder=" "
/>
<label
htmlFor="email"
className="pointer-events-none absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-100 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500 dark:text-gray-200"
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
>
{localize('com_auth_email_address')}
</label>
@@ -69,19 +69,19 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit }) => {
maxLength: { value: 128, message: localize('com_auth_password_max_length') },
})}
aria-invalid={!!errors.password}
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500"
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
placeholder=" "
/>
<label
htmlFor="password"
className="pointer-events-none absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-100 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500 dark:text-gray-200"
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
>
{localize('com_auth_password')}
</label>
</div>
{renderError('password')}
</div>
<a href="/forgot-password" className="text-sm font-medium text-green-500">
<a href="/forgot-password" className="text-sm text-green-500">
{localize('com_auth_password_forgot')}
</a>
<div className="mt-6">

View File

@@ -64,19 +64,19 @@ const Registration: React.FC = () => {
validation,
)}
aria-invalid={!!errors[id]}
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500"
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
placeholder=" "
data-testid={id}
></input>
<label
htmlFor={id}
className="pointer-events-none absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-100 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500 dark:text-gray-200"
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
>
{localize(label)}
</label>
</div>
{errors[id] && (
<span role="alert" className="mt-1 text-sm text-black dark:text-white">
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{String(errors[id]?.message) ?? ''}
</span>
)}
@@ -147,117 +147,154 @@ const Registration: React.FC = () => {
),
};
const privacyPolicy = startupConfig.interface?.privacyPolicy;
const termsOfService = startupConfig.interface?.termsOfService;
const privacyPolicyRender = privacyPolicy?.externalUrl && (
<a
className="text-sm text-green-500"
href={privacyPolicy.externalUrl}
target={privacyPolicy.openNewTab ? '_blank' : undefined}
rel="noreferrer"
>
{localize('com_ui_privacy_policy')}
</a>
);
const termsOfServiceRender = termsOfService?.externalUrl && (
<a
className="text-sm text-green-500"
href={termsOfService.externalUrl}
target={termsOfService.openNewTab ? '_blank' : undefined}
rel="noreferrer"
>
{localize('com_ui_terms_of_service')}
</a>
);
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0">
<div className="absolute bottom-0 left-0 m-4">
<div className="relative flex min-h-screen flex-col bg-white dark:bg-gray-900">
<div className="mt-12 h-24 w-full bg-cover">
<img src="/assets/logo.svg" className="h-full w-full object-contain" alt="Logo" />
</div>
<div className="absolute bottom-0 left-0 md:m-4">
<ThemeSelector />
</div>
<div className="mt-6 w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
<h1
className="mb-4 text-center text-3xl font-semibold text-black dark:text-white"
style={{ userSelect: 'none' }}
>
{localize('com_auth_create_account')}
</h1>
{error && (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
data-testid="registration-error"
<div className="flex flex-grow items-center justify-center">
<div className="w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
<h1
className="mb-4 text-center text-3xl font-semibold text-black dark:text-white"
style={{ userSelect: 'none' }}
>
{localize('com_auth_error_create')} {errorMessage}
</div>
)}
<form
className="mt-6"
aria-label="Registration form"
method="POST"
onSubmit={handleSubmit(onRegisterUserFormSubmit)}
>
{renderInput('name', 'com_auth_full_name', 'text', {
required: localize('com_auth_name_required'),
minLength: {
value: 3,
message: localize('com_auth_name_min_length'),
},
maxLength: {
value: 80,
message: localize('com_auth_name_max_length'),
},
})}
{renderInput('username', 'com_auth_username', 'text', {
minLength: {
value: 2,
message: localize('com_auth_username_min_length'),
},
maxLength: {
value: 80,
message: localize('com_auth_username_max_length'),
},
})}
{renderInput('email', 'com_auth_email', 'email', {
required: localize('com_auth_email_required'),
minLength: {
value: 1,
message: localize('com_auth_email_min_length'),
},
maxLength: {
value: 120,
message: localize('com_auth_email_max_length'),
},
pattern: {
value: /\S+@\S+\.\S+/,
message: localize('com_auth_email_pattern'),
},
})}
{renderInput('password', 'com_auth_password', 'password', {
required: localize('com_auth_password_required'),
minLength: {
value: 8,
message: localize('com_auth_password_min_length'),
},
maxLength: {
value: 128,
message: localize('com_auth_password_max_length'),
},
})}
{renderInput('confirm_password', 'com_auth_password_confirm', 'password', {
validate: (value) => value === password || localize('com_auth_password_not_match'),
})}
<div className="mt-6">
<button
disabled={Object.keys(errors).length > 0}
type="submit"
aria-label="Submit registration"
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-550 focus:bg-green-550 focus:outline-none disabled:cursor-not-allowed disabled:hover:bg-green-500"
{localize('com_auth_create_account')}
</h1>
{error && (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
data-testid="registration-error"
>
{localize('com_auth_continue')}
</button>
</div>
</form>
<p className="my-4 text-center text-sm font-light text-gray-700 dark:text-white">
{localize('com_auth_already_have_account')}{' '}
<a href="/login" aria-label="Login" className="p-1 font-medium text-green-500">
{localize('com_auth_login')}
</a>
</p>
{startupConfig.socialLoginEnabled && (
<>
{startupConfig.emailLoginEnabled && (
<>
<div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase">
<div className="absolute bg-white px-3 text-xs text-black dark:bg-gray-900 dark:text-white">
Or
</div>
</div>
<div className="mt-8" />
</>
)}
<div className="mt-2">
{socialLogins.map((provider) => providerComponents[provider] || null)}
{localize('com_auth_error_create')} {errorMessage}
</div>
</>
)}
<form
className="mt-6"
aria-label="Registration form"
method="POST"
onSubmit={handleSubmit(onRegisterUserFormSubmit)}
>
{renderInput('name', 'com_auth_full_name', 'text', {
required: localize('com_auth_name_required'),
minLength: {
value: 3,
message: localize('com_auth_name_min_length'),
},
maxLength: {
value: 80,
message: localize('com_auth_name_max_length'),
},
})}
{renderInput('username', 'com_auth_username', 'text', {
minLength: {
value: 2,
message: localize('com_auth_username_min_length'),
},
maxLength: {
value: 80,
message: localize('com_auth_username_max_length'),
},
})}
{renderInput('email', 'com_auth_email', 'email', {
required: localize('com_auth_email_required'),
minLength: {
value: 1,
message: localize('com_auth_email_min_length'),
},
maxLength: {
value: 120,
message: localize('com_auth_email_max_length'),
},
pattern: {
value: /\S+@\S+\.\S+/,
message: localize('com_auth_email_pattern'),
},
})}
{renderInput('password', 'com_auth_password', 'password', {
required: localize('com_auth_password_required'),
minLength: {
value: 8,
message: localize('com_auth_password_min_length'),
},
maxLength: {
value: 128,
message: localize('com_auth_password_max_length'),
},
})}
{renderInput('confirm_password', 'com_auth_password_confirm', 'password', {
validate: (value) => value === password || localize('com_auth_password_not_match'),
})}
<div className="mt-6">
<button
disabled={Object.keys(errors).length > 0}
type="submit"
aria-label="Submit registration"
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-550 focus:bg-green-550 focus:outline-none disabled:cursor-not-allowed disabled:hover:bg-green-500"
>
{localize('com_auth_continue')}
</button>
</div>
</form>
<p className="my-4 text-center text-sm font-light text-gray-700 dark:text-white">
{localize('com_auth_already_have_account')}{' '}
<a href="/login" aria-label="Login" className="p-1 text-green-500">
{localize('com_auth_login')}
</a>
</p>
{startupConfig.socialLoginEnabled && (
<>
{startupConfig.emailLoginEnabled && (
<>
<div className="relative mt-6 flex w-full items-center justify-center border border-t border-gray-300 uppercase dark:border-gray-600">
<div className="absolute bg-white px-3 text-xs text-black dark:bg-gray-900 dark:text-white">
Or
</div>
</div>
<div className="mt-8" />
</>
)}
<div className="mt-2">
{socialLogins.map((provider) => providerComponents[provider] || null)}
</div>
</>
)}
</div>
</div>
<div className="align-end m-4 flex justify-center gap-2">
{privacyPolicyRender}
{privacyPolicyRender && termsOfServiceRender && (
<div className="border-r-[1px] border-gray-300 dark:border-gray-600" />
)}
{termsOfServiceRender}
</div>
</div>
);

View File

@@ -49,7 +49,7 @@ function RequestPasswordReset() {
setBodyText(
<span>
{localize('com_auth_click')}{' '}
<a className="font-medium text-green-500 hover:underline" href={resetLink}>
<a className="text-green-500 hover:underline" href={resetLink}>
{localize('com_auth_here')}
</a>{' '}
{localize('com_auth_to_reset_your_password')}
@@ -66,7 +66,7 @@ function RequestPasswordReset() {
if (bodyText) {
return (
<div
className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700 dark:bg-gray-900 dark:text-white"
className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700 dark:bg-green-900 dark:text-white"
role="alert"
>
{bodyText}
@@ -103,18 +103,18 @@ function RequestPasswordReset() {
},
})}
aria-invalid={!!errors.email}
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500"
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
placeholder=" "
></input>
<label
htmlFor="email"
className="pointer-events-none absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-100 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500 dark:text-gray-200"
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
>
{localize('com_auth_email_address')}
</label>
</div>
{errors.email && (
<span role="alert" className="mt-1 text-sm text-black dark:text-white">
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{/* @ts-ignore not sure why */}
{errors.email.message}
</span>
@@ -129,7 +129,7 @@ function RequestPasswordReset() {
{localize('com_auth_continue')}
</button>
<div className="mt-4 flex justify-center">
<a href="/login" className="text-sm font-medium text-green-500">
<a href="/login" className="text-sm text-green-500">
{localize('com_auth_back_to_login')}
</a>
</div>
@@ -139,24 +139,61 @@ function RequestPasswordReset() {
}
};
const privacyPolicy = config.data?.interface?.privacyPolicy;
const termsOfService = config.data?.interface?.termsOfService;
const privacyPolicyRender = privacyPolicy?.externalUrl && (
<a
className="text-sm text-green-500"
href={privacyPolicy.externalUrl}
target={privacyPolicy.openNewTab ? '_blank' : undefined}
rel="noreferrer"
>
{localize('com_ui_privacy_policy')}
</a>
);
const termsOfServiceRender = termsOfService?.externalUrl && (
<a
className="text-sm text-green-500"
href={termsOfService.externalUrl}
target={termsOfService.openNewTab ? '_blank' : undefined}
rel="noreferrer"
>
{localize('com_ui_terms_of_service')}
</a>
);
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0">
<div className="absolute bottom-0 left-0 m-4">
<div className="relative flex min-h-screen flex-col bg-white dark:bg-gray-900">
<div className="mt-12 h-24 w-full bg-cover">
<img src="/assets/logo.svg" className="h-full w-full object-contain" alt="Logo" />
</div>
<div className="absolute bottom-0 left-0 md:m-4">
<ThemeSelector />
</div>
<div className="mt-5 w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
<h1 className="mb-4 text-center text-3xl font-semibold text-black dark:text-white">
{headerText}
</h1>
{requestError && (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
>
{localize('com_auth_error_reset_password')}
</div>
<div className="flex flex-grow items-center justify-center">
<div className="w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
<h1 className="mb-4 text-center text-3xl font-semibold text-black dark:text-white">
{headerText}
</h1>
{requestError && (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
>
{localize('com_auth_error_reset_password')}
</div>
)}
{renderFormContent()}
</div>
</div>
<div className="align-end m-4 flex justify-center gap-2">
{privacyPolicyRender}
{privacyPolicyRender && termsOfServiceRender && (
<div className="border-r-[1px] border-gray-300 dark:border-gray-600" />
)}
{renderFormContent()}
{termsOfServiceRender}
</div>
</div>
);

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useResetPasswordMutation } from 'librechat-data-provider/react-query';
import { useGetStartupConfig, useResetPasswordMutation } from 'librechat-data-provider/react-query';
import type { TResetPassword } from 'librechat-data-provider';
import { ThemeSelector } from '~/components/ui';
import { useLocalize } from '~/hooks';
@@ -15,6 +15,7 @@ function ResetPassword() {
formState: { errors },
} = useForm<TResetPassword>();
const resetPassword = useResetPasswordMutation();
const config = useGetStartupConfig();
const [resetError, setResetError] = useState<boolean>(false);
const [params] = useSearchParams();
const navigate = useNavigate();
@@ -28,6 +29,31 @@ function ResetPassword() {
});
};
const privacyPolicy = config.data?.interface?.privacyPolicy;
const termsOfService = config.data?.interface?.termsOfService;
const privacyPolicyRender = privacyPolicy?.externalUrl && (
<a
className="text-sm text-green-500"
href={privacyPolicy.externalUrl}
target={privacyPolicy.openNewTab ? '_blank' : undefined}
rel="noreferrer"
>
{localize('com_ui_privacy_policy')}
</a>
);
const termsOfServiceRender = termsOfService?.externalUrl && (
<a
className="text-sm text-green-500"
href={termsOfService.externalUrl}
target={termsOfService.openNewTab ? '_blank' : undefined}
rel="noreferrer"
>
{localize('com_ui_terms_of_service')}
</a>
);
if (resetPassword.isSuccess) {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0">
@@ -56,134 +82,146 @@ function ResetPassword() {
);
} else {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0">
<div className="absolute bottom-0 left-0 m-4">
<div className="relative flex min-h-screen flex-col bg-white dark:bg-gray-900">
<div className="mt-12 h-24 w-full bg-cover">
<img src="/assets/logo.svg" className="h-full w-full object-contain" alt="Logo" />
</div>
<div className="absolute bottom-0 left-0 md:m-4">
<ThemeSelector />
</div>
<div className="mt-6 w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
<h1 className="mb-4 text-center text-3xl font-semibold text-black dark:text-white">
{localize('com_auth_reset_password')}
</h1>
{resetError && (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
>
{localize('com_auth_error_invalid_reset_token')}{' '}
<a className="font-semibold text-green-600 hover:underline" href="/forgot-password">
{localize('com_auth_click_here')}
</a>{' '}
{localize('com_auth_to_try_again')}
</div>
)}
<form
className="mt-6"
aria-label="Password reset form"
method="POST"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-2">
<div className="relative">
<input
type="hidden"
id="token"
// @ts-ignore - Type 'string | null' is not assignable to type 'string | number | readonly string[] | undefined'
value={params.get('token')}
{...register('token', { required: 'Unable to process: No valid reset token' })}
/>
<input
type="hidden"
id="userId"
// @ts-ignore - Type 'string | null' is not assignable to type 'string | number | readonly string[] | undefined'
value={params.get('userId')}
{...register('userId', { required: 'Unable to process: No valid user id' })}
/>
<input
type="password"
id="password"
autoComplete="current-password"
aria-label={localize('com_auth_password')}
{...register('password', {
required: localize('com_auth_password_required'),
minLength: {
value: 8,
message: localize('com_auth_password_min_length'),
},
maxLength: {
value: 128,
message: localize('com_auth_password_max_length'),
},
})}
aria-invalid={!!errors.password}
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500"
placeholder=" "
></input>
<label
htmlFor="password"
className="pointer-events-none absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-100 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500 dark:text-gray-200"
>
{localize('com_auth_password')}
</label>
</div>
{errors.password && (
<span role="alert" className="mt-1 text-sm text-black dark:text-white">
{/* @ts-ignore not sure why */}
{errors.password.message}
</span>
)}
</div>
<div className="mb-2">
<div className="relative">
<input
type="password"
id="confirm_password"
aria-label={localize('com_auth_password_confirm')}
{...register('confirm_password', {
validate: (value) =>
value === password || localize('com_auth_password_not_match'),
})}
aria-invalid={!!errors.confirm_password}
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-black/10 bg-white px-2.5 pb-2.5 pt-5 text-sm text-gray-800 focus:border-green-500 focus:outline-none dark:border-white/20 dark:bg-gray-900 dark:text-white dark:focus:border-green-500"
placeholder=" "
></input>
<label
htmlFor="confirm_password"
className="pointer-events-none absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-100 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500 dark:text-gray-200"
>
{localize('com_auth_password_confirm')}
</label>
</div>
{errors.confirm_password && (
<span role="alert" className="mt-1 text-sm text-black dark:text-white">
{/* @ts-ignore not sure why */}
{errors.confirm_password.message}
</span>
)}
{errors.token && (
<span role="alert" className="mt-1 text-sm text-black dark:text-white">
{/* @ts-ignore not sure why */}
{errors.token.message}
</span>
)}
{errors.userId && (
<span role="alert" className="mt-1 text-sm text-black dark:text-white">
{/* @ts-ignore not sure why */}
{errors.userId.message}
</span>
)}
</div>
<div className="mt-6">
<button
disabled={!!errors.password || !!errors.confirm_password}
type="submit"
aria-label={localize('com_auth_submit_registration')}
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-all duration-300 hover:bg-green-550 focus:bg-green-550 focus:outline-none"
<div className="flex flex-grow items-center justify-center">
<div className="w-authPageWidth overflow-hidden bg-white px-6 py-4 dark:bg-gray-900 sm:max-w-md sm:rounded-lg">
<h1 className="mb-4 text-center text-3xl font-semibold text-black dark:text-white">
{localize('com_auth_reset_password')}
</h1>
{resetError && (
<div
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
role="alert"
>
{localize('com_auth_continue')}
</button>
</div>
</form>
{localize('com_auth_error_invalid_reset_token')}{' '}
<a className="font-semibold text-green-600 hover:underline" href="/forgot-password">
{localize('com_auth_click_here')}
</a>{' '}
{localize('com_auth_to_try_again')}
</div>
)}
<form
className="mt-6"
aria-label="Password reset form"
method="POST"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-2">
<div className="relative">
<input
type="hidden"
id="token"
// @ts-ignore - Type 'string | null' is not assignable to type 'string | number | readonly string[] | undefined'
value={params.get('token')}
{...register('token', { required: 'Unable to process: No valid reset token' })}
/>
<input
type="hidden"
id="userId"
// @ts-ignore - Type 'string | null' is not assignable to type 'string | number | readonly string[] | undefined'
value={params.get('userId')}
{...register('userId', { required: 'Unable to process: No valid user id' })}
/>
<input
type="password"
id="password"
autoComplete="current-password"
aria-label={localize('com_auth_password')}
{...register('password', {
required: localize('com_auth_password_required'),
minLength: {
value: 8,
message: localize('com_auth_password_min_length'),
},
maxLength: {
value: 128,
message: localize('com_auth_password_max_length'),
},
})}
aria-invalid={!!errors.password}
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
placeholder=" "
></input>
<label
htmlFor="password"
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
>
{localize('com_auth_password')}
</label>
</div>
{errors.password && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{/* @ts-ignore not sure why */}
{errors.password.message}
</span>
)}
</div>
<div className="mb-2">
<div className="relative">
<input
type="password"
id="confirm_password"
aria-label={localize('com_auth_password_confirm')}
{...register('confirm_password', {
validate: (value) =>
value === password || localize('com_auth_password_not_match'),
})}
aria-invalid={!!errors.confirm_password}
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
placeholder=" "
></input>
<label
htmlFor="confirm_password"
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
>
{localize('com_auth_password_confirm')}
</label>
</div>
{errors.confirm_password && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{/* @ts-ignore not sure why */}
{errors.confirm_password.message}
</span>
)}
{errors.token && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{/* @ts-ignore not sure why */}
{errors.token.message}
</span>
)}
{errors.userId && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{/* @ts-ignore not sure why */}
{errors.userId.message}
</span>
)}
</div>
<div className="mt-6">
<button
disabled={!!errors.password || !!errors.confirm_password}
type="submit"
aria-label={localize('com_auth_submit_registration')}
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-all duration-300 hover:bg-green-550 focus:bg-green-550 focus:outline-none"
>
{localize('com_auth_continue')}
</button>
</div>
</form>
</div>
</div>
<div className="align-end m-4 flex justify-center gap-2">
{privacyPolicyRender}
{privacyPolicyRender && termsOfServiceRender && (
<div className="border-r-[1px] border-gray-300 dark:border-gray-600" />
)}
{termsOfServiceRender}
</div>
</div>
);

View File

@@ -15,6 +15,7 @@ const SocialButton = ({ id, enabled, serverDomain, oauthPath, Icon, label }) =>
const handleMouseLeave = () => {
setIsHovered(false);
if (isPressed) {setIsPressed(false);}
};
const handleMouseDown = () => {
@@ -28,7 +29,7 @@ const SocialButton = ({ id, enabled, serverDomain, oauthPath, Icon, label }) =>
const getButtonStyles = () => {
// Define Tailwind CSS classes based on state
const baseStyles = 'border border-solid border-gray-300 dark:border-gray-800 transition-colors';
const baseStyles = 'border border-solid border-gray-300 dark:border-gray-600 transition-colors';
const pressedStyles = 'bg-blue-200 border-blue-200 dark:bg-blue-900 dark:border-blue-600';
const hoverStyles = 'bg-gray-100 dark:bg-gray-700';

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { useState } from 'react';
import { useLocation } from 'react-router-dom';
import type { TConversation } from 'librechat-data-provider';
import { Download } from 'lucide-react';
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
import { useLocalize } from '~/hooks';
import { ExportModal } from '../Nav';
import { useRecoilValue } from 'recoil';
import store from '~/store';
function ExportButton() {
const localize = useLocalize();
const location = useLocation();
const [showExports, setShowExports] = useState(false);
const activeConvo = useRecoilValue(store.conversationByIndex(0));
const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation);
let conversation: TConversation | null | undefined;
if (location.state?.from?.pathname.includes('/chat')) {
conversation = globalConvo;
} else {
conversation = activeConvo;
}
const clickHandler = () => {
if (exportable) {
setShowExports(true);
}
};
const exportable =
conversation &&
conversation.conversationId &&
conversation.conversationId !== 'new' &&
conversation.conversationId !== 'search';
return (
<>
{exportable && (
<div className="flex gap-1 gap-2 pr-1">
<TooltipProvider delayDuration={50}>
<Tooltip>
<TooltipTrigger asChild>
<button
className="btn btn-neutral btn-small relative flex h-9 w-9 items-center justify-center whitespace-nowrap rounded-lg"
onClick={clickHandler}
>
<div className="flex w-full items-center justify-center gap-2">
<Download size={16} />
</div>
</button>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={5}>
{localize('com_nav_export_conversation')}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
)}
{showExports && (
<ExportModal open={showExports} onOpenChange={setShowExports} conversation={conversation} />
)}
</>
);
}
export default ExportButton;

View File

@@ -5,6 +5,7 @@ import { useGetStartupConfig } from 'librechat-data-provider/react-query';
import type { ContextType } from '~/common';
import { EndpointsMenu, ModelSpecsMenu, PresetsMenu, HeaderNewChat } from './Menus';
import HeaderOptions from './Input/HeaderOptions';
import ExportButton from './ExportButton';
const defaultInterface = getConfigDefaults().interface;
@@ -19,12 +20,15 @@ export default function Header() {
return (
<div className="sticky top-0 z-10 flex h-14 w-full items-center justify-between bg-white p-2 font-semibold dark:bg-gray-800 dark:text-white">
<div className="hide-scrollbar flex items-center gap-2 overflow-x-auto">
{!navVisible && <HeaderNewChat />}
{interfaceConfig.endpointsMenu && <EndpointsMenu />}
{modelSpecs?.length > 0 && <ModelSpecsMenu modelSpecs={modelSpecs} />}
{<HeaderOptions interfaceConfig={interfaceConfig} />}
{interfaceConfig.presets && <PresetsMenu />}
<div className="hide-scrollbar flex w-full items-center justify-between gap-2 overflow-x-auto">
<div className="flex items-center gap-2">
{!navVisible && <HeaderNewChat />}
{interfaceConfig.endpointsMenu && <EndpointsMenu />}
{modelSpecs?.length > 0 && <ModelSpecsMenu modelSpecs={modelSpecs} />}
{<HeaderOptions interfaceConfig={interfaceConfig} />}
{interfaceConfig.presets && <PresetsMenu />}
</div>
<ExportButton />
</div>
{/* Empty div for spacing */}
<div />

View File

@@ -100,7 +100,7 @@ const ChatForm = ({ index = 0 }) => {
{showMentionPopover && (
<Mention setShowMentionPopover={setShowMentionPopover} textAreaRef={textAreaRef} />
)}
<div className="[&:has(textarea:focus)]:border-token-border-xheavy border-token-border-medium bg-token-main-surface-primary relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border dark:border-gray-600 dark:text-white [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)] dark:[&:has(textarea:focus)]:border-gray-500">
<div className="bg-token-main-surface-primary relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border dark:border-gray-600 dark:text-white [&:has(textarea:focus)]:border-gray-300 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)] dark:[&:has(textarea:focus)]:border-gray-500">
<FileRow
files={files}
setFiles={setFiles}

View File

@@ -27,7 +27,9 @@ export default function Files({ open, onOpenChange }) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={cn('overflow-x-auto shadow-2xl dark:bg-gray-700 dark:text-white')}>
<DialogContent
className={cn('w-11/12 overflow-x-auto shadow-2xl dark:bg-gray-700 dark:text-white')}
>
<DialogHeader>
<DialogTitle className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
{localize('com_nav_my_files')}

View File

@@ -220,7 +220,7 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
)}
</div>
<Button
className="dark:border-gray-500"
className="dark:border-gray-500 dark:hover:bg-gray-600"
variant="outline"
size="sm"
onClick={() => table.previousPage()}
@@ -229,7 +229,7 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
{localize('com_ui_prev')}
</Button>
<Button
className="dark:border-gray-500"
className="dark:border-gray-500 dark:hover:bg-gray-600"
variant="outline"
size="sm"
onClick={() => table.nextPage()}

View File

@@ -22,7 +22,6 @@ export default function MentionItem({
<div
className={cn(
'hover:bg-token-main-surface-secondary text-token-text-primary bg-token-main-surface-secondary group flex h-10 items-center gap-2 rounded-lg px-2 text-sm font-medium dark:hover:bg-gray-600',
index === 0 ? 'dark:bg-gray-600' : '',
isActive ? 'dark:bg-gray-600' : '',
)}
>

View File

@@ -45,7 +45,7 @@ export default function OptionsPopover({
const localize = useLocalize();
const cardStyle =
'shadow-xl rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white ';
'shadow-xl rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white';
if (!visible) {
return null;
@@ -66,7 +66,7 @@ export default function OptionsPopover({
{presetsDisabled ? null : (
<Button
type="button"
className="h-auto w-[150px] justify-start rounded-md border-2 border-gray-300/50 bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-gray-100 hover:text-black focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:focus:ring-green-500"
className="h-auto w-[150px] justify-start rounded-md border border-gray-300/50 bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-gray-100 hover:text-black focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:focus:ring-green-500"
onClick={saveAsPreset}
>
<Save className="mr-1 w-[14px]" />

View File

@@ -44,12 +44,7 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
<div className="relative h-full">
<div className="absolute left-0 right-0">{Header && Header}</div>
<div className="flex h-full flex-col items-center justify-center">
<div
className={cn(
'relative h-[72px] w-[72px]',
assistantName && avatar ? 'mb-0' : 'mb-3',
)}
>
<div className={cn('relative h-12 w-12', assistantName && avatar ? 'mb-0' : 'mb-3')}>
<ConvoIcon
conversation={conversation}
assistantMap={assistantMap}

View File

@@ -118,7 +118,7 @@ const MenuItem: FC<MenuItemProps> = ({
'invisible flex gap-x-1 group-hover:visible',
selected ? 'visible' : '',
expiryTime
? 'w-full rounded-lg p-2 hover:bg-gray-200 dark:hover:bg-gray-600'
? 'w-full rounded-lg p-2 hover:text-gray-400 dark:hover:text-gray-400'
: '',
)}
onClick={(e) => {

View File

@@ -8,6 +8,7 @@ const knownEndpointAssets = {
[KnownEndpoints.cohere]: '/assets/cohere.png',
[KnownEndpoints.fireworks]: '/assets/fireworks.png',
[KnownEndpoints.groq]: '/assets/groq.png',
[KnownEndpoints.huggingface]: '/assets/huggingface.svg',
[KnownEndpoints.mistral]: '/assets/mistral.png',
[KnownEndpoints.mlx]: '/assets/mlx.png',
[KnownEndpoints.ollama]: '/assets/ollama.png',

View File

@@ -49,9 +49,9 @@ const EditPresetDialog = ({
title={`${localize('com_ui_edit') + ' ' + localize('com_endpoint_preset')} - ${
preset?.title
}`}
className="h-full max-w-full overflow-y-auto pb-4 sm:w-[680px] sm:pb-0 md:h-[720px] md:w-[750px] md:overflow-y-hidden lg:w-[950px] xl:h-[720px]"
className="h-full max-w-full overflow-y-auto pb-4 sm:w-[680px] sm:pb-0 md:h-[720px] md:w-[750px] md:overflow-y-hidden md:overflow-y-hidden lg:w-[950px] xl:h-[720px]"
main={
<div className="flex w-full flex-col items-center gap-2 md:h-[530px]">
<div className="flex w-full flex-col items-center gap-2 md:h-[550px] md:overflow-y-auto">
<div className="grid w-full">
<div className="col-span-4 flex flex-col items-start justify-start gap-6 pb-4 md:flex-row">
<div className="flex w-full flex-col">
@@ -126,6 +126,7 @@ const EditPresetDialog = ({
</DialogClose>
</div>
}
footerClassName="bg-white dark:bg-gray-700"
/>
</Dialog>
);

View File

@@ -13,14 +13,14 @@ type ArchiveButtonProps = {
retainView: () => void;
shouldArchive: boolean;
icon: React.ReactNode;
twcss?: string;
className?: string;
};
export default function ArchiveButton({
conversationId,
retainView,
shouldArchive,
icon,
twcss = undefined,
className = '',
}: ArchiveButtonProps) {
const localize = useLocalize();
const { newConversation } = useNewConvo();
@@ -58,14 +58,9 @@ export default function ArchiveButton({
},
);
};
const classProp: { className?: string } = {
className: 'z-50 hover:text-black dark:hover:text-white',
};
if (twcss) {
classProp.className = twcss;
}
return (
<button type="button" className={classProp.className} onClick={archiveHandler}>
<button type="button" className={className} onClick={archiveHandler}>
<TooltipProvider delayDuration={250}>
<Tooltip>
<TooltipTrigger asChild>

View File

@@ -34,6 +34,7 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
const inputRef = useRef<HTMLInputElement | null>(null);
const [titleInput, setTitleInput] = useState(title);
const [renaming, setRenaming] = useState(false);
const [isPopoverActive, setIsPopoverActive] = useState(false);
const clickHandler = async (event: React.MouseEvent<HTMLAnchorElement>) => {
if (event.button === 0 && event.ctrlKey) {
@@ -117,13 +118,17 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
(isLatestConvo && currentConvoId === 'new' && activeConvos[0] && activeConvos[0] !== 'new');
return (
<div className="hover:bg-token-sidebar-surface-secondary group relative rounded-lg active:opacity-90">
<div
className={cn(
'hover:bg-token-sidebar-surface-secondary group relative rounded-lg active:opacity-90',
)}
>
{renaming ? (
<div className="absolute bottom-0 left-0 right-0 top-0 z-50 flex w-full items-center rounded-lg bg-gray-200 dark:bg-gray-700">
<div className="absolute inset-0 z-50 flex w-full items-center rounded-lg bg-gray-200 p-1.5 dark:bg-gray-700">
<input
ref={inputRef}
type="text"
className="w-full border border-blue-500 bg-transparent p-0 text-sm leading-tight outline-none"
className="w-full rounded border border-blue-500 bg-transparent p-0.5 text-sm leading-tight outline-none"
value={titleInput}
onChange={(e) => setTitleInput(e.target.value)}
onBlur={onRename}
@@ -131,13 +136,18 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
/>
</div>
) : (
<HoverToggle isActiveConvo={isActiveConvo}>
<HoverToggle
isActiveConvo={isActiveConvo}
isPopoverActive={isPopoverActive}
setIsPopoverActive={setIsPopoverActive}
>
<EditMenuButton>
<RenameButton
renaming={renaming}
onRename={onRename}
renameHandler={renameHandler}
appendLabel={true}
className="mb-[3.5px]"
/>
<DeleteButton
conversationId={conversationId}
@@ -145,14 +155,15 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
renaming={renaming}
title={title}
appendLabel={true}
className="group m-1.5 flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600"
className="group m-1.5 mt-[3.5px] flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600"
/>
</EditMenuButton>
<ArchiveButton
className="z-50 hover:text-black dark:hover:text-white"
conversationId={conversationId}
retainView={retainView}
shouldArchive={true}
icon={<ArchiveIcon className="w-full hover:text-gray-400" />}
icon={<ArchiveIcon className="hover:text-gray-400" />}
/>
</HoverToggle>
)}
@@ -161,9 +172,9 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
data-testid="convo-item"
onClick={clickHandler}
className={cn(
isActiveConvo
isActiveConvo || isPopoverActive
? 'group relative mt-2 flex cursor-pointer items-center gap-2 break-all rounded-lg rounded-lg bg-gray-200 px-2 py-2 active:opacity-50 dark:bg-gray-700'
: 'group relative mt-2 flex grow cursor-pointer items-center gap-2 overflow-hidden whitespace-nowrap break-all rounded-lg rounded-lg px-2 py-2 hover:bg-gray-200 active:opacity-50 dark:hover:bg-gray-800',
: 'group relative mt-2 flex grow cursor-pointer items-center gap-2 overflow-hidden whitespace-nowrap break-all rounded-lg rounded-lg px-2 py-2 hover:bg-gray-200 active:opacity-50 dark:hover:bg-gray-700',
!isActiveConvo && !renaming ? 'peer-hover:bg-gray-200 dark:peer-hover:bg-gray-800' : '',
)}
title={title}
@@ -185,7 +196,7 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
)}
/>
) : (
<div className="absolute bottom-0 right-0 top-0 w-2 bg-gradient-to-l from-0% to-transparent group-hover:w-1 group-hover:from-60%" />
<div className="absolute bottom-0 right-0 top-0 w-20 rounded-r-lg bg-gradient-to-l from-gray-50 from-0% to-transparent group-hover:from-gray-200 group-hover:from-60% dark:from-[#181818] dark:group-hover:from-gray-700" />
)}
</a>
</div>

View File

@@ -58,7 +58,7 @@ export default function DeleteButton({
<Tooltip>
<TooltipTrigger asChild>
<span>
<TrashIcon />
<TrashIcon className="h-5 w-5" />
</span>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={0}>

View File

@@ -46,7 +46,7 @@ const EditMenuButton: FC<EditMenuButtonProps> = ({ children }: EditMenuButtonPro
align="start"
className={cn(
'popover radix-side-bottom:animate-slideUpAndFade radix-side-left:animate-slideRightAndFade radix-side-right:animate-slideLeftAndFade radix-side-top:animate-slideDownAndFade overflow-hidden rounded-lg shadow-lg',
'border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-700 dark:text-white',
'border border-gray-200 bg-white dark:border-gray-600 dark:bg-gray-700 dark:text-white',
'flex min-w-[200px] max-w-xs flex-wrap',
)}
>

View File

@@ -5,22 +5,25 @@ import { cn } from '~/utils';
const HoverToggle = ({
children,
isActiveConvo,
isPopoverActive,
setIsPopoverActive,
}: {
children: React.ReactNode;
isActiveConvo: boolean;
isPopoverActive: boolean;
setIsPopoverActive: (isActive: boolean) => void;
}) => {
const [isPopoverActive, setIsPopoverActive] = useState(false);
const setPopoverActive = (value: boolean) => setIsPopoverActive(value);
return (
<ToggleContext.Provider value={{ setPopoverActive }}>
<div
className={cn(
'peer absolute bottom-0 right-0 top-0 items-center gap-1.5 rounded-r-lg from-gray-900 pl-2 pr-2 text-gray-500 dark:text-gray-300',
'peer absolute bottom-0 right-0 top-0 items-center gap-1.5 rounded-r-lg from-gray-500 from-gray-900 pl-2 pr-2 dark:text-white',
isPopoverActive || isActiveConvo ? 'flex' : 'hidden group-hover:flex',
isActiveConvo
? 'from-gray-50 from-85% to-transparent group-hover:bg-gradient-to-l group-hover:from-gray-200 dark:from-gray-750 dark:group-hover:from-gray-750'
: 'z-50 bg-gray-200 from-gray-50 from-0% to-transparent hover:bg-gray-200 hover:bg-gradient-to-l dark:bg-gray-800 dark:from-gray-750 dark:hover:bg-gray-800',
isPopoverActive && !isActiveConvo ? 'bg-gray-50 dark:bg-gray-750' : '',
? 'from-gray-50 from-85% to-transparent group-hover:bg-gradient-to-l group-hover:from-gray-200 dark:from-gray-800 dark:group-hover:from-gray-800'
: 'z-50 from-gray-200 from-gray-50 from-0% to-transparent hover:bg-gradient-to-l hover:from-gray-200 dark:from-gray-750 dark:from-gray-800 dark:hover:from-gray-800',
isPopoverActive && !isActiveConvo ? 'from-gray-50 dark:from-gray-800' : '',
)}
>
{children}

View File

@@ -1,12 +1,14 @@
import type { MouseEvent, ReactElement } from 'react';
import { EditIcon, CheckMark } from '~/components/svg';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
interface RenameButtonProps {
renaming: boolean;
renameHandler: (e: MouseEvent<HTMLButtonElement>) => void;
onRename: (e: MouseEvent<HTMLButtonElement>) => void;
appendLabel?: boolean;
className?: string;
}
export default function RenameButton({
@@ -14,13 +16,17 @@ export default function RenameButton({
renameHandler,
onRename,
appendLabel = false,
className = '',
}: RenameButtonProps): ReactElement {
const localize = useLocalize();
const handler = renaming ? onRename : renameHandler;
return (
<button
className="group m-1.5 flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600"
className={cn(
'group m-1.5 flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600',
className,
)}
onClick={handler}
>
{renaming ? (

View File

@@ -1,7 +1,13 @@
import React, { useEffect, useState } from 'react';
import { useCreatePresetMutation } from 'librechat-data-provider/react-query';
import type { TEditPresetProps } from '~/common';
import { cn, defaultTextPropsLabel, removeFocusOutlines, cleanupPreset } from '~/utils/';
import {
cn,
defaultTextPropsLabel,
removeFocusOutlines,
cleanupPreset,
defaultTextProps,
} from '~/utils/';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { Dialog, Input, Label } from '~/components/ui/';
import { NotificationSeverity } from '~/common';
@@ -59,12 +65,12 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) =>
{localize('com_endpoint_preset_name')}
</Label>
<Input
id="chatGptLabel"
id="chatGpt"
value={title || ''}
onChange={(e) => setTitle(e.target.value || '')}
placeholder="Set a custom name for this preset"
className={cn(
defaultTextPropsLabel,
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none border-gray-100 px-3 py-2 dark:border-gray-600',
removeFocusOutlines,
)}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import type { TModelSelectProps } from '~/common';
import { ESide } from '~/common';
import type { TModelSelectProps, OnInputNumberChange } from '~/common';
import {
Input,
Label,
@@ -12,17 +11,35 @@ import {
SelectDropDown,
HoverCardTrigger,
} from '~/components/ui';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils';
import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
import { useLocalize, useDebouncedInput } from '~/hooks';
import OptionHover from './OptionHover';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils/';
import { useLocalize } from '~/hooks';
import { ESide } from '~/common';
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
const localize = useLocalize();
const {
model,
modelLabel,
promptPrefix,
temperature,
topP,
topK,
maxOutputTokens,
maxContextTokens,
resendFiles,
} = conversation ?? {};
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
setOption,
optionKey: 'maxContextTokens',
initialValue: maxContextTokens,
},
);
if (!conversation) {
return null;
}
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens, resendFiles } =
conversation;
const setModel = setOption('model');
const setModelLabel = setOption('modelLabel');
@@ -83,6 +100,40 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div>
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="mt-1 flex w-full justify-between">
<Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
{localize('com_endpoint_context_tokens')}{' '}
</Label>
<InputNumber
id="max-context-tokens"
stringMode={false}
disabled={readonly}
value={maxContextTokensValue as number}
onChange={setMaxContextTokens as OnInputNumberChange}
placeholder={localize('com_nav_theme_system')}
min={10}
max={2000000}
step={1000}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_context_info"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">

View File

@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { EModelEndpoint, endpointSettings } from 'librechat-data-provider';
import type { TModelSelectProps } from '~/common';
import type { TModelSelectProps, OnInputNumberChange } from '~/common';
import {
Input,
Label,
@@ -11,16 +11,25 @@ import {
SelectDropDown,
HoverCardTrigger,
} from '~/components/ui';
import OptionHover from './OptionHover';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils';
import { useLocalize } from '~/hooks';
import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
import { useLocalize, useDebouncedInput } from '~/hooks';
import OptionHover from './OptionHover';
import { ESide } from '~/common';
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
const localize = useLocalize();
const google = endpointSettings[EModelEndpoint.google];
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } =
conversation ?? {};
const {
model,
modelLabel,
promptPrefix,
temperature,
topP,
topK,
maxContextTokens,
maxOutputTokens,
} = conversation ?? {};
const isGemini = model?.toLowerCase()?.includes('gemini');
@@ -41,6 +50,14 @@ export default function Settings({ conversation, setOption, models, readonly }:
[model],
);
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
setOption,
optionKey: 'maxContextTokens',
initialValue: maxContextTokens,
},
);
if (!conversation) {
return null;
}
@@ -103,6 +120,40 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div>
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="mt-1 flex w-full justify-between">
<Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
{localize('com_endpoint_context_tokens')}{' '}
</Label>
<InputNumber
id="max-context-tokens"
stringMode={false}
disabled={readonly}
value={maxContextTokensValue as number}
onChange={setMaxContextTokens as OnInputNumberChange}
placeholder={localize('com_nav_theme_system')}
min={10}
max={2000000}
step={1000}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_context_info"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">

View File

@@ -1,12 +1,12 @@
import { useMemo } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import * as InputNumberPrimitive from 'rc-input-number';
import {
EModelEndpoint,
ImageDetail,
imageDetailNumeric,
imageDetailValue,
} from 'librechat-data-provider';
import type { TModelSelectProps, OnInputNumberChange } from '~/common';
import {
Input,
Label,
@@ -18,14 +18,12 @@ import {
HoverCardTrigger,
} from '~/components/ui';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils';
import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
import { DynamicTags } from '~/components/SidePanel/Parameters';
import { useLocalize, useDebouncedInput } from '~/hooks';
import type { TModelSelectProps } from '~/common';
import OptionHover from './OptionHover';
import { ESide } from '~/common';
type OnInputNumberChange = InputNumberPrimitive.InputNumberProps['onChange'];
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
const localize = useLocalize();
const {
@@ -41,6 +39,8 @@ export default function Settings({ conversation, setOption, models, readonly }:
presence_penalty: presP,
resendFiles,
imageDetail,
maxContextTokens,
max_tokens,
} = conversation ?? {};
const [setChatGptLabel, chatGptLabelValue] = useDebouncedInput<string | null | undefined>({
@@ -73,6 +73,18 @@ export default function Settings({ conversation, setOption, models, readonly }:
optionKey: 'presence_penalty',
initialValue: presP,
});
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
setOption,
optionKey: 'maxContextTokens',
initialValue: maxContextTokens,
},
);
const [setMaxOutputTokens, maxOutputTokensValue] = useDebouncedInput<number | null | undefined>({
setOption,
optionKey: 'max_tokens',
initialValue: max_tokens,
});
const optionEndpoint = useMemo(() => endpointType ?? endpoint, [endpoint, endpointType]);
const isOpenAI = useMemo(
@@ -154,6 +166,74 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div>
</div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="mt-1 flex w-full justify-between">
<Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
{localize('com_endpoint_context_tokens')}{' '}
</Label>
<InputNumber
id="max-context-tokens"
stringMode={false}
disabled={readonly}
value={maxContextTokensValue as number}
onChange={setMaxContextTokens as OnInputNumberChange}
placeholder={localize('com_nav_theme_system')}
min={10}
max={2000000}
step={1000}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_context_info"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="mt-1 flex w-full justify-between">
<Label htmlFor="max-output-tokens" className="text-left text-sm font-medium">
{localize('com_endpoint_max_output_tokens')}{' '}
</Label>
<InputNumber
id="max-output-tokens"
stringMode={false}
disabled={readonly}
value={maxOutputTokensValue as number}
onChange={setMaxOutputTokens as OnInputNumberChange}
placeholder={localize('com_nav_theme_system')}
min={10}
max={2000000}
step={1000}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_openai_max_tokens"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">

View File

@@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil';
import TextareaAutosize from 'react-textarea-autosize';
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
import type { TPlugin } from 'librechat-data-provider';
import type { TModelSelectProps } from '~/common';
import type { TModelSelectProps, OnInputNumberChange } from '~/common';
import {
Input,
Label,
@@ -14,9 +14,16 @@ import {
HoverCardTrigger,
MultiSelectDropDown,
} from '~/components/ui';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils';
import {
cn,
defaultTextProps,
optionText,
removeFocusOutlines,
processPlugins,
selectPlugins,
} from '~/utils';
import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
import { useLocalize, useDebouncedInput } from '~/hooks';
import { processPlugins, selectPlugins } from '~/utils';
import OptionHover from './OptionHover';
import { ESide } from '~/common';
import store from '~/store';
@@ -62,6 +69,7 @@ export default function Settings({
top_p: topP,
frequency_penalty: freqP,
presence_penalty: presP,
maxContextTokens,
} = conversation ?? {};
const [setChatGptLabel, chatGptLabelValue] = useDebouncedInput<string | null | undefined>({
@@ -94,6 +102,13 @@ export default function Settings({
optionKey: 'presence_penalty',
initialValue: presP,
});
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
setOption,
optionKey: 'maxContextTokens',
initialValue: maxContextTokens,
},
);
const setModel = setOption('model');
@@ -170,6 +185,40 @@ export default function Settings({
containerClassName="flex w-full resize-none border border-transparent"
labelClassName="dark:text-white"
/>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="mt-1 flex w-full justify-between">
<Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
{localize('com_endpoint_context_tokens')}{' '}
</Label>
<InputNumber
id="max-context-tokens"
stringMode={false}
disabled={readonly}
value={maxContextTokensValue as number}
onChange={setMaxContextTokens as OnInputNumberChange}
placeholder={localize('com_nav_theme_system')}
min={10}
max={2000000}
step={1000}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_context_info"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">

View File

@@ -9,7 +9,7 @@ const Logout = forwardRef(() => {
return (
<button
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-black transition-colors duration-200 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700"
className="group group flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm transition-colors duration-200 hover:bg-gray-500/10 focus:ring-0 dark:text-white dark:hover:bg-gray-600"
onClick={() => logout()}
>
<LogOutIcon />

View File

@@ -15,7 +15,7 @@ export default function MobileNav({
const { title = 'New Chat' } = conversation || {};
return (
<div className="text-token-primary border-token-border-medium bg-token-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center border-b bg-white dark:bg-gray-800 dark:text-white md:hidden">
<div className="border-token-border-medium bg-token-main-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center justify-center border-b bg-white pl-1 dark:bg-gray-800 dark:text-white md:hidden md:hidden">
<button
type="button"
data-testid="mobile-header-new-chat-button"
@@ -44,7 +44,7 @@ export default function MobileNav({
/>
</svg>
</button>
<h1 className="flex-1 text-center text-base font-normal">
<h1 className="flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-center text-sm font-normal">
{title || localize('com_ui_new_chat')}
</h1>
<button

View File

@@ -6,17 +6,21 @@ interface Props {
text: string;
clickHandler?: () => void;
className?: string;
disabled?: boolean;
}
const NavLink: FC<Props> = forwardRef<HTMLAnchorElement, Props>((props, ref) => {
const { svg, text, clickHandler, className = '' } = props;
const { svg, text, clickHandler, disabled, className = '' } = props;
const defaultProps: {
className: string;
onClick?: () => void;
} = {
className: cn(
'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10',
'flex gap-2 rounded p-2.5 text-sm cursor-pointer focus:ring-0 group items-center transition-colors duration-200 hover:bg-gray-500/10 dark:text-white dark:hover:bg-gray-600',
className,
{
'opacity-50 pointer-events-none': disabled,
},
),
};

View File

@@ -1,6 +1,6 @@
import { useLocation } from 'react-router-dom';
import { Fragment, useState, memo } from 'react';
import { Download, FileText } from 'lucide-react';
import { FileText } from 'lucide-react';
import { Menu, Transition } from '@headlessui/react';
import { useRecoilValue, useRecoilState } from 'recoil';
import { useGetUserBalance, useGetStartupConfig } from 'librechat-data-provider/react-query';
@@ -8,7 +8,6 @@ import type { TConversation } from 'librechat-data-provider';
import FilesView from '~/components/Chat/Input/Files/FilesView';
import { useAuthContext } from '~/hooks/AuthContext';
import useAvatar from '~/hooks/Messages/useAvatar';
import { ExportModal } from './ExportConversation';
import { LinkIcon, GearIcon } from '~/components';
import { UserIcon } from '~/components/svg';
import { useLocalize } from '~/hooks';
@@ -26,7 +25,6 @@ function NavLinks() {
const balanceQuery = useGetUserBalance({
enabled: !!isAuthenticated && startupConfig?.checkBalance,
});
const [showExports, setShowExports] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [showFiles, setShowFiles] = useRecoilState(store.showFiles);
@@ -42,34 +40,15 @@ function NavLinks() {
conversation = activeConvo;
}
const exportable =
conversation &&
conversation.conversationId &&
conversation.conversationId !== 'new' &&
conversation.conversationId !== 'search';
const clickHandler = () => {
if (exportable) {
setShowExports(true);
}
};
return (
<>
<Menu as="div" className="group relative">
{({ open }) => (
<>
{startupConfig?.checkBalance &&
balanceQuery.data &&
!isNaN(parseFloat(balanceQuery.data)) && (
<div className="m-1 ml-3 whitespace-nowrap text-left text-sm text-black dark:text-gray-200">
{`Balance: ${parseFloat(balanceQuery.data).toFixed(2)}`}
</div>
)}
<Menu.Button
className={cn(
'group-ui-open:bg-gray-100 dark:group-ui-open:bg-gray-700 duration-350 mt-text-sm mb-1 flex h-11 w-full items-center gap-2 rounded-lg px-3 py-3 text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-700',
open ? 'bg-gray-100 dark:bg-gray-700' : '',
'group-ui-open:bg-gray-100 dark:group-ui-open:bg-gray-700 duration-350 mt-text-sm flex h-auto w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-800',
open ? 'bg-gray-100 dark:bg-gray-800' : '',
)}
data-testid="nav-user"
>
@@ -93,7 +72,7 @@ function NavLinks() {
</div>
</div>
<div
className="mt-2 grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-black dark:text-white"
className="mt-2 grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-black dark:text-gray-100"
style={{ marginTop: '0', marginLeft: '0' }}
>
{user?.name || user?.username || localize('com_nav_user')}
@@ -109,24 +88,23 @@ function NavLinks() {
leaveFrom="translate-y-0 opacity-100"
leaveTo="translate-y-2 opacity-0"
>
<Menu.Items className="absolute bottom-full left-0 z-20 mb-1 mt-1 w-full translate-y-0 overflow-hidden rounded-lg bg-white py-1.5 opacity-100 outline-none dark:bg-gray-800">
<Menu.Items className="absolute bottom-full left-0 z-[100] mb-1 mt-1 w-full translate-y-0 overflow-hidden rounded-lg border border-gray-300 bg-white p-1.5 opacity-100 shadow-lg outline-none dark:border-gray-600 dark:bg-gray-700">
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm" role="none">
{user?.email || localize('com_nav_user')}
</div>
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
{startupConfig?.checkBalance &&
balanceQuery.data &&
!isNaN(parseFloat(balanceQuery.data)) && (
<>
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm">
{`Balance: ${parseFloat(balanceQuery.data).toFixed(2)}`}
</div>
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
</>
)}
<Menu.Item as="div">
<NavLink
className={cn(
'flex w-full cursor-pointer items-center gap-3 rounded-none px-3 py-3 text-sm text-black transition-colors duration-200 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700',
exportable
? 'cursor-pointer text-black dark:text-white'
: 'cursor-not-allowed text-black/50 dark:text-white/50',
)}
svg={() => <Download size={16} />}
text={localize('com_nav_export_conversation')}
clickHandler={clickHandler}
/>
</Menu.Item>
<div className="my-1 h-px bg-black/20 dark:bg-white/20" role="none" />
<Menu.Item as="div">
<NavLink
className="flex w-full cursor-pointer items-center gap-3 rounded-none px-3 py-3 text-sm text-black transition-colors duration-200 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700"
svg={() => <FileText className="icon-md" />}
text={localize('com_nav_my_files')}
clickHandler={() => setShowFiles(true)}
@@ -135,7 +113,6 @@ function NavLinks() {
{startupConfig?.helpAndFaqURL !== '/' && (
<Menu.Item as="div">
<NavLink
className="flex w-full cursor-pointer items-center gap-3 rounded-none px-3 py-3 text-sm text-black transition-colors duration-200 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700"
svg={() => <LinkIcon />}
text={localize('com_nav_help_faq')}
clickHandler={() => window.open(startupConfig?.helpAndFaqURL, '_blank')}
@@ -144,13 +121,12 @@ function NavLinks() {
)}
<Menu.Item as="div">
<NavLink
className="flex w-full cursor-pointer items-center gap-3 rounded-none px-3 py-3 text-sm text-black transition-colors duration-200 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700"
svg={() => <GearIcon className="icon-md" />}
text={localize('com_nav_settings')}
clickHandler={() => setShowSettings(true)}
/>
</Menu.Item>
<div className="my-1 h-px bg-black/20 bg-white/20" role="none" />
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
<Menu.Item as="div">
<Logout />
</Menu.Item>
@@ -159,9 +135,6 @@ function NavLinks() {
</>
)}
</Menu>
{showExports && (
<ExportModal open={showExports} onOpenChange={setShowExports} conversation={conversation} />
)}
{showFiles && <FilesView open={showFiles} onOpenChange={setShowFiles} />}
{showSettings && <Settings open={showSettings} onOpenChange={setShowSettings} />}
</>

View File

@@ -16,8 +16,8 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
className={cn(
'shadow-2xl md:min-h-[373px] md:w-[680px]',
isSmallScreen ? 'top-20 -translate-y-0' : '',
'overflow-hidden shadow-2xl md:min-h-[373px] md:w-[680px]',
isSmallScreen ? 'top-5 -translate-y-0' : '',
)}
>
<DialogHeader>
@@ -25,19 +25,19 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_settings')}
</DialogTitle>
</DialogHeader>
<div className="px-6">
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
<Tabs.Root
defaultValue={SettingsTabValues.GENERAL}
className="flex flex-col gap-10 md:flex-row"
orientation="vertical"
orientation="horizontal"
>
<Tabs.List
aria-label="Settings"
role="tablist"
aria-orientation="vertical"
aria-orientation="horizontal"
className={cn(
'min-w-auto -ml-[8px] flex flex-shrink-0 flex-col',
isSmallScreen ? 'flex-row rounded-lg bg-gray-200 p-1 dark:bg-gray-700' : '',
'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-wrap overflow-auto sm:max-w-none',
isSmallScreen ? 'flex-row rounded-lg bg-gray-200 p-1 dark:bg-gray-800' : '',
)}
style={{ outline: 'none' }}
>
@@ -112,11 +112,13 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_account')}
</Tabs.Trigger>
</Tabs.List>
<General />
<Messages />
<Beta />
<Data />
<Account />
<div className="h-screen max-h-[373px] overflow-auto sm:w-full sm:max-w-none">
<General />
<Messages />
<Beta />
<Data />
<Account />
</div>
</Tabs.Root>
</div>
</DialogContent>

View File

@@ -22,10 +22,10 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
<Tabs.Content
value={SettingsTabValues.ACCOUNT}
role="tabpanel"
className="w-full md:min-h-[300px]"
className="w-full md:min-h-[271px]"
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<Avatar />
</div>
<div className="flex items-center justify-between">
@@ -39,7 +39,7 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
/>
</div>
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700"></div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600"></div>
</Tabs.Content>
);
}

View File

@@ -9,13 +9,13 @@ function Beta() {
<Tabs.Content
value={SettingsTabValues.BETA}
role="tabpanel"
className="w-full md:min-h-[300px]"
className="w-full md:min-h-[271px]"
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ModularChat />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<LaTeXParsing />
</div>
</div>

View File

@@ -100,18 +100,18 @@ function Data() {
<Tabs.Content
value={SettingsTabValues.DATA}
role="tabpanel"
className="w-full md:min-h-[300px]"
className="w-full md:min-h-[271px]"
ref={dataTabRef}
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ImportConversations />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<RevokeKeysButton all={true} />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ClearChatsButton
confirmClear={confirmClearConvos}
onClick={clearConvos}

View File

@@ -79,19 +79,20 @@ export default function ArchivedChatsTable({ className }: { className?: string }
{conversation.conversationId && (
<>
<ArchiveButton
className="hover:text-black dark:hover:text-white"
conversationId={conversation.conversationId}
retainView={moveToTop}
shouldArchive={false}
icon={<ArchiveRestore className="h-4 w-4 hover:text-gray-300" />}
/>
<div className="h-4 w-4 hover:text-gray-300">
<div className="h-5 w-5 hover:text-gray-300">
<DeleteButton
conversationId={conversation.conversationId}
retainView={moveToTop}
renaming={false}
title={conversation.title}
appendLabel={false}
className="mx-3 flex items-center"
className="group ml-4 flex w-full cursor-pointer items-center items-center gap-2 rounded text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600"
/>
</div>
</>

View File

@@ -33,7 +33,9 @@ export const ThemeSelector = ({
value={theme}
onChange={onChange}
options={themeOptions}
width={150}
width={220}
position={'left'}
maxHeight="200px"
testId="theme-selector"
/>
</div>
@@ -103,7 +105,13 @@ export const LangSelector = ({
return (
<div className="flex items-center justify-between">
<div> {localize('com_nav_language')} </div>
<Dropdown value={langcode} onChange={onChange} options={languageOptions} />
<Dropdown
value={langcode}
onChange={onChange}
position={'left'}
maxHeight="271px"
options={languageOptions}
/>
</div>
);
};
@@ -142,26 +150,26 @@ function General() {
<Tabs.Content
value={SettingsTabValues.GENERAL}
role="tabpanel"
className="w-full md:min-h-[300px]"
className="w-full md:min-h-[271px]"
ref={contentRef}
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ThemeSelector theme={theme} onChange={changeTheme} />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<LangSelector langcode={selectedLang} onChange={changeLang} />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<AutoScrollSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<HideSidePanelSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ArchivedChats />
</div>
{/* <div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
{/* <div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
</div> */}
</div>
</Tabs.Content>

View File

@@ -18,7 +18,7 @@ export const ForkSettings = () => {
return (
<>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<div className="flex items-center justify-between">
<div> {localize('com_ui_fork_change_default')} </div>
<Dropdown
@@ -26,11 +26,13 @@ export const ForkSettings = () => {
onChange={setForkSetting}
options={forkOptions}
width={200}
position={'left'}
maxHeight="199px"
testId="fork-setting-dropdown"
/>
</div>
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<div className="flex items-center justify-between">
<div> {localize('com_ui_fork_default')} </div>
<Switch
@@ -42,7 +44,7 @@ export const ForkSettings = () => {
/>
</div>
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<div className="flex items-center justify-between">
<div> {localize('com_ui_fork_split_target_setting')} </div>
<Switch

View File

@@ -7,16 +7,12 @@ import { ForkSettings } from './ForkSettings';
function Messages() {
return (
<Tabs.Content
value={SettingsTabValues.MESSAGES}
role="tabpanel"
className="w-full md:min-h-[300px]"
>
<Tabs.Content value={SettingsTabValues.MESSAGES} role="tabpanel" className="md: w-full">
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<SendMessageKeyEnter />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ShowCodeSwitch />
</div>
<ForkSettings />

View File

@@ -92,7 +92,7 @@ export default function PanelFileCell({ row }: { row: Row<TFile> }) {
return (
<div
onClick={handleFileClick}
className="flex cursor-pointer gap-2 rounded-md dark:hover:bg-gray-900"
className="flex cursor-pointer gap-2 rounded-md dark:hover:bg-gray-700"
>
{fileType && <FilePreview fileType={fileType} />}
<span className="self-center truncate">{file.filename}</span>

View File

@@ -0,0 +1,108 @@
import { OptionTypes } from 'librechat-data-provider';
import type { DynamicSettingProps } from 'librechat-data-provider';
import type { ValueType } from '@rc-component/mini-decimal';
import { Label, HoverCard, InputNumber, HoverCardTrigger } from '~/components/ui';
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
import { cn, defaultTextProps, optionText } from '~/utils';
import { ESide } from '~/common';
import { useChatContext } from '~/Providers';
import OptionHover from './OptionHover';
function DynamicInputNumber({
label,
settingKey,
defaultValue,
description,
columnSpan,
setOption,
optionType,
readonly = false,
showDefault = true,
labelCode,
descriptionCode,
placeholderCode,
placeholder,
conversation,
range,
className = '',
inputClassName = '',
}: DynamicSettingProps) {
const localize = useLocalize();
const { preset } = useChatContext();
const [setInputValue, inputValue] = useDebouncedInput<ValueType | null>({
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
initialValue:
optionType !== OptionTypes.Custom
? (conversation?.[settingKey] as number)
: (defaultValue as number),
setter: () => ({}),
setOption,
});
useParameterEffects({
preset,
settingKey,
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
conversation,
inputValue,
setInputValue,
});
return (
<div
className={cn(
'flex flex-col items-center justify-start gap-6',
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
className,
)}
>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label
htmlFor={`${settingKey}-dynamic-setting`}
className="text-left text-sm font-medium"
>
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
{showDefault && (
<small className="opacity-40">
({localize('com_endpoint_default')}: {defaultValue})
</small>
)}
</Label>
<InputNumber
id={`${settingKey}-dynamic-setting-input-number`}
disabled={readonly}
value={inputValue}
onChange={setInputValue}
min={range?.min}
max={range?.max}
step={range?.step}
placeholder={
placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder
}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
),
inputClassName,
)}
/>
</div>
</HoverCardTrigger>
{description && (
<OptionHover
description={descriptionCode ? localize(description) || description : description}
side={ESide.Left}
/>
)}
</HoverCard>
</div>
);
}
export default DynamicInputNumber;

View File

@@ -1,3 +1,4 @@
export { default as DynamicInputNumber } from './DynamicInputNumber';
export { default as DynamicDropdown } from './DynamicDropdown';
export { default as DynamicCheckbox } from './DynamicCheckbox';
export { default as DynamicTextarea } from './DynamicTextarea';

View File

@@ -3,21 +3,18 @@ import React from 'react';
export default function Clipboard() {
return (
<svg
fill="none"
strokeWidth="2"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
className="icon-md-heavy"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 4C10.8954 4 10 4.89543 10 6H14C14 4.89543 13.1046 4 12 4ZM8.53513 4C9.22675 2.8044 10.5194 2 12 2C13.4806 2 14.7733 2.8044 15.4649 4H17C18.6569 4 20 5.34315 20 7V19C20 20.6569 18.6569 22 17 22H7C5.34315 22 4 20.6569 4 19V7C4 5.34315 5.34315 4 7 4H8.53513ZM8 6H7C6.44772 6 6 6.44772 6 7V19C6 19.5523 6.44772 20 7 20H17C17.5523 20 18 19.5523 18 19V7C18 6.44772 17.5523 6 17 6H16C16 7.10457 15.1046 8 14 8H10C8.89543 8 8 7.10457 8 6Z"
fill="currentColor"
fillRule="evenodd"
d="M7 5a3 3 0 0 1 3-3h9a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3h-2v2a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3v-9a3 3 0 0 1 3-3h2zm2 2h5a3 3 0 0 1 3 3v5h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-9a1 1 0 0 0-1 1zM5 9a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-9a1 1 0 0 0-1-1z"
clipRule="evenodd"
></path>
</svg>
);

View File

@@ -6,7 +6,7 @@ export default function EditIcon() {
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
className="icon-md"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -3,21 +3,16 @@ import { cn } from '~/utils';
export default function RegenerateIcon({ className = '' }: { className?: string }) {
return (
<svg
fill="none"
strokeWidth="2"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className={cn('h-4 w-4', className)}
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
className={cn('icon-md-heavy', className)}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.5 2.5C5.05228 2.5 5.5 2.94772 5.5 3.5V5.07196C7.19872 3.47759 9.48483 2.5 12 2.5C17.2467 2.5 21.5 6.75329 21.5 12C21.5 17.2467 17.2467 21.5 12 21.5C7.1307 21.5 3.11828 17.8375 2.565 13.1164C2.50071 12.5679 2.89327 12.0711 3.4418 12.0068C3.99033 11.9425 4.48712 12.3351 4.5514 12.8836C4.98798 16.6089 8.15708 19.5 12 19.5C16.1421 19.5 19.5 16.1421 19.5 12C19.5 7.85786 16.1421 4.5 12 4.5C9.7796 4.5 7.7836 5.46469 6.40954 7H9C9.55228 7 10 7.44772 10 8C10 8.55228 9.55228 9 9 9H4.5C3.96064 9 3.52101 8.57299 3.50073 8.03859C3.49983 8.01771 3.49958 7.99677 3.5 7.9758V3.5C3.5 2.94772 3.94771 2.5 4.5 2.5Z"
fill="currentColor"
d="M3.07 10.876C3.623 6.436 7.41 3 12 3a9.15 9.15 0 0 1 6.012 2.254V4a1 1 0 1 1 2 0v4a1 1 0 0 1-1 1H15a1 1 0 1 1 0-2h1.957A7.15 7.15 0 0 0 12 5a7 7 0 0 0-6.946 6.124 1 1 0 1 1-1.984-.248m16.992 1.132a1 1 0 0 1 .868 1.116C20.377 17.564 16.59 21 12 21a9.15 9.15 0 0 1-6-2.244V20a1 1 0 1 1-2 0v-4a1 1 0 0 1 1-1h4a1 1 0 1 1 0 2H7.043A7.15 7.15 0 0 0 12 19a7 7 0 0 0 6.946-6.124 1 1 0 0 1 1.116-.868"
></path>
</svg>
);

View File

@@ -1,4 +1,6 @@
export default function TrashIcon() {
import { cn } from '~/utils';
export default function TrashIcon({ className = '' }) {
return (
<svg
fill="none"
@@ -6,7 +8,7 @@ export default function TrashIcon() {
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
className={cn('icon-md', className)}
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
@@ -16,7 +18,7 @@ export default function TrashIcon() {
clipRule="evenodd"
d="M10.5555 4C10.099 4 9.70052 4.30906 9.58693 4.75114L9.29382 5.8919H14.715L14.4219 4.75114C14.3083 4.30906 13.9098 4 13.4533 4H10.5555ZM16.7799 5.8919L16.3589 4.25342C16.0182 2.92719 14.8226 2 13.4533 2H10.5555C9.18616 2 7.99062 2.92719 7.64985 4.25342L7.22886 5.8919H4C3.44772 5.8919 3 6.33961 3 6.8919C3 7.44418 3.44772 7.8919 4 7.8919H4.10069L5.31544 19.3172C5.47763 20.8427 6.76455 22 8.29863 22H15.7014C17.2354 22 18.5224 20.8427 18.6846 19.3172L19.8993 7.8919H20C20.5523 7.8919 21 7.44418 21 6.8919C21 6.33961 20.5523 5.8919 20 5.8919H16.7799ZM17.888 7.8919H6.11196L7.30423 19.1057C7.3583 19.6142 7.78727 20 8.29863 20H15.7014C16.2127 20 16.6417 19.6142 16.6958 19.1057L17.888 7.8919ZM10 10C10.5523 10 11 10.4477 11 11V16C11 16.5523 10.5523 17 10 17C9.44772 17 9 16.5523 9 16V11C9 10.4477 9.44772 10 10 10ZM14 10C14.5523 10 15 10.4477 15 11V16C15 16.5523 14.5523 17 14 17C13.4477 17 13 16.5523 13 16V11C13 10.4477 13.4477 10 14 10Z"
fill="currentColor"
></path>
/>
</svg>
);
}

View File

@@ -25,6 +25,7 @@ type DialogTemplateProps = {
selection?: SelectionProps;
className?: string;
headerClassName?: string;
footerClassName?: string;
showCloseButton?: boolean;
showCancelButton?: boolean;
};
@@ -40,6 +41,7 @@ const DialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDivE
selection,
className,
headerClassName,
footerClassName,
showCloseButton,
showCancelButton = true,
} = props;
@@ -66,7 +68,7 @@ const DialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDivE
)}
</DialogHeader>
<div className="px-6">{main ? main : null}</div>
<DialogFooter>
<DialogFooter className={footerClassName}>
<div>{leftButtons ? leftButtons : null}</div>
<div className="flex h-auto gap-3">
{showCancelButton && (

View File

@@ -7,13 +7,17 @@ type OptionType = {
display?: string;
};
type DropdownPosition = 'left' | 'right';
interface DropdownProps {
value: string;
label?: string;
onChange: (value: string) => void;
options: (string | OptionType)[];
className?: string;
position?: DropdownPosition;
width?: number;
maxHeight?: string;
testId?: string;
}
@@ -23,11 +27,18 @@ const Dropdown: FC<DropdownProps> = ({
onChange,
options,
className = '',
position = 'right',
width,
maxHeight = 'auto',
testId = 'dropdown-menu',
}) => {
const [selectedValue, setSelectedValue] = useState(initialValue);
const positionClasses = {
right: 'origin-bottom-left left-0',
left: 'origin-bottom-right right-0',
};
return (
<div className={cn('relative', className)}>
<Listbox
@@ -41,7 +52,7 @@ const Dropdown: FC<DropdownProps> = ({
<Listbox.Button
data-testid={testId}
className={cn(
'relative inline-flex items-center justify-between rounded-md border-gray-300 bg-white py-2 pl-3 pr-8 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 ',
'relative inline-flex items-center justify-between rounded-md border-gray-300 bg-white py-2 pl-3 pr-8 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
'w-auto',
className,
)}
@@ -67,19 +78,19 @@ const Dropdown: FC<DropdownProps> = ({
</Listbox.Button>
<Listbox.Options
className={cn(
'absolute z-50 mt-1 max-h-[40vh] overflow-auto rounded-md border-gray-300 bg-white text-gray-700 shadow-lg transition-opacity hover:bg-gray-50 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
`absolute z-50 mt-1 flex max-h-[40vh] flex-col items-start gap-1 overflow-auto rounded-lg border border-gray-300 bg-white p-1.5 text-gray-700 shadow-lg transition-opacity focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white ${positionClasses[position]}`,
className,
)}
style={{ width: width ? `${width}px` : 'auto' }}
style={{ width: width ? `${width}px` : 'auto', maxHeight: maxHeight }}
>
{options.map((item, index) => (
<Listbox.Option
key={index}
value={typeof item === 'string' ? item : item.value}
className={cn(
'relative cursor-pointer select-none border-gray-300 bg-white py-1 pl-3 pr-6 text-gray-700 hover:bg-gray-50 dark:border-gray-300 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
'relative cursor-pointer select-none rounded border-gray-300 bg-white py-2.5 pl-3 pr-6 text-gray-700 hover:bg-gray-100 dark:border-gray-300 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
)}
style={{ width: width ? `${width}px` : 'auto' }}
style={{ width: '100%' }}
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
>
<span className="block truncate">

View File

@@ -28,7 +28,7 @@ const ThemeSelector = () => {
);
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0">
<div className="flex flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0">
<div className="absolute bottom-0 left-0 m-4">
<Theme theme={theme} onChange={changeTheme} />
</div>

View File

@@ -136,29 +136,22 @@ export default function useTextarea({
assistantMap,
]);
const handleKeyUp = useCallback(
(e: KeyEvent) => {
let isMention = false;
if (e.key === '@' || e.key === '2') {
const text = textAreaRef.current?.value;
isMention = !!(text && text[text.length - 1] === '@');
}
const handleKeyUp = useCallback(() => {
const text = textAreaRef.current?.value;
if (!(text && text[text.length - 1] === '@')) {
return;
}
if (isMention) {
const startPos = textAreaRef.current?.selectionStart;
const isAtStart = startPos === 1;
const isPrecededBySpace =
startPos && textAreaRef.current?.value.charAt(startPos - 2) === ' ';
const startPos = textAreaRef.current?.selectionStart;
if (!startPos) {
return;
}
if (isAtStart || isPrecededBySpace) {
setShowMentionPopover(true);
} else {
setShowMentionPopover(false);
}
}
},
[textAreaRef, setShowMentionPopover],
);
const isAtStart = startPos === 1;
const isPrecededBySpace = textAreaRef.current?.value.charAt(startPos - 2) === ' ';
setShowMentionPopover(isAtStart || isPrecededBySpace);
}, [textAreaRef, setShowMentionPopover]);
const handleKeyDown = useCallback(
(e: KeyEvent) => {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -144,7 +144,7 @@ export default {
com_ui_fork_success: 'Successfully forked conversation',
com_ui_fork_processing: 'Forking conversation...',
com_ui_fork_error: 'There was an error forking the conversation',
com_ui_fork_change_default: 'Change default fork option',
com_ui_fork_change_default: 'Default fork option',
com_ui_fork_default: 'Use default fork option',
com_ui_fork_remember: 'Remember',
com_ui_fork_split_target_setting: 'Start fork from target message by default',
@@ -280,6 +280,9 @@ export default {
com_endpoint_tone_style: 'Tone Style',
com_endpoint_token_count: 'Token count',
com_endpoint_output: 'Output',
com_endpoint_context_tokens: 'Max Context Tokens',
com_endpoint_context_info: `The maximum number of tokens that can be used for context. Use this for control of how many tokens are sent per request.
If unspecified, will use system defaults based on known models' context size. Setting higher values may result in errors and/or higher token cost.`,
com_endpoint_google_temp:
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
com_endpoint_google_topp:
@@ -305,6 +308,9 @@ export default {
com_endpoint_max_output_tokens: 'Max Output Tokens',
com_endpoint_stop: 'Stop Sequences',
com_endpoint_stop_placeholder: 'Separate values by pressing `Enter`',
com_endpoint_openai_max_tokens: `Optional \`max_tokens\` field, representing the maximum number of tokens that can be generated in the chat completion.
The total length of input tokens and generated tokens is limited by the models context length. You may experience errors if this number exceeds the max context tokens.`,
com_endpoint_openai_temp:
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
com_endpoint_openai_max:
@@ -425,8 +431,8 @@ export default {
'Make sure to click \'Create and Continue\' to give at least the \'Vertex AI User\' role. Lastly, create a JSON key to import here.',
com_nav_welcome_assistant: 'Please Select an Assistant',
com_nav_welcome_message: 'How can I help you today?',
com_nav_auto_scroll: 'Auto-scroll to Newest on Open',
com_nav_hide_panel: 'Hide Right-most Side Panel',
com_nav_auto_scroll: 'Auto-Scroll to latest message on chat open',
com_nav_hide_panel: 'Hide right-most side panel',
com_nav_modular_chat: 'Enable switching Endpoints mid-conversation',
com_nav_latex_parsing: 'Parsing LaTeX in messages (may affect performance)',
com_nav_profile_picture: 'Profile Picture',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -261,3 +261,970 @@ export default {
com_nav_setting_general: '일반',
com_nav_setting_data: '데이터 제어',
};
export const comparisons = {
com_ui_examples: {
english: 'Examples',
translated: '예시',
},
com_ui_new_chat: {
english: 'New chat',
translated: '새 채팅',
},
com_ui_happy_birthday: {
english: 'It\'s my 1st birthday!',
translated: '내 첫 생일이야!',
},
com_ui_example_quantum_computing: {
english: 'Explain quantum computing in simple terms',
translated: '양자 컴퓨팅을 간단하게 설명해줄래?',
},
com_ui_example_10_year_old_b_day: {
english: 'Got any creative ideas for a 10 year old\'s birthday?',
translated: '10살 아이 생일 파티를 위한 참신한 아이디어 있을까?',
},
com_ui_example_http_in_js: {
english: 'How do I make an HTTP request in Javascript?',
translated: '자바스크립트에서 HTTP 요청을 어떻게 만드나요?',
},
com_ui_capabilities: {
english: 'Capabilities',
translated: '기능',
},
com_ui_capability_remember: {
english: 'Remembers what user said earlier in the conversation',
translated: '대화 중 사용자가 이전에 말한 것을 기억해요.',
},
com_ui_capability_correction: {
english: 'Allows user to provide follow-up corrections',
translated: '사용자가 수정 사항을 제공할 수 있어요.',
},
com_ui_capability_decline_requests: {
english: 'Trained to decline inappropriate requests',
translated: '부적절한 요청을 거부하도록 훈련되었어요.',
},
com_ui_limitations: {
english: 'Limitations',
translated: '제한사항',
},
com_ui_limitation_incorrect_info: {
english: 'May occasionally generate incorrect information',
translated: '간혹 잘못된 정보를 생성할 수 있습니다.',
},
com_ui_limitation_harmful_biased: {
english: 'May occasionally produce harmful instructions or biased content',
translated: '간혹 해로운 지시나 편향된 내용을 제공할 수 있습니다.',
},
com_ui_limitation_limited_2021: {
english: 'Limited knowledge of world and events after 2021',
translated: '2021년 이후의 세계 및 이벤트에 대한 지식이 제한적입니다.',
},
com_ui_input: {
english: 'Input',
translated: '입력',
},
com_ui_close: {
english: 'Close',
translated: '닫기',
},
com_ui_model: {
english: 'Model',
translated: '모델',
},
com_ui_select_model: {
english: 'Select a model',
translated: '모델 선택',
},
com_ui_use_prompt: {
english: 'Use prompt',
translated: '프롬프트 사용',
},
com_ui_prev: {
english: 'Prev',
translated: '이전',
},
com_ui_next: {
english: 'Next',
translated: '다음',
},
com_ui_prompt_templates: {
english: 'Prompt Templates',
translated: '프롬프트 템플릿',
},
com_ui_hide_prompt_templates: {
english: 'Hide Prompt Templates',
translated: '프롬프트 템플릿 숨기기',
},
com_ui_showing: {
english: 'Showing',
translated: '표시 중',
},
com_ui_of: {
english: 'of',
translated: '/',
},
com_ui_entries: {
english: 'Entries',
translated: '개',
},
com_ui_pay_per_call: {
english: 'All AI conversations in one place. Pay per call and not per month',
translated: '모든 AI 대화를 한 곳에서. 월별이 아닌 호출 당 지불',
},
com_ui_enter: {
english: 'Enter',
translated: 'Enter',
},
com_ui_submit: {
english: 'Submit',
translated: '제출',
},
com_ui_upload_success: {
english: 'Successfully uploaded file',
translated: '파일 업로드 성공',
},
com_ui_upload_invalid: {
english: 'Invalid file for upload. Must be an image not exceeding 2 MB',
translated: '잘못된 파일입니다',
},
com_ui_cancel: {
english: 'Cancel',
translated: '취소',
},
com_ui_save: {
english: 'Save',
translated: '저장',
},
com_ui_copy_to_clipboard: {
english: 'Copy to clipboard',
translated: '클립보드에 복사',
},
com_ui_copied_to_clipboard: {
english: 'Copied to clipboard',
translated: '클립보드에 복사되었습니다',
},
com_ui_regenerate: {
english: 'Regenerate',
translated: '재생성',
},
com_ui_continue: {
english: 'Continue',
translated: '계속',
},
com_ui_edit: {
english: 'Edit',
translated: '편집',
},
com_ui_success: {
english: 'Success',
translated: '성공',
},
com_ui_all: {
english: 'all',
translated: '전체',
},
com_ui_clear: {
english: 'Clear',
translated: '지우기',
},
com_ui_revoke: {
english: 'Revoke',
translated: '취소',
},
com_ui_revoke_info: {
english: 'Revoke all user provided credentials',
translated: '사용자가 제공한 자격 증명을 모두 취소합니다.',
},
com_ui_import_conversation: {
english: 'Import',
translated: '가져오기',
},
com_ui_import_conversation_info: {
english: 'Import conversations from a JSON file',
translated: 'JSON 파일에서 대화 가져오기',
},
com_ui_import_conversation_success: {
english: 'Conversations imported successfully',
translated: '대화가 성공적으로 가져와졌습니다',
},
com_ui_import_conversation_error: {
english: 'There was an error importing your conversations',
translated: '대화를 가져오는 동안 오류가 발생했습니다',
},
com_ui_confirm_action: {
english: 'Confirm Action',
translated: '작업 확인',
},
com_ui_chats: {
english: 'chats',
translated: '채팅',
},
com_ui_delete: {
english: 'Delete',
translated: '삭제',
},
com_ui_delete_conversation: {
english: 'Delete chat?',
translated: '채팅을 삭제하시겠습니까?',
},
com_ui_delete_conversation_confirm: {
english: 'This will delete',
translated: '이 채팅이 삭제됩니다',
},
com_ui_rename: {
english: 'Rename',
translated: '이름 바꾸기',
},
com_ui_archive: {
english: 'Archive',
translated: '아카이브',
},
com_ui_archive_error: {
english: 'Failed to archive conversation',
translated: '대화 아카이브 실패',
},
com_ui_unarchive: {
english: 'Unarchive',
translated: '아카이브 해제',
},
com_ui_unarchive_error: {
english: 'Failed to unarchive conversation',
translated: '대화 아카이브 해제 실패',
},
com_ui_more_options: {
english: 'More',
translated: '더 보기',
},
com_auth_error_login: {
english:
'Unable to login with the information provided. Please check your credentials and try again.',
translated: '제공된 정보로 로그인할 수 없습니다. 자격 증명을 확인하고 다시 시도하세요.',
},
com_auth_no_account: {
english: 'Don\'t have an account?',
translated: '계정이 없으신가요?',
},
com_auth_sign_up: {
english: 'Sign up',
translated: '가입하기',
},
com_auth_sign_in: {
english: 'Sign in',
translated: '로그인',
},
com_auth_google_login: {
english: 'Continue with Google',
translated: 'Google로 로그인',
},
com_auth_facebook_login: {
english: 'Continue with Facebook',
translated: 'Facebook으로 로그인',
},
com_auth_github_login: {
english: 'Continue with Github',
translated: 'Github으로 로그인',
},
com_auth_discord_login: {
english: 'Continue with Discord',
translated: 'Discord로 로그인',
},
com_auth_email: {
english: 'Email',
translated: '이메일',
},
com_auth_email_required: {
english: 'Email is required',
translated: '이메일은 필수입니다',
},
com_auth_email_min_length: {
english: 'Email must be at least 6 characters',
translated: '이메일은 최소 6자 이상이어야 합니다',
},
com_auth_email_max_length: {
english: 'Email should not be longer than 120 characters',
translated: '이메일은 120자를 넘을 수 없습니다',
},
com_auth_email_pattern: {
english: 'You must enter a valid email address',
translated: '유효한 이메일 주소를 입력하세요',
},
com_auth_email_address: {
english: 'Email address',
translated: '이메일 주소',
},
com_auth_password: {
english: 'Password',
translated: '비밀번호',
},
com_auth_password_required: {
english: 'Password is required',
translated: '비밀번호는 필수입니다',
},
com_auth_password_min_length: {
english: 'Password must be at least 8 characters',
translated: '비밀번호는 최소 8자 이상이어야 합니다',
},
com_auth_password_max_length: {
english: 'Password must be less than 128 characters',
translated: '비밀번호는 128자를 넘을 수 없습니다',
},
com_auth_password_forgot: {
english: 'Forgot Password?',
translated: '비밀번호를 잊으셨나요?',
},
com_auth_password_confirm: {
english: 'Confirm password',
translated: '비밀번호 확인',
},
com_auth_password_not_match: {
english: 'Passwords do not match',
translated: '비밀번호가 일치하지 않습니다',
},
com_auth_continue: {
english: 'Continue',
translated: '계속',
},
com_auth_create_account: {
english: 'Create your account',
translated: '계정 만들기',
},
com_auth_error_create: {
english: 'There was an error attempting to register your account. Please try again.',
translated: '계정을 등록하는 중에 오류가 발생했습니다. 다시 시도하세요.',
},
com_auth_full_name: {
english: 'Full name',
translated: '이름',
},
com_auth_name_required: {
english: 'Name is required',
translated: '이름은 필수입니다',
},
com_auth_name_min_length: {
english: 'Name must be at least 3 characters',
translated: '이름은 최소 3자 이상이어야 합니다',
},
com_auth_name_max_length: {
english: 'Name must be less than 80 characters',
translated: '이름은 80자를 초과할 수 없습니다',
},
com_auth_username: {
english: 'Username (optional)',
translated: '사용자명',
},
com_auth_username_required: {
english: 'Username is required',
translated: '사용자명이 필요합니다',
},
com_auth_username_min_length: {
english: 'Username must be at least 2 characters',
translated: '사용자명은 최소 3자 이상이어야 합니다',
},
com_auth_username_max_length: {
english: 'Username must be less than 20 characters',
translated: '사용자명은 20자를 초과할 수 없습니다',
},
com_auth_already_have_account: {
english: 'Already have an account?',
translated: '이미 계정이 있으신가요?',
},
com_auth_login: {
english: 'Login',
translated: '로그인',
},
com_auth_reset_password: {
english: 'Reset your password',
translated: '비밀번호 재설정',
},
com_auth_click: {
english: 'Click',
translated: '클릭',
},
com_auth_here: {
english: 'HERE',
translated: '여기',
},
com_auth_to_reset_your_password: {
english: 'to reset your password.',
translated: '비밀번호를 재설정하려면',
},
com_auth_reset_password_link_sent: {
english: 'Email Sent',
translated: '이메일 전송',
},
com_auth_reset_password_email_sent: {
english: 'An email has been sent to you with further instructions to reset your password.',
translated: '비밀번호 재설정에 대한 지침이 포함된 이메일이 전송되었습니다.',
},
com_auth_error_reset_password: {
english:
'There was a problem resetting your password. There was no user found with the email address provided. Please try again.',
translated:
'비밀번호 재설정 중에 문제가 발생했습니다. 제공된 이메일 주소로 사용자를 찾을 수 없습니다. 다시 시도하세요.',
},
com_auth_reset_password_success: {
english: 'Password Reset Success',
translated: '비밀번호 재설정 성공',
},
com_auth_login_with_new_password: {
english: 'You may now login with your new password.',
translated: '새로운 비밀번호로 로그인할 수 있습니다.',
},
com_auth_error_invalid_reset_token: {
english: 'This password reset token is no longer valid.',
translated: '이 비밀번호 재설정 토큰은 더 이상 유효하지 않습니다.',
},
com_auth_click_here: {
english: 'Click here',
translated: '여기를 클릭하세요',
},
com_auth_to_try_again: {
english: 'to try again.',
translated: '다시 시도하세요.',
},
com_auth_submit_registration: {
english: 'Submit registration',
translated: '등록하기',
},
com_auth_welcome_back: {
english: 'Welcome back',
translated: '다시 오신 것을 환영합니다',
},
com_endpoint_open_menu: {
english: 'Open Menu',
translated: '메뉴 열기',
},
com_endpoint_bing_enable_sydney: {
english: 'Enable Sydney',
translated: '시드니 활성화',
},
com_endpoint_bing_to_enable_sydney: {
english: 'To enable Sydney',
translated: '시드니를 활성화하려면',
},
com_endpoint_bing_jailbreak: {
english: 'Jailbreak',
translated: 'Jailbreak',
},
com_endpoint_bing_context_placeholder: {
english:
'Bing can use up to 7k tokens for \'context\', which it can reference for the conversation. The specific limit is not known but may run into errors exceeding 7k tokens',
translated:
'Bing은 \'컨텍스트\'로 최대 7,000개의 토큰을 사용할 수 있으며, 대화에서 참조할 수 있습니다. 구체적인 제한은 알려져 있지 않지만, 7,000개의 토큰을 초과하면 오류가 발생할 수 있습니다.',
},
com_endpoint_bing_system_message_placeholder: {
english:
'WARNING: Misuse of this feature can get you BANNED from using Bing! Click on \'System Message\' for full instructions and the default message if omitted, which is the \'Sydney\' preset that is considered safe.',
translated:
'경고: 이 기능의 오용으로 인해 Bing의 사용이 \'금지\'될 수 있습니다. 모든 내용을 보려면 \'시스템 메시지\'를 클릭하세요. 생략된 경우 \'시드니\' 프리셋이 사용됩니다.',
},
com_endpoint_system_message: {
english: 'System Message',
translated: '시스템 메시지',
},
com_endpoint_default_blank: {
english: 'default: blank',
translated: '기본값: 공백',
},
com_endpoint_default_false: {
english: 'default: false',
translated: '기본값: false',
},
com_endpoint_default_creative: {
english: 'default: creative',
translated: '기본값: 창의적',
},
com_endpoint_default_empty: {
english: 'default: empty',
translated: '기본값: 비어 있음',
},
com_endpoint_default_with_num: {
english: 'default: {0}',
translated: '기본값: {0}',
},
com_endpoint_context: {
english: 'Context',
translated: '컨텍스트',
},
com_endpoint_tone_style: {
english: 'Tone Style',
translated: '톤 스타일',
},
com_endpoint_token_count: {
english: 'Token count',
translated: '토큰 수',
},
com_endpoint_output: {
english: 'Output',
translated: '출력',
},
com_endpoint_google_temp: {
english:
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
translated:
'높은 값 = 더 무작위, 낮은 값 = 더 집중적이고 결정적입니다. 이 값을 변경하거나 Top P 중 하나만 변경하는 것을 권장합니다.',
},
com_endpoint_google_topp: {
english:
'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.',
translated:
'Top-p는 모델이 출력에 사용할 토큰을 선택하는 방식을 변경합니다. 토큰은 가장 높은 확률부터 가장 낮은 확률까지 선택됩니다. 선택된 토큰의 확률의 합이 top-p 값과 같아질 때까지 선택됩니다.',
},
com_endpoint_google_topk: {
english:
'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).',
translated:
'Top-k는 모델이 출력에 사용할 토큰을 선택하는 방식을 변경합니다. top-k가 1인 경우 모델의 어휘 중 가장 확률이 높은 토큰이 선택됩니다(greedy decoding). top-k가 3인 경우 다음 토큰은 가장 확률이 높은 3개의 토큰 중에서 선택됩니다(temperature 사용).',
},
com_endpoint_google_maxoutputtokens: {
english:
' \tMaximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.',
translated:
'응답에서 생성할 수 있는 최대 토큰 수입니다. 짧은 응답에는 낮은 값을, 긴 응답에는 높은 값을 지정하세요.',
},
com_endpoint_google_custom_name_placeholder: {
english: 'Set a custom name for Google',
translated: 'Google에 대한 사용자 정의 이름 설정',
},
com_endpoint_prompt_prefix_placeholder: {
english: 'Set custom instructions or context. Ignored if empty.',
translated: '사용자 정의 지시사항 또는 컨텍스트를 설정하세요. 비어 있으면 무시됩니다.',
},
com_endpoint_custom_name: {
english: 'Custom Name',
translated: '사용자 정의 이름',
},
com_endpoint_prompt_prefix: {
english: 'Custom Instructions',
translated: '프롬프트 접두사',
},
com_endpoint_temperature: {
english: 'Temperature',
translated: '온도',
},
com_endpoint_default: {
english: 'default',
translated: '기본값',
},
com_endpoint_top_p: {
english: 'Top P',
translated: 'Top P',
},
com_endpoint_top_k: {
english: 'Top K',
translated: 'Top K',
},
com_endpoint_max_output_tokens: {
english: 'Max Output Tokens',
translated: '최대 출력 토큰 수',
},
com_endpoint_openai_temp: {
english:
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
translated:
'높은 값 = 더 무작위, 낮은 값 = 더 집중적이고 결정적입니다. 이 값을 변경하거나 Top P 중 하나만 변경하는 것을 권장합니다.',
},
com_endpoint_openai_max: {
english:
'The max tokens to generate. The total length of input tokens and generated tokens is limited by the model\'s context length.',
translated:
'생성할 최대 토큰 수입니다. 입력 토큰과 생성된 토큰의 총 길이는 모델의 컨텍스트 길이로 제한됩니다.',
},
com_endpoint_openai_topp: {
english:
'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We recommend altering this or temperature but not both.',
translated:
'온도를 사용한 샘플링 대신, top_p 확률 질량을 고려하는 nucleus 샘플링입니다. 따라서 0.1은 상위 10% 확률 질량을 구성하는 토큰만 고려합니다. 이 값을 변경하거나 온도를 변경하는 것을 권장하지만, 둘 다 변경하지는 마세요.',
},
com_endpoint_openai_freq: {
english:
'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.',
translated:
'텍스트에서 토큰의 빈도수에 따라 새로운 토큰에 패널티를 부여합니다. 이전에 나온 텍스트의 빈도수에 따라 새로운 토큰의 확률이 감소하여 동일한 문장을 반복할 가능성을 줄입니다.',
},
com_endpoint_openai_pres: {
english:
'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.',
translated:
'텍스트에서 토큰이 나타나는지 여부에 따라 새로운 토큰에 패널티를 부여합니다. 이전에 나온 텍스트에 나타나는 토큰에 대한 패널티를 증가시켜 새로운 주제에 대해 이야기할 가능성을 높입니다.',
},
com_endpoint_openai_custom_name_placeholder: {
english: 'Set a custom name for the AI',
translated: 'ChatGPT에 대한 사용자 정의 이름을 설정하세요.',
},
com_endpoint_openai_prompt_prefix_placeholder: {
english: 'Set custom instructions to include in System Message. Default: none',
translated: '시스템 메시지에 포함할 사용자 정의 지시사항을 설정하세요. 기본값: 없음',
},
com_endpoint_anthropic_temp: {
english:
'Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks. We recommend altering this or Top P but not both.',
translated:
'0에서 1 사이의 값으로, 분석/다중 선택에는 0에 가까운 값을 사용하고, 창의적이고 생성적인 작업에는 1에 가까운 값을 사용하세요. 이 값을 변경하거나 Top P 중 하나만 변경하는 것을 권장합니다.',
},
com_endpoint_anthropic_topp: {
english:
'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.',
translated:
'Top-p는 모델이 출력에 사용할 토큰을 선택하는 방식을 변경합니다. 토큰은 가장 높은 확률부터 가장 낮은 확률까지 선택됩니다. 선택된 토큰의 확률의 합이 top-p 값과 같아질 때까지 선택됩니다.',
},
com_endpoint_anthropic_topk: {
english:
'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).',
translated:
'Top-k는 모델이 출력에 사용할 토큰을 선택하는 방식을 변경합니다. top-k가 1인 경우 모델의 어휘 중 가장 확률이 높은 토큰이 선택됩니다(greedy decoding). top-k가 3인 경우 다음 토큰은 가장 확률이 높은 3개의 토큰 중에서 선택됩니다(temperature 사용).',
},
com_endpoint_anthropic_maxoutputtokens: {
english:
'Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.',
translated:
'응답에서 생성할 수 있는 최대 토큰 수입니다. 짧은 응답에는 낮은 값을, 긴 응답에는 높은 값을 지정하세요.',
},
com_endpoint_anthropic_custom_name_placeholder: {
english: 'Set a custom name for Anthropic',
translated: 'Anthropic에 대한 사용자 정의 이름 설정',
},
com_endpoint_frequency_penalty: {
english: 'Frequency Penalty',
translated: '빈도 패널티',
},
com_endpoint_presence_penalty: {
english: 'Presence Penalty',
translated: '존재 패널티',
},
com_endpoint_plug_use_functions: {
english: 'Use Functions',
translated: '함수 사용',
},
com_endpoint_plug_skip_completion: {
english: 'Skip Completion',
translated: '완료 단계 건너뛰기',
},
com_endpoint_disabled_with_tools: {
english: 'disabled with tools',
translated: '도구 사용 불가',
},
com_endpoint_disabled_with_tools_placeholder: {
english: 'Disabled with Tools Selected',
translated: '도구 선택 시 사용 불가',
},
com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: {
english: 'Set custom instructions to include in System Message. Default: none',
translated: '시스템 메시지에 포함할 사용자 정의 지시사항을 설정하세요. 기본값: 없음',
},
com_endpoint_import: {
english: 'Import',
translated: '가져오기',
},
com_endpoint_set_custom_name: {
english: 'Set a custom name, in case you can find this preset',
translated: '프리셋을 쉽게 찾을 수 있도록 사용자 정의 이름을 설정하세요',
},
com_endpoint_preset: {
english: 'preset',
translated: '프리셋',
},
com_endpoint_presets: {
english: 'presets',
translated: '프리셋',
},
com_endpoint_preset_name: {
english: 'Preset Name',
translated: '프리셋 이름',
},
com_endpoint_new_topic: {
english: 'New Topic',
translated: '새로운 주제',
},
com_endpoint: {
english: 'Endpoint',
translated: '엔드포인트',
},
com_endpoint_hide: {
english: 'Hide',
translated: '숨기기',
},
com_endpoint_show: {
english: 'Show',
translated: '표시',
},
com_endpoint_examples: {
english: ' Presets',
translated: ' 프리셋',
},
com_endpoint_completion: {
english: 'Completion',
translated: '완료',
},
com_endpoint_agent: {
english: 'Agent',
translated: '에이전트',
},
com_endpoint_show_what_settings: {
english: 'Show {0} Settings',
translated: '{0} 설정 표시',
},
com_endpoint_export: {
english: 'Export',
translated: '내보내기',
},
com_endpoint_save_as_preset: {
english: 'Save As Preset',
translated: '프리셋으로 저장',
},
com_endpoint_presets_clear_warning: {
english: 'Are you sure you want to clear all presets? This is irreversible.',
translated: '모든 프리셋을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.',
},
com_endpoint_not_implemented: {
english: 'Not implemented',
translated: '구현되지 않았습니다',
},
com_endpoint_no_presets: {
english: 'No presets yet, use the settings button to create one',
translated: '아직 프리셋이 없습니다',
},
com_endpoint_not_available: {
english: 'No endpoint available',
translated: '사용할 수 없는 엔드포인트입니다',
},
com_endpoint_view_options: {
english: 'View Options',
translated: '옵션 보기',
},
com_endpoint_save_convo_as_preset: {
english: 'Save Conversation as Preset',
translated: '대화를 프리셋으로 저장',
},
com_endpoint_my_preset: {
english: 'My Preset',
translated: '내 프리셋',
},
com_endpoint_agent_model: {
english: 'Agent Model (Recommended: GPT-3.5)',
translated: '에이전트 모델 (권장: GPT-3.5)',
},
com_endpoint_completion_model: {
english: 'Completion Model (Recommended: GPT-4)',
translated: '완료 모델 (권장: GPT-4)',
},
com_endpoint_func_hover: {
english: 'Enable use of Plugins as OpenAI Functions',
translated: '플러그인을 OpenAI 함수로 사용할 수 있도록 합니다.',
},
com_endpoint_skip_hover: {
english:
'Enable skipping the completion step, which reviews the final answer and generated steps',
translated: '완료 단계를 건너뛰도록 합니다. 최종 답변과 생성된 단계를 검토하는 단계입니다.',
},
com_endpoint_config_key: {
english: 'Set API Key',
translated: 'API 키 설정',
},
com_endpoint_config_key_for: {
english: 'Set API Key for',
translated: 'API 키 설정: ',
},
com_endpoint_config_key_name: {
english: 'Key',
translated: '키',
},
com_endpoint_config_value: {
english: 'Enter value for',
translated: '값 입력',
},
com_endpoint_config_key_name_placeholder: {
english: 'Set API key first',
translated: '먼저 API 키를 설정하세요',
},
com_endpoint_config_key_encryption: {
english: 'Your key will be encrypted and deleted at',
translated: '키는 암호화되어 저장되며, 만료 시간에 삭제됩니다',
},
com_endpoint_config_key_expiry: {
english: 'the expiry time',
translated: '만료 시간',
},
com_endpoint_config_key_import_json_key: {
english: 'Import Service Account JSON Key.',
translated: '서비스 계정 JSON 키 가져오기',
},
com_endpoint_config_key_import_json_key_success: {
english: 'Successfully Imported Service Account JSON Key',
translated: '서비스 계정 JSON 키 가져오기 성공',
},
com_endpoint_config_key_import_json_key_invalid: {
english: 'Invalid Service Account JSON Key, Did you import the correct file?',
translated: '유효하지 않은 서비스 계정 JSON 키입니다. 올바른 파일을 가져왔는지 확인하세요',
},
com_endpoint_config_key_get_edge_key: {
english: 'To get your Access token for Bing, login to',
translated: 'Bing 액세스 토큰을 얻으려면 다음 사이트에 로그인하세요',
},
com_endpoint_config_key_get_edge_key_dev_tool: {
english:
'Use dev tools or an extension while logged into the site to copy the content of the _U cookie. If this fails, follow these',
translated:
'로그인한 상태에서 개발 도구 또는 확장 프로그램을 사용하여 _U 쿠키의 내용을 복사합니다. 실패하는 경우 다음',
},
com_endpoint_config_key_edge_instructions: {
english: 'instructions',
translated: '지침',
},
com_endpoint_config_key_edge_full_key_string: {
english: 'to provide the full cookie strings.',
translated: '전체 쿠키 문자열을 제공하세요',
},
com_nav_plugin_store: {
english: 'Plugin store',
translated: '플러그인 스토어',
},
com_nav_plugin_search: {
english: 'Search plugins',
translated: '플러그인 검색',
},
com_nav_plugin_auth_error: {
english: 'There was an error attempting to authenticate this plugin. Please try again.',
translated: '이 플러그인을 인증하려는 중에 오류가 발생했습니다. 다시 시도해주세요.',
},
com_nav_export_filename: {
english: 'Filename',
translated: '파일 이름',
},
com_nav_export_filename_placeholder: {
english: 'Set the filename',
translated: '파일 이름을 설정하세요',
},
com_nav_export_type: {
english: 'Type',
translated: '유형',
},
com_nav_export_include_endpoint_options: {
english: 'Include endpoint options',
translated: '엔드포인트 옵션 포함',
},
com_nav_enabled: {
english: 'Enabled',
translated: '활성화됨',
},
com_nav_not_supported: {
english: 'Not Supported',
translated: '지원되지 않음',
},
com_nav_export_all_message_branches: {
english: 'Export all message branches',
translated: '모든 메시지 브랜치 내보내기',
},
com_nav_export_recursive_or_sequential: {
english: 'Recursive or sequential?',
translated: '재귀적 또는 순차적?',
},
com_nav_export_recursive: {
english: 'Recursive',
translated: '재귀적',
},
com_nav_export_conversation: {
english: 'Export conversation',
translated: '대화 내보내기',
},
com_nav_theme: {
english: 'Theme',
translated: '테마',
},
com_nav_theme_system: {
english: 'System',
translated: '시스템',
},
com_nav_theme_dark: {
english: 'Dark',
translated: '다크',
},
com_nav_theme_light: {
english: 'Light',
translated: '라이트',
},
com_nav_clear_all_chats: {
english: 'Clear all chats',
translated: '모든 채팅 지우기',
},
com_nav_confirm_clear: {
english: 'Confirm Clear',
translated: '지우기 확인',
},
com_nav_close_sidebar: {
english: 'Close sidebar',
translated: '사이드바 닫기',
},
com_nav_open_sidebar: {
english: 'Open sidebar',
translated: '사이드바 열기',
},
com_nav_send_message: {
english: 'Send message',
translated: '메시지 보내기',
},
com_nav_log_out: {
english: 'Log out',
translated: '로그아웃',
},
com_nav_user: {
english: 'USER',
translated: '사용자',
},
com_nav_archived_chats: {
english: 'Archived chats',
translated: '아카이브된 채팅',
},
com_nav_archived_chats_manage: {
english: 'Manage',
translated: '관리',
},
com_nav_archived_chats_empty: {
english: 'You have no archived conversations.',
translated: '아카이브된 채팅이 없습니다',
},
com_nav_archive_all_chats: {
english: 'Archive all chats',
translated: '모든 채팅 아카이브',
},
com_nav_archive_all: {
english: 'Archive all',
translated: '모든 채팅 아카이브',
},
com_nav_archive_name: {
english: 'Name',
translated: '이름',
},
com_nav_archive_created_at: {
english: 'DateCreated',
translated: '생성 날짜',
},
com_nav_clear_conversation: {
english: 'Clear conversations',
translated: '대화 지우기',
},
com_nav_clear_conversation_confirm_message: {
english: 'Are you sure you want to clear all conversations? This is irreversible.',
translated: '모든 대화를 지우시겠습니까? 이 작업은 되돌릴 수 없습니다.',
},
com_nav_help_faq: {
english: 'Help & FAQ',
translated: '도움말 및 FAQ',
},
com_nav_settings: {
english: 'Settings',
translated: '설정',
},
com_nav_search_placeholder: {
english: 'Search messages',
translated: '메시지 검색',
},
com_nav_setting_general: {
english: 'General',
translated: '일반',
},
com_nav_setting_data: {
english: 'Data controls',
translated: '데이터 제어',
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -223,3 +223,791 @@ export default {
com_ui_import_conversation_success: 'Konwersacje zostały pomyślnie zaimportowane',
com_ui_import_conversation_error: 'Wystąpił błąd podczas importowania konwersacji',
};
export const comparisons = {
com_ui_examples: {
english: 'Examples',
translated: 'Przykłady',
},
com_ui_new_chat: {
english: 'New chat',
translated: 'Nowy czat',
},
com_ui_happy_birthday: {
english: 'It\'s my 1st birthday!',
translated: 'To moje pierwsze urodziny!',
},
com_ui_example_quantum_computing: {
english: 'Explain quantum computing in simple terms',
translated: 'Wyjaśnij obliczenia kwantowe w prostych słowach',
},
com_ui_example_10_year_old_b_day: {
english: 'Got any creative ideas for a 10 year old\'s birthday?',
translated: 'Masz jakieś kreatywne pomysły na dziesiąte urodziny?',
},
com_ui_example_http_in_js: {
english: 'How do I make an HTTP request in Javascript?',
translated: 'Jak wykonać żądanie HTTP w JavaScript?',
},
com_ui_capabilities: {
english: 'Capabilities',
translated: 'Możliwości',
},
com_ui_capability_remember: {
english: 'Remembers what user said earlier in the conversation',
translated: 'Pamięta to, co użytkownik powiedział wcześniej w rozmowie',
},
com_ui_capability_correction: {
english: 'Allows user to provide follow-up corrections',
translated: 'Pozwala użytkownikowi wprowadzać poprawki do dalszej rozmowy',
},
com_ui_capability_decline_requests: {
english: 'Trained to decline inappropriate requests',
translated: 'Szkolony do odrzucania nieodpowiednich żądań',
},
com_ui_limitations: {
english: 'Limitations',
translated: 'Ograniczenia',
},
com_ui_limitation_incorrect_info: {
english: 'May occasionally generate incorrect information',
translated: 'Czasami może podać nieprawidłowe informacje',
},
com_ui_limitation_harmful_biased: {
english: 'May occasionally produce harmful instructions or biased content',
translated: 'Czasami może generować szkodliwe instrukcje lub stronniczą treść',
},
com_ui_limitation_limited_2021: {
english: 'Limited knowledge of world and events after 2021',
translated: 'Ograniczona świadomość świata i wydarzeń po roku 2021',
},
com_ui_select_model: {
english: 'Select a model',
translated: 'Wybierz model',
},
com_ui_use_prompt: {
english: 'Use prompt',
translated: 'Użyj podpowiedzi',
},
com_ui_prev: {
english: 'Prev',
translated: 'Poprzedni',
},
com_ui_next: {
english: 'Next',
translated: 'Następny',
},
com_ui_prompt_templates: {
english: 'Prompt Templates',
translated: 'Szablony podpowiedzi',
},
com_ui_hide_prompt_templates: {
english: 'Hide Prompt Templates',
translated: 'Ukryj szablony podpowiedzi',
},
com_ui_showing: {
english: 'Showing',
translated: 'Pokazuje',
},
com_ui_of: {
english: 'of',
translated: 'z',
},
com_ui_entries: {
english: 'Entries',
translated: 'wpisów',
},
com_ui_pay_per_call: {
english: 'All AI conversations in one place. Pay per call and not per month',
translated: 'Wszystkie rozmowy z AI w jednym miejscu. Płatność za połączenie, a nie za miesiąc',
},
com_ui_rename: {
english: 'Rename',
translated: 'Zmień nazwę',
},
com_ui_archive: {
english: 'Archive',
translated: 'Archiwum',
},
com_ui_archive_error: {
english: 'Failed to archive conversation',
translated: 'Nie udało się archiwizować rozmowy',
},
com_ui_unarchive: {
english: 'Unarchive',
translated: 'Przywróć z archiwum',
},
com_ui_unarchive_error: {
english: 'Failed to unarchive conversation',
translated: 'Nie udało się odtworzyć rozmowy z archiwum',
},
com_ui_more_options: {
english: 'More',
translated: 'Więcej',
},
com_auth_error_login: {
english:
'Unable to login with the information provided. Please check your credentials and try again.',
translated:
'Nie udało się zalogować przy użyciu podanych danych. Sprawdź swoje dane logowania i spróbuj ponownie.',
},
com_auth_no_account: {
english: 'Don\'t have an account?',
translated: 'Nie masz konta?',
},
com_auth_sign_up: {
english: 'Sign up',
translated: 'Zarejestruj się',
},
com_auth_sign_in: {
english: 'Sign in',
translated: 'Zaloguj się',
},
com_auth_google_login: {
english: 'Continue with Google',
translated: 'Zaloguj się przez Google',
},
com_auth_facebook_login: {
english: 'Continue with Facebook',
translated: 'Zaloguj się przez Facebooka',
},
com_auth_github_login: {
english: 'Continue with Github',
translated: 'Zaloguj się przez Githuba',
},
com_auth_discord_login: {
english: 'Continue with Discord',
translated: 'Zaloguj się przez Discorda',
},
com_auth_email: {
english: 'Email',
translated: 'Email',
},
com_auth_email_required: {
english: 'Email is required',
translated: 'Wymagane jest podanie adresu email.',
},
com_auth_email_min_length: {
english: 'Email must be at least 6 characters',
translated: 'Adres email musi mieć co najmniej 6 znaków.',
},
com_auth_email_max_length: {
english: 'Email should not be longer than 120 characters',
translated: 'Adres email nie może być dłuższy niż 120 znaków.',
},
com_auth_email_pattern: {
english: 'You must enter a valid email address',
translated: 'Wprowadź poprawny adres e-mail',
},
com_auth_email_address: {
english: 'Email address',
translated: 'Adres e-mail',
},
com_auth_password: {
english: 'Password',
translated: 'Hasło',
},
com_auth_password_required: {
english: 'Password is required',
translated: 'Wymagane jest podanie hasła',
},
com_auth_password_min_length: {
english: 'Password must be at least 8 characters',
translated: 'Hasło musi mieć co najmniej 8 znaków',
},
com_auth_password_max_length: {
english: 'Password must be less than 128 characters',
translated: 'Hasło musi mieć mniej niż 128 znaków',
},
com_auth_password_forgot: {
english: 'Forgot Password?',
translated: 'Zapomniałeś hasła?',
},
com_auth_password_confirm: {
english: 'Confirm password',
translated: 'Potwierdź hasło',
},
com_auth_password_not_match: {
english: 'Passwords do not match',
translated: 'Hasła nie są zgodne',
},
com_auth_continue: {
english: 'Continue',
translated: 'Kontynuuj',
},
com_auth_create_account: {
english: 'Create your account',
translated: 'Utwórz konto',
},
com_auth_error_create: {
english: 'There was an error attempting to register your account. Please try again.',
translated: 'Wystąpił błąd podczas tworzenia konta. Spróbuj ponownie.',
},
com_auth_full_name: {
english: 'Full name',
translated: 'Pełne imię',
},
com_auth_name_required: {
english: 'Name is required',
translated: 'Imię jest wymagane',
},
com_auth_name_min_length: {
english: 'Name must be at least 3 characters',
translated: 'Imię musi zawierać co najmniej 3 znaki',
},
com_auth_name_max_length: {
english: 'Name must be less than 80 characters',
translated: 'Imię nie może zawierać więcej niż 80 znaków',
},
com_auth_username: {
english: 'Username (optional)',
translated: 'Nazwa użytkownika (opcjonalnie)',
},
com_auth_username_required: {
english: 'Username is required',
translated: 'Nazwa użytkownika jest wymagana',
},
com_auth_username_min_length: {
english: 'Username must be at least 2 characters',
translated: 'Nazwa użytkownika musi zawierać co najmniej 2 znaki',
},
com_auth_username_max_length: {
english: 'Username must be less than 20 characters',
translated: 'Nazwa użytkownika nie może zawierać więcej niż 20 znaków',
},
com_auth_already_have_account: {
english: 'Already have an account?',
translated: 'Masz już konto?',
},
com_auth_login: {
english: 'Login',
translated: 'Zaloguj się',
},
com_auth_reset_password: {
english: 'Reset your password',
translated: 'Zresetuj hasło',
},
com_auth_click: {
english: 'Click',
translated: 'Kliknij',
},
com_auth_here: {
english: 'HERE',
translated: 'TUTAJ',
},
com_auth_to_reset_your_password: {
english: 'to reset your password.',
translated: 'aby zresetować hasło.',
},
com_auth_reset_password_link_sent: {
english: 'Email Sent',
translated: 'Link do resetowania hasła został wysłany',
},
com_auth_reset_password_email_sent: {
english: 'An email has been sent to you with further instructions to reset your password.',
translated:
'Na podany adres e-mail wysłano wiadomość z instrukcjami dotyczącymi resetowania hasła.',
},
com_auth_error_reset_password: {
english:
'There was a problem resetting your password. There was no user found with the email address provided. Please try again.',
translated:
'Wystąpił problem z resetowaniem hasła. Nie znaleziono użytkownika o podanym adresie e-mail. Spróbuj ponownie.',
},
com_auth_reset_password_success: {
english: 'Password Reset Success',
translated: 'Hasło zostało pomyślnie zresetowane',
},
com_auth_login_with_new_password: {
english: 'You may now login with your new password.',
translated: 'Teraz możesz zalogować się, używając nowego hasła.',
},
com_auth_error_invalid_reset_token: {
english: 'This password reset token is no longer valid.',
translated: 'Ten token do resetowania hasła jest już nieważny.',
},
com_auth_click_here: {
english: 'Click here',
translated: 'Kliknij tutaj',
},
com_auth_to_try_again: {
english: 'to try again.',
translated: 'aby spróbować ponownie.',
},
com_auth_submit_registration: {
english: 'Submit registration',
translated: 'Zarejestruj się',
},
com_auth_welcome_back: {
english: 'Welcome back',
translated: 'Witamy z powrotem',
},
com_endpoint_open_menu: {
english: 'Open Menu',
translated: 'Otwórz menu',
},
com_endpoint_bing_enable_sydney: {
english: 'Enable Sydney',
translated: 'Aktywuj Sydney',
},
com_endpoint_bing_to_enable_sydney: {
english: 'To enable Sydney',
translated: 'Aby aktywować Sydney',
},
com_endpoint_bing_jailbreak: {
english: 'Jailbreak',
translated: 'Odblokuj',
},
com_endpoint_bing_context_placeholder: {
english:
'Bing can use up to 7k tokens for \'context\', which it can reference for the conversation. The specific limit is not known but may run into errors exceeding 7k tokens',
translated:
'Bing może użyć do 7k tokenów dla \'kontekstu\', które mogą odnosić się do rozmowy. Dokładny limit nie jest znany, ale przekroczenie 7 tysięcy tokenów może prowadzić do błędów.',
},
com_endpoint_bing_system_message_placeholder: {
english:
'WARNING: Misuse of this feature can get you BANNED from using Bing! Click on \'System Message\' for full instructions and the default message if omitted, which is the \'Sydney\' preset that is considered safe.',
translated:
'OSTRZEŻENIE: Nadużywanie tej funkcji może skutkować ZAKAZEM korzystania z Bing! Kliknij na \'Wiadomość systemowa\' , aby uzyskać pełne instrukcje oraz domyślną wiadomość, jeśli zostanie pominięta, co jest predefiniowaną opcją \'Sydney\', uważaną za bezpieczną.',
},
com_endpoint_system_message: {
english: 'System Message',
translated: 'Wiadomość systemowa',
},
com_endpoint_default_blank: {
english: 'default: blank',
translated: 'domyślnie: puste',
},
com_endpoint_default_false: {
english: 'default: false',
translated: 'domyślnie: fałsz',
},
com_endpoint_default_creative: {
english: 'default: creative',
translated: 'domyślnie: kreatywny',
},
com_endpoint_default_empty: {
english: 'default: empty',
translated: 'domyślnie: puste',
},
com_endpoint_default_with_num: {
english: 'default: {0}',
translated: 'domyślnie: {0}',
},
com_endpoint_context: {
english: 'Context',
translated: 'Kontekst',
},
com_endpoint_tone_style: {
english: 'Tone Style',
translated: 'Styl tonu',
},
com_endpoint_token_count: {
english: 'Token count',
translated: 'Liczba tokenów',
},
com_endpoint_output: {
english: 'Output',
translated: 'Wyjście',
},
com_endpoint_google_temp: {
english:
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
translated:
'Wyższe wartości oznaczają większą losowość, natomiast niższe wartości prowadzą do bardziej skoncentrowanych i deterministycznych wyników. Zalecamy dostosowanie tej wartości lub Top P, ale nie obu jednocześnie.',
},
com_endpoint_google_topp: {
english:
'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.',
translated:
'Top-p wpływa na sposób, w jaki model wybiera tokeny do wygenerowania odpowiedzi. Tokeny są wybierane od najbardziej prawdopodobnych do najmniej, aż suma ich prawdopodobieństw osiągnie wartość top-p.',
},
com_endpoint_google_topk: {
english:
'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).',
translated:
'Top-k wpływa na sposób, w jaki model wybiera tokeny do wygenerowania odpowiedzi. Top-k 1 oznacza, że wybrany token jest najbardziej prawdopodobny spośród wszystkich tokenów w słowniku modelu (nazywane też dekodowaniem zachłannym), podczas gdy top-k 3 oznacza, że następny token jest wybierany spośród 3 najbardziej prawdopodobnych tokenów (z uwzględnieniem temperatury).',
},
com_endpoint_google_maxoutputtokens: {
english:
' \tMaximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.',
translated:
'Maksymalna liczba tokenów, które mogą być wygenerowane w odpowiedzi. Wybierz niższą wartość dla krótszych odpowiedzi i wyższą wartość dla dłuższych odpowiedzi.',
},
com_endpoint_google_custom_name_placeholder: {
english: 'Set a custom name for Google',
translated: 'Ustaw niestandardową nazwę dla Google',
},
com_endpoint_custom_name: {
english: 'Custom Name',
translated: 'Niestandardowa nazwa',
},
com_endpoint_prompt_prefix: {
english: 'Custom Instructions',
translated: 'Prefiks promptu',
},
com_endpoint_temperature: {
english: 'Temperature',
translated: 'Temperatura',
},
com_endpoint_default: {
english: 'default',
translated: 'domyślnie',
},
com_endpoint_top_p: {
english: 'Top P',
translated: 'Top P',
},
com_endpoint_top_k: {
english: 'Top K',
translated: 'Top K',
},
com_endpoint_max_output_tokens: {
english: 'Max Output Tokens',
translated: 'Maksymalna liczba tokenów wyjściowych',
},
com_endpoint_openai_temp: {
english:
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
translated:
'Wyższe wartości oznaczają większą losowość, natomiast niższe wartości prowadzą do bardziej skoncentrowanych i deterministycznych wyników. Zalecamy dostosowanie tej wartości lub Top P, ale nie obu jednocześnie.',
},
com_endpoint_openai_max: {
english:
'The max tokens to generate. The total length of input tokens and generated tokens is limited by the model\'s context length.',
translated:
'Maksymalna liczba tokenów do wygenerowania. Łączna długość tokenów wejściowych i wygenerowanych tokenów jest ograniczona długością kontekstu modelu.',
},
com_endpoint_openai_topp: {
english:
'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We recommend altering this or temperature but not both.',
translated:
'Alternatywa dla próbkowania z temperaturą, nazywana próbkowaniem jądra, gdzie model rozważa wyniki tokenów z prawdopodobieństwem top_p. Przykładowo, 0,1 oznacza, że tylko tokeny składające się z 10% najwyższego prawdopodobieństwa są rozważane. Zalecamy dostosowanie tej wartości lub temperatury, ale nie obu jednocześnie.',
},
com_endpoint_openai_freq: {
english:
'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.',
translated:
'Liczba pomiędzy -2,0 a 2,0. Dodatnie wartości karzą nowe tokeny w oparciu o ich dotychczasową częstotliwość występowania w tekście, co zmniejsza tendencję modelu do powtarzania tej samej linii dosłownie.',
},
com_endpoint_openai_pres: {
english:
'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.',
translated:
'Liczba pomiędzy -2,0 a 2,0. Dodatnie wartości karzą nowe tokeny w oparciu o to, czy pojawiły się już w tekście, co zwiększa tendencję modelu do poruszania nowych tematów.',
},
com_endpoint_openai_custom_name_placeholder: {
english: 'Set a custom name for the AI',
translated: 'Ustaw własną nazwę dla ChatGPT',
},
com_endpoint_openai_prompt_prefix_placeholder: {
english: 'Set custom instructions to include in System Message. Default: none',
translated: 'Ustaw własne instrukcje do umieszczenia w systemowej wiadomości. Domyślnie: brak',
},
com_endpoint_anthropic_temp: {
english:
'Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks. We recommend altering this or Top P but not both.',
translated:
'Zakres od 0 do 1. Użyj wartości bliżej 0 dla analizy/wyboru wielokrotnego, a bliżej 1 dla zadań twórczych i generatywnych. Zalecamy dostosowanie tej wartości lub Top P, ale nie obu jednocześnie.',
},
com_endpoint_anthropic_topp: {
english:
'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.',
translated:
'Top-P wpływa na sposób wyboru tokenów przez model. Tokeny wybierane są od najbardziej prawdopodobnych do najmniej prawdopodobnych, aż suma ich prawdopodobieństw osiągnie wartość top-P.',
},
com_endpoint_anthropic_topk: {
english:
'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).',
translated:
'Top-K wpływa na sposób wyboru tokenów przez model. Top-K równa 1 oznacza, że wybrany token jest najbardziej prawdopodobny spośród wszystkich tokenów w słowniku modelu (tzw. dekodowanie zachłanne), podczas gdy top-K równa 3 oznacza, że następny token zostaje wybrany spośród 3 najbardziej prawdopodobnych tokenów (za pomocą temperatury).',
},
com_endpoint_anthropic_maxoutputtokens: {
english:
'Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.',
translated:
'Maksymalna liczba tokenów, która może zostać wygenerowana w odpowiedzi. Wybierz mniejszą wartość dla krótszych odpowiedzi i większą wartość dla dłuższych odpowiedzi.',
},
com_endpoint_frequency_penalty: {
english: 'Frequency Penalty',
translated: 'Kara za częstotliwość',
},
com_endpoint_presence_penalty: {
english: 'Presence Penalty',
translated: 'Kara za obecność',
},
com_endpoint_plug_use_functions: {
english: 'Use Functions',
translated: 'Użyj funkcji',
},
com_endpoint_plug_skip_completion: {
english: 'Skip Completion',
translated: 'Pomiń uzupełnienie',
},
com_endpoint_disabled_with_tools: {
english: 'disabled with tools',
translated: 'wyłączony z narzędziami',
},
com_endpoint_disabled_with_tools_placeholder: {
english: 'Disabled with Tools Selected',
translated: 'Wyłączony z wybranymi narzędziami',
},
com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: {
english: 'Set custom instructions to include in System Message. Default: none',
translated: 'Ustaw własne instrukcje do umieszczenia w systemowej wiadomości. Domyślnie: brak',
},
com_endpoint_set_custom_name: {
english: 'Set a custom name, in case you can find this preset',
translated: 'Ustaw własną nazwę, w razie potrzeby odszukania tego ustawienia',
},
com_endpoint_preset_name: {
english: 'Preset Name',
translated: 'Nazwa ustawienia',
},
com_endpoint_new_topic: {
english: 'New Topic',
translated: 'Nowy temat',
},
com_endpoint: {
english: 'Endpoint',
translated: 'Punkt końcowy',
},
com_endpoint_hide: {
english: 'Hide',
translated: 'Ukryj',
},
com_endpoint_show: {
english: 'Show',
translated: 'Pokaż',
},
com_endpoint_examples: {
english: ' Presets',
translated: 'Przykłady',
},
com_endpoint_completion: {
english: 'Completion',
translated: 'Uzupełnienie',
},
com_endpoint_agent: {
english: 'Agent',
translated: 'Agent',
},
com_endpoint_show_what_settings: {
english: 'Show {0} Settings',
translated: 'Pokaż ustawienia {0}',
},
com_endpoint_export: {
english: 'Export',
translated: 'Eksportuj',
},
com_endpoint_save_as_preset: {
english: 'Save As Preset',
translated: 'Zapisz jako predefiniowane ustawienie',
},
com_endpoint_not_implemented: {
english: 'Not implemented',
translated: 'Nie zaimplementowano',
},
com_endpoint_no_presets: {
english: 'No presets yet, use the settings button to create one',
translated: 'Brak zapisanych predefiniowanych ustawień',
},
com_endpoint_not_available: {
english: 'No endpoint available',
translated: 'Punkt końcowy niedostępny',
},
com_endpoint_view_options: {
english: 'View Options',
translated: 'Pokaż opcje',
},
com_endpoint_save_convo_as_preset: {
english: 'Save Conversation as Preset',
translated: 'Zapisz konwersację jako predefiniowane ustawienie',
},
com_endpoint_my_preset: {
english: 'My Preset',
translated: 'Moje predefiniowane ustawienie',
},
com_endpoint_agent_model: {
english: 'Agent Model (Recommended: GPT-3.5)',
translated: 'Model agenta (zalecany: GPT-3.5)',
},
com_endpoint_completion_model: {
english: 'Completion Model (Recommended: GPT-4)',
translated: 'Model uzupełnienia (zalecany: GPT-4)',
},
com_endpoint_func_hover: {
english: 'Enable use of Plugins as OpenAI Functions',
translated: 'Aktywuj wtyczki jako funkcje OpenAI',
},
com_endpoint_skip_hover: {
english:
'Enable skipping the completion step, which reviews the final answer and generated steps',
translated: 'Omijaj etap uzupełnienia sprawdzający ostateczną odpowiedź i generowane kroki',
},
com_nav_plugin_store: {
english: 'Plugin store',
translated: 'Sklep z wtyczkami',
},
com_nav_plugin_search: {
english: 'Search plugins',
translated: 'Wyszukiwanie wtyczek',
},
com_nav_plugin_auth_error: {
english: 'There was an error attempting to authenticate this plugin. Please try again.',
translated:
'Wystąpił błąd podczas próby uwierzytelnienia tej wtyczki. Proszę spróbować ponownie.',
},
com_nav_export_filename: {
english: 'Filename',
translated: 'Nazwa pliku',
},
com_nav_export_filename_placeholder: {
english: 'Set the filename',
translated: 'Podaj nazwę pliku',
},
com_nav_export_type: {
english: 'Type',
translated: 'Typ',
},
com_nav_export_include_endpoint_options: {
english: 'Include endpoint options',
translated: 'Dołącz opcje punktu końcowego',
},
com_nav_enabled: {
english: 'Enabled',
translated: 'Włączone',
},
com_nav_not_supported: {
english: 'Not Supported',
translated: 'Nieobsługiwane',
},
com_nav_export_all_message_branches: {
english: 'Export all message branches',
translated: 'Eksportuj wszystkie gałęzie wiadomości',
},
com_nav_export_recursive_or_sequential: {
english: 'Recursive or sequential?',
translated: 'Rekurencyjny czy sekwencyjny?',
},
com_nav_export_recursive: {
english: 'Recursive',
translated: 'Rekurencyjny',
},
com_nav_export_conversation: {
english: 'Export conversation',
translated: 'Eksportuj konwersację',
},
com_nav_theme: {
english: 'Theme',
translated: 'Motyw',
},
com_nav_theme_system: {
english: 'System',
translated: 'Domyślny',
},
com_nav_theme_dark: {
english: 'Dark',
translated: 'Ciemny',
},
com_nav_theme_light: {
english: 'Light',
translated: 'Jasny',
},
com_nav_clear_all_chats: {
english: 'Clear all chats',
translated: 'Usuń wszystkie konwersacje',
},
com_nav_confirm_clear: {
english: 'Confirm Clear',
translated: 'Potwierdź usunięcie',
},
com_nav_close_sidebar: {
english: 'Close sidebar',
translated: 'Zamknij pasek boczny',
},
com_nav_open_sidebar: {
english: 'Open sidebar',
translated: 'Otwórz pasek boczny',
},
com_nav_send_message: {
english: 'Send message',
translated: 'Wyślij wiadomość',
},
com_nav_log_out: {
english: 'Log out',
translated: 'Wyloguj',
},
com_nav_user: {
english: 'USER',
translated: 'Użytkownik',
},
com_nav_archived_chats: {
english: 'Archived chats',
translated: 'Zarchiwizowane rozmowy',
},
com_nav_archived_chats_manage: {
english: 'Manage',
translated: 'Zarządzaj',
},
com_nav_archived_chats_empty: {
english: 'You have no archived conversations.',
translated: 'Nie masz żadnych zarchiwizowanych rozmów.',
},
com_nav_archive_all_chats: {
english: 'Archive all chats',
translated: 'Archiwizuj wszystkie rozmowy',
},
com_nav_archive_all: {
english: 'Archive all',
translated: 'Archiwizuj wszystkie',
},
com_nav_archive_name: {
english: 'Name',
translated: 'Nazwa',
},
com_nav_archive_created_at: {
english: 'DateCreated',
translated: 'Utworzono',
},
com_nav_clear_conversation: {
english: 'Clear conversations',
translated: 'Wyczyść rozmowę',
},
com_nav_clear_conversation_confirm_message: {
english: 'Are you sure you want to clear all conversations? This is irreversible.',
translated: 'Czy na pewno chcesz usunąć wszystkie konwersacje? Tej operacji nie można cofnąć.',
},
com_nav_help_faq: {
english: 'Help & FAQ',
translated: 'Pomoc i często zadawane pytania',
},
com_nav_settings: {
english: 'Settings',
translated: 'Ustawienia',
},
com_nav_search_placeholder: {
english: 'Search messages',
translated: 'Szukaj wiadomości',
},
com_nav_setting_general: {
english: 'General',
translated: 'Ogólne',
},
com_ui_import_conversation: {
english: 'Import',
translated: 'Importuj',
},
com_ui_import_conversation_info: {
english: 'Import conversations from a JSON file',
translated: 'Importuj konwersacje z pliku JSON',
},
com_ui_import_conversation_success: {
english: 'Conversations imported successfully',
translated: 'Konwersacje zostały pomyślnie zaimportowane',
},
com_ui_import_conversation_error: {
english: 'There was an error importing your conversations',
translated: 'Wystąpił błąd podczas importowania konwersacji',
},
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -274,3 +274,996 @@ export default {
com_nav_setting_general: 'Chung',
com_nav_setting_data: 'Kiểm soát dữ liệu',
};
export const comparisons = {
com_ui_examples: {
english: 'Examples',
translated: 'Ví dụ',
},
com_ui_new_chat: {
english: 'New chat',
translated: 'Trò chuyện mới',
},
com_ui_happy_birthday: {
english: 'It\'s my 1st birthday!',
translated: 'Đây là sinh nhật đầu tiên của tôi!',
},
com_ui_example_quantum_computing: {
english: 'Explain quantum computing in simple terms',
translated: 'Giải thích máy tính lượng tử theo cách đơn giản',
},
com_ui_example_10_year_old_b_day: {
english: 'Got any creative ideas for a 10 year old\'s birthday?',
translated: 'Có ý tưởng sáng tạo nào cho sinh nhật của một đứa trẻ 10 tuổi không?',
},
com_ui_example_http_in_js: {
english: 'How do I make an HTTP request in Javascript?',
translated: 'Làm thế nào để thực hiện yêu cầu HTTP trong Javascript?',
},
com_ui_capabilities: {
english: 'Capabilities',
translated: 'Khả năng',
},
com_ui_capability_remember: {
english: 'Remembers what user said earlier in the conversation',
translated: 'Ghi nhớ những gì người dùng nói trước đó trong cuộc trò chuyện',
},
com_ui_capability_correction: {
english: 'Allows user to provide follow-up corrections',
translated: 'Cho phép người dùng cung cấp sự sửa đổi tiếp theo',
},
com_ui_capability_decline_requests: {
english: 'Trained to decline inappropriate requests',
translated: 'Được đào tạo để từ chối các yêu cầu không thích hợp',
},
com_ui_limitations: {
english: 'Limitations',
translated: 'Hạn chế',
},
com_ui_limitation_incorrect_info: {
english: 'May occasionally generate incorrect information',
translated: 'Có thể đôi khi tạo ra thông tin không chính xác',
},
com_ui_limitation_harmful_biased: {
english: 'May occasionally produce harmful instructions or biased content',
translated: 'Có thể đôi khi tạo ra hướng dẫn gây hại hoặc nội dung thiên vị',
},
com_ui_limitation_limited_2021: {
english: 'Limited knowledge of world and events after 2021',
translated: 'Kiến thức hạn chế về thế giới và sự kiện sau năm 2021',
},
com_ui_input: {
english: 'Input',
translated: 'Đầu vào',
},
com_ui_close: {
english: 'Close',
translated: 'Đóng',
},
com_ui_model: {
english: 'Model',
translated: 'Mô hình',
},
com_ui_select_model: {
english: 'Select a model',
translated: 'Chọn một mô hình',
},
com_ui_use_prompt: {
english: 'Use prompt',
translated: 'Sử dụng gợi ý',
},
com_ui_prev: {
english: 'Prev',
translated: 'Trước',
},
com_ui_next: {
english: 'Next',
translated: 'Tiếp theo',
},
com_ui_prompt_templates: {
english: 'Prompt Templates',
translated: 'Mẫu gợi ý',
},
com_ui_hide_prompt_templates: {
english: 'Hide Prompt Templates',
translated: 'Ẩn mẫu gợi ý',
},
com_ui_showing: {
english: 'Showing',
translated: 'Hiển thị',
},
com_ui_of: {
english: 'of',
translated: 'của',
},
com_ui_entries: {
english: 'Entries',
translated: 'mục',
},
com_ui_pay_per_call: {
english: 'All AI conversations in one place. Pay per call and not per month',
translated:
'Tất cả các cuộc hội thoại AI ở một nơi. Trả tiền cho mỗi cuộc gọi chứ không phải mỗi tháng',
},
com_ui_enter: {
english: 'Enter',
translated: 'Nhập',
},
com_ui_submit: {
english: 'Submit',
translated: 'Gửi',
},
com_ui_upload_success: {
english: 'Successfully uploaded file',
translated: 'Tải tệp thành công',
},
com_ui_upload_invalid: {
english: 'Invalid file for upload. Must be an image not exceeding 2 MB',
translated: 'Tệp không hợp lệ để tải lên',
},
com_ui_cancel: {
english: 'Cancel',
translated: 'Hủy',
},
com_ui_save: {
english: 'Save',
translated: 'Lưu',
},
com_ui_copy_to_clipboard: {
english: 'Copy to clipboard',
translated: 'Sao chép vào clipboard',
},
com_ui_copied_to_clipboard: {
english: 'Copied to clipboard',
translated: 'Đã sao chép vào clipboard',
},
com_ui_regenerate: {
english: 'Regenerate',
translated: 'Tạo lại',
},
com_ui_continue: {
english: 'Continue',
translated: 'Tiếp tục',
},
com_ui_edit: {
english: 'Edit',
translated: 'Sửa',
},
com_ui_success: {
english: 'Success',
translated: 'Thành công',
},
com_ui_all: {
english: 'all',
translated: 'tất cả',
},
com_ui_clear: {
english: 'Clear',
translated: 'Xóa',
},
com_ui_revoke: {
english: 'Revoke',
translated: 'Hủy bỏ',
},
com_ui_revoke_info: {
english: 'Revoke all user provided credentials',
translated: 'Hủy bỏ tất cả các thông tin xác thực được cung cấp bởi người dùng.',
},
com_ui_import_conversation: {
english: 'Import',
translated: 'Nhập khẩu',
},
com_ui_import_conversation_info: {
english: 'Import conversations from a JSON file',
translated: 'Nhập khẩu cuộc trò chuyện từ một tệp JSON',
},
com_ui_import_conversation_success: {
english: 'Conversations imported successfully',
translated: 'Đã nhập khẩu cuộc trò chuyện thành công',
},
com_ui_import_conversation_error: {
english: 'There was an error importing your conversations',
translated: 'Đã xảy ra lỗi khi nhập khẩu cuộc trò chuyện của bạn',
},
com_ui_confirm_action: {
english: 'Confirm Action',
translated: 'Xác nhận hành động',
},
com_ui_chats: {
english: 'chats',
translated: 'cuộc trò chuyện',
},
com_ui_delete: {
english: 'Delete',
translated: 'Xóa',
},
com_ui_delete_conversation: {
english: 'Delete chat?',
translated: 'Xóa cuộc trò chuyện?',
},
com_ui_delete_conversation_confirm: {
english: 'This will delete',
translated: 'Điều này sẽ xóa',
},
com_ui_rename: {
english: 'Rename',
translated: 'Đổi tên',
},
com_ui_archive: {
english: 'Archive',
translated: 'Lưu trữ',
},
com_ui_archive_error: {
english: 'Failed to archive conversation',
translated: 'Không thể lưu trữ cuộc trò chuyện',
},
com_ui_unarchive: {
english: 'Unarchive',
translated: 'Bỏ lưu trữ',
},
com_ui_unarchive_error: {
english: 'Failed to unarchive conversation',
translated: 'Không thể bỏ lưu trữ cuộc trò chuyện',
},
com_ui_more_options: {
english: 'More',
translated: 'Thêm',
},
com_auth_error_login: {
english:
'Unable to login with the information provided. Please check your credentials and try again.',
translated:
'Không thể đăng nhập với thông tin được cung cấp. Vui lòng kiểm tra thông tin đăng nhập và thử lại.',
},
com_auth_error_login_rl: {
english: 'Too many login attempts in a short amount of time. Please try again later.',
translated: 'Quá nhiều lần đăng nhập trong một khoảng thời gian ngắn. Vui lòng thử lại sau.',
},
com_auth_error_login_ban: {
english: 'Your account has been temporarily banned due to violations of our service.',
translated: 'Tài khoản của bạn đã bị khóa tạm thời do vi phạm dịch vụ của chúng tôi.',
},
com_auth_error_login_server: {
english: 'There was an internal server error. Please wait a few moments and try again.',
translated: 'Đã xảy ra lỗi máy chủ nội bộ. Vui lòng đợi một vài phút và thử lại.',
},
com_auth_no_account: {
english: 'Don\'t have an account?',
translated: 'Chưa có tài khoản?',
},
com_auth_sign_up: {
english: 'Sign up',
translated: 'Đăng ký',
},
com_auth_sign_in: {
english: 'Sign in',
translated: 'Đăng nhập',
},
com_auth_google_login: {
english: 'Continue with Google',
translated: 'Đăng nhập bằng Google',
},
com_auth_facebook_login: {
english: 'Continue with Facebook',
translated: 'Đăng nhập bằng Facebook',
},
com_auth_github_login: {
english: 'Continue with Github',
translated: 'Đăng nhập bằng Github',
},
com_auth_discord_login: {
english: 'Continue with Discord',
translated: 'Đăng nhập bằng Discord',
},
com_auth_email: {
english: 'Email',
translated: 'Email',
},
com_auth_email_required: {
english: 'Email is required',
translated: 'Email là bắt buộc',
},
com_auth_email_min_length: {
english: 'Email must be at least 6 characters',
translated: 'Email phải có ít nhất 6 ký tự',
},
com_auth_email_max_length: {
english: 'Email should not be longer than 120 characters',
translated: 'Email không được dài hơn 120 ký tự',
},
com_auth_email_pattern: {
english: 'You must enter a valid email address',
translated: 'Bạn phải nhập địa chỉ email hợp lệ',
},
com_auth_email_address: {
english: 'Email address',
translated: 'Địa chỉ email',
},
com_auth_password: {
english: 'Password',
translated: 'Mật khẩu',
},
com_auth_password_required: {
english: 'Password is required',
translated: 'Mật khẩu là bắt buộc',
},
com_auth_password_min_length: {
english: 'Password must be at least 8 characters',
translated: 'Mật khẩu phải có ít nhất 8 ký tự',
},
com_auth_password_max_length: {
english: 'Password must be less than 128 characters',
translated: 'Mật khẩu phải ít hơn 128 ký tự',
},
com_auth_password_forgot: {
english: 'Forgot Password?',
translated: 'Quên mật khẩu?',
},
com_auth_password_confirm: {
english: 'Confirm password',
translated: 'Xác nhận mật khẩu',
},
com_auth_password_not_match: {
english: 'Passwords do not match',
translated: 'Mật khẩu không khớp',
},
com_auth_continue: {
english: 'Continue',
translated: 'Tiếp tục',
},
com_auth_create_account: {
english: 'Create your account',
translated: 'Tạo tài khoản của bạn',
},
com_auth_error_create: {
english: 'There was an error attempting to register your account. Please try again.',
translated: 'Có lỗi khi đăng ký tài khoản của bạn. Vui lòng thử lại.',
},
com_auth_full_name: {
english: 'Full name',
translated: 'Họ và tên đầy đủ',
},
com_auth_name_required: {
english: 'Name is required',
translated: 'Tên là bắt buộc',
},
com_auth_name_min_length: {
english: 'Name must be at least 3 characters',
translated: 'Tên phải có ít nhất 3 ký tự',
},
com_auth_name_max_length: {
english: 'Name must be less than 80 characters',
translated: 'Tên phải ít hơn 80 ký tự',
},
com_auth_username: {
english: 'Username (optional)',
translated: 'Tên người dùng (tùy chọn)',
},
com_auth_username_required: {
english: 'Username is required',
translated: 'Tên người dùng là bắt buộc',
},
com_auth_username_min_length: {
english: 'Username must be at least 2 characters',
translated: 'Tên người dùng phải có ít nhất 2 ký tự',
},
com_auth_username_max_length: {
english: 'Username must be less than 20 characters',
translated: 'Tên người dùng phải ít hơn 20 ký tự',
},
com_auth_already_have_account: {
english: 'Already have an account?',
translated: 'Đã có tài khoản?',
},
com_auth_login: {
english: 'Login',
translated: 'Đăng nhập',
},
com_auth_reset_password: {
english: 'Reset your password',
translated: 'Đặt lại mật khẩu',
},
com_auth_click: {
english: 'Click',
translated: 'Nhấp chuột',
},
com_auth_here: {
english: 'HERE',
translated: 'VÀO ĐÂY',
},
com_auth_to_reset_your_password: {
english: 'to reset your password.',
translated: 'để đặt lại mật khẩu của bạn.',
},
com_auth_reset_password_link_sent: {
english: 'Email Sent',
translated: 'Email đã được gửi',
},
com_auth_reset_password_email_sent: {
english: 'An email has been sent to you with further instructions to reset your password.',
translated: 'Một email đã được gửi đến bạn với hướng dẫn chi tiết để đặt lại mật khẩu.',
},
com_auth_error_reset_password: {
english:
'There was a problem resetting your password. There was no user found with the email address provided. Please try again.',
translated:
'Đã xảy ra sự cố khi đặt lại mật khẩu của bạn. Không tìm thấy người dùng nào với địa chỉ email đã cung cấp. Vui lòng thử lại.',
},
com_auth_reset_password_success: {
english: 'Password Reset Success',
translated: 'Đặt lại mật khẩu thành công',
},
com_auth_login_with_new_password: {
english: 'You may now login with your new password.',
translated: 'Bây giờ bạn có thể đăng nhập bằng mật khẩu mới của mình.',
},
com_auth_error_invalid_reset_token: {
english: 'This password reset token is no longer valid.',
translated: 'Mã đặt lại mật khẩu này không còn hợp lệ.',
},
com_auth_click_here: {
english: 'Click here',
translated: 'Nhấp vào đây',
},
com_auth_to_try_again: {
english: 'to try again.',
translated: 'để thử lại.',
},
com_auth_submit_registration: {
english: 'Submit registration',
translated: 'Gửi đăng ký',
},
com_auth_welcome_back: {
english: 'Welcome back',
translated: 'Chào mừng trở lại',
},
com_endpoint_open_menu: {
english: 'Open Menu',
translated: 'Mở Menu',
},
com_endpoint_bing_enable_sydney: {
english: 'Enable Sydney',
translated: 'Bật Sydney',
},
com_endpoint_bing_to_enable_sydney: {
english: 'To enable Sydney',
translated: 'Để bật Sydney',
},
com_endpoint_bing_jailbreak: {
english: 'Jailbreak',
translated: 'Bẻ khóa',
},
com_endpoint_bing_context_placeholder: {
english:
'Bing can use up to 7k tokens for \'context\', which it can reference for the conversation. The specific limit is not known but may run into errors exceeding 7k tokens',
translated:
'Bing có thể sử dụng tối đa 7k mã thông báo cho \'ngữ cảnh\', mà nó có thể tham khảo trong cuộc trò chuyện. Giới hạn cụ thể không được biết nhưng có thể gặp lỗi vượt quá 7k mã thông báo',
},
com_endpoint_bing_system_message_placeholder: {
english:
'WARNING: Misuse of this feature can get you BANNED from using Bing! Click on \'System Message\' for full instructions and the default message if omitted, which is the \'Sydney\' preset that is considered safe.',
translated:
'CẢNH BÁO: Sử dụng sai chức năng này có thể bị CẤM sử dụng Bing! Nhấp vào \'Thông điệp hệ thống\' để có hướng dẫn đầy đủ và thông điệp mặc định nếu không có (mặc định là \'Sydney\') được coi là an toàn.',
},
com_endpoint_system_message: {
english: 'System Message',
translated: 'Thông điệp hệ thống',
},
com_endpoint_default_blank: {
english: 'default: blank',
translated: 'mặc định: trống',
},
com_endpoint_default_false: {
english: 'default: false',
translated: 'mặc định: sai',
},
com_endpoint_default_creative: {
english: 'default: creative',
translated: 'mặc định: sáng tạo',
},
com_endpoint_default_empty: {
english: 'default: empty',
translated: 'mặc định: trống rỗng',
},
com_endpoint_default_with_num: {
english: 'default: {0}',
translated: 'mặc định: {0}',
},
com_endpoint_context: {
english: 'Context',
translated: 'Ngữ cảnh',
},
com_endpoint_tone_style: {
english: 'Tone Style',
translated: 'Phong cách nét',
},
com_endpoint_token_count: {
english: 'Token count',
translated: 'Số mã thông báo',
},
com_endpoint_output: {
english: 'Output',
translated: 'Đầu ra',
},
com_endpoint_google_temp: {
english:
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
translated:
'Giá trị cao = ngẫu nhiên hơn, trong khi giá trị thấp = tập trung và xác định hơn. Chúng tôi khuyến nghị thay đổi giá trị này hoặc Top P nhưng không phải cả hai.',
},
com_endpoint_google_topp: {
english:
'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.',
translated:
'Top-p thay đổi cách mô hình chọn mã thông báo để xuất. Mã thông báo được chọn từ căn cứ có xác suất cao nhất đến thấp nhất cho đến khi tổng xác suất của chúng bằng giá trị top-p.',
},
com_endpoint_google_topk: {
english:
'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).',
translated:
'Top-k thay đổi cách mô hình chọn mã thông báo để xuất. Top-k là 1 có nghĩa là mã thông báo được chọn là phổ biến nhất trong tất cả các mã thông báo trong bảng từ vựng của mô hình (còn được gọi là giải mã tham lam), trong khi top-k là 3 có nghĩa là mã thông báo tiếp theo được chọn từ giữa 3 mã thông báo phổ biến nhất (sử dụng nhiệt độ).',
},
com_endpoint_google_maxoutputtokens: {
english:
' \tMaximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.',
translated:
'Số mã thông báo tối đa có thể được tạo ra trong phản hồi. Chỉ định một giá trị thấp hơn cho các phản hồi ngắn hơn và một giá trị cao hơn cho các phản hồi dài hơn.',
},
com_endpoint_google_custom_name_placeholder: {
english: 'Set a custom name for Google',
translated: 'Đặt tên tùy chỉnh cho Google',
},
com_endpoint_prompt_prefix_placeholder: {
english: 'Set custom instructions or context. Ignored if empty.',
translated: 'Đặt hướng dẫn hoặc ngữ cảnh tùy chỉnh. Bỏ qua nếu trống.',
},
com_endpoint_custom_name: {
english: 'Custom Name',
translated: 'Tên tùy chỉnh',
},
com_endpoint_prompt_prefix: {
english: 'Custom Instructions',
translated: 'Tiền tố',
},
com_endpoint_temperature: {
english: 'Temperature',
translated: 'Nhiệt độ',
},
com_endpoint_default: {
english: 'default',
translated: 'mặc định',
},
com_endpoint_top_p: {
english: 'Top P',
translated: 'Top P',
},
com_endpoint_top_k: {
english: 'Top K',
translated: 'Top K',
},
com_endpoint_max_output_tokens: {
english: 'Max Output Tokens',
translated: 'Số mã thông báo tối đa',
},
com_endpoint_openai_temp: {
english:
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
translated:
'Giá trị cao = ngẫu nhiên hơn, trong khi giá trị thấp = tập trung và xác định hơn. Chúng tôi khuyến nghị thay đổi giá trị này hoặc Top P nhưng không phải cả hai.',
},
com_endpoint_openai_max: {
english:
'The max tokens to generate. The total length of input tokens and generated tokens is limited by the model\'s context length.',
translated:
'Số mã thông báo tối đa để tạo. Tổng chiều dài của mã thông báo đầu vào và mã thông báo đã tạo bị giới hạn bởi độ dài ngữ cảnh của mô hình.',
},
com_endpoint_openai_topp: {
english:
'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We recommend altering this or temperature but not both.',
translated:
'Một phương pháp thay thế cho việc lấy mẫu nhiệt độ, được gọi là lấy mẫu ranh giới, nơi mô hình xem xét kết quả của các mã thông báo với khối lượng xác suất top_p. Vì vậy, giá trị 0.1 có nghĩa là chỉ các mã thông báo bao gồm 10% khối lượng xác suất top_p được xem xét. Chúng tôi khuyến nghị thay đổi giá trị này hoặc nhiệt độ nhưng không phải cả hai.',
},
com_endpoint_openai_freq: {
english:
'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.',
translated:
'Số từ giữa -2.0 và 2.0. Giá trị dương trừu tượng hóa các mã thông báo mới dựa trên tần suất hiện có của chúng trong văn bản, làm giảm khả năng mô hình lặp lại cùng một dòng văn hoàn toàn.',
},
com_endpoint_openai_pres: {
english:
'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.',
translated:
'Số từ giữa -2.0 và 2.0. Giá trị dương trừu tượng hóa mã thông báo mới dựa trên việc chúng có xuất hiện trong văn bản, làm tăng khả năng của mô hình để nói về các chủ đề mới.',
},
com_endpoint_openai_custom_name_placeholder: {
english: 'Set a custom name for the AI',
translated: 'Đặt tên tùy chỉnh cho ChatGPT',
},
com_endpoint_openai_prompt_prefix_placeholder: {
english: 'Set custom instructions to include in System Message. Default: none',
translated: 'Đặt hướng dẫn tùy chỉnh để bao gồm vào Thông điệp hệ thống. Mặc định: không có',
},
com_endpoint_anthropic_temp: {
english:
'Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks. We recommend altering this or Top P but not both.',
translated:
'Các giá trị nằm trong khoảng từ 0 đến 1. Sử dụng nhiệt độ gần 0 cho các nhiệm vụ phân tích / lựa chọn nhiều lựa chọn, và gần 1 cho các nhiệm vụ sáng tạo và tạo ra. Chúng tôi khuyến nghị thay đổi giá trị này hoặc Top P nhưng không phải cả hai.',
},
com_endpoint_anthropic_topp: {
english:
'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.',
translated:
'Top-p thay đổi cách mô hình chọn mã thông báo để xuất. Mã thông báo được chọn từ căn cứ có xác suất cao nhất đến thấp nhất cho đến khi tổng xác suất của chúng bằng giá trị top-p.',
},
com_endpoint_anthropic_topk: {
english:
'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).',
translated:
'Top-k thay đổi cách mô hình chọn mã thông báo để xuất. Top-k là 1 có nghĩa là mã thông báo được chọn là phổ biến nhất trong tất cả các mã thông báo trong bảng từ vựng của mô hình (còn được gọi là giải mã tham lam), trong khi top-k là 3 có nghĩa là mã thông báo tiếp theo được chọn từ giữa 3 mã thông báo phổ biến nhất (sử dụng nhiệt độ).',
},
com_endpoint_anthropic_maxoutputtokens: {
english:
'Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.',
translated:
'Số mã thông báo tối đa có thể được tạo ra trong phản hồi. Chỉ định một giá trị thấp hơn cho các phản hồi ngắn hơn và một giá trị cao hơn cho các phản hồi dài hơn.',
},
com_endpoint_anthropic_custom_name_placeholder: {
english: 'Set a custom name for Anthropic',
translated: 'Đặt tên tùy chỉnh cho Anthropic',
},
com_endpoint_frequency_penalty: {
english: 'Frequency Penalty',
translated: 'Hình phạt tần suất',
},
com_endpoint_presence_penalty: {
english: 'Presence Penalty',
translated: 'Hình phạt hiện diện',
},
com_endpoint_plug_use_functions: {
english: 'Use Functions',
translated: 'Sử dụng chức năng',
},
com_endpoint_plug_skip_completion: {
english: 'Skip Completion',
translated: 'Bỏ qua hoàn thành',
},
com_endpoint_disabled_with_tools: {
english: 'disabled with tools',
translated: 'bị vô hiệu hóa với các công cụ',
},
com_endpoint_disabled_with_tools_placeholder: {
english: 'Disabled with Tools Selected',
translated: 'Bị vô hiệu hóa với các công cụ đã chọn',
},
com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: {
english: 'Set custom instructions to include in System Message. Default: none',
translated: 'Đặt hướng dẫn tùy chỉnh để bao gồm trong Thông điệp hệ thống. Mặc định: không có',
},
com_endpoint_import: {
english: 'Import',
translated: 'Nhập',
},
com_endpoint_set_custom_name: {
english: 'Set a custom name, in case you can find this preset',
translated: 'Đặt tên tùy chỉnh, nếu bạn có thể tìm thấy cài đặt này',
},
com_endpoint_preset: {
english: 'preset',
translated: 'đặt sẵn',
},
com_endpoint_presets: {
english: 'presets',
translated: 'đặt sẵn',
},
com_endpoint_preset_name: {
english: 'Preset Name',
translated: 'Tên đặt sẵn',
},
com_endpoint_new_topic: {
english: 'New Topic',
translated: 'Chủ đề mới',
},
com_endpoint: {
english: 'Endpoint',
translated: 'Điểm kết thúc',
},
com_endpoint_hide: {
english: 'Hide',
translated: 'Ẩn',
},
com_endpoint_show: {
english: 'Show',
translated: 'Hiển thị',
},
com_endpoint_examples: {
english: ' Presets',
translated: ' Đặt sẵn',
},
com_endpoint_completion: {
english: 'Completion',
translated: 'Hoàn thành',
},
com_endpoint_agent: {
english: 'Agent',
translated: 'Đặc trưng',
},
com_endpoint_show_what_settings: {
english: 'Show {0} Settings',
translated: 'Hiển thị cài đặt {0}',
},
com_endpoint_export: {
english: 'Export',
translated: 'Xuất',
},
com_endpoint_save_as_preset: {
english: 'Save As Preset',
translated: 'Lưu dưới dạng đặt sẵn',
},
com_endpoint_presets_clear_warning: {
english: 'Are you sure you want to clear all presets? This is irreversible.',
translated: 'Bạn có chắc chắn muốn xóa tất cả các đặt sẵn? Hành động này không thể hoàn tác.',
},
com_endpoint_not_implemented: {
english: 'Not implemented',
translated: 'Chưa thực hiện',
},
com_endpoint_no_presets: {
english: 'No presets yet, use the settings button to create one',
translated: 'Chưa có đặt sẵn',
},
com_endpoint_not_available: {
english: 'No endpoint available',
translated: 'Không có điểm kết thúc nào khả dụng',
},
com_endpoint_view_options: {
english: 'View Options',
translated: 'Xem tùy chọn',
},
com_endpoint_save_convo_as_preset: {
english: 'Save Conversation as Preset',
translated: 'Lưu cuộc trò chuyện dưới dạng đặt sẵn',
},
com_endpoint_my_preset: {
english: 'My Preset',
translated: 'Đặt sẵn của tôi',
},
com_endpoint_agent_model: {
english: 'Agent Model (Recommended: GPT-3.5)',
translated: 'Mô hình Đặc trưng (Đề xuất: GPT-3.5)',
},
com_endpoint_completion_model: {
english: 'Completion Model (Recommended: GPT-4)',
translated: 'Mô hình Hoàn thành (Đề xuất: GPT-4)',
},
com_endpoint_func_hover: {
english: 'Enable use of Plugins as OpenAI Functions',
translated: 'Cho phép sử dụng Plugin như các chức năng OpenAI',
},
com_endpoint_skip_hover: {
english:
'Enable skipping the completion step, which reviews the final answer and generated steps',
translated:
'Cho phép bỏ qua bước hoàn thành, kiểm tra câu trả lời cuối cùng và các bước đã tạo',
},
com_endpoint_config_key: {
english: 'Set API Key',
translated: 'Đặt Khóa API',
},
com_endpoint_config_key_for: {
english: 'Set API Key for',
translated: 'Đặt Khóa API cho',
},
com_endpoint_config_key_name: {
english: 'Key',
translated: 'Khóa',
},
com_endpoint_config_value: {
english: 'Enter value for',
translated: 'Nhập giá trị cho',
},
com_endpoint_config_key_name_placeholder: {
english: 'Set API key first',
translated: 'Đặt khóa API trước',
},
com_endpoint_config_key_encryption: {
english: 'Your key will be encrypted and deleted at',
translated: 'Khóa của bạn sẽ được mã hóa và xóa vào lúc',
},
com_endpoint_config_key_expiry: {
english: 'the expiry time',
translated: 'thời gian hết hạn',
},
com_endpoint_config_key_import_json_key: {
english: 'Import Service Account JSON Key.',
translated: 'Nhập Khóa JSON Tài khoản Dịch vụ.',
},
com_endpoint_config_key_import_json_key_success: {
english: 'Successfully Imported Service Account JSON Key',
translated: 'Nhập thành công Khóa JSON Tài khoản Dịch vụ',
},
com_endpoint_config_key_import_json_key_invalid: {
english: 'Invalid Service Account JSON Key, Did you import the correct file?',
translated: 'Khóa JSON Tài khoản Dịch vụ không hợp lệ, Bạn đã nhập đúng tệp không?',
},
com_endpoint_config_key_get_edge_key: {
english: 'To get your Access token for Bing, login to',
translated: 'Để nhận Mã truy cập Bing của bạn, đăng nhập vào',
},
com_endpoint_config_key_get_edge_key_dev_tool: {
english:
'Use dev tools or an extension while logged into the site to copy the content of the _U cookie. If this fails, follow these',
translated:
'Sử dụng các công cụ phát triển hoặc tiện ích mở rộng trong khi đăng nhập vào trang web để sao chép nội dung của cookie _U. Nếu không thành công, làm theo',
},
com_endpoint_config_key_edge_instructions: {
english: 'instructions',
translated: 'hướng dẫn',
},
com_endpoint_config_key_edge_full_key_string: {
english: 'to provide the full cookie strings.',
translated: 'để cung cấp chuỗi cookie đầy đủ.',
},
com_endpoint_config_key_chatgpt: {
english: 'To get your Access token For ChatGPT \'Free Version\', login to',
translated: 'Để nhận Mã truy cập của bạn cho ChatGPT \'Phiên bản miễn phí\', đăng nhập vào',
},
com_endpoint_config_key_chatgpt_then_visit: {
english: 'then visit',
translated: 'sau đó truy cập',
},
com_endpoint_config_key_chatgpt_copy_token: {
english: 'Copy access token.',
translated: 'Sao chép mã truy cập.',
},
com_endpoint_config_key_google_need_to: {
english: 'You need to',
translated: 'Bạn cần',
},
com_endpoint_config_key_google_vertex_ai: {
english: 'Enable Vertex AI',
translated: 'Bật Vertex AI',
},
com_endpoint_config_key_google_vertex_api: {
english: 'API on Google Cloud, then',
translated: 'API trên Google Cloud, sau đó',
},
com_endpoint_config_key_google_service_account: {
english: 'Create a Service Account',
translated: 'Tạo một Tài khoản Dịch vụ',
},
com_endpoint_config_key_google_vertex_api_role: {
english:
'Make sure to click \'Create and Continue\' to give at least the \'Vertex AI User\' role. Lastly, create a JSON key to import here.',
translated:
'Hãy chắc chắn nhấp vào \'Tạo và Tiếp tục\' để cấp ít nhất vai trò \'Người dùng Vertex AI\' thì còn lại, tạo một khóa JSON để nhập vào đây.',
},
com_nav_auto_scroll: {
english: 'Auto-Scroll to latest message on chat open',
translated: 'Cuộn tự động đến tin nhắn mới nhất khi mở',
},
com_nav_plugin_store: {
english: 'Plugin store',
translated: 'Cửa hàng Plugin',
},
com_nav_plugin_search: {
english: 'Search plugins',
translated: 'Tìm kiếm plugin',
},
com_nav_plugin_auth_error: {
english: 'There was an error attempting to authenticate this plugin. Please try again.',
translated: 'Đã xảy ra lỗi khi xác thực plugin này. Vui lòng thử lại.',
},
com_nav_export_filename: {
english: 'Filename',
translated: 'Tên tệp',
},
com_nav_export_filename_placeholder: {
english: 'Set the filename',
translated: 'Đặt tên cho tệp',
},
com_nav_export_type: {
english: 'Type',
translated: 'Loại',
},
com_nav_export_include_endpoint_options: {
english: 'Include endpoint options',
translated: 'Bao gồm các tùy chọn điểm kết thúc',
},
com_nav_enabled: {
english: 'Enabled',
translated: 'Đã bật',
},
com_nav_not_supported: {
english: 'Not Supported',
translated: 'Không được hỗ trợ',
},
com_nav_export_all_message_branches: {
english: 'Export all message branches',
translated: 'Xuất tất cả các nhánh tin nhắn',
},
com_nav_export_recursive_or_sequential: {
english: 'Recursive or sequential?',
translated: 'Đệ quy hay tuần tự?',
},
com_nav_export_recursive: {
english: 'Recursive',
translated: 'Đệ quy',
},
com_nav_export_conversation: {
english: 'Export conversation',
translated: 'Xuất cuộc trò chuyện',
},
com_nav_theme: {
english: 'Theme',
translated: 'Chủ đề',
},
com_nav_theme_system: {
english: 'System',
translated: 'Hệ thống',
},
com_nav_theme_dark: {
english: 'Dark',
translated: 'Tối',
},
com_nav_theme_light: {
english: 'Light',
translated: 'Sáng',
},
com_nav_clear_all_chats: {
english: 'Clear all chats',
translated: 'Xóa tất cả cuộc trò chuyện',
},
com_nav_confirm_clear: {
english: 'Confirm Clear',
translated: 'Xác nhận xóa',
},
com_nav_close_sidebar: {
english: 'Close sidebar',
translated: 'Đóng thanh bên',
},
com_nav_open_sidebar: {
english: 'Open sidebar',
translated: 'Mở thanh bên',
},
com_nav_send_message: {
english: 'Send message',
translated: 'Gửi tin nhắn',
},
com_nav_log_out: {
english: 'Log out',
translated: 'Đăng xuất',
},
com_nav_user: {
english: 'USER',
translated: 'NGƯỜI DÙNG',
},
com_nav_clear_conversation: {
english: 'Clear conversations',
translated: 'Xóa cuộc trò chuyện',
},
com_nav_clear_conversation_confirm_message: {
english: 'Are you sure you want to clear all conversations? This is irreversible.',
translated:
'Bạn có chắc chắn muốn xóa tất cả cuộc trò chuyện? Hành động này không thể hoàn tác.',
},
com_nav_help_faq: {
english: 'Help & FAQ',
translated: 'Trợ giúp & Câu hỏi thường gặp',
},
com_nav_settings: {
english: 'Settings',
translated: 'Cài đặt',
},
com_nav_search_placeholder: {
english: 'Search messages',
translated: 'Tìm kiếm tin nhắn',
},
com_nav_setting_general: {
english: 'General',
translated: 'Chung',
},
com_nav_setting_data: {
english: 'Data controls',
translated: 'Kiểm soát dữ liệu',
},
};

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